Maestro 0.2.5
Unified interface for quantum circuit simulation
Loading...
Searching...
No Matches
Circuit.h
Go to the documentation of this file.
1
17#pragma once
18
19#ifndef _CIRCUIT_H_
20#define _CIRCUIT_H_
21
22#define _USE_MATH_DEFINES
23#include <math.h>
24#include <set>
25
26#include "Conditional.h"
27#include "Operations.h"
28#include "QuantumGates.h"
29#include "Reset.h"
30#include <vector>
31
32namespace Circuits {
33
45template <typename Time = Types::time_type>
46class Circuit : public IOperation<Time> {
47 public:
49 std::unordered_map<std::vector<bool>,
50 size_t>;
52 using BitMapping =
53 std::unordered_map<Types::qubit_t,
58 using OperationPtr = std::shared_ptr<Operation>;
61 std::vector<OperationPtr>;
63 using value_type = typename OperationsVector::value_type;
64 using allocator_type = typename OperationsVector::allocator_type;
65 using pointer = typename OperationsVector::pointer;
66 using const_pointer = typename OperationsVector::const_pointer;
67 using reference = typename OperationsVector::reference;
68 using const_reference = typename OperationsVector::const_reference;
69 using size_type = typename OperationsVector::size_type;
70 using difference_type = typename OperationsVector::difference_type;
71
72 using iterator = typename OperationsVector::iterator;
73 using const_iterator = typename OperationsVector::const_iterator;
74 using reverse_iterator = typename OperationsVector::reverse_iterator;
76 typename OperationsVector::const_reverse_iterator;
77
85 Circuit(const OperationsVector &ops = {}) : Operation(), operations(ops) {}
86
96 void Execute(const std::shared_ptr<Simulators::ISimulator> &sim,
97 OperationState &state) const override {
98 state.Reset();
99 if (!sim) return;
100
101 for (const auto &op : operations) op->Execute(sim, state);
102 // sim->Flush();
103 }
104
113
121 void AddOperation(const OperationPtr &op) { operations.push_back(op); }
122
131 void ReplaceOperation(size_t index, const OperationPtr &op) {
132 if (index >= operations.size()) return;
133 operations[index] = op;
134 }
135
143 void SetOperations(const OperationsVector &ops) { operations = ops; }
144
153 operations.insert(operations.end(), ops.begin(), ops.end());
154 }
155
162 void AddCircuit(const std::shared_ptr<Circuit<Time>> &circuit) {
163 AddOperations(circuit->GetOperations());
164 }
165
173 const OperationsVector &GetOperations() const { return operations; }
174
180 void Clear() { operations.clear(); }
181
188 OperationPtr Clone() const override {
189 OperationsVector newops;
190
191 for (auto &op : operations) newops.emplace_back(op->Clone());
192
193 return std::make_shared<Circuit<Time>>(newops);
194 }
195
204 OperationsVector newops;
205
206 for (auto &op : operations) newops.push_back(op);
207
208 return std::make_shared<Circuit<Time>>(newops);
209 }
210
221 OperationPtr Remap(const BitMapping &qubitsMap,
222 const BitMapping &bitsMap = {}) const override {
223 OperationsVector newops;
224
225 for (const auto &op : operations)
226 newops.emplace_back(op->Remap(qubitsMap, bitsMap));
227
228 return std::make_shared<Circuit<Time>>(newops);
229 }
230
242 std::shared_ptr<Circuit<Time>> RemapToContinuous(BitMapping &newQubitsMap,
243 BitMapping &reverseBitsMap,
244 size_t &nrQubits,
245 size_t &nrCbits) const {
246 OperationsVector newops;
247
248 BitMapping newBitsMap;
249
250 nrQubits = 0;
251 nrCbits = 0;
252
253 for (const auto &op : operations) {
254 const auto affectedBits = op->AffectedBits();
255 const auto affectedQubits = op->AffectedQubits();
256
257 for (const auto qubit : affectedQubits) {
258 const auto it = newQubitsMap.find(qubit);
259 if (it == newQubitsMap.end()) {
260 newQubitsMap[qubit] = nrQubits;
261 ++nrQubits;
262 }
263 }
264
265 for (const auto bit : affectedBits) {
266 const auto it = newBitsMap.find(bit);
267 if (it == newBitsMap.end()) {
268 newBitsMap[bit] = nrCbits;
269 reverseBitsMap[nrCbits] = bit;
270 ++nrCbits;
271 }
272 }
273
274 newops.emplace_back(op->Remap(newQubitsMap, newBitsMap));
275 }
276
277 return std::make_shared<Circuit<Time>>(newops);
278 }
279
295 const BitMapping &bitsMap = {},
296 bool ignoreNotMapped = false,
297 size_t sz = 0) {
298 ExecuteResults newResults;
299
300 if (!ignoreNotMapped && sz == 0) {
301 for (const auto &[from, to] : bitsMap)
302 if (to > sz) sz = to;
303
304 ++sz;
305 }
306
307 for (const auto &res : results) {
308 Circuits::OperationState mappedState(res.first);
309
310 mappedState.Remap(bitsMap, ignoreNotMapped,
311 ignoreNotMapped ? bitsMap.size() : sz);
312 newResults[mappedState.GetAllBits()] += res.second;
313 }
314
315 return newResults;
316 }
317
326 static void AccumulateResults(ExecuteResults &results,
327 const ExecuteResults &newResults) {
328 for (const auto &res : newResults) results[res.first] += res.second;
329 }
330
346 const ExecuteResults &newResults,
347 const BitMapping &bitsMap = {},
348 bool ignoreNotMapped = true,
349 size_t sz = 0) {
350 if (!ignoreNotMapped && sz == 0) {
351 for (const auto &[from, to] : bitsMap)
352 if (to > sz) sz = to;
353
354 ++sz;
355 }
356
357 for (const auto &res : newResults) {
358 Circuits::OperationState mappedState(res.first);
359
360 mappedState.Remap(bitsMap, ignoreNotMapped,
361 ignoreNotMapped ? bitsMap.size() : sz);
362 results[mappedState.GetAllBits()] += res.second;
363 /*
364 const auto it = bitsMap.find(res.first);
365 if (it != bitsMap.end())
366 results[it->second] += res.second;
367 */
368 }
369 }
370
371 // TODO: This converts all swap, cswap and ccnot gates
372 // it's not really needed to convert them all,
373 // only those that are not applied locally, that is, on a single host
374 // the local ones can remain as they are
375 // so use network topology to decide which ones to convert
376 // that's for later, when we have the network part implemented
377 // and also the code that splits the circuit
378
387 // this will make the circuit better for distributed computing
388 // TODO: if composite operations will be implemented, those need to be
389 // optimized as well
390
391 ReplaceThreeQubitAndSwapGates();
392 }
393
401 void ConvertForCutting() { ReplaceThreeQubitAndSwapGates(true); }
402
403 /*
404 * @brief Splits the measurements to measurements on individual qubits and
405 * tries to order them as needed by the following clasically conditional
406 * gates.
407 *
408 * Splits the measurements to measurements on individual qubits and tries to
409 * order them as needed by the following clasically conditional gates. This is
410 * needed for netqasm, which requires the measurements to be in the right
411 * order, if the following clasically conditional gates need sending to
412 * another host. This wouldn't be needed if they are local, but we don't know
413 * that at this point.
414 *
415 * So in short, all measurements on more than one qubit are converted to one
416 * qubit measurements, and then all measurements that are grouped together
417 * (not separated by some other operation) are ordered in the same order as
418 * the conditions on the following clasically conditional gates.
419 */
421 // TODO: Maybe this should be moved at the netqasm level circuit conversion,
422 // since it's not needed for all kinds of distributed computing currently
423 // there is a virtual function in controller that does nothing in the base
424 // class, but for netqasm it calls this method
425 OperationsVector newops;
426
427 for (size_t i = 0; i < operations.size(); ++i) {
428 const auto op = operations[i];
429 if (op->GetType() != OperationType::kMeasurement) {
430 newops.emplace_back(op);
431 continue;
432 }
433
434 // ok, if it's a measurement, look ahead, accumulate all measurements and
435 // then add them in the right order
436 std::unordered_set<size_t> bits;
437 std::unordered_map<size_t, Types::qubit_t> measQubits;
438 std::unordered_map<size_t, Time> measDelays;
439
440 auto affectedBits = op->AffectedBits();
441 auto affectedQubits = op->AffectedQubits();
442
443 for (size_t q = 0; q < affectedQubits.size(); ++q) {
444 bits.insert(affectedBits[q]);
445 measQubits[affectedBits[q]] = affectedQubits[q];
446 measDelays[affectedBits[q]] = op->GetDelay();
447 }
448
449 size_t j = i + 1;
450 for (; j < operations.size(); ++j) {
451 const auto op2 = operations[j];
452
453 if (op2->GetType() != OperationType::kMeasurement) break;
454
455 affectedQubits = op2->AffectedQubits();
456
457 const auto meas =
458 std::static_pointer_cast<MeasurementOperation<Time>>(op2);
459 affectedBits = meas->GetBitsIndices();
460 for (size_t q = 0; q < affectedBits.size(); ++q) {
461 bits.insert(affectedBits[q]);
462 measQubits[affectedBits[q]] = affectedQubits[q];
463 measDelays[affectedBits[q]] = op2->GetDelay();
464 }
465 }
466
467 i = j - 1;
468
469 // the right order is the one following in the classically controlled
470 // gates
471 for (; j < operations.size(); ++j) {
472 const auto op2 = operations[j];
473 if (op2->GetType() == OperationType::kConditionalGate ||
474 op2->GetType() == OperationType::kConditionalMeasurement ||
475 op2->GetType() == OperationType::kConditionalRandomGen) {
476 auto condop =
477 std::static_pointer_cast<IConditionalOperation<Time>>(op2);
478 const auto condbits = condop->AffectedBits();
479 for (const auto bit : condbits)
480 if (bits.find(bit) != bits.end()) {
481 newops.emplace_back(std::make_shared<MeasurementOperation<Time>>(
482 std::vector{std::make_pair(measQubits[bit], bit)},
483 measDelays[bit]));
484 bits.erase(bit);
485 }
486 }
487 if (bits.empty()) break;
488 }
489
490 // now add the measurements that were left in any order
491 for (auto bit : bits)
492 newops.emplace_back(std::make_shared<MeasurementOperation<Time>>(
493 std::vector{std::make_pair(measQubits[bit], bit)},
494 measDelays[bit]));
495 }
496
497 operations.swap(newops);
498 }
499
507 size_t GetMaxQubitIndex() const {
508 size_t mx = 0;
509 for (const auto &op : operations) {
510 const auto qbits = op->AffectedQubits();
511 for (auto q : qbits)
512 if (q > mx) mx = q;
513 }
514
515 return mx;
516 }
517
525 size_t GetMinQubitIndex() const {
526 size_t mn = std::numeric_limits<size_t>::max();
527 for (const auto &op : operations) {
528 const auto qbits = op->AffectedQubits();
529 for (auto q : qbits)
530 if (q < mn) mn = q;
531 }
532
533 return mn;
534 }
535
543 size_t GetMaxCbitIndex() const {
544 size_t mx = 0;
545 for (const auto &op : operations) {
546 const auto cbits = op->AffectedBits();
547 for (auto q : cbits)
548 if (q > mx) mx = q;
549 }
550
551 return mx;
552 }
553
561 size_t GetMinCbitIndex() const {
562 size_t mn = std::numeric_limits<size_t>::max();
563 for (const auto &op : operations) {
564 const auto cbits = op->AffectedBits();
565 for (auto q : cbits)
566 if (q < mn) mn = q;
567 }
568
569 return mn;
570 }
571
581 std::set<size_t> GetQubits() const {
582 std::set<size_t> qubits;
583 for (const auto &op : operations) {
584 const auto qbits = op->AffectedQubits();
585 qubits.insert(qbits.begin(), qbits.end());
586 }
587
588 return qubits;
589 }
590
598 std::set<size_t> GetBits() const {
599 std::set<size_t> cbits;
600 for (const auto &op : operations) {
601 const auto bits = op->AffectedBits();
602 cbits.insert(bits.begin(), bits.end());
603 }
604
605 return cbits;
606 }
607
615 auto qubits = GetQubits();
616
617 Types::qubits_vector qubitsVec;
618 qubitsVec.reserve(qubits.size());
619
620 for (auto q : qubits) qubitsVec.emplace_back(q);
621
622 return qubitsVec;
623 }
624
631 std::vector<size_t> AffectedBits() const override {
632 auto bits = GetBits();
633
634 std::vector<size_t> bitsVec;
635 bitsVec.reserve(bits.size());
636
637 for (auto b : bits) bitsVec.emplace_back(b);
638
639 return bitsVec;
640 }
641
651 bool NeedsEntanglementForDistribution() const override {
652 for (const auto &op : operations)
653 if (op->NeedsEntanglementForDistribution()) return true;
654
655 return false;
656 }
657
665 bool CanAffectQuantumState() const override {
666 for (const auto &op : operations)
667 if (op->CanAffectQuantumState()) return true;
668
669 return false;
670 }
671
679 std::unordered_map<size_t, OperationPtr> GetLastOperationsOnQubits() const {
680 std::unordered_map<size_t, OperationPtr> lastOps;
681
682 for (const auto &op : operations) {
683 const auto qbits = op->AffectedQubits();
684 for (auto q : qbits) lastOps[q] = op;
685 }
686
687 return lastOps;
688 }
689
697 std::unordered_map<size_t, OperationPtr> GetFirstOperationsOnQubits() const {
698 std::unordered_map<size_t, OperationPtr> firstOps;
699
700 for (const auto &op : operations) {
701 const auto qbits = op->AffectedQubits();
702 for (auto q : qbits) {
703 if (firstOps.find(q) == firstOps.end()) firstOps[q] = op;
704 }
705 }
706
707 return firstOps;
708 }
709
718 void AddResetsIfNeeded(Time delay = 0) {
719 const auto GetLastOps = GetLastOperationsOnQubits();
720
721 for (const auto &[q, op] : GetLastOps)
722 if (op->GetType() !=
723 OperationType::kReset) // don't add it if there is already a reset
724 // operation on the qubit
725 operations.emplace_back(
726 std::make_shared<Reset<Time>>(Types::qubits_vector{q}, delay));
727 }
728
737 void AddResetsAtBeginningIfNeeded(Time delay = 0) {
738 const auto GetFirstOps = GetFirstOperationsOnQubits();
739
740 for (const auto &[q, op] : GetFirstOps)
741 if (op->GetType() !=
742 OperationType::kReset) // don't add it if there is already a reset
743 // operation on the qubit
744 operations.insert(
745 operations.begin(),
746 std::make_shared<Reset<Time>>(Types::qubits_vector{q}, delay));
747 }
748
756 void Optimize(bool optimizeRotationGates = true) {
757 // Some ideas, from simple to more complex:
758 //
759 // IMPORTANT: Focus on the gates that are added for distributed computing,
760 // either for the one with entanglement or the one with cutting the reason
761 // is that maybe the provided circuit is not that bad, but due of the
762 // supplementary gates added, duplicates (for example) will occur the most
763 // important one qubit ones added are hadamard then X, S, Sdag and Z
764 //
765 // 1. First, one qubit gates can be combined into a single gate or even no
766 // gate a) Straightforward for those that are their own inverse (that is,
767 // hermitian/involutory): Hadamard and Pauli gates for example, if one finds
768 // two of them in sequence, they can be removed other ones are the one that
769 // are followed by their 'dag' (inverse, since the gates are unitary) in the
770 // circuit, those can be removed, too
771
772 // several resets can be also changed into a single one, also repeated
773 // measurements of the same qubit, with result in the same cbit can be
774 // replaced by a single measurement
775
776 // b) other ones can be combined into a single gate, for example phase shift
777 // gates or rotation gates two phase gates (not the general phase shift we
778 // have, but the one with 1, i on the diagonal) can be combined into a Z
779 // gate, for example, or two sqrtNot gates can be combined into a single X
780 // gate combinations of phase gates and hadamard can be replaced by pauli
781 // gates in some cases, and so on even the U gate could be used to join
782 // together several one qubit gates
783
784 // c) even more complex... three or more one qubit gates could be combined
785 // into a single gate... an example is HXH = Z other examples SXS^t = Y,
786 // SZS^t = Z
787
788 // 2. Two qubit gates can be optimized, too
789 // for example two CNOT gates in sequence can be removed if the control
790 // qubit is the same, the same goes for two CZ or CY or SWAP gates (this
791 // goes for the three qubit gates, CCX, CCY, CCZ, CSWAP, too)
792
793 // 3. Some gates commute, the reorder can give some opportunities for more
794 // optimization
795
796 // 4. Groups of gates
797 // the possibilities are endless, but might be easier to focus first on
798 // Clifford gates for example a X sandwiched between CNOTs can be replaced
799 // with two X on each qubit if the original X is on the control qubit or
800 // with an X on the target qubit if the original X is on the target qubit a
801 // similar thing happens if Z is sandwiched between CNOTs, but this time Z x
802 // Z appears if the original Z is on the target qubit and if original Z is
803 // on the control qubit, then the CNOTs dissapear and the Z remains on the
804 // control qubit
805
806 // three CNOT gates with the one in the middle turned upside down compare
807 // with the other two can be replaced by a single swap gate
808
809 // first stage, take out duplicates of H, X, Y, Z
810
811 bool changed;
812
813 do {
814 changed = false;
815
816 std::vector<std::shared_ptr<IOperation<Time>>> newops;
817 newops.reserve(operations.size());
818
819 for (int i = 0; i < static_cast<int>(operations.size()); ++i) {
820 const std::shared_ptr<IOperation<Time>> &op = operations[i];
821
822 const auto type = op->GetType();
823 if (type == OperationType::kNoOp)
824 continue;
825 else if (type == OperationType::kGate) {
826 std::shared_ptr<IQuantumGate<Time>> gate =
827 std::static_pointer_cast<IQuantumGate<Time>>(op);
828 const auto qubits = gate->AffectedQubits();
829
830 if (qubits.size() == 1) {
831 // TODO: HXH = Z, SXS^t = Y, SZS^t = Z ????
832
833 auto gateType = gate->GetGateType();
834 bool replace = false;
835
836 // if it's one of the interesting gates, look ahead to see if it's
837 // followed by the same gate on the same qubit if yes, replace the
838 // next one with a nop and skip the current one (or replace the pair
839 // with a single gate, depending on the type) set changed to true if
840 // something was changed
841 switch (gateType) {
843 [[fallthrough]];
845 [[fallthrough]];
847 if (!optimizeRotationGates) {
848 newops.push_back(op);
849 break;
850 }
851 [[fallthrough]];
853 replace = true;
854 [[fallthrough]];
855 // those above will be replaced the pair with a single gate, all
856 // the following are the ones that get removed
858 [[fallthrough]];
860 [[fallthrough]];
862 [[fallthrough]];
864 [[fallthrough]];
866 [[fallthrough]];
868 [[fallthrough]];
870 [[fallthrough]];
872 [[fallthrough]];
874 [[fallthrough]];
876 [[fallthrough]];
878 bool found = false;
879
880 if (gateType == QuantumGateType::kSGateType)
882 else if (gateType == QuantumGateType::kSdgGateType)
884 else if (gateType == QuantumGateType::kTGateType)
886 else if (gateType == QuantumGateType::kTdgGateType)
888 else if (gateType == QuantumGateType::kSxGateType)
890 else if (gateType == QuantumGateType::kSxDagGateType)
892
893 for (size_t j = i + 1; j < operations.size(); ++j) {
894 auto &nextOp = operations[j];
895 if (!nextOp->CanAffectQuantumState()) continue;
896
897 const auto nextQubits = nextOp->AffectedQubits();
898 bool hasQubit = false;
899
900 for (auto q : nextQubits)
901 if (q == qubits[0]) {
902 hasQubit = true;
903 break;
904 }
905
906 if (!hasQubit)
907 continue; // an op that does not touch the current qubit
908 // can be skipped
909 else if (nextQubits.size() != 1)
910 break; // if it touches the current qubit and it's
911 // something else than a single qubit gate, stop
912
913 const auto nextType = nextOp->GetType();
914 if (nextType != OperationType::kGate)
915 break; // could be a classically conditioned gate, stop
916
917 const auto &nextGate =
918 std::static_pointer_cast<SingleQubitGate<Time>>(nextOp);
919 if (nextGate->GetGateType() == gateType) {
920 if (replace) {
921 const auto params1 = gate->GetParams();
922 const auto params2 = nextGate->GetParams();
923
924 const double param = params1[0] + params2[0];
925 const auto delay =
926 gate->GetDelay() + nextGate->GetDelay();
927
928 if (gateType == QuantumGateType::kPhaseGateType)
929 newops.push_back(std::make_shared<PhaseGate<Time>>(
930 qubits[0], param, delay));
931 else if (gateType == QuantumGateType::kRxGateType)
932 newops.push_back(std::make_shared<RxGate<Time>>(
933 qubits[0], param, delay));
934 else if (gateType == QuantumGateType::kRyGateType)
935 newops.push_back(std::make_shared<RyGate<Time>>(
936 qubits[0], param, delay));
937 else
938 newops.push_back(std::make_shared<RzGate<Time>>(
939 qubits[0], param, delay));
940 }
941 nextOp = std::make_shared<NoOperation<Time>>();
942 changed = true;
943 found = true;
944 break;
945 } else if ((gateType == QuantumGateType::kSGateType &&
946 nextGate->GetGateType() ==
948 (gateType == QuantumGateType::kSdgGateType &&
949 nextGate->GetGateType() ==
951 // if expecting an S gate (or a Sdg gate) and found the
952 // original one instead, replace the pair with a Z gate (S *
953 // S = Z, Sdag * Sdag = Z)
954 const auto delay = gate->GetDelay() + nextGate->GetDelay();
955 newops.push_back(
956 std::make_shared<ZGate<Time>>(qubits[0], delay));
957 nextOp = std::make_shared<NoOperation<Time>>();
958 changed = true;
959 found = true;
960 break;
961 } else if ((gateType == QuantumGateType::kSxGateType &&
962 nextGate->GetGateType() ==
964 (gateType == QuantumGateType::kSxDagGateType &&
965 nextGate->GetGateType() ==
967 // if expecting an S gate (or a Sdg gate) and found the
968 // original one instead, replace the pair with a X gate (Sx
969 // * Sx = X, SXdag * SXdag = X)
970 const auto delay = gate->GetDelay() + nextGate->GetDelay();
971 newops.push_back(
972 std::make_shared<XGate<Time>>(qubits[0], delay));
973 nextOp = std::make_shared<NoOperation<Time>>();
974 changed = true;
975 found = true;
976 break;
977 } else if (gateType == QuantumGateType::kTGateType &&
978 nextGate->GetGateType() ==
980 // if expecting a T gate and found the Tdgate instead,
981 // replace the pair with a Sdag gate (Tdg * Tdg = Sdag)
982 const auto delay = gate->GetDelay() + nextGate->GetDelay();
983 newops.push_back(
984 std::make_shared<SdgGate<Time>>(qubits[0], delay));
985 nextOp = std::make_shared<NoOperation<Time>>();
986 changed = true;
987 found = true;
988 break;
989 } else if (gateType == QuantumGateType::kTdgGateType &&
990 nextGate->GetGateType() ==
992 // if expecting a Tdg gate and found the T gate instead,
993 // replace the pair with a S gate (T * T = S)
994 const auto delay = gate->GetDelay() + nextGate->GetDelay();
995 newops.push_back(
996 std::make_shared<SGate<Time>>(qubits[0], delay));
997 nextOp = std::make_shared<NoOperation<Time>>();
998 changed = true;
999 found = true;
1000 break;
1001 } else if (gateType == QuantumGateType::kPhaseGateType &&
1002 (nextGate->GetGateType() ==
1004 nextGate->GetGateType() ==
1006 nextGate->GetGateType() ==
1008 nextGate->GetGateType() ==
1010 const auto delay = gate->GetDelay() + nextGate->GetDelay();
1011 double param2;
1012 if (nextGate->GetGateType() == QuantumGateType::kSGateType)
1013 param2 = 0.5 * M_PI;
1014 else if (nextGate->GetGateType() ==
1016 param2 = -0.5 * M_PI;
1017 else if (nextGate->GetGateType() ==
1019 param2 = 0.25 * M_PI;
1020 else
1021 param2 = -0.25 * M_PI;
1022
1023 const auto param = gate->GetParams()[0] + param2;
1024 newops.push_back(std::make_shared<PhaseGate<Time>>(
1025 qubits[0], param, delay));
1026 nextOp = std::make_shared<NoOperation<Time>>();
1027 changed = true;
1028 found = true;
1029 break;
1030 } else if (nextGate->GetGateType() ==
1032 (gateType == QuantumGateType::kSGateType ||
1033 gateType == QuantumGateType::kSdgGateType ||
1034 gateType == QuantumGateType::kTGateType ||
1035 gateType == QuantumGateType::kTdgGateType)) {
1036 const auto delay = gate->GetDelay() + nextGate->GetDelay();
1037 double param1;
1038 if (gateType == QuantumGateType::kSGateType)
1039 param1 = -0.5 * M_PI;
1040 else if (gateType == QuantumGateType::kSdgGateType)
1041 param1 = 0.5 * M_PI;
1042 else if (gateType == QuantumGateType::kTGateType)
1043 param1 = -0.25 * M_PI;
1044 else
1045 param1 = 0.25 * M_PI;
1046
1047 const auto param = nextGate->GetParams()[0] + param1;
1048 newops.push_back(std::make_shared<PhaseGate<Time>>(
1049 qubits[0], param, delay));
1050 nextOp = std::make_shared<NoOperation<Time>>();
1051 changed = true;
1052 found = true;
1053 break;
1054 } else
1055 break; // not the expected gate, acting on same qubit, bail
1056 // out
1057 }
1058
1059 if (!found) newops.push_back(op);
1060 } break;
1061 default:
1062 // if no, just add it
1063 newops.push_back(op);
1064 break;
1065 }
1066 } else if (qubits.size() == 2) {
1067 auto gateType = gate->GetGateType();
1068 bool replace = false;
1069
1070 // if it's one of the interesting gates, look ahead to see if it's
1071 // followed by the same gate on the same qubit if yes, replace the
1072 // next one with a nop and skip the current one (or replace the pair
1073 // with a single gate, depending on the type) set changed to true if
1074 // something was changed
1075 switch (gateType) {
1077 [[fallthrough]];
1079 [[fallthrough]];
1081 if (!optimizeRotationGates) {
1082 newops.push_back(op);
1083 break;
1084 }
1085 [[fallthrough]];
1087 replace = true;
1088 [[fallthrough]];
1089 // those above will be replaced the pair with a single gate, all
1090 // the following are the ones that get removed
1092 [[fallthrough]];
1094 [[fallthrough]];
1096 [[fallthrough]];
1098 [[fallthrough]];
1100 [[fallthrough]];
1102 [[fallthrough]];
1104 bool found = false;
1105
1106 if (gateType == QuantumGateType::kCSxGateType)
1108 else if (gateType == QuantumGateType::kCSxDagGateType)
1110
1111 // looking forward for the next operation that acts on the same
1112 // qubits
1113 for (size_t j = i + 1; j < operations.size(); ++j) {
1114 auto &nextOp = operations[j];
1115 if (!nextOp->CanAffectQuantumState()) continue;
1116
1117 const auto nextQubits = nextOp->AffectedQubits();
1118
1119 bool hasQubit = false;
1120
1121 for (auto q : nextQubits)
1122 if (q == qubits[0] || q == qubits[1]) {
1123 hasQubit = true;
1124 break;
1125 }
1126
1127 if (!hasQubit)
1128 continue; // an op that does not touch the current qubit
1129 // can be skipped
1130 else if (nextQubits.size() != 2)
1131 break; // if it touches a current qubit and it's something
1132 // else than a two qubits gate, stop
1133 // if it's not the same qubits, bail out
1134 else if (gateType == QuantumGateType::kSwapGateType &&
1135 !((qubits[0] == nextQubits[0] &&
1136 qubits[1] == nextQubits[1]) ||
1137 (qubits[0] == nextQubits[1] &&
1138 qubits[1] == nextQubits[0])))
1139 break;
1140 else if (!(qubits[0] == nextQubits[0] &&
1141 qubits[1] == nextQubits[1]))
1142 break;
1143
1144 const auto nextType = nextOp->GetType();
1145 if (nextType != OperationType::kGate)
1146 break; // could be a classically conditioned gate, stop
1147
1148 const auto &nextGate =
1149 std::static_pointer_cast<TwoQubitsGate<Time>>(nextOp);
1150 if (nextGate->GetGateType() == gateType) {
1151 if (replace) {
1152 const auto params1 = gate->GetParams();
1153 const auto params2 = nextGate->GetParams();
1154 const double param = params1[0] + params2[0];
1155 const auto delay =
1156 gate->GetDelay() + nextGate->GetDelay();
1157
1158 if (gateType == QuantumGateType::kCPGateType)
1159 newops.push_back(std::make_shared<CPGate<Time>>(
1160 qubits[0], qubits[1], param, delay));
1161 else if (gateType == QuantumGateType::kCRxGateType)
1162 newops.push_back(std::make_shared<CRxGate<Time>>(
1163 qubits[0], qubits[1], param, delay));
1164 else if (gateType == QuantumGateType::kCRyGateType)
1165 newops.push_back(std::make_shared<CRyGate<Time>>(
1166 qubits[0], qubits[1], param, delay));
1167 else
1168 newops.push_back(std::make_shared<CRzGate<Time>>(
1169 qubits[0], qubits[1], param, delay));
1170 }
1171 nextOp = std::make_shared<NoOperation<Time>>();
1172 changed = true; // continue merging gates, we found one
1173 // that was merged/removed
1174 found = true; // don't put op in the new operations, we
1175 // handled it
1176 break;
1177 } else
1178 break; // not the expected gate, acting on same qubits,
1179 // bail out
1180 } // end for of looking forward
1181
1182 if (!found) newops.push_back(op);
1183 } break;
1184 default:
1185 // if no, just add it
1186 newops.push_back(op);
1187 break;
1188 }
1189 } else if (qubits.size() == 3) {
1190 auto gateType = gate->GetGateType();
1191
1192 // if it's one of the interesting gates, look ahead to see if it's
1193 // followed by the same gate on the same qubit if yes, replace the
1194 // next one with a nop and skip the current one (or replace the pair
1195 // with a single gate, depending on the type) set changed to true if
1196 // something was changed
1197 switch (gateType) {
1199 [[fallthrough]];
1201 bool found = false;
1202
1203 for (size_t j = i + 1; j < operations.size(); ++j) {
1204 auto &nextOp = operations[j];
1205 if (!nextOp->CanAffectQuantumState()) continue;
1206
1207 const auto nextQubits = nextOp->AffectedQubits();
1208
1209 bool hasQubit = false;
1210
1211 for (auto q : nextQubits)
1212 if (q == qubits[0] || q == qubits[1] || q == qubits[2]) {
1213 hasQubit = true;
1214 break;
1215 }
1216
1217 if (!hasQubit)
1218 continue; // an op that does not touch the current qubit
1219 // can be skipped
1220 else if (nextQubits.size() != 3)
1221 break; // if it touches a current qubit and it's something
1222 // else than a three qubits gate, stop
1223 // if it's not the same qubits, bail out
1224 else if (gateType == QuantumGateType::kCSwapGateType &&
1225 (qubits[0] != nextQubits[0] ||
1226 !((qubits[1] == nextQubits[1] &&
1227 qubits[2] == nextQubits[2]) ||
1228 (qubits[1] == nextQubits[2] &&
1229 qubits[2] == nextQubits[1]))))
1230 break;
1231 else if (gateType == QuantumGateType::kCCXGateType &&
1232 (qubits[2] != nextQubits[2] ||
1233 !(qubits[1] == nextQubits[1] &&
1234 qubits[2] == nextQubits[2]) ||
1235 !(qubits[1] == nextQubits[2] &&
1236 qubits[2] == nextQubits[1])))
1237 break;
1238
1239 const auto nextType = nextOp->GetType();
1240 if (nextType != OperationType::kGate)
1241 break; // could be a classically conditioned gate, stop
1242
1243 const auto &nextGate =
1244 std::static_pointer_cast<ThreeQubitsGate<Time>>(nextOp);
1245 if (nextGate->GetGateType() == gateType) {
1246 nextOp = std::make_shared<NoOperation<Time>>();
1247 changed = true;
1248 found = true;
1249 break;
1250 } else
1251 break; // not the expected gate, acting on same qubits,
1252 // bail out
1253 }
1254
1255 if (!found) newops.push_back(op);
1256 } break;
1257 default:
1258 // if no, just add it
1259 newops.push_back(op);
1260 break;
1261 }
1262 } else
1263 newops.push_back(op);
1264 } else
1265 newops.push_back(op);
1266 } // end for on circuit operations
1267
1268 operations.swap(newops);
1269 } while (changed);
1270 }
1271
1279 OperationsVector newops;
1280 newops.reserve(operations.size());
1281
1282 size_t qubitsNo = std::max(GetMaxQubitIndex(), GetMaxCbitIndex()) + 1;
1283
1284 std::unordered_map<Types::qubit_t, std::vector<OperationPtr>> qubitOps;
1285
1286 std::vector<OperationPtr> lastOps(qubitsNo);
1287
1288 std::unordered_map<OperationPtr, std::unordered_set<OperationPtr>>
1289 dependenciesMap;
1290
1291 for (const auto &op : operations) {
1292 std::unordered_set<OperationPtr> dependencies;
1293
1294 const auto cbits = op->AffectedBits();
1295 for (auto c : cbits) {
1296 const auto lastOp = lastOps[c];
1297 if (lastOp) dependencies.insert(lastOp);
1298 }
1299
1300 const auto qubits = op->AffectedQubits();
1301 for (auto q : qubits) {
1302 qubitOps[q].push_back(op);
1303
1304 const auto lastOp = lastOps[q];
1305 if (lastOp) dependencies.insert(lastOp);
1306
1307 lastOps[q] = op;
1308 }
1309
1310 for (auto c : cbits) lastOps[c] = op;
1311
1312 dependenciesMap[op] = dependencies;
1313 }
1314 lastOps.clear();
1315
1316 std::vector<Types::qubit_t> indices(qubitsNo, 0);
1317
1318 while (!dependenciesMap.empty()) {
1319 OperationPtr nextOp;
1320
1321 // try to locate a 'next' gate for a qubit that is either a measurement or
1322 // a reset
1323 for (size_t q = 0; q < qubitsNo; ++q) {
1324 if (qubitOps.find(q) ==
1325 qubitOps.end()) // no operation left on this qubit
1326 continue;
1327
1328 // grab the current operation for this qubit
1329 const auto &ops = qubitOps[q];
1330 const auto &op = ops[indices[q]];
1331
1332 // consider only measurements and resets
1333 if (op->GetType() == OperationType::kMeasurement ||
1334 op->GetType() == OperationType::kReset) {
1335 bool hasDependencies = false;
1336
1337 for (const auto &opd : dependenciesMap[op])
1338 if (dependenciesMap.find(opd) != dependenciesMap.end()) {
1339 hasDependencies = true;
1340 break;
1341 }
1342
1343 if (!hasDependencies) {
1344 nextOp = op;
1345 break;
1346 }
1347 }
1348 }
1349
1350 if (nextOp) {
1351 dependenciesMap.erase(nextOp);
1352
1353 const auto qubits = nextOp->AffectedQubits();
1354 for (auto q : qubits) {
1355 ++indices[q];
1356 if (indices[q] >= qubitOps[q].size()) qubitOps.erase(q);
1357 }
1358
1359 newops.emplace_back(std::move(nextOp));
1360 continue;
1361 }
1362
1363 // if there is no measurement or reset, add the next gate
1364 for (Types::qubit_t q = 0; q < qubitsNo; ++q) {
1365 if (qubitOps.find(q) ==
1366 qubitOps.end()) // no operation left on this qubit
1367 continue;
1368
1369 // grab the current operation for this qubit
1370 const auto &ops = qubitOps[q];
1371 const auto &op = ops[indices[q]];
1372
1373 bool hasDependencies = false;
1374
1375 for (const auto &opd : dependenciesMap[op])
1376 if (dependenciesMap.find(opd) != dependenciesMap.end()) {
1377 hasDependencies = true;
1378 break;
1379 }
1380
1381 if (!hasDependencies) {
1382 nextOp = op;
1383 break;
1384 }
1385 }
1386
1387 if (nextOp) {
1388 dependenciesMap.erase(nextOp);
1389
1390 const auto qubits = nextOp->AffectedQubits();
1391 for (auto q : qubits) {
1392 ++indices[q];
1393 if (indices[q] >= qubitOps[q].size()) qubitOps.erase(q);
1394 }
1395
1396 newops.emplace_back(std::move(nextOp));
1397 }
1398 }
1399
1400 assert(newops.size() == operations.size());
1401
1402 operations.swap(newops);
1403 }
1404
1414 std::pair<std::vector<size_t>, std::vector<Time>> GetDepth() const {
1415 size_t maxDepth;
1416 Time maxTime;
1417
1418 size_t qubitsNo = GetMaxQubitIndex() + 1;
1419 std::vector<Time> qubitTimes(qubitsNo, 0);
1420 std::vector<size_t> qubitDepths(qubitsNo, 0);
1421
1422 std::unordered_map<size_t, size_t> fromQubits;
1423
1424 for (const auto &op : operations) {
1425 const auto qbits = op->AffectedQubits();
1426 const auto delay = op->GetDelay();
1427
1428 maxTime = 0;
1429 maxDepth = 0;
1430 for (auto q : qbits) {
1431 qubitTimes[q] += delay;
1432 ++qubitDepths[q];
1433 if (qubitTimes[q] > maxTime) maxTime = qubitTimes[q];
1434 if (qubitDepths[q] > maxDepth) maxDepth = qubitDepths[q];
1435 }
1436
1437 const auto t = op->GetType();
1438 std::vector<size_t> condbits;
1439
1440 // TODO: deal with 'random gen' operations, those do not affect qubits
1441 // directly, but they do affect the classical bits and can be used in
1442 // conditional operations
1443
1446 condbits = op->AffectedBits();
1447
1448 for (auto bit : condbits) {
1449 if (fromQubits.find(bit) != fromQubits.end()) bit = fromQubits[bit];
1450
1451 bool found = false;
1452 for (auto q : qbits) {
1453 if (q == bit) {
1454 found = true;
1455 break;
1456 }
1457 }
1458 if (found || bit >= qubitsNo) continue;
1459
1460 qubitTimes[bit] += delay;
1461 ++qubitDepths[bit];
1462 if (qubitTimes[bit] > maxTime) maxTime = qubitTimes[bit];
1463 if (qubitDepths[bit] > maxDepth) maxDepth = qubitDepths[bit];
1464 }
1465
1467 const auto condMeas =
1468 std::static_pointer_cast<ConditionalMeasurement<Time>>(op);
1469 const auto meas = condMeas->GetOperation();
1470 const auto measQubits = meas->AffectedQubits();
1471 const auto measBits = meas->AffectedBits();
1472
1473 for (size_t i = 0; i < measQubits.size(); ++i) {
1474 if (i < measBits.size())
1475 fromQubits[measBits[i]] = measQubits[i];
1476 else
1477 fromQubits[measQubits[i]] = measQubits[i];
1478 }
1479 }
1480 } else if (t == OperationType::kMeasurement) {
1481 condbits = op->AffectedBits();
1482
1483 for (size_t i = 0; i < qbits.size(); ++i) {
1484 if (i < condbits.size())
1485 fromQubits[condbits[i]] = qbits[i];
1486 else
1487 fromQubits[qbits[i]] = qbits[i];
1488 }
1489 }
1490
1491 for (auto q : qbits) {
1492 qubitTimes[q] = maxTime;
1493 qubitDepths[q] = maxDepth;
1494 }
1495
1496 for (auto bit : condbits) {
1497 qubitTimes[bit] = maxTime;
1498 qubitDepths[bit] = maxDepth;
1499 }
1500 }
1501
1502 return std::make_pair(qubitDepths, qubitTimes);
1503 }
1504
1514 std::pair<size_t, Time> GetMaxDepth() const {
1515 auto [qubitDepths, qubitTimes] = GetDepth();
1516
1517 Time maxTime = 0;
1518 size_t maxDepth = 0;
1519 for (size_t qubit = 0; qubit < qubitDepths.size(); ++qubit) {
1520 if (qubitTimes[qubit] > maxTime) maxTime = qubitTimes[qubit];
1521 if (qubitDepths[qubit] > maxDepth) maxDepth = qubitDepths[qubit];
1522 }
1523
1524 return std::make_pair(maxDepth, maxTime);
1525 }
1526
1533 size_t GetNumberOfOperations() const { return operations.size(); }
1534
1543 OperationPtr GetOperation(size_t pos) const {
1544 if (pos >= operations.size()) return nullptr;
1545
1546 return operations[pos];
1547 }
1548
1562 std::shared_ptr<Circuit<Time>> GetCircuitCut(Types::qubit_t startQubit,
1563 Types::qubit_t endQubit) const {
1564 OperationsVector newops;
1565 newops.reserve(operations.size());
1566
1567 for (const auto &op : operations) {
1568 const auto qubits = op->AffectedQubits();
1569 bool containsOutsideQubits = false;
1570 bool containsInsideQubits = false;
1571 for (const auto q : qubits) {
1572 if (q < startQubit || q > endQubit) {
1573 containsOutsideQubits = true;
1574 if (containsInsideQubits) break;
1575 } else {
1576 containsInsideQubits = true;
1577 if (containsOutsideQubits) break;
1578 }
1579 }
1580
1581 if (containsInsideQubits) {
1582 if (containsOutsideQubits)
1583 throw std::runtime_error(
1584 "Cannot cut the circuit with the specified interval");
1585 newops.emplace_back(op->Clone());
1586 }
1587 }
1588
1589 return std::make_shared<Circuit<Time>>(newops);
1590 }
1591
1603 std::unordered_set<Types::qubit_t> measuredQubits;
1604 std::unordered_set<Types::qubit_t> affectedQubits;
1605 std::unordered_set<Types::qubit_t> resetQubits;
1606
1607 for (const auto &op : operations) {
1608 const auto qubits = op->AffectedQubits();
1609
1610 if (op->GetType() == OperationType::kMeasurement) {
1611 for (const auto qbit : qubits)
1612 if (resetQubits.find(qbit) !=
1613 resetQubits.end()) // there is a reset on this qubit already and
1614 // it's not at the beginning of the circuit
1615 return true;
1616
1617 /*
1618 const auto bits = op->AffectedBits();
1619 if (bits.size() != qubits.size())
1620 return true;
1621
1622 for (size_t b = 0; b < bits.size(); ++b)
1623 if (bits[b] != qubits[b])
1624 return true;
1625 */
1626 measuredQubits.insert(qubits.begin(), qubits.end());
1627 } else if (op->GetType() == OperationType::kConditionalGate ||
1628 op->GetType() == OperationType::kConditionalMeasurement ||
1629 op->GetType() == OperationType::kRandomGen ||
1630 op->GetType() == OperationType::kConditionalRandomGen)
1631 return true;
1632 else if (op->GetType() == OperationType::kReset) {
1633 // resets in the middle of the circuit are treated as measurements
1634 for (const auto qbit : qubits) {
1635 // if there is already a gate applied on the qubit but no measurement
1636 // yet, it's considered in the middle
1637 // if there is no gate applied,
1638 // then it's the first operation on the qubit
1639 if (affectedQubits.find(qbit) != affectedQubits.end() ||
1640 measuredQubits.find(qbit) != measuredQubits.end())
1641 resetQubits.insert(qbit);
1642
1643 affectedQubits.insert(qbit);
1644 }
1645 } else {
1646 for (const auto qbit : qubits) {
1647 if (measuredQubits.find(qbit) !=
1648 measuredQubits
1649 .end()) // there is a measurement on this qubit already
1650 return true;
1651
1652 if (resetQubits.find(qbit) !=
1653 resetQubits.end()) // there is a reset on this qubit already and
1654 // it's not at the beginning of the circuit
1655 return true;
1656
1657 affectedQubits.insert(qbit);
1658 }
1659 }
1660 }
1661
1662 return false;
1663 }
1664
1677 std::vector<bool> ExecuteNonMeasurements(
1678 const std::shared_ptr<Simulators::ISimulator> &sim,
1679 OperationState &state) const {
1680 std::vector<bool> executedOps;
1681 executedOps.reserve(operations.size());
1682
1683 std::unordered_set<Types::qubit_t> measuredQubits;
1684 std::unordered_set<Types::qubit_t> affectedQubits;
1685
1686 bool executionStopped = false;
1687
1688 for (size_t i = 0; i < operations.size(); ++i) {
1689 auto &op = operations[i];
1690 const auto qubits = op->AffectedQubits();
1691
1692 bool executed = false;
1693
1694 if (op->GetType() == OperationType::kMeasurement ||
1695 op->GetType() == OperationType::kConditionalMeasurement ||
1696 op->GetType() == OperationType::kRandomGen ||
1697 op->GetType() == OperationType::kConditionalRandomGen)
1698 measuredQubits.insert(qubits.begin(), qubits.end());
1699 else if (op->GetType() == OperationType::kReset) {
1700 // if it's the first op on qubit(s), execute it, otherwise treat it as a
1701 // measurement
1702 executed = true;
1703 for (auto qubit : qubits)
1704 if (affectedQubits.find(qubit) != affectedQubits.end()) {
1705 executed = false;
1706 break;
1707 }
1708
1709 if (executed) {
1710 if (sim) op->Execute(sim, state);
1711 } else
1712 measuredQubits.insert(qubits.begin(), qubits.end());
1713 } else // regular gate or conditional gate
1714 {
1715 const auto bits = op->AffectedBits();
1716
1717 // a measurement on a qubit prevents execution of any following gate
1718 // than affects the same qubit also a gate that's not executed and it
1719 // would affect certain qubits will prevent the execution of any
1720 // following gate that affects those qubits
1721
1722 bool canExecute = op->GetType() == OperationType::kGate;
1723
1724 if (canExecute) // a conditional gate cannot be executed, it needs
1725 // something executed at each shot, either a
1726 // measurement or a random number generated
1727 {
1728 for (auto bit : bits)
1729 if (measuredQubits.find(bit) != measuredQubits.end()) {
1730 canExecute = false;
1731 break;
1732 }
1733
1734 for (auto qubit : qubits)
1735 if (measuredQubits.find(qubit) != measuredQubits.end()) {
1736 canExecute = false;
1737 break;
1738 }
1739 }
1740
1741 if (canExecute) {
1742 if (sim) op->Execute(sim, state);
1743 executed = true;
1744 } else {
1745 // this is a 'trick', if it cannot execute, then neither can any
1746 // following gate that affects any of the already involved qubits
1747 measuredQubits.insert(bits.begin(), bits.end());
1748 measuredQubits.insert(qubits.begin(), qubits.end());
1749 }
1750 }
1751
1752 affectedQubits.insert(qubits.begin(), qubits.end());
1753
1754 if (!executed) executionStopped = true;
1755 if (executionStopped) executedOps.emplace_back(executed);
1756 }
1757
1758 // if (sim) sim->Flush();
1759
1760 return executedOps;
1761 }
1762
1774 void ExecuteMeasurements(const std::shared_ptr<Simulators::ISimulator> &sim,
1775 OperationState &state,
1776 const std::vector<bool> &executedOps) const {
1777 state.Reset();
1778 if (!sim) return;
1779
1780 // if (executedOps.empty() && !operations.empty()) throw
1781 // std::runtime_error("The executed operations vector is empty");
1782
1783 const size_t dif = operations.size() - executedOps.size();
1784
1785 for (size_t i = dif; i < operations.size(); ++i)
1786 if (!executedOps[i - dif]) operations[i]->Execute(sim, state);
1787
1788 // sim->Flush();
1789 }
1790
1791 // used internally to optimize measurements in the case of having measurements
1792 // only at the end of the circuit
1793 std::shared_ptr<MeasurementOperation<Time>> GetLastMeasurements(
1794 const std::vector<bool> &executedOps, bool sort = true) const {
1795 const size_t dif = operations.size() - executedOps.size();
1796 std::vector<std::pair<Types::qubit_t, size_t>> measurements;
1797 measurements.reserve(dif);
1798
1799 for (size_t i = dif; i < operations.size(); ++i)
1800 if (!executedOps[i - dif] &&
1801 operations[i]->GetType() == OperationType::kMeasurement) {
1802 auto measOp =
1803 std::static_pointer_cast<MeasurementOperation<Time>>(operations[i]);
1804 const auto &qubits = measOp->GetQubits();
1805 const auto &bits = measOp->GetBitsIndices();
1806
1807 for (size_t j = 0; j < qubits.size(); ++j)
1808 measurements.emplace_back(qubits[j], bits[j]);
1809 }
1810
1811 // qiskit aer expects sometimes to have them in sorted order, so...
1812 if (sort)
1813 std::sort(
1814 measurements.begin(), measurements.end(),
1815 [](const auto &p1, const auto &p2) { return p1.first < p2.first; });
1816
1817 return std::make_shared<MeasurementOperation<Time>>(measurements);
1818 }
1819
1829 for (const auto &op : operations)
1830 if (op->GetType() == OperationType::kConditionalGate ||
1831 op->GetType() == OperationType::kConditionalMeasurement ||
1832 op->GetType() == OperationType::kConditionalRandomGen)
1833 return true;
1834
1835 return false;
1836 }
1837
1852 for (const auto &op : operations) {
1853 const auto qubits = op->AffectedQubits();
1854 if (qubits.size() <= 1) continue;
1855
1856 if (qubits.size() == 2) {
1857 if (std::abs(qubits[0] - qubits[1]) != 1) return false;
1858 } else {
1859 Types::qubit_t minQubit = qubits[0];
1860 Types::qubit_t maxQubit = qubits[0];
1861
1862 for (size_t i = 1; i < qubits.size(); ++i) {
1863 if (qubits[i] < minQubit)
1864 minQubit = qubits[i];
1865 else if (qubits[i] > maxQubit)
1866 maxQubit = qubits[i];
1867 }
1868
1869 if (maxQubit - minQubit >= qubits.size()) return false;
1870 }
1871 }
1872
1873 return true;
1874 }
1875
1883 bool IsForest() const {
1884 std::unordered_map<Types::qubit_t, size_t> qubits;
1885 std::unordered_map<Types::qubit_t, Types::qubits_vector> lastQubits;
1886
1887 for (const auto &op : operations) {
1888 const auto q = op->AffectedQubits();
1889 // one qubit gates or other operations that do not affect qubits do not
1890 // change anything
1891 if (q.size() <= 1) continue;
1892
1893 bool allInTheLastQubits = true;
1894
1895 for (const auto qubit : q) {
1896 if (lastQubits.find(qubit) == lastQubits.end()) {
1897 allInTheLastQubits = false;
1898 break;
1899 } else {
1900 const auto &lastQ = lastQubits[qubit];
1901
1902 for (const auto q1 : q)
1903 if (std::find(lastQ.cbegin(), lastQ.cend(), q1) == lastQ.cend()) {
1904 allInTheLastQubits = false;
1905 break;
1906 }
1907
1908 if (!allInTheLastQubits) break;
1909 }
1910 }
1911
1912 if (allInTheLastQubits) continue;
1913
1914 for (const auto qubit : q) {
1915 if (qubits[qubit] > 1) // if the qubit is affected again...
1916 return false;
1917
1918 ++qubits[qubit];
1919
1920 lastQubits[qubit] = q;
1921 }
1922 }
1923
1924 return true;
1925 }
1926
1936 bool IsClifford() const override {
1937 for (const auto &op : operations)
1938 if (!op->IsClifford()) return false;
1939
1940 return true;
1941 }
1942
1952 double CliffordPercentage() const {
1953 size_t cliffordOps = 0;
1954 for (const auto &op : operations)
1955 if (op->IsClifford()) ++cliffordOps;
1956
1957 return static_cast<double>(cliffordOps) / operations.size();
1958 }
1959
1969 std::unordered_set<Types::qubit_t> GetCliffordQubits() const {
1970 std::unordered_set<Types::qubit_t> cliffordQubits;
1971 std::unordered_set<Types::qubit_t> nonCliffordQubits;
1972
1973 for (const auto &op : operations) {
1974 const auto qubits = op->AffectedQubits();
1975 if (op->IsClifford()) {
1976 for (const auto q : qubits) cliffordQubits.insert(q);
1977 } else {
1978 for (const auto q : qubits) nonCliffordQubits.insert(q);
1979 }
1980 }
1981
1982 for (const auto q : nonCliffordQubits) cliffordQubits.erase(q);
1983
1984 return cliffordQubits;
1985 }
1986
1996 std::vector<std::shared_ptr<Circuit<Time>>> SplitCircuit() const {
1997 std::vector<std::shared_ptr<Circuit<Time>>> circuits;
1998
1999 // find how many disjoint circuits we have in this circuit
2000
2001 std::unordered_map<Types::qubit_t, std::unordered_set<Types::qubit_t>>
2002 circuitsMap;
2003 auto allQubits = GetQubits();
2004 std::unordered_map<Types::qubit_t, Types::qubit_t> qubitCircuitMap;
2005
2006 // start with a bunch of disjoint sets of qubits, containing each a single
2007 // qubit
2008
2009 for (auto qubit : allQubits) {
2010 circuitsMap[qubit] = std::unordered_set<Types::qubit_t>{qubit};
2011 qubitCircuitMap[qubit] = qubit;
2012 }
2013
2014 // then the gates will join them together into circuits
2015
2016 for (const auto &op : operations) {
2017 const auto qubits = op->AffectedQubits();
2018
2019 if (qubits.empty()) continue;
2020
2021 auto qubitIt = qubits.cbegin();
2022 auto firstQubit = *qubitIt;
2023 // where is the first qubit in the disjoint sets?
2024 auto firstQubitCircuit = qubitCircuitMap[firstQubit];
2025
2026 ++qubitIt;
2027
2028 for (; qubitIt != qubits.cend(); ++qubitIt) {
2029 auto qubit = *qubitIt;
2030
2031 // where is the qubit in the disjoint sets?
2032 auto qubitCircuit = qubitCircuitMap[qubit];
2033
2034 // join the circuits / qubits sets
2035
2036 if (firstQubitCircuit != qubitCircuit) {
2037 // join the circuits
2038 circuitsMap[firstQubitCircuit].insert(
2039 circuitsMap[qubitCircuit].begin(),
2040 circuitsMap[qubitCircuit].end());
2041
2042 // update the qubit to circuit map
2043 for (auto q : circuitsMap[qubitCircuit])
2044 qubitCircuitMap[q] = firstQubitCircuit;
2045
2046 // remove the joined circuit
2047 circuitsMap.erase(qubitCircuit);
2048 }
2049 }
2050 }
2051
2052 size_t circSize = 1ULL;
2053 // static cast added to prevent compiler error on MacOS
2054 circSize = std::max(circSize, static_cast<size_t>(circuitsMap.size()));
2055 circuits.resize(circSize);
2056
2057 for (size_t i = 0; i < circuits.size(); ++i)
2058 circuits[i] = std::make_shared<Circuit<Time>>();
2059
2060 std::unordered_map<Types::qubit_t, size_t> qubitsSetsToCircuit;
2061
2062 size_t circuitNo = 0;
2063 for (const auto &[id, qubitSet] : circuitsMap) {
2064 qubitsSetsToCircuit[id] = circuitNo;
2065
2066 ++circuitNo;
2067 }
2068
2069 // now fill them up with the operations
2070
2071 for (const auto &op : operations) {
2072 const auto qubits = op->AffectedQubits();
2073
2074 if (qubits.empty()) {
2075 circuits[0]->AddOperation(op->Clone());
2076 continue;
2077 }
2078
2079 const auto circ = qubitsSetsToCircuit[qubitCircuitMap[*qubits.cbegin()]];
2080
2081 circuits[circ]->AddOperation(op->Clone());
2082 }
2083
2084 return circuits;
2085 }
2086
2094 std::vector<std::shared_ptr<Circuits::Circuit<Time>>> ToLayers() const {
2095 std::vector<std::shared_ptr<Circuits::Circuit<Time>>> layers;
2096 layers.emplace_back(std::make_shared<Circuits::Circuit<Time>>());
2097
2098 std::unordered_map<Types::qubit_t, Types::qubit_t> qubitsUsed;
2099
2100 for (const auto &op : GetOperations()) {
2101 // check the instruction, see if a new layer is needed
2102
2103 // only qubits matter here, the others can be classicaly sent if they are
2104 // needed, even if they are shared... but that should be handled
2105 // somewhere, when not done implicitely (as for the 'simple network' case)
2106 if (op->CanAffectQuantumState()) {
2107 const auto qubits = op->AffectedQubits();
2108 size_t maxLevel = 0;
2109
2110 for (Types::qubit_t qbit : qubits) {
2111 ++qubitsUsed[qbit];
2112 maxLevel = std::max(maxLevel, static_cast<size_t>(qubitsUsed[qbit]));
2113
2114 if (layers.size() < qubitsUsed[qbit]) {
2115 auto circ = std::make_shared<Circuits::Circuit<Time>>();
2116 layers.push_back(std::move(circ));
2117 }
2118 }
2119
2120 // now set all the qubits in the instruction to the max level
2121 for (Types::qubit_t qbit : qubits) qubitsUsed[qbit] = maxLevel;
2122
2123 layers[maxLevel - 1]->AddOperation(op->Clone());
2124 } else
2125 // add the instruction to the last layer
2126 layers.back()->AddOperation(op->Clone());
2127 }
2128
2129 return layers;
2130 }
2131
2140 std::vector<std::shared_ptr<Circuits::Circuit<Time>>> ToLayersNoClone()
2141 const {
2142 std::vector<std::shared_ptr<Circuits::Circuit<Time>>> layers;
2143 layers.emplace_back(std::make_shared<Circuits::Circuit<Time>>());
2144
2145 std::unordered_map<Types::qubit_t, Types::qubit_t> qubitsUsed;
2146
2147 for (const auto &op : GetOperations()) {
2148 // check the instruction, see if a new layer is needed
2149
2150 // only qubits matter here, the others can be classicaly sent if they are
2151 // needed, even if they are shared... but that should be handled
2152 // somewhere, when not done implicitely (as for the 'simple network' case)
2153 if (op->CanAffectQuantumState()) {
2154 const auto qubits = op->AffectedQubits();
2155 size_t maxLevel = 0;
2156
2157 for (Types::qubit_t qbit : qubits) {
2158 ++qubitsUsed[qbit];
2159 maxLevel = std::max(maxLevel, static_cast<size_t>(qubitsUsed[qbit]));
2160
2161 if (layers.size() < qubitsUsed[qbit]) {
2162 auto circ = std::make_shared<Circuits::Circuit<Time>>();
2163 layers.push_back(std::move(circ));
2164 }
2165 }
2166
2167 // now set all the qubits in the instruction to the max level
2168 for (Types::qubit_t qbit : qubits) qubitsUsed[qbit] = maxLevel;
2169
2170 layers[maxLevel - 1]->AddOperation(op);
2171 } else
2172 // add the instruction to the last layer
2173 layers.back()->AddOperation(op);
2174 }
2175
2176 return layers;
2177 }
2178
2189 std::vector<std::shared_ptr<Circuits::Circuit<Time>>> ToMultipleQubitsLayers()
2190 const {
2191 std::vector<std::shared_ptr<Circuits::Circuit<Time>>> layers;
2192 layers.emplace_back(std::make_shared<Circuits::Circuit<Time>>());
2193
2194 std::unordered_map<Types::qubit_t, Types::qubit_t> qubitsUsed;
2195
2196 for (const auto &op : GetOperations()) {
2197 // check the instruction, see if a new layer is needed
2198
2199 // only qubits matter here, the others can be classicaly sent if they are
2200 // needed, even if they are shared... but that should be handled
2201 // somewhere, when not done implicitely (as for the 'simple network' case)
2202 if (op->CanAffectQuantumState()) {
2203 const auto qubits = op->AffectedQubits();
2204 size_t maxLevel = 0;
2205
2206 for (Types::qubit_t qbit : qubits) {
2207 if (qubits.size() > 1) ++qubitsUsed[qbit];
2208 maxLevel = std::max(maxLevel, static_cast<size_t>(qubitsUsed[qbit]));
2209
2210 if (layers.size() < qubitsUsed[qbit]) {
2211 auto circ = std::make_shared<Circuits::Circuit<Time>>();
2212 layers.push_back(std::move(circ));
2213 }
2214 }
2215
2216 // now set all the qubits in the instruction to the max level
2217 for (Types::qubit_t qbit : qubits) qubitsUsed[qbit] = maxLevel;
2218
2219 layers[maxLevel > 0 ? maxLevel - 1 : 0]->AddOperation(op->Clone());
2220 } else
2221 // add the instruction to the last layer
2222 layers.back()->AddOperation(op->Clone());
2223 }
2224
2225 return layers;
2226 }
2227
2238 std::vector<std::shared_ptr<Circuits::Circuit<Time>>>
2240 std::vector<std::shared_ptr<Circuits::Circuit<Time>>> layers;
2241 layers.emplace_back(std::make_shared<Circuits::Circuit<Time>>());
2242
2243 std::unordered_map<Types::qubit_t, Types::qubit_t> qubitsUsed;
2244
2245 for (const auto &op : GetOperations()) {
2246 // check the instruction, see if a new layer is needed
2247
2248 // only qubits matter here, the others can be classicaly sent if they are
2249 // needed, even if they are shared... but that should be handled
2250 // somewhere, when not done implicitely (as for the 'simple network' case)
2251 if (op->CanAffectQuantumState()) {
2252 const auto qubits = op->AffectedQubits();
2253 size_t maxLevel = 0;
2254
2255 for (Types::qubit_t qbit : qubits) {
2256 if (qubits.size() > 1) ++qubitsUsed[qbit];
2257 maxLevel = std::max(maxLevel, static_cast<size_t>(qubitsUsed[qbit]));
2258
2259 if (layers.size() < qubitsUsed[qbit]) {
2260 auto circ = std::make_shared<Circuits::Circuit<Time>>();
2261 layers.push_back(std::move(circ));
2262 }
2263 }
2264
2265 // now set all the qubits in the instruction to the max level
2266 for (Types::qubit_t qbit : qubits) qubitsUsed[qbit] = maxLevel;
2267
2268 layers[maxLevel > 0 ? maxLevel - 1 : 0]->AddOperation(op);
2269 } else
2270 // add the instruction to the last layer
2271 layers.back()->AddOperation(op);
2272 }
2273
2274 return layers;
2275 }
2276
2286 static std::shared_ptr<Circuits::Circuit<Time>> LayersToCircuit(
2287 const std::vector<std::shared_ptr<Circuits::Circuit<Time>>> &layers) {
2288 auto circuit{std::make_shared<Circuits::Circuit<Time>>()};
2289
2290 for (const auto &layer : layers)
2291 circuit->AddOperations(layer->GetOperations());
2292
2293 return circuit;
2294 }
2295
2302 iterator begin() noexcept { return operations.begin(); }
2303
2310 iterator end() noexcept { return operations.end(); }
2311
2318 const_iterator cbegin() const noexcept { return operations.cbegin(); }
2319
2326 const_iterator cend() const noexcept { return operations.cend(); }
2327
2334 reverse_iterator rbegin() noexcept { return operations.rbegin(); }
2335
2342 reverse_iterator rend() noexcept { return operations.rend(); }
2343
2351 return operations.crbegin();
2352 }
2353
2360 const_reverse_iterator crend() const noexcept { return operations.crend(); }
2361
2368 auto size() const { return operations.size(); }
2369
2376 auto empty() const { return operations.empty(); }
2377
2385 auto &operator[](size_t pos) { return operations[pos]; }
2386
2394 const auto &operator[](size_t pos) const { return operations[pos]; }
2395
2403 void resize(size_t size) {
2404 if (size < operations.size()) operations.resize(size);
2405 }
2406
2407 private:
2418 void ReplaceThreeQubitAndSwapGates(bool onlyThreeQubits = false) {
2419 // just replace all three qubit gates(just ccnot and cswap will exist in the
2420 // first phase) with several gates on less qubits also replace swap gates
2421 // with three cnots (in the first phase) the controlled ones must be
2422 // replaced as well
2423
2424 // TODO: if composite operations will be implemented, those need to be
2425 // optimized as well
2426
2427 std::vector<std::shared_ptr<IOperation<Time>>> newops;
2428 newops.reserve(operations.size());
2429
2430 for (std::shared_ptr<IOperation<Time>> op : operations) {
2431 if (op->GetType() == OperationType::kGate) {
2432 std::shared_ptr<IQuantumGate<Time>> gate =
2433 std::static_pointer_cast<IQuantumGate<Time>>(op);
2434
2435 if (NeedsConversion(gate, onlyThreeQubits)) {
2436 std::vector<std::shared_ptr<IGateOperation<Time>>> newgates =
2437 ConvertGate(gate, onlyThreeQubits);
2438 newops.insert(newops.end(), newgates.begin(), newgates.end());
2439 } else
2440 newops.push_back(op);
2441 } else if (op->GetType() == OperationType::kConditionalGate) {
2442 std::shared_ptr<ConditionalGate<Time>> condgate =
2443 std::static_pointer_cast<ConditionalGate<Time>>(op);
2444 std::shared_ptr<IQuantumGate<Time>> gate =
2445 std::static_pointer_cast<IQuantumGate<Time>>(
2446 condgate->GetOperation());
2447
2448 if (NeedsConversion(gate, onlyThreeQubits)) {
2449 std::vector<std::shared_ptr<IGateOperation<Time>>> newgates =
2450 ConvertGate(gate, onlyThreeQubits);
2451 std::shared_ptr<ICondition> cond = condgate->GetCondition();
2452
2453 for (auto gate : newgates)
2454 newops.push_back(
2455 std::make_shared<ConditionalGate<Time>>(gate, cond));
2456 } else
2457 newops.push_back(op);
2458 } else
2459 newops.push_back(op);
2460 }
2461
2462 operations.swap(newops);
2463 }
2464
2474 static bool NeedsConversion(const std::shared_ptr<IQuantumGate<Time>> &gate,
2475 bool onlyThreeQubits = false) {
2476 const bool hasThreeQubits = gate->GetNumQubits() == 3;
2477 if (onlyThreeQubits) return hasThreeQubits;
2478
2479 return hasThreeQubits ||
2480 gate->GetGateType() == QuantumGateType::kSwapGateType;
2481 }
2482
2495 static std::vector<std::shared_ptr<IGateOperation<Time>>> ConvertGate(
2496 std::shared_ptr<IQuantumGate<Time>> &gate, bool onlyThreeQubits = false) {
2497 // TODO: if delays are used, how to transfer delays from the converted gate
2498 // to the resulting gates?
2499 std::vector<std::shared_ptr<IGateOperation<Time>>> newops;
2500
2501 if (gate->GetNumQubits() == 3) {
2502 // must be converted no matter what
2503 if (gate->GetGateType() == QuantumGateType::kCCXGateType) {
2504 const size_t q1 = gate->GetQubit(0); // control 1
2505 const size_t q2 = gate->GetQubit(1); // control 2
2506 const size_t q3 = gate->GetQubit(2); // target
2507
2508 // Sleator-Weinfurter decomposition
2509 newops.push_back(std::make_shared<CSxGate<Time>>(q2, q3));
2510 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
2511 newops.push_back(std::make_shared<CSxDagGate<Time>>(q2, q3));
2512 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
2513 newops.push_back(std::make_shared<CSxGate<Time>>(q1, q3));
2514 } else if (gate->GetGateType() == QuantumGateType::kCSwapGateType) {
2515 const size_t q1 = gate->GetQubit(0); // control 1
2516 const size_t q2 = gate->GetQubit(1); // control 2
2517 const size_t q3 = gate->GetQubit(2); // target
2518
2519 // TODO: find a better decomposition
2520 // this one I've got with the qiskit transpiler
2521 newops.push_back(std::make_shared<CXGate<Time>>(q3, q2));
2522
2523 newops.push_back(std::make_shared<CSxGate<Time>>(q2, q3));
2524 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
2525 newops.push_back(std::make_shared<PhaseGate<Time>>(q3, M_PI));
2526
2527 newops.push_back(std::make_shared<PhaseGate<Time>>(q2, -M_PI_2));
2528
2529 newops.push_back(std::make_shared<CSxGate<Time>>(q2, q3));
2530 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
2531 newops.push_back(std::make_shared<PhaseGate<Time>>(q3, M_PI));
2532
2533 newops.push_back(std::make_shared<CSxGate<Time>>(q1, q3));
2534
2535 newops.push_back(std::make_shared<CXGate<Time>>(q3, q2));
2536 } else
2537 newops.push_back(gate);
2538 } else if (!onlyThreeQubits &&
2539 gate->GetGateType() == QuantumGateType::kSwapGateType) {
2540 // must be converted no matter what
2541 const size_t q1 = gate->GetQubit(0);
2542 const size_t q2 = gate->GetQubit(1);
2543
2544 // for now replace it with three cnots, but maybe later make it
2545 // configurable there are other possibilities, for example three cy gates
2546 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
2547 newops.push_back(std::make_shared<CXGate<Time>>(q2, q1));
2548 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
2549 } else
2550 newops.push_back(gate);
2551
2552 return newops;
2553 }
2554
2555 OperationsVector operations;
2556};
2557
2571template <typename Time = Types::time_type>
2572class ComparableCircuit : public Circuit<Time> {
2573 public:
2576 using OperationPtr = std::shared_ptr<Operation>;
2579 std::vector<OperationPtr>;
2589
2599
2609
2610 return *this;
2611 }
2612
2619 bool operator==(const BaseClass &rhs) const {
2620 if (BaseClass::GetOperations().size() != rhs.GetOperations().size())
2621 return false;
2622
2623 for (size_t i = 0; i < BaseClass::GetOperations().size(); ++i) {
2624 if (BaseClass::GetOperations()[i]->GetType() !=
2625 rhs.GetOperations()[i]->GetType())
2626 return false;
2627
2628 switch (BaseClass::GetOperations()[i]->GetType()) {
2630 if (std::static_pointer_cast<IQuantumGate<Time>>(
2632 ->GetGateType() !=
2633 std::static_pointer_cast<IQuantumGate<Time>>(
2634 rhs.GetOperations()[i])
2635 ->GetGateType() ||
2636 BaseClass::GetOperations()[i]->AffectedBits() !=
2637 rhs.GetOperations()[i]->AffectedBits())
2638 return false;
2639 if (approximateParamsCheck) {
2640 const auto params1 = std::static_pointer_cast<IQuantumGate<Time>>(
2642 ->GetParams();
2643 const auto params2 = std::static_pointer_cast<IQuantumGate<Time>>(
2644 rhs.GetOperations()[i])
2645 ->GetParams();
2646 if (params1.size() != params2.size()) return false;
2647
2648 for (size_t j = 0; j < params1.size(); ++j)
2649 if (std::abs(params1[j] - params2[j]) > paramsEpsilon)
2650 return false;
2651 } else if (std::static_pointer_cast<IQuantumGate<Time>>(
2653 ->GetParams() !=
2654 std::static_pointer_cast<IQuantumGate<Time>>(
2655 rhs.GetOperations()[i])
2656 ->GetParams())
2657 return false;
2658 break;
2661 rhs.GetOperations()[i]->AffectedQubits() ||
2662 BaseClass::GetOperations()[i]->AffectedBits() !=
2663 rhs.GetOperations()[i]->AffectedBits())
2664 return false;
2665 break;
2668 rhs.GetOperations()[i]->AffectedBits())
2669 return false;
2670 break;
2674 // first, check the conditions
2675 const auto leftCondition =
2676 std::static_pointer_cast<IConditionalOperation<Time>>(
2678 ->GetCondition();
2679 const auto rightCondition =
2680 std::static_pointer_cast<IConditionalOperation<Time>>(
2681 rhs.GetOperations()[i])
2682 ->GetCondition();
2683 if (leftCondition->GetBitsIndices() !=
2684 rightCondition->GetBitsIndices())
2685 return false;
2686
2687 const auto leftEqCondition =
2688 std::static_pointer_cast<EqualCondition>(leftCondition);
2689 const auto rightEqCondition =
2690 std::static_pointer_cast<EqualCondition>(rightCondition);
2691 if (!leftEqCondition || !rightEqCondition) return false;
2692
2693 if (leftEqCondition->GetAllBits() != rightEqCondition->GetAllBits())
2694 return false;
2695
2696 // now check the operations
2697 const auto leftOp =
2698 std::static_pointer_cast<IConditionalOperation<Time>>(
2700 ->GetOperation();
2701 const auto rightOp =
2702 std::static_pointer_cast<IConditionalOperation<Time>>(
2703 rhs.GetOperations()[i])
2704 ->GetOperation();
2705
2706 ComparableCircuit<Time> leftCircuit;
2707 BaseClass rightCircuit;
2708 leftCircuit.SetApproximateParamsCheck(approximateParamsCheck);
2709 leftCircuit.AddOperation(leftOp);
2710 rightCircuit.AddOperation(rightOp);
2711
2712 if (leftCircuit != rightCircuit) return false;
2713 } break;
2716 rhs.GetOperations()[i]->AffectedQubits() ||
2717 std::static_pointer_cast<Reset<Time>>(
2719 ->GetResetTargets() !=
2720 std::static_pointer_cast<Reset<Time>>(rhs.GetOperations()[i])
2721 ->GetResetTargets())
2722 return false;
2723 break;
2725 break;
2726 default:
2727 return false;
2728 }
2729
2730 if (BaseClass::GetOperations()[i]->GetDelay() !=
2731 rhs.GetOperations()[i]->GetDelay())
2732 return false;
2733 }
2734
2735 return true;
2736 }
2737
2744 bool operator!=(const BaseClass &rhs) const { return !(*this == rhs); }
2745
2752 void SetApproximateParamsCheck(bool check) { approximateParamsCheck = check; }
2753
2760 bool GetApproximateParamsCheck() const { return approximateParamsCheck; }
2761
2770 void SetParamsEpsilon(double eps) { paramsEpsilon = eps; }
2771
2780 double GetParamsEpsilon() const { return paramsEpsilon; }
2781
2782 private:
2783 bool approximateParamsCheck =
2784 false;
2785 double paramsEpsilon = 1e-8;
2787};
2788
2789} // namespace Circuits
2790
2791#endif // !_CIRCUIT_H_
The controlled P gate.
The controlled x rotation gate.
The controlled y rotation gate.
The controlled z rotation gate.
Circuit class for holding the sequence of operations.
Definition Circuit.h:46
iterator begin() noexcept
Get the begin iterator for the operations.
Definition Circuit.h:2302
typename OperationsVector::const_reverse_iterator const_reverse_iterator
Definition Circuit.h:76
void ConvertForCutting()
Converts the circuit for distributed computing.
Definition Circuit.h:401
typename OperationsVector::reverse_iterator reverse_iterator
Definition Circuit.h:74
void Execute(const std::shared_ptr< Simulators::ISimulator > &sim, OperationState &state) const override
Execute the circuit on the given simulator.
Definition Circuit.h:96
typename OperationsVector::allocator_type allocator_type
Definition Circuit.h:64
std::unordered_map< Types::qubit_t, Types::qubit_t > BitMapping
The (qu)bit mapping for remapping.
Definition Circuit.h:55
const_iterator cbegin() const noexcept
Get the const begin iterator for the operations.
Definition Circuit.h:2318
double CliffordPercentage() const
Get the percentage of Clifford operations in the circuit.
Definition Circuit.h:1952
auto size() const
Get the number of operations in the circuit.
Definition Circuit.h:2368
bool IsClifford() const override
Checks if the circuit is a Clifford circuit.
Definition Circuit.h:1936
iterator end() noexcept
Get the end iterator for the operations.
Definition Circuit.h:2310
std::unordered_set< Types::qubit_t > GetCliffordQubits() const
Get the qubits that are acted on by Clifford operations.
Definition Circuit.h:1969
typename OperationsVector::iterator iterator
Definition Circuit.h:72
void AddOperation(const OperationPtr &op)
Adds an operation to the circuit.
Definition Circuit.h:121
std::set< size_t > GetBits() const
Returns the classical bits affected by the operations.
Definition Circuit.h:598
void Optimize(bool optimizeRotationGates=true)
Circuit optimization.
Definition Circuit.h:756
void EnsureProperOrderForMeasurements()
Definition Circuit.h:420
std::pair< size_t, Time > GetMaxDepth() const
Get max circuit depth.
Definition Circuit.h:1514
std::vector< std::shared_ptr< Circuits::Circuit< Time > > > ToMultipleQubitsLayersNoClone() const
Converts the circuit to layers oriented on multiple qubit gates.
Definition Circuit.h:2239
std::set< size_t > GetQubits() const
Returns the qubits affected by the operations.
Definition Circuit.h:581
std::vector< OperationPtr > OperationsVector
The vector of operations.
Definition Circuit.h:61
void AddResetsAtBeginningIfNeeded(Time delay=0)
Add resets at the beginning of the circuit.
Definition Circuit.h:737
std::vector< std::shared_ptr< Circuits::Circuit< Time > > > ToMultipleQubitsLayers() const
Converts the circuit to layers oriented on multiple qubit gates.
Definition Circuit.h:2189
void Clear()
Clears the operations from the circuit.
Definition Circuit.h:180
typename OperationsVector::value_type value_type
Definition Circuit.h:63
std::shared_ptr< Operation > OperationPtr
The shared pointer to the operation type.
Definition Circuit.h:59
typename OperationsVector::reference reference
Definition Circuit.h:67
bool CanAffectQuantumState() const override
Find if the circuit can affect the quantum state.
Definition Circuit.h:665
auto & operator[](size_t pos)
Get the operation at a given position.
Definition Circuit.h:2385
typename OperationsVector::difference_type difference_type
Definition Circuit.h:70
std::pair< std::vector< size_t >, std::vector< Time > > GetDepth() const
Get circuit depth.
Definition Circuit.h:1414
std::unordered_map< size_t, OperationPtr > GetLastOperationsOnQubits() const
Returns the last operations on circuit's qubits.
Definition Circuit.h:679
const_reverse_iterator crend() const noexcept
Get the const reverse end iterator for the operations.
Definition Circuit.h:2360
std::shared_ptr< MeasurementOperation< Time > > GetLastMeasurements(const std::vector< bool > &executedOps, bool sort=true) const
Definition Circuit.h:1793
bool ActsOnlyOnAdjacentQubits() const
Checks if the circuit has only operations that act on adjacent qubits.
Definition Circuit.h:1851
Circuit(const OperationsVector &ops={})
Construct a new Circuit object.
Definition Circuit.h:85
typename OperationsVector::pointer pointer
Definition Circuit.h:65
std::unordered_map< std::vector< bool >, size_t > ExecuteResults
The results of the execution of the circuit.
Definition Circuit.h:51
typename OperationsVector::size_type size_type
Definition Circuit.h:69
auto empty() const
Check if the circuit is empty.
Definition Circuit.h:2376
OperationPtr Remap(const BitMapping &qubitsMap, const BitMapping &bitsMap={}) const override
Get a shared pointer to a circuit remapped.
Definition Circuit.h:221
static void AccumulateResults(ExecuteResults &results, const ExecuteResults &newResults)
Accumulate the results of a circuit execution to already existing results.
Definition Circuit.h:326
std::shared_ptr< Circuit< Time > > GetCircuitCut(Types::qubit_t startQubit, Types::qubit_t endQubit) const
Get the circuit cut.
Definition Circuit.h:1562
void ConvertForDistribution()
Converts the circuit for distributed computing.
Definition Circuit.h:386
Types::qubits_vector AffectedQubits() const override
Returns the affected qubits.
Definition Circuit.h:614
bool HasOpsAfterMeasurements() const
Checks if the circuit has measurements that are followed by operations that affect the measured qubit...
Definition Circuit.h:1602
IOperation< Time > Operation
The operation type.
Definition Circuit.h:57
OperationPtr CloneFlyweight() const
Get a shared pointer to a clone of this object, but without cloning the operations.
Definition Circuit.h:203
bool HasConditionalOperations() const
Checks if the circuit has clasically conditional operations.
Definition Circuit.h:1828
void AddResetsIfNeeded(Time delay=0)
Add resets at the end of the circuit.
Definition Circuit.h:718
static void AccumulateResultsWithRemapBack(ExecuteResults &results, const ExecuteResults &newResults, const BitMapping &bitsMap={}, bool ignoreNotMapped=true, size_t sz=0)
Accumulate the results of a circuit execution to already existing results with remapping.
Definition Circuit.h:345
const_reverse_iterator crbegin() const noexcept
Get the const reverse begin iterator for the operations.
Definition Circuit.h:2350
OperationPtr GetOperation(size_t pos) const
Get an operation at a given position.
Definition Circuit.h:1543
const_iterator cend() const noexcept
Get the const end iterator for the operations.
Definition Circuit.h:2326
void MoveMeasurementsAndResets()
Move the measurements and resets closer to the beginning of the circuit.
Definition Circuit.h:1278
std::shared_ptr< Circuit< Time > > RemapToContinuous(BitMapping &newQubitsMap, BitMapping &reverseBitsMap, size_t &nrQubits, size_t &nrCbits) const
Get a shared pointer to a circuit remapped to a continuous interval starting from zero.
Definition Circuit.h:242
typename OperationsVector::const_iterator const_iterator
Definition Circuit.h:73
std::vector< std::shared_ptr< Circuits::Circuit< Time > > > ToLayers() const
Converts the circuit to layers.
Definition Circuit.h:2094
bool NeedsEntanglementForDistribution() const override
Find if the circuit needs entanglement for distribution.
Definition Circuit.h:651
std::unordered_map< size_t, OperationPtr > GetFirstOperationsOnQubits() const
Returns the first operations on circuit's qubits.
Definition Circuit.h:697
reverse_iterator rbegin() noexcept
Get the reverse begin iterator for the operations.
Definition Circuit.h:2334
void SetOperations(const OperationsVector &ops)
Set the operations in the circuit.
Definition Circuit.h:143
typename OperationsVector::const_pointer const_pointer
Definition Circuit.h:66
void AddOperations(const OperationsVector &ops)
Adds operations to the circuit.
Definition Circuit.h:152
size_t GetMaxQubitIndex() const
Returns the max qubit id for all operations.
Definition Circuit.h:507
OperationPtr Clone() const override
Get a shared pointer to a clone of this object.
Definition Circuit.h:188
size_t GetMaxCbitIndex() const
Returns the max classical bit id for all operations.
Definition Circuit.h:543
void AddCircuit(const std::shared_ptr< Circuit< Time > > &circuit)
Adds operations from another circuit to the circuit.
Definition Circuit.h:162
typename OperationsVector::const_reference const_reference
Definition Circuit.h:68
static ExecuteResults RemapResultsBack(const ExecuteResults &results, const BitMapping &bitsMap={}, bool ignoreNotMapped=false, size_t sz=0)
Map back the results for a remapped circuit.
Definition Circuit.h:294
static std::shared_ptr< Circuits::Circuit< Time > > LayersToCircuit(const std::vector< std::shared_ptr< Circuits::Circuit< Time > > > &layers)
Converts the layers back to a circuit.
Definition Circuit.h:2286
const auto & operator[](size_t pos) const
Get the operation at a given position.
Definition Circuit.h:2394
std::vector< std::shared_ptr< Circuits::Circuit< Time > > > ToLayersNoClone() const
Converts the circuit to layers.
Definition Circuit.h:2140
size_t GetMinCbitIndex() const
Returns the min classical bit id for all operations.
Definition Circuit.h:561
size_t GetNumberOfOperations() const
Get the number of operations in the circuit.
Definition Circuit.h:1533
std::vector< size_t > AffectedBits() const override
Returns the affected bits.
Definition Circuit.h:631
size_t GetMinQubitIndex() const
Returns the min qubit id for all operations.
Definition Circuit.h:525
void resize(size_t size)
Resizes the circuit.
Definition Circuit.h:2403
std::vector< bool > ExecuteNonMeasurements(const std::shared_ptr< Simulators::ISimulator > &sim, OperationState &state) const
Execute the non-measurements operations from the circuit on the given simulator.
Definition Circuit.h:1677
OperationType GetType() const override
Get the type of the circuit.
Definition Circuit.h:112
void ExecuteMeasurements(const std::shared_ptr< Simulators::ISimulator > &sim, OperationState &state, const std::vector< bool > &executedOps) const
Execute the measurement operations from the circuit on the given simulator.
Definition Circuit.h:1774
void ReplaceOperation(size_t index, const OperationPtr &op)
Replaces an operation in the circuit.
Definition Circuit.h:131
reverse_iterator rend() noexcept
Get the reverse end iterator for the operations.
Definition Circuit.h:2342
bool IsForest() const
Checks if the circuit is a forest circuit.
Definition Circuit.h:1883
std::vector< std::shared_ptr< Circuit< Time > > > SplitCircuit() const
Splits a circuit that has disjoint subcircuits in it into separate circuits.
Definition Circuit.h:1996
const OperationsVector & GetOperations() const
Get the operations in the circuit.
Definition Circuit.h:173
Circuit class for holding the sequence of operations that can be compared with another circuit.
Definition Circuit.h:2572
std::shared_ptr< Operation > OperationPtr
The shared pointer to the operation type.
Definition Circuit.h:2577
double GetParamsEpsilon() const
Gets the epsilon used for checking approximate equality of gate parameters.
Definition Circuit.h:2780
std::vector< OperationPtr > OperationsVector
The vector of operations.
Definition Circuit.h:2579
void SetApproximateParamsCheck(bool check)
Sets whether to check approximate equality of gate parameters.
Definition Circuit.h:2752
ComparableCircuit & operator=(const BaseClass &circ)
Assignment operator.
Definition Circuit.h:2607
bool operator==(const BaseClass &rhs) const
Comparison operator.
Definition Circuit.h:2619
bool GetApproximateParamsCheck() const
Gets whether to check approximate equality of gate parameters.
Definition Circuit.h:2760
ComparableCircuit(const BaseClass &circ)
Construct a new ComparableCircuit object.
Definition Circuit.h:2598
bool operator!=(const BaseClass &rhs) const
Comparison operator.
Definition Circuit.h:2744
Circuit< Time > BaseClass
The base class type.
Definition Circuit.h:2574
ComparableCircuit(const OperationsVector &ops={})
Construct a new ComparableCircuit object.
Definition Circuit.h:2588
void SetParamsEpsilon(double eps)
Sets the epsilon used for checking approximate equality of gate parameters.
Definition Circuit.h:2770
The operation interface.
Definition Operations.h:358
Time GetDelay() const
Get the delay of the operation.
Definition Operations.h:497
The interface for quantum gates.
virtual QuantumGateType GetGateType() const =0
Get the type of the quantum gate.
virtual std::vector< double > GetParams() const
Get the gate parameters.
Measurement operation class.
The state class that stores the classical state of a quantum circuit execution.
Definition Operations.h:63
void Reset(bool value=false)
Set the classical bits with the specified value.
Definition Operations.h:244
The phase gate.
Reset operation class.
Definition Reset.h:33
const std::vector< bool > & GetResetTargets() const
Get the values to reset the qubits to.
Definition Reset.h:112
The S gate.
The S dagger gate.
The X gate.
The Z gate.
OperationType
The type of operations.
Definition Operations.h:27
@ kConditionalGate
conditional gate, similar with gate, but conditioned on something from 'OperationState'
@ kNoOp
no operation, just a placeholder, could be used to erase some operation from a circuit
@ kComposite
a composite operation, contains other operations - should not be used in the beginning,...
@ kRandomGen
random classical bit generator, result in 'OperationState'
@ kConditionalRandomGen
conditional random generator, similar with random gen, but conditioned on something from 'OperationSt...
@ kConditionalMeasurement
conditional measurement, similar with measurement, but conditioned on something from 'OperationState'
@ kMeasurement
measurement, result in 'OperationState'
@ kGate
the usual quantum gate, result stays in simulator's state
@ kReset
reset, no result in 'state', just apply measurement, then apply not on all qubits that were measured ...
uint_fast64_t qubit_t
The type of a qubit.
Definition Types.h:21
std::vector< qubit_t > qubits_vector
The type of a vector of qubits.
Definition Types.h:23