Maestro 0.2.11
Unified interface for quantum circuit simulation
Loading...
Searching...
No Matches
Circuit.h
Go to the documentation of this file.
1
16
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,
56
58 using OperationPtr = std::shared_ptr<Operation>;
61 std::vector<OperationPtr>;
62
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) {
1755 executionStopped = true;
1756 if (sim && sim->GetSimulationType() ==
1757 Simulators::SimulationType::kMatrixProductState && sim->SupportsMPSSwapOptimization() &&
1758 op->GetType() != OperationType::kRandomGen &&
1759 op->GetType() != OperationType::kConditionalRandomGen && op->GetType() != OperationType::kNoOp)
1760 sim->SetGatesCounter(sim->GetGatesCounter() + 1);
1761 }
1762 if (executionStopped) executedOps.emplace_back(executed);
1763 }
1764
1765 // if (sim) sim->Flush();
1766
1767 return executedOps;
1768 }
1769
1781 void ExecuteMeasurements(const std::shared_ptr<Simulators::ISimulator> &sim,
1782 OperationState &state,
1783 const std::vector<bool> &executedOps) const {
1784 state.Reset();
1785 if (!sim) return;
1786
1787 // if (executedOps.empty() && !operations.empty()) throw
1788 // std::runtime_error("The executed operations vector is empty");
1789
1790 const size_t dif = operations.size() - executedOps.size();
1791
1792 for (size_t i = dif; i < operations.size(); ++i)
1793 if (!executedOps[i - dif]) operations[i]->Execute(sim, state);
1794
1795 // sim->Flush();
1796 }
1797
1798
1808 std::shared_ptr<Circuits::Circuit<Time>> RemoveExecutedOperations(
1809 std::vector<bool> &executedOps) const {
1810 if (executedOps.empty()) {
1811 executedOps.resize(size(), false);
1812 return std::make_shared<Circuit<Time>>(operations);
1813 }
1814
1815 OperationsVector newops;
1816 newops.reserve(operations.size());
1817
1818 const size_t dif = operations.size() - executedOps.size();
1819 for (size_t i = dif; i < operations.size(); ++i)
1820 if (!executedOps[i - dif]) newops.emplace_back(operations[i]);
1821
1822 std::vector<bool> newExecutedOps(newops.size(), false);
1823 executedOps.swap(newExecutedOps);
1824
1825 return std::make_shared<Circuit<Time>>(newops);
1826 }
1827
1828
1829
1830 // used internally to optimize measurements in the case of having measurements
1831 // only at the end of the circuit
1832 std::shared_ptr<MeasurementOperation<Time>> GetLastMeasurements(
1833 const std::vector<bool> &executedOps, bool sort = true) const {
1834 const size_t dif = operations.size() - executedOps.size();
1835 std::vector<std::pair<Types::qubit_t, size_t>> measurements;
1836 measurements.reserve(dif);
1837
1838 for (size_t i = dif; i < operations.size(); ++i)
1839 if (!executedOps[i - dif] &&
1840 operations[i]->GetType() == OperationType::kMeasurement) {
1841 auto measOp =
1842 std::static_pointer_cast<MeasurementOperation<Time>>(operations[i]);
1843 const auto &qubits = measOp->GetQubits();
1844 const auto &bits = measOp->GetBitsIndices();
1845
1846 for (size_t j = 0; j < qubits.size(); ++j)
1847 measurements.emplace_back(qubits[j], bits[j]);
1848 }
1849
1850 // qiskit aer expects sometimes to have them in sorted order, so...
1851 if (sort)
1852 std::sort(
1853 measurements.begin(), measurements.end(),
1854 [](const auto &p1, const auto &p2) { return p1.first < p2.first; });
1855
1856 return std::make_shared<MeasurementOperation<Time>>(measurements);
1857 }
1858
1868 for (const auto &op : operations)
1869 if (op->GetType() == OperationType::kConditionalGate ||
1870 op->GetType() == OperationType::kConditionalMeasurement ||
1871 op->GetType() == OperationType::kConditionalRandomGen)
1872 return true;
1873
1874 return false;
1875 }
1876
1891 for (const auto &op : operations) {
1892 const auto qubits = op->AffectedQubits();
1893 if (qubits.size() <= 1) continue;
1894
1895 if (qubits.size() == 2) {
1896 if (std::abs(qubits[0] - qubits[1]) != 1) return false;
1897 } else {
1898 Types::qubit_t minQubit = qubits[0];
1899 Types::qubit_t maxQubit = qubits[0];
1900
1901 for (size_t i = 1; i < qubits.size(); ++i) {
1902 if (qubits[i] < minQubit)
1903 minQubit = qubits[i];
1904 else if (qubits[i] > maxQubit)
1905 maxQubit = qubits[i];
1906 }
1907
1908 if (maxQubit - minQubit >= qubits.size()) return false;
1909 }
1910 }
1911
1912 return true;
1913 }
1914
1922 bool IsForest() const {
1923 std::unordered_map<Types::qubit_t, size_t> qubits;
1924 std::unordered_map<Types::qubit_t, Types::qubits_vector> lastQubits;
1925
1926 for (const auto &op : operations) {
1927 const auto q = op->AffectedQubits();
1928 // one qubit gates or other operations that do not affect qubits do not
1929 // change anything
1930 if (q.size() <= 1) continue;
1931
1932 bool allInTheLastQubits = true;
1933
1934 for (const auto qubit : q) {
1935 if (lastQubits.find(qubit) == lastQubits.end()) {
1936 allInTheLastQubits = false;
1937 break;
1938 } else {
1939 const auto &lastQ = lastQubits[qubit];
1940
1941 for (const auto q1 : q)
1942 if (std::find(lastQ.cbegin(), lastQ.cend(), q1) == lastQ.cend()) {
1943 allInTheLastQubits = false;
1944 break;
1945 }
1946
1947 if (!allInTheLastQubits) break;
1948 }
1949 }
1950
1951 if (allInTheLastQubits) continue;
1952
1953 for (const auto qubit : q) {
1954 if (qubits[qubit] > 1) // if the qubit is affected again...
1955 return false;
1956
1957 ++qubits[qubit];
1958
1959 lastQubits[qubit] = q;
1960 }
1961 }
1962
1963 return true;
1964 }
1965
1975 bool IsClifford() const override {
1976 for (const auto &op : operations)
1977 if (!op->IsClifford()) return false;
1978
1979 return true;
1980 }
1981
1991 double CliffordPercentage() const {
1992 size_t cliffordOps = 0;
1993 for (const auto &op : operations)
1994 if (op->IsClifford()) ++cliffordOps;
1995
1996 return static_cast<double>(cliffordOps) / operations.size();
1997 }
1998
2008 std::unordered_set<Types::qubit_t> GetCliffordQubits() const {
2009 std::unordered_set<Types::qubit_t> cliffordQubits;
2010 std::unordered_set<Types::qubit_t> nonCliffordQubits;
2011
2012 for (const auto &op : operations) {
2013 const auto qubits = op->AffectedQubits();
2014 if (op->IsClifford()) {
2015 for (const auto q : qubits) cliffordQubits.insert(q);
2016 } else {
2017 for (const auto q : qubits) nonCliffordQubits.insert(q);
2018 }
2019 }
2020
2021 for (const auto q : nonCliffordQubits) cliffordQubits.erase(q);
2022
2023 return cliffordQubits;
2024 }
2025
2035 std::vector<std::shared_ptr<Circuit<Time>>> SplitCircuit() const {
2036 std::vector<std::shared_ptr<Circuit<Time>>> circuits;
2037
2038 // find how many disjoint circuits we have in this circuit
2039
2040 std::unordered_map<Types::qubit_t, std::unordered_set<Types::qubit_t>>
2041 circuitsMap;
2042 auto allQubits = GetQubits();
2043 std::unordered_map<Types::qubit_t, Types::qubit_t> qubitCircuitMap;
2044
2045 // start with a bunch of disjoint sets of qubits, containing each a single
2046 // qubit
2047
2048 for (auto qubit : allQubits) {
2049 circuitsMap[qubit] = std::unordered_set<Types::qubit_t>{qubit};
2050 qubitCircuitMap[qubit] = qubit;
2051 }
2052
2053 // then the gates will join them together into circuits
2054
2055 for (const auto &op : operations) {
2056 const auto qubits = op->AffectedQubits();
2057
2058 if (qubits.empty()) continue;
2059
2060 auto qubitIt = qubits.cbegin();
2061 auto firstQubit = *qubitIt;
2062 // where is the first qubit in the disjoint sets?
2063 auto firstQubitCircuit = qubitCircuitMap[firstQubit];
2064
2065 ++qubitIt;
2066
2067 for (; qubitIt != qubits.cend(); ++qubitIt) {
2068 auto qubit = *qubitIt;
2069
2070 // where is the qubit in the disjoint sets?
2071 auto qubitCircuit = qubitCircuitMap[qubit];
2072
2073 // join the circuits / qubits sets
2074
2075 if (firstQubitCircuit != qubitCircuit) {
2076 // join the circuits
2077 circuitsMap[firstQubitCircuit].insert(
2078 circuitsMap[qubitCircuit].begin(),
2079 circuitsMap[qubitCircuit].end());
2080
2081 // update the qubit to circuit map
2082 for (auto q : circuitsMap[qubitCircuit])
2083 qubitCircuitMap[q] = firstQubitCircuit;
2084
2085 // remove the joined circuit
2086 circuitsMap.erase(qubitCircuit);
2087 }
2088 }
2089 }
2090
2091 size_t circSize = 1ULL;
2092 // static cast added to prevent compiler error on MacOS
2093 circSize = std::max(circSize, static_cast<size_t>(circuitsMap.size()));
2094 circuits.resize(circSize);
2095
2096 for (size_t i = 0; i < circuits.size(); ++i)
2097 circuits[i] = std::make_shared<Circuit<Time>>();
2098
2099 std::unordered_map<Types::qubit_t, size_t> qubitsSetsToCircuit;
2100
2101 size_t circuitNo = 0;
2102 for (const auto &[id, qubitSet] : circuitsMap) {
2103 qubitsSetsToCircuit[id] = circuitNo;
2104
2105 ++circuitNo;
2106 }
2107
2108 // now fill them up with the operations
2109
2110 for (const auto &op : operations) {
2111 const auto qubits = op->AffectedQubits();
2112
2113 if (qubits.empty()) {
2114 circuits[0]->AddOperation(op->Clone());
2115 continue;
2116 }
2117
2118 const auto circ = qubitsSetsToCircuit[qubitCircuitMap[*qubits.cbegin()]];
2119
2120 circuits[circ]->AddOperation(op->Clone());
2121 }
2122
2123 return circuits;
2124 }
2125
2133 std::vector<std::shared_ptr<Circuits::Circuit<Time>>> ToLayers() const {
2134 std::vector<std::shared_ptr<Circuits::Circuit<Time>>> layers;
2135 layers.emplace_back(std::make_shared<Circuits::Circuit<Time>>());
2136
2137 std::unordered_map<Types::qubit_t, Types::qubit_t> qubitsUsed;
2138 std::unordered_map<size_t, size_t> classicalBitLayer;
2139
2140 for (const auto &op : GetOperations()) {
2141 // check the instruction, see if a new layer is needed
2142
2143 // only qubits matter here, the others can be classicaly sent if they are
2144 // needed, even if they are shared... but that should be handled
2145 // somewhere, when not done implicitely (as for the 'simple network' case)
2146 if (op->CanAffectQuantumState()) {
2147 const auto qubits = op->AffectedQubits();
2148 size_t maxLevel = 0;
2149
2150 for (Types::qubit_t qbit : qubits) {
2151 ++qubitsUsed[qbit];
2152 maxLevel = std::max(maxLevel, static_cast<size_t>(qubitsUsed[qbit]));
2153
2154 if (layers.size() < qubitsUsed[qbit]) {
2155 auto circ = std::make_shared<Circuits::Circuit<Time>>();
2156 layers.push_back(std::move(circ));
2157 }
2158 }
2159
2160 if (op->IsConditional()) {
2161 const auto bits = op->AffectedBits();
2162 for (const auto bit : bits)
2163 maxLevel = std::max(maxLevel, classicalBitLayer[bit]);
2164 }
2165
2166 // now set all the qubits in the instruction to the max level
2167 for (Types::qubit_t qbit : qubits) qubitsUsed[qbit] = maxLevel;
2168
2169 const size_t layerIdx = maxLevel > 0 ? maxLevel - 1 : 0;
2170
2171 // ensure enough layers exist for the computed level
2172 while (layers.size() <= layerIdx)
2173 layers.push_back(std::make_shared<Circuits::Circuit<Time>>());
2174
2175 layers[layerIdx]->AddOperation(op->Clone());
2176
2177 const auto writtenBits = op->AffectedBits();
2178 if (!writtenBits.empty() && !op->IsConditional()) {
2179 const size_t writtenLevel = maxLevel > 0 ? maxLevel : 1;
2180 for (const auto bit : writtenBits)
2181 classicalBitLayer[bit] =
2182 std::max(classicalBitLayer[bit], writtenLevel);
2183 }
2184 } else
2185 // add the instruction to the last layer
2186 layers.back()->AddOperation(op->Clone());
2187 }
2188
2189 return layers;
2190 }
2191
2200 std::vector<std::shared_ptr<Circuits::Circuit<Time>>> ToLayersNoClone()
2201 const {
2202 std::vector<std::shared_ptr<Circuits::Circuit<Time>>> layers;
2203 layers.emplace_back(std::make_shared<Circuits::Circuit<Time>>());
2204
2205 std::unordered_map<Types::qubit_t, Types::qubit_t> qubitsUsed;
2206 std::unordered_map<size_t, size_t> classicalBitLayer;
2207
2208 for (const auto &op : GetOperations()) {
2209 // check the instruction, see if a new layer is needed
2210
2211 // only qubits matter here, the others can be classicaly sent if they are
2212 // needed, even if they are shared... but that should be handled
2213 // somewhere, when not done implicitely (as for the 'simple network' case)
2214 if (op->CanAffectQuantumState()) {
2215 const auto qubits = op->AffectedQubits();
2216 size_t maxLevel = 0;
2217
2218 for (Types::qubit_t qbit : qubits) {
2219 ++qubitsUsed[qbit];
2220 maxLevel = std::max(maxLevel, static_cast<size_t>(qubitsUsed[qbit]));
2221
2222 if (layers.size() < qubitsUsed[qbit]) {
2223 auto circ = std::make_shared<Circuits::Circuit<Time>>();
2224 layers.push_back(std::move(circ));
2225 }
2226 }
2227
2228 if (op->IsConditional()) {
2229 const auto bits = op->AffectedBits();
2230 for (const auto bit : bits)
2231 maxLevel = std::max(maxLevel, classicalBitLayer[bit]);
2232 }
2233
2234 // now set all the qubits in the instruction to the max level
2235 for (Types::qubit_t qbit : qubits) qubitsUsed[qbit] = maxLevel;
2236
2237 const size_t layerIdx = maxLevel > 0 ? maxLevel - 1 : 0;
2238
2239 // ensure enough layers exist for the computed level
2240 while (layers.size() <= layerIdx)
2241 layers.push_back(std::make_shared<Circuits::Circuit<Time>>());
2242
2243 layers[layerIdx]->AddOperation(op);
2244
2245 const auto writtenBits = op->AffectedBits();
2246 if (!writtenBits.empty() && !op->IsConditional()) {
2247 const size_t writtenLevel = maxLevel > 0 ? maxLevel : 1;
2248 for (const auto bit : writtenBits)
2249 classicalBitLayer[bit] =
2250 std::max(classicalBitLayer[bit], writtenLevel);
2251 }
2252 } else
2253 // add the instruction to the last layer
2254 layers.back()->AddOperation(op);
2255 }
2256
2257 return layers;
2258 }
2259
2270 std::vector<std::shared_ptr<Circuits::Circuit<Time>>> ToMultipleQubitsLayers()
2271 const {
2272 std::vector<std::shared_ptr<Circuits::Circuit<Time>>> layers;
2273 layers.emplace_back(std::make_shared<Circuits::Circuit<Time>>());
2274
2275 std::unordered_map<Types::qubit_t, Types::qubit_t> qubitsUsed;
2276 std::unordered_map<size_t, size_t> classicalBitLayer;
2277
2278 for (const auto &op : GetOperations()) {
2279 // check the instruction, see if a new layer is needed
2280
2281 // only qubits matter here, the others can be classicaly sent if they are
2282 // needed, even if they are shared... but that should be handled
2283 // somewhere, when not done implicitely (as for the 'simple network' case)
2284 if (op->CanAffectQuantumState()) {
2285 const auto qubits = op->AffectedQubits();
2286 size_t maxLevel = 0;
2287
2288 for (Types::qubit_t qbit : qubits) {
2289 if (qubits.size() > 1) ++qubitsUsed[qbit];
2290 maxLevel = std::max(maxLevel, static_cast<size_t>(qubitsUsed[qbit]));
2291
2292 if (layers.size() < qubitsUsed[qbit]) {
2293 auto circ = std::make_shared<Circuits::Circuit<Time>>();
2294 layers.push_back(std::move(circ));
2295 }
2296 }
2297
2298 if (op->IsConditional()) {
2299 const auto bits = op->AffectedBits();
2300 for (const auto bit : bits)
2301 maxLevel = std::max(maxLevel, classicalBitLayer[bit]);
2302 }
2303
2304 // now set all the qubits in the instruction to the max level
2305 for (Types::qubit_t qbit : qubits) qubitsUsed[qbit] = maxLevel;
2306
2307 const size_t layerIdx = maxLevel > 0 ? maxLevel - 1 : 0;
2308
2309 // ensure enough layers exist for the computed level
2310 while (layers.size() <= layerIdx)
2311 layers.push_back(std::make_shared<Circuits::Circuit<Time>>());
2312
2313 layers[layerIdx]->AddOperation(op->Clone());
2314
2315 const auto writtenBits = op->AffectedBits();
2316 if (!writtenBits.empty() && !op->IsConditional()) {
2317 const size_t writtenLevel = maxLevel > 0 ? maxLevel : 1;
2318 for (const auto bit : writtenBits)
2319 classicalBitLayer[bit] =
2320 std::max(classicalBitLayer[bit], writtenLevel);
2321 }
2322 } else
2323 // add the instruction to the last layer
2324 layers.back()->AddOperation(op->Clone());
2325 }
2326
2327 return layers;
2328 }
2329
2340 std::vector<std::shared_ptr<Circuits::Circuit<Time>>>
2342 std::vector<std::shared_ptr<Circuits::Circuit<Time>>> layers;
2343 layers.emplace_back(std::make_shared<Circuits::Circuit<Time>>());
2344
2345 std::unordered_map<Types::qubit_t, Types::qubit_t> qubitsUsed;
2346
2347 // Track which layer (1-based, like qubitsUsed) each classical bit was last
2348 // written to, so that conditional operations reading those bits are placed
2349 // in the same layer or later.
2350 std::unordered_map<size_t, size_t> classicalBitLayer;
2351
2352 for (const auto &op : GetOperations()) {
2353 // check the instruction, see if a new layer is needed
2354
2355 // only qubits matter here, the others can be classicaly sent if they are
2356 // needed, even if they are shared... but that should be handled
2357 // somewhere, when not done implicitely (as for the 'simple network' case)
2358 if (op->CanAffectQuantumState()) {
2359 const auto qubits = op->AffectedQubits();
2360 size_t maxLevel = 0;
2361
2362 for (Types::qubit_t qbit : qubits) {
2363 if (qubits.size() > 1) ++qubitsUsed[qbit];
2364 maxLevel = std::max(maxLevel, static_cast<size_t>(qubitsUsed[qbit]));
2365
2366 if (layers.size() < qubitsUsed[qbit]) {
2367 auto circ = std::make_shared<Circuits::Circuit<Time>>();
2368 layers.push_back(std::move(circ));
2369 }
2370 }
2371
2372 // For conditional operations, ensure this op is placed no earlier than
2373 // the layer where the classical bits it depends on were written.
2374 if (op->IsConditional()) {
2375 const auto bits = op->AffectedBits();
2376 for (const auto bit : bits)
2377 maxLevel = std::max(maxLevel, classicalBitLayer[bit]);
2378 }
2379
2380 // now set all the qubits in the instruction to the max level
2381 for (Types::qubit_t qbit : qubits) qubitsUsed[qbit] = maxLevel;
2382
2383 const size_t layerIdx = maxLevel > 0 ? maxLevel - 1 : 0;
2384
2385 // ensure enough layers exist for the computed level
2386 while (layers.size() <= layerIdx)
2387 layers.push_back(std::make_shared<Circuits::Circuit<Time>>());
2388
2389 layers[layerIdx]->AddOperation(op);
2390
2391 // Record the layer for classical bits written by measurements / resets
2392 // so that later conditional ops respect the dependency.
2393 const auto writtenBits = op->AffectedBits();
2394 if (!writtenBits.empty() && !op->IsConditional()) {
2395 const size_t writtenLevel = maxLevel > 0 ? maxLevel : 1;
2396 for (const auto bit : writtenBits)
2397 classicalBitLayer[bit] =
2398 std::max(classicalBitLayer[bit], writtenLevel);
2399 }
2400 } else
2401 // add the instruction to the last layer
2402 layers.back()->AddOperation(op);
2403 }
2404
2405 return layers;
2406 }
2407
2417 static std::shared_ptr<Circuits::Circuit<Time>> LayersToCircuit(
2418 const std::vector<std::shared_ptr<Circuits::Circuit<Time>>> &layers) {
2419 auto circuit{std::make_shared<Circuits::Circuit<Time>>()};
2420
2421 for (const auto &layer : layers)
2422 circuit->AddOperations(layer->GetOperations());
2423
2424 return circuit;
2425 }
2426
2435 bool IsBranching() const override {
2436 for (const auto &op : GetOperations())
2437 if (op->IsBranching()) return true;
2438
2439 return false;
2440 }
2441
2448 iterator begin() noexcept { return operations.begin(); }
2449
2456 iterator end() noexcept { return operations.end(); }
2457
2464 const_iterator cbegin() const noexcept { return operations.cbegin(); }
2465
2472 const_iterator cend() const noexcept { return operations.cend(); }
2473
2480 reverse_iterator rbegin() noexcept { return operations.rbegin(); }
2481
2488 reverse_iterator rend() noexcept { return operations.rend(); }
2489
2497 return operations.crbegin();
2498 }
2499
2506 const_reverse_iterator crend() const noexcept { return operations.crend(); }
2507
2514 auto size() const { return operations.size(); }
2515
2522 auto empty() const { return operations.empty(); }
2523
2531 auto &operator[](size_t pos) { return operations[pos]; }
2532
2540 const auto &operator[](size_t pos) const { return operations[pos]; }
2541
2549 void resize(size_t size) {
2550 if (size < operations.size()) operations.resize(size);
2551 }
2552
2553 private:
2564 void ReplaceThreeQubitAndSwapGates(bool onlyThreeQubits = false) {
2565 // just replace all three qubit gates(just ccnot and cswap will exist in the
2566 // first phase) with several gates on less qubits also replace swap gates
2567 // with three cnots (in the first phase) the controlled ones must be
2568 // replaced as well
2569
2570 // TODO: if composite operations will be implemented, those need to be
2571 // optimized as well
2572
2573 std::vector<std::shared_ptr<IOperation<Time>>> newops;
2574 newops.reserve(operations.size());
2575
2576 for (std::shared_ptr<IOperation<Time>> op : operations) {
2577 if (op->GetType() == OperationType::kGate) {
2578 std::shared_ptr<IQuantumGate<Time>> gate =
2579 std::static_pointer_cast<IQuantumGate<Time>>(op);
2580
2581 if (NeedsConversion(gate, onlyThreeQubits)) {
2582 std::vector<std::shared_ptr<IGateOperation<Time>>> newgates =
2583 ConvertGate(gate, onlyThreeQubits);
2584 newops.insert(newops.end(), newgates.begin(), newgates.end());
2585 } else
2586 newops.push_back(op);
2587 } else if (op->GetType() == OperationType::kConditionalGate) {
2588 std::shared_ptr<ConditionalGate<Time>> condgate =
2589 std::static_pointer_cast<ConditionalGate<Time>>(op);
2590 std::shared_ptr<IQuantumGate<Time>> gate =
2591 std::static_pointer_cast<IQuantumGate<Time>>(
2592 condgate->GetOperation());
2593
2594 if (NeedsConversion(gate, onlyThreeQubits)) {
2595 std::vector<std::shared_ptr<IGateOperation<Time>>> newgates =
2596 ConvertGate(gate, onlyThreeQubits);
2597 std::shared_ptr<ICondition> cond = condgate->GetCondition();
2598
2599 for (auto gate : newgates)
2600 newops.push_back(
2601 std::make_shared<ConditionalGate<Time>>(gate, cond));
2602 } else
2603 newops.push_back(op);
2604 } else
2605 newops.push_back(op);
2606 }
2607
2608 operations.swap(newops);
2609 }
2610
2620 static bool NeedsConversion(const std::shared_ptr<IQuantumGate<Time>> &gate,
2621 bool onlyThreeQubits = false) {
2622 const bool hasThreeQubits = gate->GetNumQubits() == 3;
2623 if (onlyThreeQubits) return hasThreeQubits;
2624
2625 return hasThreeQubits ||
2626 gate->GetGateType() == QuantumGateType::kSwapGateType;
2627 }
2628
2641 static std::vector<std::shared_ptr<IGateOperation<Time>>> ConvertGate(
2642 std::shared_ptr<IQuantumGate<Time>> &gate, bool onlyThreeQubits = false) {
2643 // TODO: if delays are used, how to transfer delays from the converted gate
2644 // to the resulting gates?
2645 std::vector<std::shared_ptr<IGateOperation<Time>>> newops;
2646
2647 if (gate->GetNumQubits() == 3) {
2648 // must be converted no matter what
2649 if (gate->GetGateType() == QuantumGateType::kCCXGateType) {
2650 const size_t q1 = gate->GetQubit(0); // control 1
2651 const size_t q2 = gate->GetQubit(1); // control 2
2652 const size_t q3 = gate->GetQubit(2); // target
2653
2654 // Sleator-Weinfurter decomposition
2655 newops.push_back(std::make_shared<CSxGate<Time>>(q2, q3));
2656 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
2657 newops.push_back(std::make_shared<CSxDagGate<Time>>(q2, q3));
2658 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
2659 newops.push_back(std::make_shared<CSxGate<Time>>(q1, q3));
2660 } else if (gate->GetGateType() == QuantumGateType::kCSwapGateType) {
2661 const size_t q1 = gate->GetQubit(0); // control 1
2662 const size_t q2 = gate->GetQubit(1); // control 2
2663 const size_t q3 = gate->GetQubit(2); // target
2664
2665 // TODO: find a better decomposition
2666 // this one I've got with the qiskit transpiler
2667 newops.push_back(std::make_shared<CXGate<Time>>(q3, q2));
2668
2669 newops.push_back(std::make_shared<CSxGate<Time>>(q2, q3));
2670 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
2671 newops.push_back(std::make_shared<PhaseGate<Time>>(q3, M_PI));
2672
2673 newops.push_back(std::make_shared<PhaseGate<Time>>(q2, -M_PI_2));
2674
2675 newops.push_back(std::make_shared<CSxGate<Time>>(q2, q3));
2676 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
2677 newops.push_back(std::make_shared<PhaseGate<Time>>(q3, M_PI));
2678
2679 newops.push_back(std::make_shared<CSxGate<Time>>(q1, q3));
2680
2681 newops.push_back(std::make_shared<CXGate<Time>>(q3, q2));
2682 } else
2683 newops.push_back(gate);
2684 } else if (!onlyThreeQubits &&
2685 gate->GetGateType() == QuantumGateType::kSwapGateType) {
2686 // must be converted no matter what
2687 const size_t q1 = gate->GetQubit(0);
2688 const size_t q2 = gate->GetQubit(1);
2689
2690 // for now replace it with three cnots, but maybe later make it
2691 // configurable there are other possibilities, for example three cy gates
2692 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
2693 newops.push_back(std::make_shared<CXGate<Time>>(q2, q1));
2694 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
2695 } else
2696 newops.push_back(gate);
2697
2698 return newops;
2699 }
2700
2701 OperationsVector operations;
2702};
2703
2717template <typename Time = Types::time_type>
2718class ComparableCircuit : public Circuit<Time> {
2719 public:
2722 using OperationPtr = std::shared_ptr<Operation>;
2725 std::vector<OperationPtr>;
2726
2735
2745
2755
2756 return *this;
2757 }
2758
2765 bool operator==(const BaseClass &rhs) const {
2766 if (BaseClass::GetOperations().size() != rhs.GetOperations().size())
2767 return false;
2768
2769 for (size_t i = 0; i < BaseClass::GetOperations().size(); ++i) {
2770 if (BaseClass::GetOperations()[i]->GetType() !=
2771 rhs.GetOperations()[i]->GetType())
2772 return false;
2773
2774 switch (BaseClass::GetOperations()[i]->GetType()) {
2776 if (std::static_pointer_cast<IQuantumGate<Time>>(
2778 ->GetGateType() !=
2779 std::static_pointer_cast<IQuantumGate<Time>>(
2780 rhs.GetOperations()[i])
2781 ->GetGateType() ||
2782 BaseClass::GetOperations()[i]->AffectedBits() !=
2783 rhs.GetOperations()[i]->AffectedBits())
2784 return false;
2785 if (approximateParamsCheck) {
2786 const auto params1 = std::static_pointer_cast<IQuantumGate<Time>>(
2788 ->GetParams();
2789 const auto params2 = std::static_pointer_cast<IQuantumGate<Time>>(
2790 rhs.GetOperations()[i])
2791 ->GetParams();
2792 if (params1.size() != params2.size()) return false;
2793
2794 for (size_t j = 0; j < params1.size(); ++j)
2795 if (std::abs(params1[j] - params2[j]) > paramsEpsilon)
2796 return false;
2797 } else if (std::static_pointer_cast<IQuantumGate<Time>>(
2799 ->GetParams() !=
2800 std::static_pointer_cast<IQuantumGate<Time>>(
2801 rhs.GetOperations()[i])
2802 ->GetParams())
2803 return false;
2804 break;
2807 rhs.GetOperations()[i]->AffectedQubits() ||
2808 BaseClass::GetOperations()[i]->AffectedBits() !=
2809 rhs.GetOperations()[i]->AffectedBits())
2810 return false;
2811 break;
2814 rhs.GetOperations()[i]->AffectedBits())
2815 return false;
2816 break;
2820 // first, check the conditions
2821 const auto leftCondition =
2822 std::static_pointer_cast<IConditionalOperation<Time>>(
2824 ->GetCondition();
2825 const auto rightCondition =
2826 std::static_pointer_cast<IConditionalOperation<Time>>(
2827 rhs.GetOperations()[i])
2828 ->GetCondition();
2829 if (leftCondition->GetBitsIndices() !=
2830 rightCondition->GetBitsIndices())
2831 return false;
2832
2833 const auto leftEqCondition =
2834 std::static_pointer_cast<EqualCondition>(leftCondition);
2835 const auto rightEqCondition =
2836 std::static_pointer_cast<EqualCondition>(rightCondition);
2837 if (!leftEqCondition || !rightEqCondition) return false;
2838
2839 if (leftEqCondition->GetAllBits() != rightEqCondition->GetAllBits())
2840 return false;
2841
2842 // now check the operations
2843 const auto leftOp =
2844 std::static_pointer_cast<IConditionalOperation<Time>>(
2846 ->GetOperation();
2847 const auto rightOp =
2848 std::static_pointer_cast<IConditionalOperation<Time>>(
2849 rhs.GetOperations()[i])
2850 ->GetOperation();
2851
2852 ComparableCircuit<Time> leftCircuit;
2853 BaseClass rightCircuit;
2854 leftCircuit.SetApproximateParamsCheck(approximateParamsCheck);
2855 leftCircuit.AddOperation(leftOp);
2856 rightCircuit.AddOperation(rightOp);
2857
2858 if (leftCircuit != rightCircuit) return false;
2859 } break;
2862 rhs.GetOperations()[i]->AffectedQubits() ||
2863 std::static_pointer_cast<Reset<Time>>(
2865 ->GetResetTargets() !=
2866 std::static_pointer_cast<Reset<Time>>(rhs.GetOperations()[i])
2867 ->GetResetTargets())
2868 return false;
2869 break;
2871 break;
2872 default:
2873 return false;
2874 }
2875
2876 if (BaseClass::GetOperations()[i]->GetDelay() !=
2877 rhs.GetOperations()[i]->GetDelay())
2878 return false;
2879 }
2880
2881 return true;
2882 }
2883
2890 bool operator!=(const BaseClass &rhs) const { return !(*this == rhs); }
2891
2898 void SetApproximateParamsCheck(bool check) { approximateParamsCheck = check; }
2899
2906 bool GetApproximateParamsCheck() const { return approximateParamsCheck; }
2907
2916 void SetParamsEpsilon(double eps) { paramsEpsilon = eps; }
2917
2926 double GetParamsEpsilon() const { return paramsEpsilon; }
2927
2928 private:
2929 bool approximateParamsCheck =
2930 false;
2931 double paramsEpsilon = 1e-8;
2933};
2934
2935} // namespace Circuits
2936
2937#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:2448
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
const_iterator cbegin() const noexcept
Get the const begin iterator for the operations.
Definition Circuit.h:2464
double CliffordPercentage() const
Get the percentage of Clifford operations in the circuit.
Definition Circuit.h:1991
auto size() const
Get the number of operations in the circuit.
Definition Circuit.h:2514
bool IsClifford() const override
Checks if the circuit is a Clifford circuit.
Definition Circuit.h:1975
iterator end() noexcept
Get the end iterator for the operations.
Definition Circuit.h:2456
std::unordered_set< Types::qubit_t > GetCliffordQubits() const
Get the qubits that are acted on by Clifford operations.
Definition Circuit.h:2008
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:2341
std::set< size_t > GetQubits() const
Returns the qubits affected by the operations.
Definition Circuit.h:581
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:2270
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:58
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:2531
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:2506
std::shared_ptr< MeasurementOperation< Time > > GetLastMeasurements(const std::vector< bool > &executedOps, bool sort=true) const
Definition Circuit.h:1832
bool ActsOnlyOnAdjacentQubits() const
Checks if the circuit has only operations that act on adjacent qubits.
Definition Circuit.h:1890
Circuit(const OperationsVector &ops={})
Construct a new Circuit object.
Definition Circuit.h:85
bool IsBranching() const override
Checks if any operation is a branching one.
Definition Circuit.h:2435
typename OperationsVector::pointer pointer
Definition Circuit.h:65
std::shared_ptr< Circuits::Circuit< Time > > RemoveExecutedOperations(std::vector< bool > &executedOps) const
Returns a new circuit with the operations that were not yet executed.
Definition Circuit.h:1808
typename OperationsVector::size_type size_type
Definition Circuit.h:69
auto empty() const
Check if the circuit is empty.
Definition Circuit.h:2522
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
std::vector< OperationPtr > OperationsVector
The vector of operations.
Definition Circuit.h:60
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:1867
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:2496
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:2472
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
std::unordered_map< Types::qubit_t, Types::qubit_t > BitMapping
The (qu)bit mapping for remapping.
Definition Circuit.h:52
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:2133
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:2480
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
std::unordered_map< std::vector< bool >, size_t > ExecuteResults
The results of the execution of the circuit.
Definition Circuit.h:48
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:2417
const auto & operator[](size_t pos) const
Get the operation at a given position.
Definition Circuit.h:2540
std::vector< std::shared_ptr< Circuits::Circuit< Time > > > ToLayersNoClone() const
Converts the circuit to layers.
Definition Circuit.h:2200
typename OperationsVector::const_reverse_iterator const_reverse_iterator
Definition Circuit.h:75
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:2549
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:1781
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:2488
bool IsForest() const
Checks if the circuit is a forest circuit.
Definition Circuit.h:1922
std::vector< std::shared_ptr< Circuit< Time > > > SplitCircuit() const
Splits a circuit that has disjoint subcircuits in it into separate circuits.
Definition Circuit.h:2035
const OperationsVector & GetOperations() const
Get the operations in the circuit.
Definition Circuit.h:173
std::shared_ptr< Operation > OperationPtr
The shared pointer to the operation type.
Definition Circuit.h:2722
double GetParamsEpsilon() const
Gets the epsilon used for checking approximate equality of gate parameters.
Definition Circuit.h:2926
void SetApproximateParamsCheck(bool check)
Sets whether to check approximate equality of gate parameters.
Definition Circuit.h:2898
ComparableCircuit & operator=(const BaseClass &circ)
Assignment operator.
Definition Circuit.h:2753
bool operator==(const BaseClass &rhs) const
Comparison operator.
Definition Circuit.h:2765
bool GetApproximateParamsCheck() const
Gets whether to check approximate equality of gate parameters.
Definition Circuit.h:2906
ComparableCircuit(const BaseClass &circ)
Construct a new ComparableCircuit object.
Definition Circuit.h:2744
std::vector< OperationPtr > OperationsVector
The vector of operations.
Definition Circuit.h:2724
IOperation< Time > Operation
The operation type.
Definition Circuit.h:2721
bool operator!=(const BaseClass &rhs) const
Comparison operator.
Definition Circuit.h:2890
Circuit< Time > BaseClass
The base class type.
Definition Circuit.h:2720
ComparableCircuit(const OperationsVector &ops={})
Construct a new ComparableCircuit object.
Definition Circuit.h:2734
void SetParamsEpsilon(double eps)
Sets the epsilon used for checking approximate equality of gate parameters.
Definition Circuit.h:2916
The operation interface.
Definition Operations.h:358
virtual Types::qubits_vector AffectedQubits() const
Returns the affected qubits.
Definition Operations.h:470
Types::time_type GetDelay() const
Definition Operations.h:497
IOperation(Types::time_type delay=0)
Definition Operations.h:366
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:118
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'
Definition Operations.h:31
@ kNoOp
no operation, just a placeholder, could be used to erase some operation from a circuit
Definition Operations.h:42
@ kComposite
a composite operation, contains other operations - should not be used in the beginning,...
Definition Operations.h:44
@ kRandomGen
random classical bit generator, result in 'OperationState'
Definition Operations.h:30
@ kConditionalRandomGen
conditional random generator, similar with random gen, but conditioned on something from 'OperationSt...
Definition Operations.h:36
@ kConditionalMeasurement
conditional measurement, similar with measurement, but conditioned on something from 'OperationState'
Definition Operations.h:33
@ kMeasurement
measurement, result in 'OperationState'
Definition Operations.h:29
@ kGate
the usual quantum gate, result stays in simulator's state
Definition Operations.h:28
@ kReset
reset, no result in 'state', just apply measurement, then apply not on all qubits that were measured ...
Definition Operations.h:39
@ kMatrixProductState
matrix product state simulation type
Definition State.h:87
std::vector< qubit_t > qubits_vector
The type of a vector of qubits.
Definition Types.h:22
uint_fast64_t qubit_t
The type of a qubit.
Definition Types.h:21