Maestro 0.3.1
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 "Delay.h"
28#include "Operations.h"
30#include "QuantumGates.h"
31#include "Reset.h"
32#include <vector>
33
34namespace Circuits {
35
47template <typename Time = Types::time_type>
48class Circuit : public IOperation<Time> {
49 public:
51 std::unordered_map<std::vector<bool>,
52 size_t>;
54 using BitMapping =
55 std::unordered_map<Types::qubit_t,
58
60 using OperationPtr = std::shared_ptr<Operation>;
63 std::vector<OperationPtr>;
64
65 using value_type = typename OperationsVector::value_type;
66 using allocator_type = typename OperationsVector::allocator_type;
67 using pointer = typename OperationsVector::pointer;
68 using const_pointer = typename OperationsVector::const_pointer;
69 using reference = typename OperationsVector::reference;
70 using const_reference = typename OperationsVector::const_reference;
71 using size_type = typename OperationsVector::size_type;
72 using difference_type = typename OperationsVector::difference_type;
73
74 using iterator = typename OperationsVector::iterator;
75 using const_iterator = typename OperationsVector::const_iterator;
76 using reverse_iterator = typename OperationsVector::reverse_iterator;
78 typename OperationsVector::const_reverse_iterator;
79
87 Circuit(const OperationsVector &ops = {}) : Operation(), operations(ops) {}
88
98 void Execute(const std::shared_ptr<Simulators::ISimulator>& sim,
99 OperationState& state) const override {
100 ExecuteBD(sim, state);
101 }
102
114 void ExecuteBD(const std::shared_ptr<Simulators::ISimulator> &sim,
115 OperationState &state, size_t* curMaxBondDim = nullptr) const {
116 state.Reset();
117 if (!sim) return;
118
119 for (const auto& op : operations) {
120 op->Execute(sim, state);
121 if (curMaxBondDim) {
122 const auto bondDim = sim->GetCurrentMaxBondDimension();
123 if (bondDim > *curMaxBondDim) *curMaxBondDim = bondDim;
124 }
125 }
126 // sim->Flush();
127 }
128
137
145 void AddOperation(const OperationPtr &op) { operations.push_back(op); }
146
152 void Delay(Types::qubit_t qubit, Time duration) {
153 operations.push_back(std::make_shared<Circuits::Delay<Time>>(qubit, duration));
154 }
155
164 void ReplaceOperation(size_t index, const OperationPtr &op) {
165 if (index >= operations.size()) return;
166 operations[index] = op;
167 }
168
176 void SetOperations(const OperationsVector &ops) { operations = ops; }
177
186 operations.insert(operations.end(), ops.begin(), ops.end());
187 }
188
195 void AddCircuit(const std::shared_ptr<Circuit<Time>> &circuit) {
196 AddOperations(circuit->GetOperations());
197 }
198
206 const OperationsVector &GetOperations() const { return operations; }
207
213 void Clear() { operations.clear(); }
214
221 OperationPtr Clone() const override {
222 OperationsVector newops;
223
224 for (auto &op : operations) newops.emplace_back(op->Clone());
225
226 return std::make_shared<Circuit<Time>>(newops);
227 }
228
237 OperationsVector newops;
238
239 for (auto &op : operations) newops.push_back(op);
240
241 return std::make_shared<Circuit<Time>>(newops);
242 }
243
254 OperationPtr Remap(const BitMapping &qubitsMap,
255 const BitMapping &bitsMap = {}) const override {
256 OperationsVector newops;
257
258 for (const auto &op : operations)
259 newops.emplace_back(op->Remap(qubitsMap, bitsMap));
260
261 return std::make_shared<Circuit<Time>>(newops);
262 }
263
275 std::shared_ptr<Circuit<Time>> RemapToContinuous(BitMapping &newQubitsMap,
276 BitMapping &reverseBitsMap,
277 size_t &nrQubits,
278 size_t &nrCbits) const {
279 OperationsVector newops;
280
281 BitMapping newBitsMap;
282
283 nrQubits = 0;
284 nrCbits = 0;
285
286 for (const auto &op : operations) {
287 const auto affectedBits = op->AffectedBits();
288 const auto affectedQubits = op->AffectedQubits();
289
290 for (const auto qubit : affectedQubits) {
291 const auto it = newQubitsMap.find(qubit);
292 if (it == newQubitsMap.end()) {
293 newQubitsMap[qubit] = nrQubits;
294 ++nrQubits;
295 }
296 }
297
298 for (const auto bit : affectedBits) {
299 const auto it = newBitsMap.find(bit);
300 if (it == newBitsMap.end()) {
301 newBitsMap[bit] = nrCbits;
302 reverseBitsMap[nrCbits] = bit;
303 ++nrCbits;
304 }
305 }
306
307 newops.emplace_back(op->Remap(newQubitsMap, newBitsMap));
308 }
309
310 return std::make_shared<Circuit<Time>>(newops);
311 }
312
328 const BitMapping &bitsMap = {},
329 bool ignoreNotMapped = false,
330 size_t sz = 0) {
331 ExecuteResults newResults;
332
333 if (!ignoreNotMapped && sz == 0) {
334 for (const auto &[from, to] : bitsMap)
335 if (to > sz) sz = to;
336
337 ++sz;
338 }
339
340 for (const auto &res : results) {
341 Circuits::OperationState mappedState(res.first);
342
343 mappedState.Remap(bitsMap, ignoreNotMapped,
344 ignoreNotMapped ? bitsMap.size() : sz);
345 newResults[mappedState.GetAllBits()] += res.second;
346 }
347
348 return newResults;
349 }
350
359 static void AccumulateResults(ExecuteResults &results,
360 const ExecuteResults &newResults) {
361 for (const auto &res : newResults) results[res.first] += res.second;
362 }
363
379 const ExecuteResults &newResults,
380 const BitMapping &bitsMap = {},
381 bool ignoreNotMapped = true,
382 size_t sz = 0) {
383 if (!ignoreNotMapped && sz == 0) {
384 for (const auto &[from, to] : bitsMap)
385 if (to > sz) sz = to;
386
387 ++sz;
388 }
389
390 for (const auto &res : newResults) {
391 Circuits::OperationState mappedState(res.first);
392
393 mappedState.Remap(bitsMap, ignoreNotMapped,
394 ignoreNotMapped ? bitsMap.size() : sz);
395 results[mappedState.GetAllBits()] += res.second;
396 /*
397 const auto it = bitsMap.find(res.first);
398 if (it != bitsMap.end())
399 results[it->second] += res.second;
400 */
401 }
402 }
403
404 // TODO: This converts all swap, cswap and ccnot gates
405 // it's not really needed to convert them all,
406 // only those that are not applied locally, that is, on a single host
407 // the local ones can remain as they are
408 // so use network topology to decide which ones to convert
409 // that's for later, when we have the network part implemented
410 // and also the code that splits the circuit
411
420 // this will make the circuit better for distributed computing
421 // TODO: if composite operations will be implemented, those need to be
422 // optimized as well
423
424 ReplaceThreeQubitAndSwapGates();
425 }
426
434 void ConvertForCutting() { ReplaceThreeQubitAndSwapGates(true); }
435
436 /*
437 * @brief Splits the measurements to measurements on individual qubits and
438 * tries to order them as needed by the following clasically conditional
439 * gates.
440 *
441 * Splits the measurements to measurements on individual qubits and tries to
442 * order them as needed by the following clasically conditional gates. This is
443 * needed for netqasm, which requires the measurements to be in the right
444 * order, if the following clasically conditional gates need sending to
445 * another host. This wouldn't be needed if they are local, but we don't know
446 * that at this point.
447 *
448 * So in short, all measurements on more than one qubit are converted to one
449 * qubit measurements, and then all measurements that are grouped together
450 * (not separated by some other operation) are ordered in the same order as
451 * the conditions on the following clasically conditional gates.
452 */
454 // TODO: Maybe this should be moved at the netqasm level circuit conversion,
455 // since it's not needed for all kinds of distributed computing currently
456 // there is a virtual function in controller that does nothing in the base
457 // class, but for netqasm it calls this method
458 OperationsVector newops;
459
460 for (size_t i = 0; i < operations.size(); ++i) {
461 const auto op = operations[i];
462 if (op->GetType() != OperationType::kMeasurement) {
463 newops.emplace_back(op);
464 continue;
465 }
466
467 // ok, if it's a measurement, look ahead, accumulate all measurements and
468 // then add them in the right order
469 std::unordered_set<size_t> bits;
470 std::unordered_map<size_t, Types::qubit_t> measQubits;
471 std::unordered_map<size_t, Time> measDelays;
472
473 auto affectedBits = op->AffectedBits();
474 auto affectedQubits = op->AffectedQubits();
475
476 for (size_t q = 0; q < affectedQubits.size(); ++q) {
477 bits.insert(affectedBits[q]);
478 measQubits[affectedBits[q]] = affectedQubits[q];
479 measDelays[affectedBits[q]] = op->GetDelay();
480 }
481
482 size_t j = i + 1;
483 for (; j < operations.size(); ++j) {
484 const auto op2 = operations[j];
485
486 if (op2->GetType() != OperationType::kMeasurement) break;
487
488 affectedQubits = op2->AffectedQubits();
489
490 const auto meas =
491 std::static_pointer_cast<MeasurementOperation<Time>>(op2);
492 affectedBits = meas->GetBitsIndices();
493 for (size_t q = 0; q < affectedBits.size(); ++q) {
494 bits.insert(affectedBits[q]);
495 measQubits[affectedBits[q]] = affectedQubits[q];
496 measDelays[affectedBits[q]] = op2->GetDelay();
497 }
498 }
499
500 i = j - 1;
501
502 // the right order is the one following in the classically controlled
503 // gates
504 for (; j < operations.size(); ++j) {
505 const auto op2 = operations[j];
506 if (op2->GetType() == OperationType::kConditionalGate ||
507 op2->GetType() == OperationType::kConditionalMeasurement ||
508 op2->GetType() == OperationType::kConditionalRandomGen) {
509 auto condop =
510 std::static_pointer_cast<IConditionalOperation<Time>>(op2);
511 const auto condbits = condop->AffectedBits();
512 for (const auto bit : condbits)
513 if (bits.find(bit) != bits.end()) {
514 newops.emplace_back(std::make_shared<MeasurementOperation<Time>>(
515 std::vector{std::make_pair(measQubits[bit], bit)},
516 measDelays[bit]));
517 bits.erase(bit);
518 }
519 }
520 if (bits.empty()) break;
521 }
522
523 // now add the measurements that were left in any order
524 for (auto bit : bits)
525 newops.emplace_back(std::make_shared<MeasurementOperation<Time>>(
526 std::vector{std::make_pair(measQubits[bit], bit)},
527 measDelays[bit]));
528 }
529
530 operations.swap(newops);
531 }
532
540 size_t GetMaxQubitIndex() const {
541 size_t mx = 0;
542 for (const auto &op : operations) {
543 const auto qbits = op->AffectedQubits();
544 for (auto q : qbits)
545 if (q > mx) mx = q;
546 }
547
548 return mx;
549 }
550
558 size_t GetMinQubitIndex() const {
559 size_t mn = std::numeric_limits<size_t>::max();
560 for (const auto &op : operations) {
561 const auto qbits = op->AffectedQubits();
562 for (auto q : qbits)
563 if (q < mn) mn = q;
564 }
565
566 return mn;
567 }
568
576 size_t GetMaxCbitIndex() const {
577 size_t mx = 0;
578 for (const auto &op : operations) {
579 const auto cbits = op->AffectedBits();
580 for (auto q : cbits)
581 if (q > mx) mx = q;
582 }
583
584 return mx;
585 }
586
594 size_t GetMinCbitIndex() const {
595 size_t mn = std::numeric_limits<size_t>::max();
596 for (const auto &op : operations) {
597 const auto cbits = op->AffectedBits();
598 for (auto q : cbits)
599 if (q < mn) mn = q;
600 }
601
602 return mn;
603 }
604
614 std::set<size_t> GetQubits() const {
615 std::set<size_t> qubits;
616 for (const auto &op : operations) {
617 const auto qbits = op->AffectedQubits();
618 qubits.insert(qbits.begin(), qbits.end());
619 }
620
621 return qubits;
622 }
623
631 std::set<size_t> GetBits() const {
632 std::set<size_t> cbits;
633 for (const auto &op : operations) {
634 const auto bits = op->AffectedBits();
635 cbits.insert(bits.begin(), bits.end());
636 }
637
638 return cbits;
639 }
640
648 auto qubits = GetQubits();
649
650 Types::qubits_vector qubitsVec;
651 qubitsVec.reserve(qubits.size());
652
653 for (auto q : qubits) qubitsVec.emplace_back(q);
654
655 return qubitsVec;
656 }
657
664 std::vector<size_t> AffectedBits() const override {
665 auto bits = GetBits();
666
667 std::vector<size_t> bitsVec;
668 bitsVec.reserve(bits.size());
669
670 for (auto b : bits) bitsVec.emplace_back(b);
671
672 return bitsVec;
673 }
674
684 bool NeedsEntanglementForDistribution() const override {
685 for (const auto &op : operations)
686 if (op->NeedsEntanglementForDistribution()) return true;
687
688 return false;
689 }
690
698 bool CanAffectQuantumState() const override {
699 for (const auto &op : operations)
700 if (op->CanAffectQuantumState()) return true;
701
702 return false;
703 }
704
712 std::unordered_map<size_t, OperationPtr> GetLastOperationsOnQubits() const {
713 std::unordered_map<size_t, OperationPtr> lastOps;
714
715 for (const auto &op : operations) {
716 const auto qbits = op->AffectedQubits();
717 for (auto q : qbits) lastOps[q] = op;
718 }
719
720 return lastOps;
721 }
722
730 std::unordered_map<size_t, OperationPtr> GetFirstOperationsOnQubits() const {
731 std::unordered_map<size_t, OperationPtr> firstOps;
732
733 for (const auto &op : operations) {
734 const auto qbits = op->AffectedQubits();
735 for (auto q : qbits) {
736 if (firstOps.find(q) == firstOps.end()) firstOps[q] = op;
737 }
738 }
739
740 return firstOps;
741 }
742
751 void AddResetsIfNeeded(Time delay = 0) {
752 const auto GetLastOps = GetLastOperationsOnQubits();
753
754 for (const auto &[q, op] : GetLastOps)
755 if (op->GetType() !=
756 OperationType::kReset) // don't add it if there is already a reset
757 // operation on the qubit
758 operations.emplace_back(
759 std::make_shared<Reset<Time>>(Types::qubits_vector{q}, delay));
760 }
761
770 void AddResetsAtBeginningIfNeeded(Time delay = 0) {
771 const auto GetFirstOps = GetFirstOperationsOnQubits();
772
773 for (const auto &[q, op] : GetFirstOps)
774 if (op->GetType() !=
775 OperationType::kReset) // don't add it if there is already a reset
776 // operation on the qubit
777 operations.insert(
778 operations.begin(),
779 std::make_shared<Reset<Time>>(Types::qubits_vector{q}, delay));
780 }
781
789 void Optimize(bool optimizeRotationGates = true) {
790 // Some ideas, from simple to more complex:
791 //
792 // IMPORTANT: Focus on the gates that are added for distributed computing,
793 // either for the one with entanglement or the one with cutting the reason
794 // is that maybe the provided circuit is not that bad, but due of the
795 // supplementary gates added, duplicates (for example) will occur the most
796 // important one qubit ones added are hadamard then X, S, Sdag and Z
797 //
798 // 1. First, one qubit gates can be combined into a single gate or even no
799 // gate a) Straightforward for those that are their own inverse (that is,
800 // hermitian/involutory): Hadamard and Pauli gates for example, if one finds
801 // two of them in sequence, they can be removed other ones are the one that
802 // are followed by their 'dag' (inverse, since the gates are unitary) in the
803 // circuit, those can be removed, too
804
805 // several resets can be also changed into a single one, also repeated
806 // measurements of the same qubit, with result in the same cbit can be
807 // replaced by a single measurement
808
809 // b) other ones can be combined into a single gate, for example phase shift
810 // gates or rotation gates two phase gates (not the general phase shift we
811 // have, but the one with 1, i on the diagonal) can be combined into a Z
812 // gate, for example, or two sqrtNot gates can be combined into a single X
813 // gate combinations of phase gates and hadamard can be replaced by pauli
814 // gates in some cases, and so on even the U gate could be used to join
815 // together several one qubit gates
816
817 // c) even more complex... three or more one qubit gates could be combined
818 // into a single gate... an example is HXH = Z other examples SXS^t = Y,
819 // SZS^t = Z
820
821 // 2. Two qubit gates can be optimized, too
822 // for example two CNOT gates in sequence can be removed if the control
823 // qubit is the same, the same goes for two CZ or CY or SWAP gates (this
824 // goes for the three qubit gates, CCX, CCY, CCZ, CSWAP, too)
825
826 // 3. Some gates commute, the reorder can give some opportunities for more
827 // optimization
828
829 // 4. Groups of gates
830 // the possibilities are endless, but might be easier to focus first on
831 // Clifford gates for example a X sandwiched between CNOTs can be replaced
832 // with two X on each qubit if the original X is on the control qubit or
833 // with an X on the target qubit if the original X is on the target qubit a
834 // similar thing happens if Z is sandwiched between CNOTs, but this time Z x
835 // Z appears if the original Z is on the target qubit and if original Z is
836 // on the control qubit, then the CNOTs dissapear and the Z remains on the
837 // control qubit
838
839 // three CNOT gates with the one in the middle turned upside down compare
840 // with the other two can be replaced by a single swap gate
841
842 // first stage, take out duplicates of H, X, Y, Z
843
844 bool changed;
845
846 do {
847 changed = false;
848
849 std::vector<std::shared_ptr<IOperation<Time>>> newops;
850 newops.reserve(operations.size());
851
852 for (int i = 0; i < static_cast<int>(operations.size()); ++i) {
853 const std::shared_ptr<IOperation<Time>> &op = operations[i];
854
855 const auto type = op->GetType();
856 if (type == OperationType::kNoOp)
857 continue;
858 else if (type == OperationType::kGate) {
859 std::shared_ptr<IQuantumGate<Time>> gate =
860 std::static_pointer_cast<IQuantumGate<Time>>(op);
861 const auto qubits = gate->AffectedQubits();
862
863 if (qubits.size() == 1) {
864 // TODO: HXH = Z, SXS^t = Y, SZS^t = Z ????
865
866 auto gateType = gate->GetGateType();
867 bool replace = false;
868
869 // if it's one of the interesting gates, look ahead to see if it's
870 // followed by the same gate on the same qubit if yes, replace the
871 // next one with a nop and skip the current one (or replace the pair
872 // with a single gate, depending on the type) set changed to true if
873 // something was changed
874 switch (gateType) {
876 [[fallthrough]];
878 [[fallthrough]];
880 if (!optimizeRotationGates) {
881 newops.push_back(op);
882 break;
883 }
884 [[fallthrough]];
886 replace = true;
887 [[fallthrough]];
888 // those above will be replaced the pair with a single gate, all
889 // the following are the ones that get removed
891 [[fallthrough]];
893 [[fallthrough]];
895 [[fallthrough]];
897 [[fallthrough]];
899 [[fallthrough]];
901 [[fallthrough]];
903 [[fallthrough]];
905 [[fallthrough]];
907 [[fallthrough]];
909 [[fallthrough]];
911 bool found = false;
912
913 if (gateType == QuantumGateType::kSGateType)
915 else if (gateType == QuantumGateType::kSdgGateType)
917 else if (gateType == QuantumGateType::kTGateType)
919 else if (gateType == QuantumGateType::kTdgGateType)
921 else if (gateType == QuantumGateType::kSxGateType)
923 else if (gateType == QuantumGateType::kSxDagGateType)
925
926 for (size_t j = i + 1; j < operations.size(); ++j) {
927 auto &nextOp = operations[j];
928 if (!nextOp->CanAffectQuantumState()) continue;
929
930 const auto nextQubits = nextOp->AffectedQubits();
931 bool hasQubit = false;
932
933 for (auto q : nextQubits)
934 if (q == qubits[0]) {
935 hasQubit = true;
936 break;
937 }
938
939 if (!hasQubit)
940 continue; // an op that does not touch the current qubit
941 // can be skipped
942 else if (nextQubits.size() != 1)
943 break; // if it touches the current qubit and it's
944 // something else than a single qubit gate, stop
945
946 const auto nextType = nextOp->GetType();
947 if (nextType != OperationType::kGate)
948 break; // could be a classically conditioned gate, stop
949
950 const auto &nextGate =
951 std::static_pointer_cast<SingleQubitGate<Time>>(nextOp);
952 if (nextGate->GetGateType() == gateType) {
953 if (replace) {
954 const auto params1 = gate->GetParams();
955 const auto params2 = nextGate->GetParams();
956
957 const double param = params1[0] + params2[0];
958 const auto delay =
959 gate->GetDelay() + nextGate->GetDelay();
960
961 if (gateType == QuantumGateType::kPhaseGateType)
962 newops.push_back(std::make_shared<PhaseGate<Time>>(
963 qubits[0], param, delay));
964 else if (gateType == QuantumGateType::kRxGateType)
965 newops.push_back(std::make_shared<RxGate<Time>>(
966 qubits[0], param, delay));
967 else if (gateType == QuantumGateType::kRyGateType)
968 newops.push_back(std::make_shared<RyGate<Time>>(
969 qubits[0], param, delay));
970 else
971 newops.push_back(std::make_shared<RzGate<Time>>(
972 qubits[0], param, delay));
973 }
974 nextOp = std::make_shared<NoOperation<Time>>();
975 changed = true;
976 found = true;
977 break;
978 } else if ((gateType == QuantumGateType::kSGateType &&
979 nextGate->GetGateType() ==
981 (gateType == QuantumGateType::kSdgGateType &&
982 nextGate->GetGateType() ==
984 // if expecting an S gate (or a Sdg gate) and found the
985 // original one instead, replace the pair with a Z gate (S *
986 // S = Z, Sdag * Sdag = Z)
987 const auto delay = gate->GetDelay() + nextGate->GetDelay();
988 newops.push_back(
989 std::make_shared<ZGate<Time>>(qubits[0], delay));
990 nextOp = std::make_shared<NoOperation<Time>>();
991 changed = true;
992 found = true;
993 break;
994 } else if ((gateType == QuantumGateType::kSxGateType &&
995 nextGate->GetGateType() ==
997 (gateType == QuantumGateType::kSxDagGateType &&
998 nextGate->GetGateType() ==
1000 // if expecting an S gate (or a Sdg gate) and found the
1001 // original one instead, replace the pair with a X gate (Sx
1002 // * Sx = X, SXdag * SXdag = X)
1003 const auto delay = gate->GetDelay() + nextGate->GetDelay();
1004 newops.push_back(
1005 std::make_shared<XGate<Time>>(qubits[0], delay));
1006 nextOp = std::make_shared<NoOperation<Time>>();
1007 changed = true;
1008 found = true;
1009 break;
1010 } else if (gateType == QuantumGateType::kTGateType &&
1011 nextGate->GetGateType() ==
1013 // if expecting a T gate and found the Tdgate instead,
1014 // replace the pair with a Sdag gate (Tdg * Tdg = Sdag)
1015 const auto delay = gate->GetDelay() + nextGate->GetDelay();
1016 newops.push_back(
1017 std::make_shared<SdgGate<Time>>(qubits[0], delay));
1018 nextOp = std::make_shared<NoOperation<Time>>();
1019 changed = true;
1020 found = true;
1021 break;
1022 } else if (gateType == QuantumGateType::kTdgGateType &&
1023 nextGate->GetGateType() ==
1025 // if expecting a Tdg gate and found the T gate instead,
1026 // replace the pair with a S gate (T * T = S)
1027 const auto delay = gate->GetDelay() + nextGate->GetDelay();
1028 newops.push_back(
1029 std::make_shared<SGate<Time>>(qubits[0], delay));
1030 nextOp = std::make_shared<NoOperation<Time>>();
1031 changed = true;
1032 found = true;
1033 break;
1034 } else if (gateType == QuantumGateType::kPhaseGateType &&
1035 (nextGate->GetGateType() ==
1037 nextGate->GetGateType() ==
1039 nextGate->GetGateType() ==
1041 nextGate->GetGateType() ==
1043 const auto delay = gate->GetDelay() + nextGate->GetDelay();
1044 double param2;
1045 if (nextGate->GetGateType() == QuantumGateType::kSGateType)
1046 param2 = 0.5 * M_PI;
1047 else if (nextGate->GetGateType() ==
1049 param2 = -0.5 * M_PI;
1050 else if (nextGate->GetGateType() ==
1052 param2 = 0.25 * M_PI;
1053 else
1054 param2 = -0.25 * M_PI;
1055
1056 const auto param = gate->GetParams()[0] + param2;
1057 newops.push_back(std::make_shared<PhaseGate<Time>>(
1058 qubits[0], param, delay));
1059 nextOp = std::make_shared<NoOperation<Time>>();
1060 changed = true;
1061 found = true;
1062 break;
1063 } else if (nextGate->GetGateType() ==
1065 (gateType == QuantumGateType::kSGateType ||
1066 gateType == QuantumGateType::kSdgGateType ||
1067 gateType == QuantumGateType::kTGateType ||
1068 gateType == QuantumGateType::kTdgGateType)) {
1069 const auto delay = gate->GetDelay() + nextGate->GetDelay();
1070 double param1;
1071 if (gateType == QuantumGateType::kSGateType)
1072 param1 = -0.5 * M_PI;
1073 else if (gateType == QuantumGateType::kSdgGateType)
1074 param1 = 0.5 * M_PI;
1075 else if (gateType == QuantumGateType::kTGateType)
1076 param1 = -0.25 * M_PI;
1077 else
1078 param1 = 0.25 * M_PI;
1079
1080 const auto param = nextGate->GetParams()[0] + param1;
1081 newops.push_back(std::make_shared<PhaseGate<Time>>(
1082 qubits[0], param, delay));
1083 nextOp = std::make_shared<NoOperation<Time>>();
1084 changed = true;
1085 found = true;
1086 break;
1087 } else
1088 break; // not the expected gate, acting on same qubit, bail
1089 // out
1090 }
1091
1092 if (!found) newops.push_back(op);
1093 } break;
1094 default:
1095 // if no, just add it
1096 newops.push_back(op);
1097 break;
1098 }
1099 } else if (qubits.size() == 2) {
1100 auto gateType = gate->GetGateType();
1101 bool replace = false;
1102
1103 // if it's one of the interesting gates, look ahead to see if it's
1104 // followed by the same gate on the same qubit if yes, replace the
1105 // next one with a nop and skip the current one (or replace the pair
1106 // with a single gate, depending on the type) set changed to true if
1107 // something was changed
1108 switch (gateType) {
1110 [[fallthrough]];
1112 [[fallthrough]];
1114 if (!optimizeRotationGates) {
1115 newops.push_back(op);
1116 break;
1117 }
1118 [[fallthrough]];
1120 replace = true;
1121 [[fallthrough]];
1122 // those above will be replaced the pair with a single gate, all
1123 // the following are the ones that get removed
1125 [[fallthrough]];
1127 [[fallthrough]];
1129 [[fallthrough]];
1131 [[fallthrough]];
1133 [[fallthrough]];
1135 [[fallthrough]];
1137 bool found = false;
1138
1139 if (gateType == QuantumGateType::kCSxGateType)
1141 else if (gateType == QuantumGateType::kCSxDagGateType)
1143
1144 // looking forward for the next operation that acts on the same
1145 // qubits
1146 for (size_t j = i + 1; j < operations.size(); ++j) {
1147 auto &nextOp = operations[j];
1148 if (!nextOp->CanAffectQuantumState()) continue;
1149
1150 const auto nextQubits = nextOp->AffectedQubits();
1151
1152 bool hasQubit = false;
1153
1154 for (auto q : nextQubits)
1155 if (q == qubits[0] || q == qubits[1]) {
1156 hasQubit = true;
1157 break;
1158 }
1159
1160 if (!hasQubit)
1161 continue; // an op that does not touch the current qubit
1162 // can be skipped
1163 else if (nextQubits.size() != 2)
1164 break; // if it touches a current qubit and it's something
1165 // else than a two qubits gate, stop
1166 // if it's not the same qubits, bail out
1167 else if (gateType == QuantumGateType::kSwapGateType &&
1168 !((qubits[0] == nextQubits[0] &&
1169 qubits[1] == nextQubits[1]) ||
1170 (qubits[0] == nextQubits[1] &&
1171 qubits[1] == nextQubits[0])))
1172 break;
1173 else if (!(qubits[0] == nextQubits[0] &&
1174 qubits[1] == nextQubits[1]))
1175 break;
1176
1177 const auto nextType = nextOp->GetType();
1178 if (nextType != OperationType::kGate)
1179 break; // could be a classically conditioned gate, stop
1180
1181 const auto &nextGate =
1182 std::static_pointer_cast<TwoQubitsGate<Time>>(nextOp);
1183 if (nextGate->GetGateType() == gateType) {
1184 if (replace) {
1185 const auto params1 = gate->GetParams();
1186 const auto params2 = nextGate->GetParams();
1187 const double param = params1[0] + params2[0];
1188 const auto delay =
1189 gate->GetDelay() + nextGate->GetDelay();
1190
1191 if (gateType == QuantumGateType::kCPGateType)
1192 newops.push_back(std::make_shared<CPGate<Time>>(
1193 qubits[0], qubits[1], param, delay));
1194 else if (gateType == QuantumGateType::kCRxGateType)
1195 newops.push_back(std::make_shared<CRxGate<Time>>(
1196 qubits[0], qubits[1], param, delay));
1197 else if (gateType == QuantumGateType::kCRyGateType)
1198 newops.push_back(std::make_shared<CRyGate<Time>>(
1199 qubits[0], qubits[1], param, delay));
1200 else
1201 newops.push_back(std::make_shared<CRzGate<Time>>(
1202 qubits[0], qubits[1], param, delay));
1203 }
1204 nextOp = std::make_shared<NoOperation<Time>>();
1205 changed = true; // continue merging gates, we found one
1206 // that was merged/removed
1207 found = true; // don't put op in the new operations, we
1208 // handled it
1209 break;
1210 } else
1211 break; // not the expected gate, acting on same qubits,
1212 // bail out
1213 } // end for of looking forward
1214
1215 if (!found) newops.push_back(op);
1216 } break;
1217 default:
1218 // if no, just add it
1219 newops.push_back(op);
1220 break;
1221 }
1222 } else if (qubits.size() == 3) {
1223 auto gateType = gate->GetGateType();
1224
1225 // if it's one of the interesting gates, look ahead to see if it's
1226 // followed by the same gate on the same qubit if yes, replace the
1227 // next one with a nop and skip the current one (or replace the pair
1228 // with a single gate, depending on the type) set changed to true if
1229 // something was changed
1230 switch (gateType) {
1232 [[fallthrough]];
1234 bool found = false;
1235
1236 for (size_t j = i + 1; j < operations.size(); ++j) {
1237 auto &nextOp = operations[j];
1238 if (!nextOp->CanAffectQuantumState()) continue;
1239
1240 const auto nextQubits = nextOp->AffectedQubits();
1241
1242 bool hasQubit = false;
1243
1244 for (auto q : nextQubits)
1245 if (q == qubits[0] || q == qubits[1] || q == qubits[2]) {
1246 hasQubit = true;
1247 break;
1248 }
1249
1250 if (!hasQubit)
1251 continue; // an op that does not touch the current qubit
1252 // can be skipped
1253 else if (nextQubits.size() != 3)
1254 break; // if it touches a current qubit and it's something
1255 // else than a three qubits gate, stop
1256 // if it's not the same qubits, bail out
1257 else if (gateType == QuantumGateType::kCSwapGateType &&
1258 (qubits[0] != nextQubits[0] ||
1259 !((qubits[1] == nextQubits[1] &&
1260 qubits[2] == nextQubits[2]) ||
1261 (qubits[1] == nextQubits[2] &&
1262 qubits[2] == nextQubits[1]))))
1263 break;
1264 else if (gateType == QuantumGateType::kCCXGateType &&
1265 (qubits[2] != nextQubits[2] ||
1266 !(qubits[1] == nextQubits[1] &&
1267 qubits[2] == nextQubits[2]) ||
1268 !(qubits[1] == nextQubits[2] &&
1269 qubits[2] == nextQubits[1])))
1270 break;
1271
1272 const auto nextType = nextOp->GetType();
1273 if (nextType != OperationType::kGate)
1274 break; // could be a classically conditioned gate, stop
1275
1276 const auto &nextGate =
1277 std::static_pointer_cast<ThreeQubitsGate<Time>>(nextOp);
1278 if (nextGate->GetGateType() == gateType) {
1279 nextOp = std::make_shared<NoOperation<Time>>();
1280 changed = true;
1281 found = true;
1282 break;
1283 } else
1284 break; // not the expected gate, acting on same qubits,
1285 // bail out
1286 }
1287
1288 if (!found) newops.push_back(op);
1289 } break;
1290 default:
1291 // if no, just add it
1292 newops.push_back(op);
1293 break;
1294 }
1295 } else
1296 newops.push_back(op);
1297 } else
1298 newops.push_back(op);
1299 } // end for on circuit operations
1300
1301 operations.swap(newops);
1302 } while (changed);
1303 }
1304
1312 OperationsVector newops;
1313 newops.reserve(operations.size());
1314
1315 size_t qubitsNo = std::max(GetMaxQubitIndex(), GetMaxCbitIndex()) + 1;
1316
1317 std::unordered_map<Types::qubit_t, std::vector<OperationPtr>> qubitOps;
1318
1319 std::vector<OperationPtr> lastOps(qubitsNo);
1320
1321 std::unordered_map<OperationPtr, std::unordered_set<OperationPtr>>
1322 dependenciesMap;
1323
1324 for (const auto &op : operations) {
1325 std::unordered_set<OperationPtr> dependencies;
1326
1327 const auto cbits = op->AffectedBits();
1328 for (auto c : cbits) {
1329 const auto lastOp = lastOps[c];
1330 if (lastOp) dependencies.insert(lastOp);
1331 }
1332
1333 const auto qubits = op->AffectedQubits();
1334 for (auto q : qubits) {
1335 qubitOps[q].push_back(op);
1336
1337 const auto lastOp = lastOps[q];
1338 if (lastOp) dependencies.insert(lastOp);
1339
1340 lastOps[q] = op;
1341 }
1342
1343 for (auto c : cbits) lastOps[c] = op;
1344
1345 dependenciesMap[op] = dependencies;
1346 }
1347 lastOps.clear();
1348
1349 std::vector<Types::qubit_t> indices(qubitsNo, 0);
1350
1351 while (!dependenciesMap.empty()) {
1352 OperationPtr nextOp;
1353
1354 // try to locate a 'next' gate for a qubit that is either a measurement or
1355 // a reset
1356 for (size_t q = 0; q < qubitsNo; ++q) {
1357 if (qubitOps.find(q) ==
1358 qubitOps.end()) // no operation left on this qubit
1359 continue;
1360
1361 // grab the current operation for this qubit
1362 const auto &ops = qubitOps[q];
1363 const auto &op = ops[indices[q]];
1364
1365 // consider only measurements and resets
1366 if (op->GetType() == OperationType::kMeasurement ||
1367 op->GetType() == OperationType::kReset) {
1368 bool hasDependencies = false;
1369
1370 for (const auto &opd : dependenciesMap[op])
1371 if (dependenciesMap.find(opd) != dependenciesMap.end()) {
1372 hasDependencies = true;
1373 break;
1374 }
1375
1376 if (!hasDependencies) {
1377 nextOp = op;
1378 break;
1379 }
1380 }
1381 }
1382
1383 if (nextOp) {
1384 dependenciesMap.erase(nextOp);
1385
1386 const auto qubits = nextOp->AffectedQubits();
1387 for (auto q : qubits) {
1388 ++indices[q];
1389 if (indices[q] >= qubitOps[q].size()) qubitOps.erase(q);
1390 }
1391
1392 newops.emplace_back(std::move(nextOp));
1393 continue;
1394 }
1395
1396 // if there is no measurement or reset, add the next gate
1397 for (Types::qubit_t q = 0; q < qubitsNo; ++q) {
1398 if (qubitOps.find(q) ==
1399 qubitOps.end()) // no operation left on this qubit
1400 continue;
1401
1402 // grab the current operation for this qubit
1403 const auto &ops = qubitOps[q];
1404 const auto &op = ops[indices[q]];
1405
1406 bool hasDependencies = false;
1407
1408 for (const auto &opd : dependenciesMap[op])
1409 if (dependenciesMap.find(opd) != dependenciesMap.end()) {
1410 hasDependencies = true;
1411 break;
1412 }
1413
1414 if (!hasDependencies) {
1415 nextOp = op;
1416 break;
1417 }
1418 }
1419
1420 if (nextOp) {
1421 dependenciesMap.erase(nextOp);
1422
1423 const auto qubits = nextOp->AffectedQubits();
1424 for (auto q : qubits) {
1425 ++indices[q];
1426 if (indices[q] >= qubitOps[q].size()) qubitOps.erase(q);
1427 }
1428
1429 newops.emplace_back(std::move(nextOp));
1430 }
1431 }
1432
1433 assert(newops.size() == operations.size());
1434
1435 operations.swap(newops);
1436 }
1437
1447 std::pair<std::vector<size_t>, std::vector<Time>> GetDepth() const {
1448 size_t maxDepth;
1449 Time maxTime;
1450
1451 size_t qubitsNo = GetMaxQubitIndex() + 1;
1452 std::vector<Time> qubitTimes(qubitsNo, 0);
1453 std::vector<size_t> qubitDepths(qubitsNo, 0);
1454
1455 std::unordered_map<size_t, size_t> fromQubits;
1456
1457 for (const auto &op : operations) {
1458 const auto qbits = op->AffectedQubits();
1459 const auto delay = op->GetDelay();
1460
1461 maxTime = 0;
1462 maxDepth = 0;
1463 for (auto q : qbits) {
1464 qubitTimes[q] += delay;
1465 ++qubitDepths[q];
1466 if (qubitTimes[q] > maxTime) maxTime = qubitTimes[q];
1467 if (qubitDepths[q] > maxDepth) maxDepth = qubitDepths[q];
1468 }
1469
1470 const auto t = op->GetType();
1471 std::vector<size_t> condbits;
1472
1473 // TODO: deal with 'random gen' operations, those do not affect qubits
1474 // directly, but they do affect the classical bits and can be used in
1475 // conditional operations
1476
1479 condbits = op->AffectedBits();
1480
1481 for (auto bit : condbits) {
1482 if (fromQubits.find(bit) != fromQubits.end()) bit = fromQubits[bit];
1483
1484 bool found = false;
1485 for (auto q : qbits) {
1486 if (q == bit) {
1487 found = true;
1488 break;
1489 }
1490 }
1491 if (found || bit >= qubitsNo) continue;
1492
1493 qubitTimes[bit] += delay;
1494 ++qubitDepths[bit];
1495 if (qubitTimes[bit] > maxTime) maxTime = qubitTimes[bit];
1496 if (qubitDepths[bit] > maxDepth) maxDepth = qubitDepths[bit];
1497 }
1498
1500 const auto condMeas =
1501 std::static_pointer_cast<ConditionalMeasurement<Time>>(op);
1502 const auto meas = condMeas->GetOperation();
1503 const auto measQubits = meas->AffectedQubits();
1504 const auto measBits = meas->AffectedBits();
1505
1506 for (size_t i = 0; i < measQubits.size(); ++i) {
1507 if (i < measBits.size())
1508 fromQubits[measBits[i]] = measQubits[i];
1509 else
1510 fromQubits[measQubits[i]] = measQubits[i];
1511 }
1512 }
1513 } else if (t == OperationType::kMeasurement) {
1514 condbits = op->AffectedBits();
1515
1516 for (size_t i = 0; i < qbits.size(); ++i) {
1517 if (i < condbits.size())
1518 fromQubits[condbits[i]] = qbits[i];
1519 else
1520 fromQubits[qbits[i]] = qbits[i];
1521 }
1522 }
1523
1524 for (auto q : qbits) {
1525 qubitTimes[q] = maxTime;
1526 qubitDepths[q] = maxDepth;
1527 }
1528
1529 for (auto bit : condbits) {
1530 qubitTimes[bit] = maxTime;
1531 qubitDepths[bit] = maxDepth;
1532 }
1533 }
1534
1535 return std::make_pair(qubitDepths, qubitTimes);
1536 }
1537
1547 std::pair<size_t, Time> GetMaxDepth() const {
1548 auto [qubitDepths, qubitTimes] = GetDepth();
1549
1550 Time maxTime = 0;
1551 size_t maxDepth = 0;
1552 for (size_t qubit = 0; qubit < qubitDepths.size(); ++qubit) {
1553 if (qubitTimes[qubit] > maxTime) maxTime = qubitTimes[qubit];
1554 if (qubitDepths[qubit] > maxDepth) maxDepth = qubitDepths[qubit];
1555 }
1556
1557 return std::make_pair(maxDepth, maxTime);
1558 }
1559
1566 size_t GetNumberOfOperations() const { return operations.size(); }
1567
1576 OperationPtr GetOperation(size_t pos) const {
1577 if (pos >= operations.size()) return nullptr;
1578
1579 return operations[pos];
1580 }
1581
1595 std::shared_ptr<Circuit<Time>> GetCircuitCut(Types::qubit_t startQubit,
1596 Types::qubit_t endQubit) const {
1597 OperationsVector newops;
1598 newops.reserve(operations.size());
1599
1600 for (const auto &op : operations) {
1601 const auto qubits = op->AffectedQubits();
1602 bool containsOutsideQubits = false;
1603 bool containsInsideQubits = false;
1604 for (const auto q : qubits) {
1605 if (q < startQubit || q > endQubit) {
1606 containsOutsideQubits = true;
1607 if (containsInsideQubits) break;
1608 } else {
1609 containsInsideQubits = true;
1610 if (containsOutsideQubits) break;
1611 }
1612 }
1613
1614 if (containsInsideQubits) {
1615 if (containsOutsideQubits)
1616 throw std::runtime_error(
1617 "Cannot cut the circuit with the specified interval");
1618 newops.emplace_back(op->Clone());
1619 }
1620 }
1621
1622 return std::make_shared<Circuit<Time>>(newops);
1623 }
1624
1636 std::unordered_set<Types::qubit_t> measuredQubits;
1637 std::unordered_set<Types::qubit_t> affectedQubits;
1638 std::unordered_set<Types::qubit_t> resetQubits;
1639
1640 for (const auto &op : operations) {
1641 const auto qubits = op->AffectedQubits();
1642
1643 if (op->GetType() == OperationType::kMeasurement) {
1644 for (const auto qbit : qubits)
1645 if (resetQubits.find(qbit) !=
1646 resetQubits.end()) // there is a reset on this qubit already and
1647 // it's not at the beginning of the circuit
1648 return true;
1649
1650 /*
1651 const auto bits = op->AffectedBits();
1652 if (bits.size() != qubits.size())
1653 return true;
1654
1655 for (size_t b = 0; b < bits.size(); ++b)
1656 if (bits[b] != qubits[b])
1657 return true;
1658 */
1659 measuredQubits.insert(qubits.begin(), qubits.end());
1660 } else if (op->GetType() == OperationType::kConditionalGate ||
1661 op->GetType() == OperationType::kConditionalMeasurement ||
1662 op->GetType() == OperationType::kRandomGen ||
1663 op->GetType() == OperationType::kConditionalRandomGen)
1664 return true;
1665 else if (op->GetType() == OperationType::kReset) {
1666 // resets in the middle of the circuit are treated as measurements
1667 for (const auto qbit : qubits) {
1668 // if there is already a gate applied on the qubit but no measurement
1669 // yet, it's considered in the middle
1670 // if there is no gate applied,
1671 // then it's the first operation on the qubit
1672 if (affectedQubits.find(qbit) != affectedQubits.end() ||
1673 measuredQubits.find(qbit) != measuredQubits.end())
1674 resetQubits.insert(qbit);
1675
1676 affectedQubits.insert(qbit);
1677 }
1678 } else {
1679 for (const auto qbit : qubits) {
1680 if (measuredQubits.find(qbit) !=
1681 measuredQubits
1682 .end()) // there is a measurement on this qubit already
1683 return true;
1684
1685 if (resetQubits.find(qbit) !=
1686 resetQubits.end()) // there is a reset on this qubit already and
1687 // it's not at the beginning of the circuit
1688 return true;
1689
1690 affectedQubits.insert(qbit);
1691 }
1692 }
1693 }
1694
1695 return false;
1696 }
1697
1712 std::vector<bool> ExecuteNonMeasurements(
1713 const std::shared_ptr<Simulators::ISimulator> &sim,
1714 OperationState &state, size_t* curMaxBondDim = nullptr) const {
1715 std::vector<bool> executedOps;
1716 executedOps.reserve(operations.size());
1717
1718 std::unordered_set<Types::qubit_t> measuredQubits;
1719 std::unordered_set<Types::qubit_t> affectedQubits;
1720
1721 bool executionStopped = false;
1722
1723 for (size_t i = 0; i < operations.size(); ++i) {
1724 auto &op = operations[i];
1725 const auto qubits = op->AffectedQubits();
1726
1727 bool executed = false;
1728
1729 if (op->GetType() == OperationType::kMeasurement ||
1730 op->GetType() == OperationType::kConditionalMeasurement ||
1731 op->GetType() == OperationType::kRandomGen ||
1732 op->GetType() == OperationType::kConditionalRandomGen)
1733 measuredQubits.insert(qubits.begin(), qubits.end());
1734 else if (op->GetType() == OperationType::kReset) {
1735 // if it's the first op on qubit(s), execute it, otherwise treat it as a
1736 // measurement
1737 executed = true;
1738 for (auto qubit : qubits)
1739 if (affectedQubits.find(qubit) != affectedQubits.end()) {
1740 executed = false;
1741 break;
1742 }
1743
1744 if (executed) {
1745 if (sim) {
1746 op->Execute(sim, state);
1747 if (curMaxBondDim) {
1748 const auto bondDim = sim->GetCurrentMaxBondDimension();
1749 if (bondDim > *curMaxBondDim) *curMaxBondDim = bondDim;
1750 }
1751 }
1752 } else
1753 measuredQubits.insert(qubits.begin(), qubits.end());
1754 } else // regular gate or conditional gate
1755 {
1756 const auto bits = op->AffectedBits();
1757
1758 // a measurement on a qubit prevents execution of any following gate
1759 // than affects the same qubit also a gate that's not executed and it
1760 // would affect certain qubits will prevent the execution of any
1761 // following gate that affects those qubits
1762
1763 bool canExecute = op->GetType() == OperationType::kGate ||
1764 op->GetType() == OperationType::kQuantumChannel ||
1765 op->GetType() == OperationType::kDelay;
1766
1767 if (canExecute) // a conditional gate cannot be executed, it needs
1768 // something executed at each shot, either a
1769 // measurement or a random number generated
1770 {
1771 for (auto bit : bits)
1772 if (measuredQubits.find(bit) != measuredQubits.end()) {
1773 canExecute = false;
1774 break;
1775 }
1776
1777 for (auto qubit : qubits)
1778 if (measuredQubits.find(qubit) != measuredQubits.end()) {
1779 canExecute = false;
1780 break;
1781 }
1782 }
1783
1784 if (canExecute) {
1785 if (sim) {
1786 op->Execute(sim, state);
1787 if (curMaxBondDim) {
1788 const auto bondDim = sim->GetCurrentMaxBondDimension();
1789 if (bondDim > *curMaxBondDim) *curMaxBondDim = bondDim;
1790 }
1791 }
1792 executed = true;
1793 } else {
1794 // this is a 'trick', if it cannot execute, then neither can any
1795 // following gate that affects any of the already involved qubits
1796 measuredQubits.insert(bits.begin(), bits.end());
1797 measuredQubits.insert(qubits.begin(), qubits.end());
1798 }
1799 }
1800
1801 affectedQubits.insert(qubits.begin(), qubits.end());
1802
1803 if (!executed) {
1804 executionStopped = true;
1805 if (sim && sim->GetSimulationType() ==
1806 Simulators::SimulationType::kMatrixProductState && sim->SupportsMPSSwapOptimization() &&
1807 op->GetType() != OperationType::kRandomGen &&
1808 op->GetType() != OperationType::kConditionalRandomGen && op->GetType() != OperationType::kNoOp)
1809 sim->SetGatesCounter(sim->GetGatesCounter() + 1);
1810 }
1811 if (executionStopped) executedOps.emplace_back(executed);
1812 }
1813
1814 // if (sim) sim->Flush();
1815
1816 return executedOps;
1817 }
1818
1832 void ExecuteMeasurements(const std::shared_ptr<Simulators::ISimulator> &sim,
1833 OperationState &state,
1834 const std::vector<bool> &executedOps, size_t* curMaxBondDim = nullptr) const {
1835 state.Reset();
1836 if (!sim) return;
1837
1838 // if (executedOps.empty() && !operations.empty()) throw
1839 // std::runtime_error("The executed operations vector is empty");
1840
1841 const size_t dif = operations.size() - executedOps.size();
1842
1843 for (size_t i = dif; i < operations.size(); ++i)
1844 if (!executedOps[i - dif]) {
1845 operations[i]->Execute(sim, state);
1846 if (curMaxBondDim) {
1847 const auto bondDim = sim->GetCurrentMaxBondDimension();
1848 if (bondDim > *curMaxBondDim) *curMaxBondDim = bondDim;
1849 }
1850 }
1851
1852 // sim->Flush();
1853 }
1854
1855
1865 std::shared_ptr<Circuits::Circuit<Time>> RemoveExecutedOperations(
1866 std::vector<bool> &executedOps) const {
1867 if (executedOps.empty()) {
1868 executedOps.resize(size(), false);
1869 return std::make_shared<Circuit<Time>>(operations);
1870 }
1871
1872 OperationsVector newops;
1873 newops.reserve(operations.size());
1874
1875 const size_t dif = operations.size() - executedOps.size();
1876 for (size_t i = dif; i < operations.size(); ++i)
1877 if (!executedOps[i - dif]) newops.emplace_back(operations[i]);
1878
1879 std::vector<bool> newExecutedOps(newops.size(), false);
1880 executedOps.swap(newExecutedOps);
1881
1882 return std::make_shared<Circuit<Time>>(newops);
1883 }
1884
1885
1886
1887 // used internally to optimize measurements in the case of having measurements
1888 // only at the end of the circuit
1889 std::shared_ptr<MeasurementOperation<Time>> GetLastMeasurements(
1890 const std::vector<bool> &executedOps, bool sort = true) const {
1891 const size_t dif = operations.size() - executedOps.size();
1892 std::vector<std::pair<Types::qubit_t, size_t>> measurements;
1893 measurements.reserve(dif);
1894
1895 for (size_t i = dif; i < operations.size(); ++i)
1896 if (!executedOps[i - dif] &&
1897 operations[i]->GetType() == OperationType::kMeasurement) {
1898 auto measOp =
1899 std::static_pointer_cast<MeasurementOperation<Time>>(operations[i]);
1900 const auto &qubits = measOp->GetQubits();
1901 const auto &bits = measOp->GetBitsIndices();
1902
1903 for (size_t j = 0; j < qubits.size(); ++j)
1904 measurements.emplace_back(qubits[j], bits[j]);
1905 }
1906
1907 // qiskit aer expects sometimes to have them in sorted order, so...
1908 if (sort)
1909 std::sort(
1910 measurements.begin(), measurements.end(),
1911 [](const auto &p1, const auto &p2) { return p1.first < p2.first; });
1912
1913 return std::make_shared<MeasurementOperation<Time>>(measurements);
1914 }
1915
1925 for (const auto &op : operations)
1926 if (op->GetType() == OperationType::kConditionalGate ||
1927 op->GetType() == OperationType::kConditionalMeasurement ||
1928 op->GetType() == OperationType::kConditionalRandomGen)
1929 return true;
1930
1931 return false;
1932 }
1933
1948 for (const auto &op : operations) {
1949 const auto qubits = op->AffectedQubits();
1950 if (qubits.size() <= 1) continue;
1951
1952 if (qubits.size() == 2) {
1953 if (std::abs(qubits[0] - qubits[1]) != 1) return false;
1954 } else {
1955 Types::qubit_t minQubit = qubits[0];
1956 Types::qubit_t maxQubit = qubits[0];
1957
1958 for (size_t i = 1; i < qubits.size(); ++i) {
1959 if (qubits[i] < minQubit)
1960 minQubit = qubits[i];
1961 else if (qubits[i] > maxQubit)
1962 maxQubit = qubits[i];
1963 }
1964
1965 if (maxQubit - minQubit >= qubits.size()) return false;
1966 }
1967 }
1968
1969 return true;
1970 }
1971
1979 bool IsForest() const {
1980 std::unordered_map<Types::qubit_t, size_t> qubits;
1981 std::unordered_map<Types::qubit_t, Types::qubits_vector> lastQubits;
1982
1983 for (const auto &op : operations) {
1984 const auto q = op->AffectedQubits();
1985 // one qubit gates or other operations that do not affect qubits do not
1986 // change anything
1987 if (q.size() <= 1) continue;
1988
1989 bool allInTheLastQubits = true;
1990
1991 for (const auto qubit : q) {
1992 if (lastQubits.find(qubit) == lastQubits.end()) {
1993 allInTheLastQubits = false;
1994 break;
1995 } else {
1996 const auto &lastQ = lastQubits[qubit];
1997
1998 for (const auto q1 : q)
1999 if (std::find(lastQ.cbegin(), lastQ.cend(), q1) == lastQ.cend()) {
2000 allInTheLastQubits = false;
2001 break;
2002 }
2003
2004 if (!allInTheLastQubits) break;
2005 }
2006 }
2007
2008 if (allInTheLastQubits) continue;
2009
2010 for (const auto qubit : q) {
2011 if (qubits[qubit] > 1) // if the qubit is affected again...
2012 return false;
2013
2014 ++qubits[qubit];
2015
2016 lastQubits[qubit] = q;
2017 }
2018 }
2019
2020 return true;
2021 }
2022
2032 bool IsClifford() const override {
2033 for (const auto &op : operations)
2034 if (!op->IsClifford()) return false;
2035
2036 return true;
2037 }
2038
2048 double CliffordPercentage() const {
2049 size_t cliffordOps = 0;
2050 for (const auto &op : operations)
2051 if (op->IsClifford()) ++cliffordOps;
2052
2053 return static_cast<double>(cliffordOps) / operations.size();
2054 }
2055
2065 std::unordered_set<Types::qubit_t> GetCliffordQubits() const {
2066 std::unordered_set<Types::qubit_t> cliffordQubits;
2067 std::unordered_set<Types::qubit_t> nonCliffordQubits;
2068
2069 for (const auto &op : operations) {
2070 const auto qubits = op->AffectedQubits();
2071 if (op->IsClifford()) {
2072 for (const auto q : qubits) cliffordQubits.insert(q);
2073 } else {
2074 for (const auto q : qubits) nonCliffordQubits.insert(q);
2075 }
2076 }
2077
2078 for (const auto q : nonCliffordQubits) cliffordQubits.erase(q);
2079
2080 return cliffordQubits;
2081 }
2082
2092 std::vector<std::shared_ptr<Circuit<Time>>> SplitCircuit() const {
2093 std::vector<std::shared_ptr<Circuit<Time>>> circuits;
2094
2095 // find how many disjoint circuits we have in this circuit
2096
2097 std::unordered_map<Types::qubit_t, std::unordered_set<Types::qubit_t>>
2098 circuitsMap;
2099 auto allQubits = GetQubits();
2100 std::unordered_map<Types::qubit_t, Types::qubit_t> qubitCircuitMap;
2101
2102 // start with a bunch of disjoint sets of qubits, containing each a single
2103 // qubit
2104
2105 for (auto qubit : allQubits) {
2106 circuitsMap[qubit] = std::unordered_set<Types::qubit_t>{qubit};
2107 qubitCircuitMap[qubit] = qubit;
2108 }
2109
2110 // then the gates will join them together into circuits
2111
2112 for (const auto &op : operations) {
2113 const auto qubits = op->AffectedQubits();
2114
2115 if (qubits.empty()) continue;
2116
2117 auto qubitIt = qubits.cbegin();
2118 auto firstQubit = *qubitIt;
2119 // where is the first qubit in the disjoint sets?
2120 auto firstQubitCircuit = qubitCircuitMap[firstQubit];
2121
2122 ++qubitIt;
2123
2124 for (; qubitIt != qubits.cend(); ++qubitIt) {
2125 auto qubit = *qubitIt;
2126
2127 // where is the qubit in the disjoint sets?
2128 auto qubitCircuit = qubitCircuitMap[qubit];
2129
2130 // join the circuits / qubits sets
2131
2132 if (firstQubitCircuit != qubitCircuit) {
2133 // join the circuits
2134 circuitsMap[firstQubitCircuit].insert(
2135 circuitsMap[qubitCircuit].begin(),
2136 circuitsMap[qubitCircuit].end());
2137
2138 // update the qubit to circuit map
2139 for (auto q : circuitsMap[qubitCircuit])
2140 qubitCircuitMap[q] = firstQubitCircuit;
2141
2142 // remove the joined circuit
2143 circuitsMap.erase(qubitCircuit);
2144 }
2145 }
2146 }
2147
2148 size_t circSize = 1ULL;
2149 // static cast added to prevent compiler error on MacOS
2150 circSize = std::max(circSize, static_cast<size_t>(circuitsMap.size()));
2151 circuits.resize(circSize);
2152
2153 for (size_t i = 0; i < circuits.size(); ++i)
2154 circuits[i] = std::make_shared<Circuit<Time>>();
2155
2156 std::unordered_map<Types::qubit_t, size_t> qubitsSetsToCircuit;
2157
2158 size_t circuitNo = 0;
2159 for (const auto &[id, qubitSet] : circuitsMap) {
2160 qubitsSetsToCircuit[id] = circuitNo;
2161
2162 ++circuitNo;
2163 }
2164
2165 // now fill them up with the operations
2166
2167 for (const auto &op : operations) {
2168 const auto qubits = op->AffectedQubits();
2169
2170 if (qubits.empty()) {
2171 circuits[0]->AddOperation(op->Clone());
2172 continue;
2173 }
2174
2175 const auto circ = qubitsSetsToCircuit[qubitCircuitMap[*qubits.cbegin()]];
2176
2177 circuits[circ]->AddOperation(op->Clone());
2178 }
2179
2180 return circuits;
2181 }
2182
2190 std::vector<std::shared_ptr<Circuits::Circuit<Time>>> ToLayers() 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 std::unordered_map<size_t, size_t> classicalBitLayer;
2196
2197 for (const auto &op : GetOperations()) {
2198 // check the instruction, see if a new layer is needed
2199
2200 // only qubits matter here, the others can be classicaly sent if they are
2201 // needed, even if they are shared... but that should be handled
2202 // somewhere, when not done implicitely (as for the 'simple network' case)
2203 if (op->CanAffectQuantumState()) {
2204 const auto qubits = op->AffectedQubits();
2205 size_t maxLevel = 0;
2206
2207 for (Types::qubit_t qbit : qubits) {
2208 ++qubitsUsed[qbit];
2209 maxLevel = std::max(maxLevel, static_cast<size_t>(qubitsUsed[qbit]));
2210
2211 if (layers.size() < qubitsUsed[qbit]) {
2212 auto circ = std::make_shared<Circuits::Circuit<Time>>();
2213 layers.push_back(std::move(circ));
2214 }
2215 }
2216
2217 if (op->IsConditional()) {
2218 const auto bits = op->AffectedBits();
2219 for (const auto bit : bits)
2220 maxLevel = std::max(maxLevel, classicalBitLayer[bit]);
2221 }
2222
2223 // now set all the qubits in the instruction to the max level
2224 for (Types::qubit_t qbit : qubits) qubitsUsed[qbit] = maxLevel;
2225
2226 const size_t layerIdx = maxLevel > 0 ? maxLevel - 1 : 0;
2227
2228 // ensure enough layers exist for the computed level
2229 while (layers.size() <= layerIdx)
2230 layers.push_back(std::make_shared<Circuits::Circuit<Time>>());
2231
2232 layers[layerIdx]->AddOperation(op->Clone());
2233
2234 const auto writtenBits = op->AffectedBits();
2235 if (!writtenBits.empty() && !op->IsConditional()) {
2236 const size_t writtenLevel = maxLevel > 0 ? maxLevel : 1;
2237 for (const auto bit : writtenBits)
2238 classicalBitLayer[bit] =
2239 std::max(classicalBitLayer[bit], writtenLevel);
2240 }
2241 } else
2242 // add the instruction to the last layer
2243 layers.back()->AddOperation(op->Clone());
2244 }
2245
2246 return layers;
2247 }
2248
2257 std::vector<std::shared_ptr<Circuits::Circuit<Time>>> ToLayersNoClone()
2258 const {
2259 std::vector<std::shared_ptr<Circuits::Circuit<Time>>> layers;
2260 layers.emplace_back(std::make_shared<Circuits::Circuit<Time>>());
2261
2262 std::unordered_map<Types::qubit_t, Types::qubit_t> qubitsUsed;
2263 std::unordered_map<size_t, size_t> classicalBitLayer;
2264
2265 for (const auto &op : GetOperations()) {
2266 // check the instruction, see if a new layer is needed
2267
2268 // only qubits matter here, the others can be classicaly sent if they are
2269 // needed, even if they are shared... but that should be handled
2270 // somewhere, when not done implicitely (as for the 'simple network' case)
2271 if (op->CanAffectQuantumState()) {
2272 const auto qubits = op->AffectedQubits();
2273 size_t maxLevel = 0;
2274
2275 for (Types::qubit_t qbit : qubits) {
2276 ++qubitsUsed[qbit];
2277 maxLevel = std::max(maxLevel, static_cast<size_t>(qubitsUsed[qbit]));
2278
2279 if (layers.size() < qubitsUsed[qbit]) {
2280 auto circ = std::make_shared<Circuits::Circuit<Time>>();
2281 layers.push_back(std::move(circ));
2282 }
2283 }
2284
2285 if (op->IsConditional()) {
2286 const auto bits = op->AffectedBits();
2287 for (const auto bit : bits)
2288 maxLevel = std::max(maxLevel, classicalBitLayer[bit]);
2289 }
2290
2291 // now set all the qubits in the instruction to the max level
2292 for (Types::qubit_t qbit : qubits) qubitsUsed[qbit] = maxLevel;
2293
2294 const size_t layerIdx = maxLevel > 0 ? maxLevel - 1 : 0;
2295
2296 // ensure enough layers exist for the computed level
2297 while (layers.size() <= layerIdx)
2298 layers.push_back(std::make_shared<Circuits::Circuit<Time>>());
2299
2300 layers[layerIdx]->AddOperation(op);
2301
2302 const auto writtenBits = op->AffectedBits();
2303 if (!writtenBits.empty() && !op->IsConditional()) {
2304 const size_t writtenLevel = maxLevel > 0 ? maxLevel : 1;
2305 for (const auto bit : writtenBits)
2306 classicalBitLayer[bit] =
2307 std::max(classicalBitLayer[bit], writtenLevel);
2308 }
2309 } else
2310 // add the instruction to the last layer
2311 layers.back()->AddOperation(op);
2312 }
2313
2314 return layers;
2315 }
2316
2327 std::vector<std::shared_ptr<Circuits::Circuit<Time>>> ToMultipleQubitsLayers()
2328 const {
2329 std::vector<std::shared_ptr<Circuits::Circuit<Time>>> layers;
2330 layers.emplace_back(std::make_shared<Circuits::Circuit<Time>>());
2331
2332 std::unordered_map<Types::qubit_t, Types::qubit_t> qubitsUsed;
2333 std::unordered_map<size_t, size_t> classicalBitLayer;
2334
2335 for (const auto &op : GetOperations()) {
2336 // check the instruction, see if a new layer is needed
2337
2338 // only qubits matter here, the others can be classicaly sent if they are
2339 // needed, even if they are shared... but that should be handled
2340 // somewhere, when not done implicitely (as for the 'simple network' case)
2341 if (op->CanAffectQuantumState()) {
2342 const auto qubits = op->AffectedQubits();
2343 size_t maxLevel = 0;
2344
2345 for (Types::qubit_t qbit : qubits) {
2346 if (qubits.size() > 1) ++qubitsUsed[qbit];
2347 maxLevel = std::max(maxLevel, static_cast<size_t>(qubitsUsed[qbit]));
2348
2349 if (layers.size() < qubitsUsed[qbit]) {
2350 auto circ = std::make_shared<Circuits::Circuit<Time>>();
2351 layers.push_back(std::move(circ));
2352 }
2353 }
2354
2355 if (op->IsConditional()) {
2356 const auto bits = op->AffectedBits();
2357 for (const auto bit : bits)
2358 maxLevel = std::max(maxLevel, classicalBitLayer[bit]);
2359 }
2360
2361 // now set all the qubits in the instruction to the max level
2362 for (Types::qubit_t qbit : qubits) qubitsUsed[qbit] = maxLevel;
2363
2364 const size_t layerIdx = maxLevel > 0 ? maxLevel - 1 : 0;
2365
2366 // ensure enough layers exist for the computed level
2367 while (layers.size() <= layerIdx)
2368 layers.push_back(std::make_shared<Circuits::Circuit<Time>>());
2369
2370 layers[layerIdx]->AddOperation(op->Clone());
2371
2372 const auto writtenBits = op->AffectedBits();
2373 if (!writtenBits.empty() && !op->IsConditional()) {
2374 const size_t writtenLevel = maxLevel > 0 ? maxLevel : 1;
2375 for (const auto bit : writtenBits)
2376 classicalBitLayer[bit] =
2377 std::max(classicalBitLayer[bit], writtenLevel);
2378 }
2379 } else
2380 // add the instruction to the last layer
2381 layers.back()->AddOperation(op->Clone());
2382 }
2383
2384 return layers;
2385 }
2386
2397 std::vector<std::shared_ptr<Circuits::Circuit<Time>>>
2399 std::vector<std::shared_ptr<Circuits::Circuit<Time>>> layers;
2400 layers.emplace_back(std::make_shared<Circuits::Circuit<Time>>());
2401
2402 std::unordered_map<Types::qubit_t, Types::qubit_t> qubitsUsed;
2403
2404 // Track which layer (1-based, like qubitsUsed) each classical bit was last
2405 // written to, so that conditional operations reading those bits are placed
2406 // in the same layer or later.
2407 std::unordered_map<size_t, size_t> classicalBitLayer;
2408
2409 for (const auto &op : GetOperations()) {
2410 // check the instruction, see if a new layer is needed
2411
2412 // only qubits matter here, the others can be classicaly sent if they are
2413 // needed, even if they are shared... but that should be handled
2414 // somewhere, when not done implicitely (as for the 'simple network' case)
2415 if (op->CanAffectQuantumState()) {
2416 const auto qubits = op->AffectedQubits();
2417 size_t maxLevel = 0;
2418
2419 for (Types::qubit_t qbit : qubits) {
2420 if (qubits.size() > 1) ++qubitsUsed[qbit];
2421 maxLevel = std::max(maxLevel, static_cast<size_t>(qubitsUsed[qbit]));
2422
2423 if (layers.size() < qubitsUsed[qbit]) {
2424 auto circ = std::make_shared<Circuits::Circuit<Time>>();
2425 layers.push_back(std::move(circ));
2426 }
2427 }
2428
2429 // For conditional operations, ensure this op is placed no earlier than
2430 // the layer where the classical bits it depends on were written.
2431 if (op->IsConditional()) {
2432 const auto bits = op->AffectedBits();
2433 for (const auto bit : bits)
2434 maxLevel = std::max(maxLevel, classicalBitLayer[bit]);
2435 }
2436
2437 // now set all the qubits in the instruction to the max level
2438 for (Types::qubit_t qbit : qubits) qubitsUsed[qbit] = maxLevel;
2439
2440 const size_t layerIdx = maxLevel > 0 ? maxLevel - 1 : 0;
2441
2442 // ensure enough layers exist for the computed level
2443 while (layers.size() <= layerIdx)
2444 layers.push_back(std::make_shared<Circuits::Circuit<Time>>());
2445
2446 layers[layerIdx]->AddOperation(op);
2447
2448 // Record the layer for classical bits written by measurements / resets
2449 // so that later conditional ops respect the dependency.
2450 const auto writtenBits = op->AffectedBits();
2451 if (!writtenBits.empty() && !op->IsConditional()) {
2452 const size_t writtenLevel = maxLevel > 0 ? maxLevel : 1;
2453 for (const auto bit : writtenBits)
2454 classicalBitLayer[bit] =
2455 std::max(classicalBitLayer[bit], writtenLevel);
2456 }
2457 } else
2458 // add the instruction to the last layer
2459 layers.back()->AddOperation(op);
2460 }
2461
2462 return layers;
2463 }
2464
2474 static std::shared_ptr<Circuits::Circuit<Time>> LayersToCircuit(
2475 const std::vector<std::shared_ptr<Circuits::Circuit<Time>>> &layers) {
2476 auto circuit{std::make_shared<Circuits::Circuit<Time>>()};
2477
2478 for (const auto &layer : layers)
2479 circuit->AddOperations(layer->GetOperations());
2480
2481 return circuit;
2482 }
2483
2492 bool IsBranching() const override {
2493 for (const auto &op : GetOperations())
2494 if (op->IsBranching()) return true;
2495
2496 return false;
2497 }
2498
2505 iterator begin() noexcept { return operations.begin(); }
2506
2513 iterator end() noexcept { return operations.end(); }
2514
2515
2522 const_iterator begin() const noexcept { return operations.begin(); }
2523
2530 const_iterator end() const noexcept { return operations.end(); }
2531
2538 const_iterator cbegin() const noexcept { return operations.cbegin(); }
2539
2546 const_iterator cend() const noexcept { return operations.cend(); }
2547
2554 reverse_iterator rbegin() noexcept { return operations.rbegin(); }
2555
2562 reverse_iterator rend() noexcept { return operations.rend(); }
2563
2571 return operations.crbegin();
2572 }
2573
2580 const_reverse_iterator crend() const noexcept { return operations.crend(); }
2581
2588 auto size() const { return operations.size(); }
2589
2596 auto empty() const { return operations.empty(); }
2597
2605 auto &operator[](size_t pos) { return operations[pos]; }
2606
2614 const auto &operator[](size_t pos) const { return operations[pos]; }
2615
2623 void resize(size_t size) {
2624 if (size < operations.size()) operations.resize(size);
2625 }
2626
2627 private:
2638 void ReplaceThreeQubitAndSwapGates(bool onlyThreeQubits = false) {
2639 // just replace all three qubit gates(just ccnot and cswap will exist in the
2640 // first phase) with several gates on less qubits also replace swap gates
2641 // with three cnots (in the first phase) the controlled ones must be
2642 // replaced as well
2643
2644 // TODO: if composite operations will be implemented, those need to be
2645 // optimized as well
2646
2647 std::vector<std::shared_ptr<IOperation<Time>>> newops;
2648 newops.reserve(operations.size());
2649
2650 for (std::shared_ptr<IOperation<Time>> op : operations) {
2651 if (op->GetType() == OperationType::kGate) {
2652 std::shared_ptr<IQuantumGate<Time>> gate =
2653 std::static_pointer_cast<IQuantumGate<Time>>(op);
2654
2655 if (NeedsConversion(gate, onlyThreeQubits)) {
2656 std::vector<std::shared_ptr<IGateOperation<Time>>> newgates =
2657 ConvertGate(gate, onlyThreeQubits);
2658 newops.insert(newops.end(), newgates.begin(), newgates.end());
2659 } else
2660 newops.push_back(op);
2661 } else if (op->GetType() == OperationType::kConditionalGate) {
2662 std::shared_ptr<ConditionalGate<Time>> condgate =
2663 std::static_pointer_cast<ConditionalGate<Time>>(op);
2664 std::shared_ptr<IQuantumGate<Time>> gate =
2665 std::static_pointer_cast<IQuantumGate<Time>>(
2666 condgate->GetOperation());
2667
2668 if (NeedsConversion(gate, onlyThreeQubits)) {
2669 std::vector<std::shared_ptr<IGateOperation<Time>>> newgates =
2670 ConvertGate(gate, onlyThreeQubits);
2671 std::shared_ptr<ICondition> cond = condgate->GetCondition();
2672
2673 for (auto gate : newgates)
2674 newops.push_back(
2675 std::make_shared<ConditionalGate<Time>>(gate, cond));
2676 } else
2677 newops.push_back(op);
2678 } else
2679 newops.push_back(op);
2680 }
2681
2682 operations.swap(newops);
2683 }
2684
2694 static bool NeedsConversion(const std::shared_ptr<IQuantumGate<Time>> &gate,
2695 bool onlyThreeQubits = false) {
2696 const bool hasThreeQubits = gate->GetNumQubits() == 3;
2697 if (onlyThreeQubits) return hasThreeQubits;
2698
2699 return hasThreeQubits ||
2700 gate->GetGateType() == QuantumGateType::kSwapGateType;
2701 }
2702
2715 static std::vector<std::shared_ptr<IGateOperation<Time>>> ConvertGate(
2716 std::shared_ptr<IQuantumGate<Time>> &gate, bool onlyThreeQubits = false) {
2717 // TODO: if delays are used, how to transfer delays from the converted gate
2718 // to the resulting gates?
2719 std::vector<std::shared_ptr<IGateOperation<Time>>> newops;
2720
2721 if (gate->GetNumQubits() == 3) {
2722 // must be converted no matter what
2723 if (gate->GetGateType() == QuantumGateType::kCCXGateType) {
2724 const size_t q1 = gate->GetQubit(0); // control 1
2725 const size_t q2 = gate->GetQubit(1); // control 2
2726 const size_t q3 = gate->GetQubit(2); // target
2727
2728 // Sleator-Weinfurter decomposition
2729 newops.push_back(std::make_shared<CSxGate<Time>>(q2, q3));
2730 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
2731 newops.push_back(std::make_shared<CSxDagGate<Time>>(q2, q3));
2732 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
2733 newops.push_back(std::make_shared<CSxGate<Time>>(q1, q3));
2734 } else if (gate->GetGateType() == QuantumGateType::kCSwapGateType) {
2735 const size_t q1 = gate->GetQubit(0); // control 1
2736 const size_t q2 = gate->GetQubit(1); // control 2
2737 const size_t q3 = gate->GetQubit(2); // target
2738
2739 // TODO: find a better decomposition
2740 // this one I've got with the qiskit transpiler
2741 newops.push_back(std::make_shared<CXGate<Time>>(q3, q2));
2742
2743 newops.push_back(std::make_shared<CSxGate<Time>>(q2, q3));
2744 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
2745 newops.push_back(std::make_shared<PhaseGate<Time>>(q3, M_PI));
2746
2747 newops.push_back(std::make_shared<PhaseGate<Time>>(q2, -M_PI_2));
2748
2749 newops.push_back(std::make_shared<CSxGate<Time>>(q2, q3));
2750 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
2751 newops.push_back(std::make_shared<PhaseGate<Time>>(q3, M_PI));
2752
2753 newops.push_back(std::make_shared<CSxGate<Time>>(q1, q3));
2754
2755 newops.push_back(std::make_shared<CXGate<Time>>(q3, q2));
2756 } else
2757 newops.push_back(gate);
2758 } else if (!onlyThreeQubits &&
2759 gate->GetGateType() == QuantumGateType::kSwapGateType) {
2760 // must be converted no matter what
2761 const size_t q1 = gate->GetQubit(0);
2762 const size_t q2 = gate->GetQubit(1);
2763
2764 // for now replace it with three cnots, but maybe later make it
2765 // configurable there are other possibilities, for example three cy gates
2766 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
2767 newops.push_back(std::make_shared<CXGate<Time>>(q2, q1));
2768 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
2769 } else
2770 newops.push_back(gate);
2771
2772 return newops;
2773 }
2774
2775 OperationsVector operations;
2776};
2777
2791template <typename Time = Types::time_type>
2792class ComparableCircuit : public Circuit<Time> {
2793 public:
2796 using OperationPtr = std::shared_ptr<Operation>;
2799 std::vector<OperationPtr>;
2800
2809
2819
2829
2830 return *this;
2831 }
2832
2839 bool operator==(const BaseClass &rhs) const {
2840 if (BaseClass::GetOperations().size() != rhs.GetOperations().size())
2841 return false;
2842
2843 for (size_t i = 0; i < BaseClass::GetOperations().size(); ++i) {
2844 if (BaseClass::GetOperations()[i]->GetType() !=
2845 rhs.GetOperations()[i]->GetType())
2846 return false;
2847
2848 switch (BaseClass::GetOperations()[i]->GetType()) {
2850 if (std::static_pointer_cast<IQuantumGate<Time>>(
2852 ->GetGateType() !=
2853 std::static_pointer_cast<IQuantumGate<Time>>(
2854 rhs.GetOperations()[i])
2855 ->GetGateType() ||
2856 BaseClass::GetOperations()[i]->AffectedBits() !=
2857 rhs.GetOperations()[i]->AffectedBits())
2858 return false;
2859 if (approximateParamsCheck) {
2860 const auto params1 = std::static_pointer_cast<IQuantumGate<Time>>(
2862 ->GetParams();
2863 const auto params2 = std::static_pointer_cast<IQuantumGate<Time>>(
2864 rhs.GetOperations()[i])
2865 ->GetParams();
2866 if (params1.size() != params2.size()) return false;
2867
2868 for (size_t j = 0; j < params1.size(); ++j)
2869 if (std::abs(params1[j] - params2[j]) > paramsEpsilon)
2870 return false;
2871 } else if (std::static_pointer_cast<IQuantumGate<Time>>(
2873 ->GetParams() !=
2874 std::static_pointer_cast<IQuantumGate<Time>>(
2875 rhs.GetOperations()[i])
2876 ->GetParams())
2877 return false;
2878 break;
2881 rhs.GetOperations()[i]->AffectedQubits() ||
2882 BaseClass::GetOperations()[i]->AffectedBits() !=
2883 rhs.GetOperations()[i]->AffectedBits())
2884 return false;
2885 break;
2888 rhs.GetOperations()[i]->AffectedBits())
2889 return false;
2890 break;
2894 // first, check the conditions
2895 const auto leftCondition =
2896 std::static_pointer_cast<IConditionalOperation<Time>>(
2898 ->GetCondition();
2899 const auto rightCondition =
2900 std::static_pointer_cast<IConditionalOperation<Time>>(
2901 rhs.GetOperations()[i])
2902 ->GetCondition();
2903 if (leftCondition->GetBitsIndices() !=
2904 rightCondition->GetBitsIndices())
2905 return false;
2906
2907 const auto leftEqCondition =
2908 std::static_pointer_cast<EqualCondition>(leftCondition);
2909 const auto rightEqCondition =
2910 std::static_pointer_cast<EqualCondition>(rightCondition);
2911 if (!leftEqCondition || !rightEqCondition) return false;
2912
2913 if (leftEqCondition->GetAllBits() != rightEqCondition->GetAllBits())
2914 return false;
2915
2916 // now check the operations
2917 const auto leftOp =
2918 std::static_pointer_cast<IConditionalOperation<Time>>(
2920 ->GetOperation();
2921 const auto rightOp =
2922 std::static_pointer_cast<IConditionalOperation<Time>>(
2923 rhs.GetOperations()[i])
2924 ->GetOperation();
2925
2926 ComparableCircuit<Time> leftCircuit;
2927 BaseClass rightCircuit;
2928 leftCircuit.SetApproximateParamsCheck(approximateParamsCheck);
2929 leftCircuit.AddOperation(leftOp);
2930 rightCircuit.AddOperation(rightOp);
2931
2932 if (leftCircuit != rightCircuit) return false;
2933 } break;
2936 rhs.GetOperations()[i]->AffectedQubits() ||
2937 std::static_pointer_cast<Reset<Time>>(
2939 ->GetResetTargets() !=
2940 std::static_pointer_cast<Reset<Time>>(rhs.GetOperations()[i])
2941 ->GetResetTargets())
2942 return false;
2943 break;
2945 const auto left =
2946 std::static_pointer_cast<QuantumChannelOperation<Time>>(
2948 const auto right =
2949 std::static_pointer_cast<QuantumChannelOperation<Time>>(
2950 rhs.GetOperations()[i]);
2951 if (left->AffectedQubits() != right->AffectedQubits() ||
2952 !left->GetChannel().IsApprox(
2953 right->GetChannel(),
2954 approximateParamsCheck ? paramsEpsilon : 0.0))
2955 return false;
2956 } break;
2957 case OperationType::kDelay: {
2958 const auto left =
2959 std::static_pointer_cast<Delay<Time>>(
2961 const auto right =
2962 std::static_pointer_cast<Delay<Time>>(
2963 rhs.GetOperations()[i]);
2964 if (left->GetQubit() != right->GetQubit()) return false;
2965 if (approximateParamsCheck) {
2966 if (std::abs(left->GetDuration() - right->GetDuration()) > paramsEpsilon)
2967 return false;
2968 } else if (left->GetDuration() != right->GetDuration()) {
2969 return false;
2970 }
2971 } break;
2973 break;
2974 default:
2975 return false;
2976 }
2977
2978 if (BaseClass::GetOperations()[i]->GetDelay() !=
2979 rhs.GetOperations()[i]->GetDelay())
2980 return false;
2981 }
2982
2983 return true;
2984 }
2985
2992 bool operator!=(const BaseClass &rhs) const { return !(*this == rhs); }
2993
3000 void SetApproximateParamsCheck(bool check) { approximateParamsCheck = check; }
3001
3008 bool GetApproximateParamsCheck() const { return approximateParamsCheck; }
3009
3018 void SetParamsEpsilon(double eps) { paramsEpsilon = eps; }
3019
3028 double GetParamsEpsilon() const { return paramsEpsilon; }
3029
3030 private:
3031 bool approximateParamsCheck =
3032 false;
3033 double paramsEpsilon = 1e-8;
3035};
3036
3037} // namespace Circuits
3038
3039#endif // !_CIRCUIT_H_
Idle / delay operation for quantum circuits.
Circuit operation for an exact local CPTP quantum channel.
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:48
iterator begin() noexcept
Get the begin iterator for the operations.
Definition Circuit.h:2505
void ConvertForCutting()
Converts the circuit for distributed computing.
Definition Circuit.h:434
typename OperationsVector::reverse_iterator reverse_iterator
Definition Circuit.h:76
void Execute(const std::shared_ptr< Simulators::ISimulator > &sim, OperationState &state) const override
Execute the circuit on the given simulator.
Definition Circuit.h:98
typename OperationsVector::allocator_type allocator_type
Definition Circuit.h:66
const_iterator cbegin() const noexcept
Get the const begin iterator for the operations.
Definition Circuit.h:2538
double CliffordPercentage() const
Get the percentage of Clifford operations in the circuit.
Definition Circuit.h:2048
auto size() const
Get the number of operations in the circuit.
Definition Circuit.h:2588
bool IsClifford() const override
Checks if the circuit is a Clifford circuit.
Definition Circuit.h:2032
iterator end() noexcept
Get the end iterator for the operations.
Definition Circuit.h:2513
std::unordered_set< Types::qubit_t > GetCliffordQubits() const
Get the qubits that are acted on by Clifford operations.
Definition Circuit.h:2065
typename OperationsVector::iterator iterator
Definition Circuit.h:74
void AddOperation(const OperationPtr &op)
Adds an operation to the circuit.
Definition Circuit.h:145
std::set< size_t > GetBits() const
Returns the classical bits affected by the operations.
Definition Circuit.h:631
void Optimize(bool optimizeRotationGates=true)
Circuit optimization.
Definition Circuit.h:789
void EnsureProperOrderForMeasurements()
Definition Circuit.h:453
std::pair< size_t, Time > GetMaxDepth() const
Get max circuit depth.
Definition Circuit.h:1547
std::vector< std::shared_ptr< Circuits::Circuit< Time > > > ToMultipleQubitsLayersNoClone() const
Converts the circuit to layers oriented on multiple qubit gates.
Definition Circuit.h:2398
std::set< size_t > GetQubits() const
Returns the qubits affected by the operations.
Definition Circuit.h:614
std::vector< bool > ExecuteNonMeasurements(const std::shared_ptr< Simulators::ISimulator > &sim, OperationState &state, size_t *curMaxBondDim=nullptr) const
Execute the non-measurements operations from the circuit on the given simulator.
Definition Circuit.h:1712
void AddResetsAtBeginningIfNeeded(Time delay=0)
Add resets at the beginning of the circuit.
Definition Circuit.h:770
std::vector< std::shared_ptr< Circuits::Circuit< Time > > > ToMultipleQubitsLayers() const
Converts the circuit to layers oriented on multiple qubit gates.
Definition Circuit.h:2327
void Clear()
Clears the operations from the circuit.
Definition Circuit.h:213
typename OperationsVector::value_type value_type
Definition Circuit.h:65
void ExecuteMeasurements(const std::shared_ptr< Simulators::ISimulator > &sim, OperationState &state, const std::vector< bool > &executedOps, size_t *curMaxBondDim=nullptr) const
Execute the measurement operations from the circuit on the given simulator.
Definition Circuit.h:1832
std::shared_ptr< Operation > OperationPtr
The shared pointer to the operation type.
Definition Circuit.h:60
void Delay(Types::qubit_t qubit, Time duration)
Adds a delay (idle) operation on the specified qubit.
Definition Circuit.h:152
typename OperationsVector::reference reference
Definition Circuit.h:69
bool CanAffectQuantumState() const override
Find if the circuit can affect the quantum state.
Definition Circuit.h:698
auto & operator[](size_t pos)
Get the operation at a given position.
Definition Circuit.h:2605
typename OperationsVector::difference_type difference_type
Definition Circuit.h:72
std::pair< std::vector< size_t >, std::vector< Time > > GetDepth() const
Get circuit depth.
Definition Circuit.h:1447
const_iterator begin() const noexcept
Get the begin iterator for the operations.
Definition Circuit.h:2522
std::unordered_map< size_t, OperationPtr > GetLastOperationsOnQubits() const
Returns the last operations on circuit's qubits.
Definition Circuit.h:712
const_reverse_iterator crend() const noexcept
Get the const reverse end iterator for the operations.
Definition Circuit.h:2580
std::shared_ptr< MeasurementOperation< Time > > GetLastMeasurements(const std::vector< bool > &executedOps, bool sort=true) const
Definition Circuit.h:1889
bool ActsOnlyOnAdjacentQubits() const
Checks if the circuit has only operations that act on adjacent qubits.
Definition Circuit.h:1947
Circuit(const OperationsVector &ops={})
Construct a new Circuit object.
Definition Circuit.h:87
bool IsBranching() const override
Checks if any operation is a branching one.
Definition Circuit.h:2492
typename OperationsVector::pointer pointer
Definition Circuit.h:67
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:1865
typename OperationsVector::size_type size_type
Definition Circuit.h:71
auto empty() const
Check if the circuit is empty.
Definition Circuit.h:2596
OperationPtr Remap(const BitMapping &qubitsMap, const BitMapping &bitsMap={}) const override
Get a shared pointer to a circuit remapped.
Definition Circuit.h:254
static void AccumulateResults(ExecuteResults &results, const ExecuteResults &newResults)
Accumulate the results of a circuit execution to already existing results.
Definition Circuit.h:359
std::shared_ptr< Circuit< Time > > GetCircuitCut(Types::qubit_t startQubit, Types::qubit_t endQubit) const
Get the circuit cut.
Definition Circuit.h:1595
void ConvertForDistribution()
Converts the circuit for distributed computing.
Definition Circuit.h:419
std::vector< OperationPtr > OperationsVector
The vector of operations.
Definition Circuit.h:62
Types::qubits_vector AffectedQubits() const override
Returns the affected qubits.
Definition Circuit.h:647
bool HasOpsAfterMeasurements() const
Checks if the circuit has measurements that are followed by operations that affect the measured qubit...
Definition Circuit.h:1635
IOperation< Time > Operation
The operation type.
Definition Circuit.h:59
OperationPtr CloneFlyweight() const
Get a shared pointer to a clone of this object, but without cloning the operations.
Definition Circuit.h:236
bool HasConditionalOperations() const
Checks if the circuit has clasically conditional operations.
Definition Circuit.h:1924
void AddResetsIfNeeded(Time delay=0)
Add resets at the end of the circuit.
Definition Circuit.h:751
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:378
const_reverse_iterator crbegin() const noexcept
Get the const reverse begin iterator for the operations.
Definition Circuit.h:2570
OperationPtr GetOperation(size_t pos) const
Get an operation at a given position.
Definition Circuit.h:1576
const_iterator cend() const noexcept
Get the const end iterator for the operations.
Definition Circuit.h:2546
const_iterator end() const noexcept
Get the end iterator for the operations.
Definition Circuit.h:2530
void MoveMeasurementsAndResets()
Move the measurements and resets closer to the beginning of the circuit.
Definition Circuit.h:1311
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:275
std::unordered_map< Types::qubit_t, Types::qubit_t > BitMapping
The (qu)bit mapping for remapping.
Definition Circuit.h:54
typename OperationsVector::const_iterator const_iterator
Definition Circuit.h:75
std::vector< std::shared_ptr< Circuits::Circuit< Time > > > ToLayers() const
Converts the circuit to layers.
Definition Circuit.h:2190
bool NeedsEntanglementForDistribution() const override
Find if the circuit needs entanglement for distribution.
Definition Circuit.h:684
std::unordered_map< size_t, OperationPtr > GetFirstOperationsOnQubits() const
Returns the first operations on circuit's qubits.
Definition Circuit.h:730
void ExecuteBD(const std::shared_ptr< Simulators::ISimulator > &sim, OperationState &state, size_t *curMaxBondDim=nullptr) const
Execute the circuit on the given simulator.
Definition Circuit.h:114
reverse_iterator rbegin() noexcept
Get the reverse begin iterator for the operations.
Definition Circuit.h:2554
void SetOperations(const OperationsVector &ops)
Set the operations in the circuit.
Definition Circuit.h:176
typename OperationsVector::const_pointer const_pointer
Definition Circuit.h:68
void AddOperations(const OperationsVector &ops)
Adds operations to the circuit.
Definition Circuit.h:185
size_t GetMaxQubitIndex() const
Returns the max qubit id for all operations.
Definition Circuit.h:540
std::unordered_map< std::vector< bool >, size_t > ExecuteResults
The results of the execution of the circuit.
Definition Circuit.h:50
OperationPtr Clone() const override
Get a shared pointer to a clone of this object.
Definition Circuit.h:221
size_t GetMaxCbitIndex() const
Returns the max classical bit id for all operations.
Definition Circuit.h:576
void AddCircuit(const std::shared_ptr< Circuit< Time > > &circuit)
Adds operations from another circuit to the circuit.
Definition Circuit.h:195
typename OperationsVector::const_reference const_reference
Definition Circuit.h:70
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:327
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:2474
const auto & operator[](size_t pos) const
Get the operation at a given position.
Definition Circuit.h:2614
std::vector< std::shared_ptr< Circuits::Circuit< Time > > > ToLayersNoClone() const
Converts the circuit to layers.
Definition Circuit.h:2257
typename OperationsVector::const_reverse_iterator const_reverse_iterator
Definition Circuit.h:77
size_t GetMinCbitIndex() const
Returns the min classical bit id for all operations.
Definition Circuit.h:594
size_t GetNumberOfOperations() const
Get the number of operations in the circuit.
Definition Circuit.h:1566
std::vector< size_t > AffectedBits() const override
Returns the affected bits.
Definition Circuit.h:664
size_t GetMinQubitIndex() const
Returns the min qubit id for all operations.
Definition Circuit.h:558
void resize(size_t size)
Resizes the circuit.
Definition Circuit.h:2623
OperationType GetType() const override
Get the type of the circuit.
Definition Circuit.h:136
void ReplaceOperation(size_t index, const OperationPtr &op)
Replaces an operation in the circuit.
Definition Circuit.h:164
reverse_iterator rend() noexcept
Get the reverse end iterator for the operations.
Definition Circuit.h:2562
bool IsForest() const
Checks if the circuit is a forest circuit.
Definition Circuit.h:1979
std::vector< std::shared_ptr< Circuit< Time > > > SplitCircuit() const
Splits a circuit that has disjoint subcircuits in it into separate circuits.
Definition Circuit.h:2092
const OperationsVector & GetOperations() const
Get the operations in the circuit.
Definition Circuit.h:206
std::shared_ptr< Operation > OperationPtr
The shared pointer to the operation type.
Definition Circuit.h:2796
double GetParamsEpsilon() const
Gets the epsilon used for checking approximate equality of gate parameters.
Definition Circuit.h:3028
void SetApproximateParamsCheck(bool check)
Sets whether to check approximate equality of gate parameters.
Definition Circuit.h:3000
ComparableCircuit & operator=(const BaseClass &circ)
Assignment operator.
Definition Circuit.h:2827
bool operator==(const BaseClass &rhs) const
Comparison operator.
Definition Circuit.h:2839
bool GetApproximateParamsCheck() const
Gets whether to check approximate equality of gate parameters.
Definition Circuit.h:3008
ComparableCircuit(const BaseClass &circ)
Construct a new ComparableCircuit object.
Definition Circuit.h:2818
std::vector< OperationPtr > OperationsVector
The vector of operations.
Definition Circuit.h:2798
IOperation< Time > Operation
The operation type.
Definition Circuit.h:2795
bool operator!=(const BaseClass &rhs) const
Comparison operator.
Definition Circuit.h:2992
Circuit< Time > BaseClass
The base class type.
Definition Circuit.h:2794
ComparableCircuit(const OperationsVector &ops={})
Construct a new ComparableCircuit object.
Definition Circuit.h:2808
void SetParamsEpsilon(double eps)
Sets the epsilon used for checking approximate equality of gate parameters.
Definition Circuit.h:3018
Delay (idle) operation class.
Definition Delay.h:30
The operation interface.
Definition Operations.h:360
virtual Types::qubits_vector AffectedQubits() const
Returns the affected qubits.
Definition Operations.h:474
Types::time_type GetDelay() const
Definition Operations.h:501
IOperation(Types::time_type delay=0)
Definition Operations.h:368
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:65
void Reset(bool value=false)
Set the classical bits with the specified value.
Definition Operations.h:246
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
@ kDelay
a delay or idle period on one or more qubits
Definition Operations.h:48
@ 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
@ kQuantumChannel
a non-unitary CPTP operation on the simulator state
Definition Operations.h:47
@ kMatrixProductState
matrix product state simulation type
Definition State.h:100
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