Maestro 0.1.0
Unified interface for quantum circuit simulation
Loading...
Searching...
No Matches
Circuit.h
Go to the documentation of this file.
1
16#pragma once
17
18#ifndef _CIRCUIT_H_
19#define _CIRCUIT_H_
20
21#define _USE_MATH_DEFINES
22#include <math.h>
23#include <set>
24
25#include "Conditional.h"
26#include "Operations.h"
27#include "QuantumGates.h"
28#include "Reset.h"
29#include <vector>
30
31namespace Circuits {
32
44template <typename Time = Types::time_type>
45class Circuit : public IOperation<Time> {
46 public:
48 std::unordered_map<std::vector<bool>,
49 size_t>;
51 using BitMapping =
52 std::unordered_map<Types::qubit_t,
53 Types::qubit_t>;
57 using OperationPtr = std::shared_ptr<Operation>;
60 std::vector<OperationPtr>;
62 using value_type = typename OperationsVector::value_type;
63 using allocator_type = typename OperationsVector::allocator_type;
64 using pointer = typename OperationsVector::pointer;
65 using const_pointer = typename OperationsVector::const_pointer;
66 using reference = typename OperationsVector::reference;
67 using const_reference = typename OperationsVector::const_reference;
68 using size_type = typename OperationsVector::size_type;
69 using difference_type = typename OperationsVector::difference_type;
70
71 using iterator = typename OperationsVector::iterator;
72 using const_iterator = typename OperationsVector::const_iterator;
73 using reverse_iterator = typename OperationsVector::reverse_iterator;
75 typename OperationsVector::const_reverse_iterator;
76
84 Circuit(const OperationsVector &ops = {}) : Operation(), operations(ops) {}
85
95 void Execute(const std::shared_ptr<Simulators::ISimulator> &sim,
96 OperationState &state) const override {
97 state.Reset();
98 if (!sim) return;
99
100 for (const auto &op : operations) op->Execute(sim, state);
101 // sim->Flush();
102 }
103
112
120 void AddOperation(const OperationPtr &op) { operations.push_back(op); }
121
130 void ReplaceOperation(size_t index, const OperationPtr &op) {
131 if (index >= operations.size()) return;
132 operations[index] = op;
133 }
134
142 void SetOperations(const OperationsVector &ops) { operations = ops; }
143
152 operations.insert(operations.end(), ops.begin(), ops.end());
153 }
154
161 void AddCircuit(const std::shared_ptr<Circuit<Time>> &circuit) {
162 AddOperations(circuit->GetOperations());
163 }
164
172 const OperationsVector &GetOperations() const { return operations; }
173
179 void Clear() { operations.clear(); }
180
187 OperationPtr Clone() const override {
188 OperationsVector newops;
189
190 for (auto &op : operations) newops.emplace_back(op->Clone());
191
192 return std::make_shared<Circuit<Time>>(newops);
193 }
194
203 OperationsVector newops;
204
205 for (auto &op : operations) newops.push_back(op);
206
207 return std::make_shared<Circuit<Time>>(newops);
208 }
209
220 OperationPtr Remap(const BitMapping &qubitsMap,
221 const BitMapping &bitsMap = {}) const override {
222 OperationsVector newops;
223
224 for (const auto &op : operations)
225 newops.emplace_back(op->Remap(qubitsMap, bitsMap));
226
227 return std::make_shared<Circuit<Time>>(newops);
228 }
229
241 std::shared_ptr<Circuit<Time>> RemapToContinuous(BitMapping &newQubitsMap,
242 BitMapping &reverseBitsMap,
243 size_t &nrQubits,
244 size_t &nrCbits) const {
245 OperationsVector newops;
246
247 BitMapping newBitsMap;
248
249 nrQubits = 0;
250 nrCbits = 0;
251
252 for (const auto &op : operations) {
253 const auto affectedBits = op->AffectedBits();
254 const auto affectedQubits = op->AffectedQubits();
255
256 for (const auto qubit : affectedQubits) {
257 const auto it = newQubitsMap.find(qubit);
258 if (it == newQubitsMap.end()) {
259 newQubitsMap[qubit] = nrQubits;
260 ++nrQubits;
261 }
262 }
263
264 for (const auto bit : affectedBits) {
265 const auto it = newBitsMap.find(bit);
266 if (it == newBitsMap.end()) {
267 newBitsMap[bit] = nrCbits;
268 reverseBitsMap[nrCbits] = bit;
269 ++nrCbits;
270 }
271 }
272
273 newops.emplace_back(op->Remap(newQubitsMap, newBitsMap));
274 }
275
276 return std::make_shared<Circuit<Time>>(newops);
277 }
278
294 const BitMapping &bitsMap = {},
295 bool ignoreNotMapped = false,
296 size_t sz = 0) {
297 ExecuteResults newResults;
298
299 if (!ignoreNotMapped && sz == 0) {
300 for (const auto &[from, to] : bitsMap)
301 if (to > sz) sz = to;
302
303 ++sz;
304 }
305
306 for (const auto &res : results) {
307 Circuits::OperationState mappedState(res.first);
308
309 mappedState.Remap(bitsMap, ignoreNotMapped,
310 ignoreNotMapped ? bitsMap.size() : sz);
311 newResults[mappedState.GetAllBits()] += res.second;
312 }
313
314 return newResults;
315 }
316
325 static void AccumulateResults(ExecuteResults &results,
326 const ExecuteResults &newResults) {
327 for (const auto &res : newResults) results[res.first] += res.second;
328 }
329
345 const ExecuteResults &newResults,
346 const BitMapping &bitsMap = {},
347 bool ignoreNotMapped = true,
348 size_t sz = 0) {
349 if (!ignoreNotMapped && sz == 0) {
350 for (const auto &[from, to] : bitsMap)
351 if (to > sz) sz = to;
352
353 ++sz;
354 }
355
356 for (const auto &res : newResults) {
357 Circuits::OperationState mappedState(res.first);
358
359 mappedState.Remap(bitsMap, ignoreNotMapped,
360 ignoreNotMapped ? bitsMap.size() : sz);
361 results[mappedState.GetAllBits()] += res.second;
362 /*
363 const auto it = bitsMap.find(res.first);
364 if (it != bitsMap.end())
365 results[it->second] += res.second;
366 */
367 }
368 }
369
370 // TODO: This converts all swap, cswap and ccnot gates
371 // it's not really needed to convert them all,
372 // only those that are not applied locally, that is, on a single host
373 // the local ones can remain as they are
374 // so use network topology to decide which ones to convert
375 // that's for later, when we have the network part implemented
376 // and also the code that splits the circuit
377
386 // this will make the circuit better for distributed computing
387 // TODO: if composite operations will be implemented, those need to be
388 // optimized as well
389
390 ReplaceThreeQubitAndSwapGates();
391 }
392
400 void ConvertForCutting() { ReplaceThreeQubitAndSwapGates(true); }
401
402 /*
403 * @brief Splits the measurements to measurements on individual qubits and
404 * tries to order them as needed by the following clasically conditional
405 * gates.
406 *
407 * Splits the measurements to measurements on individual qubits and tries to
408 * order them as needed by the following clasically conditional gates. This is
409 * needed for netqasm, which requires the measurements to be in the right
410 * order, if the following clasically conditional gates need sending to
411 * another host. This wouldn't be needed if they are local, but we don't know
412 * that at this point.
413 *
414 * So in short, all measurements on more than one qubit are converted to one
415 * qubit measurements, and then all measurements that are grouped together
416 * (not separated by some other operation) are ordered in the same order as
417 * the conditions on the following clasically conditional gates.
418 */
420 // TODO: Maybe this should be moved at the netqasm level circuit conversion,
421 // since it's not needed for all kinds of distributed computing currently
422 // there is a virtual function in controller that does nothing in the base
423 // class, but for netqasm it calls this method
424 OperationsVector newops;
425
426 for (size_t i = 0; i < operations.size(); ++i) {
427 const auto op = operations[i];
428 if (op->GetType() != OperationType::kMeasurement) {
429 newops.emplace_back(op);
430 continue;
431 }
432
433 // ok, if it's a measurement, look ahead, accumulate all measurements and
434 // then add them in the right order
435 std::unordered_set<size_t> bits;
436 std::unordered_map<size_t, Types::qubit_t> measQubits;
437 std::unordered_map<size_t, Time> measDelays;
438
439 auto affectedBits = op->AffectedBits();
440 auto affectedQubits = op->AffectedQubits();
441
442 for (size_t q = 0; q < affectedQubits.size(); ++q) {
443 bits.insert(affectedBits[q]);
444 measQubits[affectedBits[q]] = affectedQubits[q];
445 measDelays[affectedBits[q]] = op->GetDelay();
446 }
447
448 size_t j = i + 1;
449 for (; j < operations.size(); ++j) {
450 const auto op2 = operations[j];
451
452 if (op2->GetType() != OperationType::kMeasurement) break;
453
454 affectedQubits = op2->AffectedQubits();
455
456 const auto meas =
457 std::static_pointer_cast<MeasurementOperation<Time>>(op2);
458 affectedBits = meas->GetBitsIndices();
459 for (size_t q = 0; q < affectedBits.size(); ++q) {
460 bits.insert(affectedBits[q]);
461 measQubits[affectedBits[q]] = affectedQubits[q];
462 measDelays[affectedBits[q]] = op2->GetDelay();
463 }
464 }
465
466 i = j - 1;
467
468 // the right order is the one following in the classically controlled
469 // gates
470 for (; j < operations.size(); ++j) {
471 const auto op2 = operations[j];
472 if (op2->GetType() == OperationType::kConditionalGate ||
473 op2->GetType() == OperationType::kConditionalMeasurement ||
474 op2->GetType() == OperationType::kConditionalRandomGen) {
475 auto condop =
476 std::static_pointer_cast<IConditionalOperation<Time>>(op2);
477 const auto condbits = condop->AffectedBits();
478 for (const auto bit : condbits)
479 if (bits.find(bit) != bits.end()) {
480 newops.emplace_back(std::make_shared<MeasurementOperation<Time>>(
481 std::vector{std::make_pair(measQubits[bit], bit)},
482 measDelays[bit]));
483 bits.erase(bit);
484 }
485 }
486 if (bits.empty()) break;
487 }
488
489 // now add the measurements that were left in any order
490 for (auto bit : bits)
491 newops.emplace_back(std::make_shared<MeasurementOperation<Time>>(
492 std::vector{std::make_pair(measQubits[bit], bit)},
493 measDelays[bit]));
494 }
495
496 operations.swap(newops);
497 }
498
506 size_t GetMaxQubitIndex() const {
507 size_t mx = 0;
508 for (const auto &op : operations) {
509 const auto qbits = op->AffectedQubits();
510 for (auto q : qbits)
511 if (q > mx) mx = q;
512 }
513
514 return mx;
515 }
516
524 size_t GetMinQubitIndex() const {
525 size_t mn = std::numeric_limits<size_t>::max();
526 for (const auto &op : operations) {
527 const auto qbits = op->AffectedQubits();
528 for (auto q : qbits)
529 if (q < mn) mn = q;
530 }
531
532 return mn;
533 }
534
542 size_t GetMaxCbitIndex() const {
543 size_t mx = 0;
544 for (const auto &op : operations) {
545 const auto cbits = op->AffectedBits();
546 for (auto q : cbits)
547 if (q > mx) mx = q;
548 }
549
550 return mx;
551 }
552
560 size_t GetMinCbitIndex() const {
561 size_t mn = std::numeric_limits<size_t>::max();
562 for (const auto &op : operations) {
563 const auto cbits = op->AffectedBits();
564 for (auto q : cbits)
565 if (q < mn) mn = q;
566 }
567
568 return mn;
569 }
570
580 std::set<size_t> GetQubits() const {
581 std::set<size_t> qubits;
582 for (const auto &op : operations) {
583 const auto qbits = op->AffectedQubits();
584 qubits.insert(qbits.begin(), qbits.end());
585 }
586
587 return qubits;
588 }
589
597 std::set<size_t> GetBits() const {
598 std::set<size_t> cbits;
599 for (const auto &op : operations) {
600 const auto bits = op->AffectedBits();
601 cbits.insert(bits.begin(), bits.end());
602 }
603
604 return cbits;
605 }
606
613 Types::qubits_vector AffectedQubits() const override {
614 auto qubits = GetQubits();
615
616 Types::qubits_vector qubitsVec;
617 qubitsVec.reserve(qubits.size());
618
619 for (auto q : qubits) qubitsVec.emplace_back(q);
620
621 return qubitsVec;
622 }
623
630 std::vector<size_t> AffectedBits() const override {
631 auto bits = GetBits();
632
633 std::vector<size_t> bitsVec;
634 bitsVec.reserve(bits.size());
635
636 for (auto b : bits) bitsVec.emplace_back(b);
637
638 return bitsVec;
639 }
640
650 bool NeedsEntanglementForDistribution() const override {
651 for (const auto &op : operations)
652 if (op->NeedsEntanglementForDistribution()) return true;
653
654 return false;
655 }
656
664 bool CanAffectQuantumState() const override {
665 for (const auto &op : operations)
666 if (op->CanAffectQuantumState()) return true;
667
668 return false;
669 }
670
678 std::unordered_map<size_t, OperationPtr> GetLastOperationsOnQubits() const {
679 std::unordered_map<size_t, OperationPtr> lastOps;
680
681 for (const auto &op : operations) {
682 const auto qbits = op->AffectedQubits();
683 for (auto q : qbits) lastOps[q] = op;
684 }
685
686 return lastOps;
687 }
688
696 std::unordered_map<size_t, OperationPtr> GetFirstOperationsOnQubits() const {
697 std::unordered_map<size_t, OperationPtr> firstOps;
698
699 for (const auto &op : operations) {
700 const auto qbits = op->AffectedQubits();
701 for (auto q : qbits) {
702 if (firstOps.find(q) == firstOps.end()) firstOps[q] = op;
703 }
704 }
705
706 return firstOps;
707 }
708
717 void AddResetsIfNeeded(Time delay = 0) {
718 const auto GetLastOps = GetLastOperationsOnQubits();
719
720 for (const auto &[q, op] : GetLastOps)
721 if (op->GetType() !=
722 OperationType::kReset) // don't add it if there is already a reset
723 // operation on the qubit
724 operations.emplace_back(
725 std::make_shared<Reset<Time>>(Types::qubits_vector{q}, delay));
726 }
727
736 void AddResetsAtBeginningIfNeeded(Time delay = 0) {
737 const auto GetFirstOps = GetFirstOperationsOnQubits();
738
739 for (const auto &[q, op] : GetFirstOps)
740 if (op->GetType() !=
741 OperationType::kReset) // don't add it if there is already a reset
742 // operation on the qubit
743 operations.insert(
744 operations.begin(),
745 std::make_shared<Reset<Time>>(Types::qubits_vector{q}, delay));
746 }
747
755 void Optimize(bool optimizeRotationGates = true) {
756 // Some ideas, from simple to more complex:
757 //
758 // IMPORTANT: Focus on the gates that are added for distributed computing,
759 // either for the one with entanglement or the one with cutting the reason
760 // is that maybe the provided circuit is not that bad, but due of the
761 // supplementary gates added, duplicates (for example) will occur the most
762 // important one qubit ones added are hadamard then X, S, Sdag and Z
763 //
764 // 1. First, one qubit gates can be combined into a single gate or even no
765 // gate a) Straightforward for those that are their own inverse (that is,
766 // hermitian/involutory): Hadamard and Pauli gates for example, if one finds
767 // two of them in sequence, they can be removed other ones are the one that
768 // are followed by their 'dag' (inverse, since the gates are unitary) in the
769 // circuit, those can be removed, too
770
771 // several resets can be also changed into a single one, also repeated
772 // measurements of the same qubit, with result in the same cbit can be
773 // replaced by a single measurement
774
775 // b) other ones can be combined into a single gate, for example phase shift
776 // gates or rotation gates two phase gates (not the general phase shift we
777 // have, but the one with 1, i on the diagonal) can be combined into a Z
778 // gate, for example, or two sqrtNot gates can be combined into a single X
779 // gate combinations of phase gates and hadamard can be replaced by pauli
780 // gates in some cases, and so on even the U gate could be used to join
781 // together several one qubit gates
782
783 // c) even more complex... three or more one qubit gates could be combined
784 // into a single gate... an example is HXH = Z other examples SXS^t = Y,
785 // SZS^t = Z
786
787 // 2. Two qubit gates can be optimized, too
788 // for example two CNOT gates in sequence can be removed if the control
789 // qubit is the same, the same goes for two CZ or CY or SWAP gates (this
790 // goes for the three qubit gates, CCX, CCY, CCZ, CSWAP, too)
791
792 // 3. Some gates commute, the reorder can give some opportunities for more
793 // optimization
794
795 // 4. Groups of gates
796 // the possibilities are endless, but might be easier to focus first on
797 // Clifford gates for example a X sandwiched between CNOTs can be replaced
798 // with two X on each qubit if the original X is on the control qubit or
799 // with an X on the target qubit if the original X is on the target qubit a
800 // similar thing happens if Z is sandwiched between CNOTs, but this time Z x
801 // Z appears if the original Z is on the target qubit and if original Z is
802 // on the control qubit, then the CNOTs dissapear and the Z remains on the
803 // control qubit
804
805 // three CNOT gates with the one in the middle turned upside down compare
806 // with the other two can be replaced by a single swap gate
807
808 // first stage, take out duplicates of H, X, Y, Z
809
810 bool changed;
811
812 do {
813 changed = false;
814
815 std::vector<std::shared_ptr<IOperation<Time>>> newops;
816 newops.reserve(operations.size());
817
818 for (int i = 0; i < static_cast<int>(operations.size()); ++i) {
819 const std::shared_ptr<IOperation<Time>> &op = operations[i];
820
821 const auto type = op->GetType();
822 if (type == OperationType::kNoOp)
823 continue;
824 else if (type == OperationType::kGate) {
825 std::shared_ptr<IQuantumGate<Time>> gate =
826 std::static_pointer_cast<IQuantumGate<Time>>(op);
827 const auto qubits = gate->AffectedQubits();
828
829 if (qubits.size() == 1) {
830 // TODO: HXH = Z, SXS^t = Y, SZS^t = Z ????
831
832 auto gateType = gate->GetGateType();
833 bool replace = false;
834
835 // if it's one of the interesting gates, look ahead to see if it's
836 // followed by the same gate on the same qubit if yes, replace the
837 // next one with a nop and skip the current one (or replace the pair
838 // with a single gate, depending on the type) set changed to true if
839 // something was changed
840 switch (gateType) {
842 [[fallthrough]];
844 [[fallthrough]];
846 if (!optimizeRotationGates) {
847 newops.push_back(op);
848 break;
849 }
850 [[fallthrough]];
852 replace = true;
853 [[fallthrough]];
854 // those above will be replaced the pair with a single gate, all
855 // the following are the ones that get removed
857 [[fallthrough]];
859 [[fallthrough]];
861 [[fallthrough]];
863 [[fallthrough]];
865 [[fallthrough]];
867 [[fallthrough]];
869 [[fallthrough]];
871 [[fallthrough]];
873 [[fallthrough]];
875 [[fallthrough]];
877 bool found = false;
878
879 if (gateType == QuantumGateType::kSGateType)
881 else if (gateType == QuantumGateType::kSdgGateType)
883 else if (gateType == QuantumGateType::kTGateType)
885 else if (gateType == QuantumGateType::kTdgGateType)
887 else if (gateType == QuantumGateType::kSxGateType)
889 else if (gateType == QuantumGateType::kSxDagGateType)
891
892 for (size_t j = i + 1; j < operations.size(); ++j) {
893 auto &nextOp = operations[j];
894 if (!nextOp->CanAffectQuantumState()) continue;
895
896 const auto nextQubits = nextOp->AffectedQubits();
897 bool hasQubit = false;
898
899 for (auto q : nextQubits)
900 if (q == qubits[0]) {
901 hasQubit = true;
902 break;
903 }
904
905 if (!hasQubit)
906 continue; // an op that does not touch the current qubit
907 // can be skipped
908 else if (nextQubits.size() != 1)
909 break; // if it touches the current qubit and it's
910 // something else than a single qubit gate, stop
911
912 const auto nextType = nextOp->GetType();
913 if (nextType != OperationType::kGate)
914 break; // could be a classically conditioned gate, stop
915
916 const auto &nextGate =
917 std::static_pointer_cast<SingleQubitGate<Time>>(nextOp);
918 if (nextGate->GetGateType() == gateType) {
919 if (replace) {
920 const auto params1 = gate->GetParams();
921 const auto params2 = nextGate->GetParams();
922
923 const double param = params1[0] + params2[0];
924 const auto delay =
925 gate->GetDelay() + nextGate->GetDelay();
926
927 if (gateType == QuantumGateType::kPhaseGateType)
928 newops.push_back(std::make_shared<PhaseGate<Time>>(
929 qubits[0], param, delay));
930 else if (gateType == QuantumGateType::kRxGateType)
931 newops.push_back(std::make_shared<RxGate<Time>>(
932 qubits[0], param, delay));
933 else if (gateType == QuantumGateType::kRyGateType)
934 newops.push_back(std::make_shared<RyGate<Time>>(
935 qubits[0], param, delay));
936 else
937 newops.push_back(std::make_shared<RzGate<Time>>(
938 qubits[0], param, delay));
939 }
940 nextOp = std::make_shared<NoOperation<Time>>();
941 changed = true;
942 found = true;
943 break;
944 } else if ((gateType == QuantumGateType::kSGateType &&
945 nextGate->GetGateType() ==
947 (gateType == QuantumGateType::kSdgGateType &&
948 nextGate->GetGateType() ==
950 // if expecting an S gate (or a Sdg gate) and found the
951 // original one instead, replace the pair with a Z gate (S *
952 // S = Z, Sdag * Sdag = Z)
953 const auto delay = gate->GetDelay() + nextGate->GetDelay();
954 newops.push_back(
955 std::make_shared<ZGate<Time>>(qubits[0], delay));
956 nextOp = std::make_shared<NoOperation<Time>>();
957 changed = true;
958 found = true;
959 break;
960 } else if ((gateType == QuantumGateType::kSxGateType &&
961 nextGate->GetGateType() ==
963 (gateType == QuantumGateType::kSxDagGateType &&
964 nextGate->GetGateType() ==
966 // if expecting an S gate (or a Sdg gate) and found the
967 // original one instead, replace the pair with a X gate (Sx
968 // * Sx = X, SXdag * SXdag = X)
969 const auto delay = gate->GetDelay() + nextGate->GetDelay();
970 newops.push_back(
971 std::make_shared<XGate<Time>>(qubits[0], delay));
972 nextOp = std::make_shared<NoOperation<Time>>();
973 changed = true;
974 found = true;
975 break;
976 } else if (gateType == QuantumGateType::kTGateType &&
977 nextGate->GetGateType() ==
979 // if expecting a T gate and found the Tdgate instead,
980 // replace the pair with a Sdag gate (Tdg * Tdg = Sdag)
981 const auto delay = gate->GetDelay() + nextGate->GetDelay();
982 newops.push_back(
983 std::make_shared<SdgGate<Time>>(qubits[0], delay));
984 nextOp = std::make_shared<NoOperation<Time>>();
985 changed = true;
986 found = true;
987 break;
988 } else if (gateType == QuantumGateType::kTdgGateType &&
989 nextGate->GetGateType() ==
991 // if expecting a Tdg gate and found the T gate instead,
992 // replace the pair with a S gate (T * T = S)
993 const auto delay = gate->GetDelay() + nextGate->GetDelay();
994 newops.push_back(
995 std::make_shared<SGate<Time>>(qubits[0], delay));
996 nextOp = std::make_shared<NoOperation<Time>>();
997 changed = true;
998 found = true;
999 break;
1000 } else if (gateType == QuantumGateType::kPhaseGateType &&
1001 (nextGate->GetGateType() ==
1003 nextGate->GetGateType() ==
1005 nextGate->GetGateType() ==
1007 nextGate->GetGateType() ==
1009 const auto delay = gate->GetDelay() + nextGate->GetDelay();
1010 double param2;
1011 if (nextGate->GetGateType() == QuantumGateType::kSGateType)
1012 param2 = 0.5 * M_PI;
1013 else if (nextGate->GetGateType() ==
1015 param2 = -0.5 * M_PI;
1016 else if (nextGate->GetGateType() ==
1018 param2 = 0.25 * M_PI;
1019 else
1020 param2 = -0.25 * M_PI;
1021
1022 const auto param = gate->GetParams()[0] + param2;
1023 newops.push_back(std::make_shared<PhaseGate<Time>>(
1024 qubits[0], param, delay));
1025 nextOp = std::make_shared<NoOperation<Time>>();
1026 changed = true;
1027 found = true;
1028 break;
1029 } else if (nextGate->GetGateType() ==
1031 (gateType == QuantumGateType::kSGateType ||
1032 gateType == QuantumGateType::kSdgGateType ||
1033 gateType == QuantumGateType::kTGateType ||
1034 gateType == QuantumGateType::kTdgGateType)) {
1035 const auto delay = gate->GetDelay() + nextGate->GetDelay();
1036 double param1;
1037 if (gateType == QuantumGateType::kSGateType)
1038 param1 = -0.5 * M_PI;
1039 else if (gateType == QuantumGateType::kSdgGateType)
1040 param1 = 0.5 * M_PI;
1041 else if (gateType == QuantumGateType::kTGateType)
1042 param1 = -0.25 * M_PI;
1043 else
1044 param1 = 0.25 * M_PI;
1045
1046 const auto param = nextGate->GetParams()[0] + param1;
1047 newops.push_back(std::make_shared<PhaseGate<Time>>(
1048 qubits[0], param, delay));
1049 nextOp = std::make_shared<NoOperation<Time>>();
1050 changed = true;
1051 found = true;
1052 break;
1053 } else
1054 break; // not the expected gate, acting on same qubit, bail
1055 // out
1056 }
1057
1058 if (!found) newops.push_back(op);
1059 } break;
1060 default:
1061 // if no, just add it
1062 newops.push_back(op);
1063 break;
1064 }
1065 } else if (qubits.size() == 2) {
1066 auto gateType = gate->GetGateType();
1067 bool replace = false;
1068
1069 // if it's one of the interesting gates, look ahead to see if it's
1070 // followed by the same gate on the same qubit if yes, replace the
1071 // next one with a nop and skip the current one (or replace the pair
1072 // with a single gate, depending on the type) set changed to true if
1073 // something was changed
1074 switch (gateType) {
1076 [[fallthrough]];
1078 [[fallthrough]];
1080 if (!optimizeRotationGates) {
1081 newops.push_back(op);
1082 break;
1083 }
1084 [[fallthrough]];
1086 replace = true;
1087 [[fallthrough]];
1088 // those above will be replaced the pair with a single gate, all
1089 // the following are the ones that get removed
1091 [[fallthrough]];
1093 [[fallthrough]];
1095 [[fallthrough]];
1097 [[fallthrough]];
1099 [[fallthrough]];
1101 [[fallthrough]];
1103 bool found = false;
1104
1105 if (gateType == QuantumGateType::kCSxGateType)
1107 else if (gateType == QuantumGateType::kCSxDagGateType)
1109
1110 // looking forward for the next operation that acts on the same
1111 // qubits
1112 for (size_t j = i + 1; j < operations.size(); ++j) {
1113 auto &nextOp = operations[j];
1114 if (!nextOp->CanAffectQuantumState()) continue;
1115
1116 const auto nextQubits = nextOp->AffectedQubits();
1117
1118 bool hasQubit = false;
1119
1120 for (auto q : nextQubits)
1121 if (q == qubits[0] || q == qubits[1]) {
1122 hasQubit = true;
1123 break;
1124 }
1125
1126 if (!hasQubit)
1127 continue; // an op that does not touch the current qubit
1128 // can be skipped
1129 else if (nextQubits.size() != 2)
1130 break; // if it touches a current qubit and it's something
1131 // else than a two qubits gate, stop
1132 // if it's not the same qubits, bail out
1133 else if (gateType == QuantumGateType::kSwapGateType &&
1134 !((qubits[0] == nextQubits[0] &&
1135 qubits[1] == nextQubits[1]) ||
1136 (qubits[0] == nextQubits[1] &&
1137 qubits[1] == nextQubits[0])))
1138 break;
1139 else if (!(qubits[0] == nextQubits[0] &&
1140 qubits[1] == nextQubits[1]))
1141 break;
1142
1143 const auto nextType = nextOp->GetType();
1144 if (nextType != OperationType::kGate)
1145 break; // could be a classically conditioned gate, stop
1146
1147 const auto &nextGate =
1148 std::static_pointer_cast<TwoQubitsGate<Time>>(nextOp);
1149 if (nextGate->GetGateType() == gateType) {
1150 if (replace) {
1151 const auto params1 = gate->GetParams();
1152 const auto params2 = nextGate->GetParams();
1153 const double param = params1[0] + params2[0];
1154 const auto delay =
1155 gate->GetDelay() + nextGate->GetDelay();
1156
1157 if (gateType == QuantumGateType::kCPGateType)
1158 newops.push_back(std::make_shared<CPGate<Time>>(
1159 qubits[0], qubits[1], param, delay));
1160 else if (gateType == QuantumGateType::kCRxGateType)
1161 newops.push_back(std::make_shared<CRxGate<Time>>(
1162 qubits[0], qubits[1], param, delay));
1163 else if (gateType == QuantumGateType::kCRyGateType)
1164 newops.push_back(std::make_shared<CRyGate<Time>>(
1165 qubits[0], qubits[1], param, delay));
1166 else
1167 newops.push_back(std::make_shared<CRzGate<Time>>(
1168 qubits[0], qubits[1], param, delay));
1169 }
1170 nextOp = std::make_shared<NoOperation<Time>>();
1171 changed = true; // continue merging gates, we found one
1172 // that was merged/removed
1173 found = true; // don't put op in the new operations, we
1174 // handled it
1175 break;
1176 } else
1177 break; // not the expected gate, acting on same qubits,
1178 // bail out
1179 } // end for of looking forward
1180
1181 if (!found) newops.push_back(op);
1182 } break;
1183 default:
1184 // if no, just add it
1185 newops.push_back(op);
1186 break;
1187 }
1188 } else if (qubits.size() == 3) {
1189 auto gateType = gate->GetGateType();
1190
1191 // if it's one of the interesting gates, look ahead to see if it's
1192 // followed by the same gate on the same qubit if yes, replace the
1193 // next one with a nop and skip the current one (or replace the pair
1194 // with a single gate, depending on the type) set changed to true if
1195 // something was changed
1196 switch (gateType) {
1198 [[fallthrough]];
1200 bool found = false;
1201
1202 for (size_t j = i + 1; j < operations.size(); ++j) {
1203 auto &nextOp = operations[j];
1204 if (!nextOp->CanAffectQuantumState()) continue;
1205
1206 const auto nextQubits = nextOp->AffectedQubits();
1207
1208 bool hasQubit = false;
1209
1210 for (auto q : nextQubits)
1211 if (q == qubits[0] || q == qubits[1] || q == qubits[2]) {
1212 hasQubit = true;
1213 break;
1214 }
1215
1216 if (!hasQubit)
1217 continue; // an op that does not touch the current qubit
1218 // can be skipped
1219 else if (nextQubits.size() != 3)
1220 break; // if it touches a current qubit and it's something
1221 // else than a three qubits gate, stop
1222 // if it's not the same qubits, bail out
1223 else if (gateType == QuantumGateType::kCSwapGateType &&
1224 (qubits[0] != nextQubits[0] ||
1225 !((qubits[1] == nextQubits[1] &&
1226 qubits[2] == nextQubits[2]) ||
1227 (qubits[1] == nextQubits[2] &&
1228 qubits[2] == nextQubits[1]))))
1229 break;
1230 else if (gateType == QuantumGateType::kCCXGateType &&
1231 (qubits[2] != nextQubits[2] ||
1232 !(qubits[1] == nextQubits[1] &&
1233 qubits[2] == nextQubits[2]) ||
1234 !(qubits[1] == nextQubits[2] &&
1235 qubits[2] == nextQubits[1])))
1236 break;
1237
1238 const auto nextType = nextOp->GetType();
1239 if (nextType != OperationType::kGate)
1240 break; // could be a classically conditioned gate, stop
1241
1242 const auto &nextGate =
1243 std::static_pointer_cast<ThreeQubitsGate<Time>>(nextOp);
1244 if (nextGate->GetGateType() == gateType) {
1245 nextOp = std::make_shared<NoOperation<Time>>();
1246 changed = true;
1247 found = true;
1248 break;
1249 } else
1250 break; // not the expected gate, acting on same qubits,
1251 // bail out
1252 }
1253
1254 if (!found) newops.push_back(op);
1255 } break;
1256 default:
1257 // if no, just add it
1258 newops.push_back(op);
1259 break;
1260 }
1261 } else
1262 newops.push_back(op);
1263 } else
1264 newops.push_back(op);
1265 } // end for on circuit operations
1266
1267 operations.swap(newops);
1268 } while (changed);
1269 }
1270
1278 OperationsVector newops;
1279 newops.reserve(operations.size());
1280
1281 size_t qubitsNo = std::max(GetMaxQubitIndex(), GetMaxCbitIndex()) + 1;
1282
1283 std::unordered_map<Types::qubit_t, std::vector<OperationPtr>> qubitOps;
1284
1285 std::vector<OperationPtr> lastOps(qubitsNo);
1286
1287 std::unordered_map<OperationPtr, std::unordered_set<OperationPtr>>
1288 dependenciesMap;
1289
1290 for (const auto &op : operations) {
1291 std::unordered_set<OperationPtr> dependencies;
1292
1293 const auto cbits = op->AffectedBits();
1294 for (auto c : cbits) {
1295 const auto lastOp = lastOps[c];
1296 if (lastOp) dependencies.insert(lastOp);
1297 }
1298
1299 const auto qubits = op->AffectedQubits();
1300 for (auto q : qubits) {
1301 qubitOps[q].push_back(op);
1302
1303 const auto lastOp = lastOps[q];
1304 if (lastOp) dependencies.insert(lastOp);
1305
1306 lastOps[q] = op;
1307 }
1308
1309 for (auto c : cbits) lastOps[c] = op;
1310
1311 dependenciesMap[op] = dependencies;
1312 }
1313 lastOps.clear();
1314
1315 std::vector<Types::qubit_t> indices(qubitsNo, 0);
1316
1317 while (!dependenciesMap.empty()) {
1318 OperationPtr nextOp;
1319
1320 // try to locate a 'next' gate for a qubit that is either a measurement or
1321 // a reset
1322 for (size_t q = 0; q < qubitsNo; ++q) {
1323 if (qubitOps.find(q) ==
1324 qubitOps.end()) // no operation left on this qubit
1325 continue;
1326
1327 // grab the current operation for this qubit
1328 const auto &ops = qubitOps[q];
1329 const auto &op = ops[indices[q]];
1330
1331 // consider only measurements and resets
1332 if (op->GetType() == OperationType::kMeasurement ||
1333 op->GetType() == OperationType::kReset) {
1334 bool hasDependencies = false;
1335
1336 for (const auto &opd : dependenciesMap[op])
1337 if (dependenciesMap.find(opd) != dependenciesMap.end()) {
1338 hasDependencies = true;
1339 break;
1340 }
1341
1342 if (!hasDependencies) {
1343 nextOp = op;
1344 break;
1345 }
1346 }
1347 }
1348
1349 if (nextOp) {
1350 dependenciesMap.erase(nextOp);
1351
1352 const auto qubits = nextOp->AffectedQubits();
1353 for (auto q : qubits) {
1354 ++indices[q];
1355 if (indices[q] >= qubitOps[q].size()) qubitOps.erase(q);
1356 }
1357
1358 newops.emplace_back(std::move(nextOp));
1359 continue;
1360 }
1361
1362 // if there is no measurement or reset, add the next gate
1363 for (Types::qubit_t q = 0; q < qubitsNo; ++q) {
1364 if (qubitOps.find(q) ==
1365 qubitOps.end()) // no operation left on this qubit
1366 continue;
1367
1368 // grab the current operation for this qubit
1369 const auto &ops = qubitOps[q];
1370 const auto &op = ops[indices[q]];
1371
1372 bool hasDependencies = false;
1373
1374 for (const auto &opd : dependenciesMap[op])
1375 if (dependenciesMap.find(opd) != dependenciesMap.end()) {
1376 hasDependencies = true;
1377 break;
1378 }
1379
1380 if (!hasDependencies) {
1381 nextOp = op;
1382 break;
1383 }
1384 }
1385
1386 if (nextOp) {
1387 dependenciesMap.erase(nextOp);
1388
1389 const auto qubits = nextOp->AffectedQubits();
1390 for (auto q : qubits) {
1391 ++indices[q];
1392 if (indices[q] >= qubitOps[q].size()) qubitOps.erase(q);
1393 }
1394
1395 newops.emplace_back(std::move(nextOp));
1396 }
1397 }
1398
1399 assert(newops.size() == operations.size());
1400
1401 operations.swap(newops);
1402 }
1403
1413 std::pair<std::vector<size_t>, std::vector<Time>> GetDepth() const {
1414 size_t maxDepth;
1415 Time maxTime;
1416
1417 size_t qubitsNo = GetMaxQubitIndex() + 1;
1418 std::vector<Time> qubitTimes(qubitsNo, 0);
1419 std::vector<size_t> qubitDepths(qubitsNo, 0);
1420
1421 std::unordered_map<size_t, size_t> fromQubits;
1422
1423 for (const auto &op : operations) {
1424 const auto qbits = op->AffectedQubits();
1425 const auto delay = op->GetDelay();
1426
1427 maxTime = 0;
1428 maxDepth = 0;
1429 for (auto q : qbits) {
1430 qubitTimes[q] += delay;
1431 ++qubitDepths[q];
1432 if (qubitTimes[q] > maxTime) maxTime = qubitTimes[q];
1433 if (qubitDepths[q] > maxDepth) maxDepth = qubitDepths[q];
1434 }
1435
1436 const auto t = op->GetType();
1437 std::vector<size_t> condbits;
1438
1439 // TODO: deal with 'random gen' operations, those do not affect qubits
1440 // directly, but they do affect the classical bits and can be used in
1441 // conditional operations
1442
1445 condbits = op->AffectedBits();
1446
1447 for (auto bit : condbits) {
1448 if (fromQubits.find(bit) != fromQubits.end()) bit = fromQubits[bit];
1449
1450 bool found = false;
1451 for (auto q : qbits) {
1452 if (q == bit) {
1453 found = true;
1454 break;
1455 }
1456 }
1457 if (found || bit >= qubitsNo) continue;
1458
1459 qubitTimes[bit] += delay;
1460 ++qubitDepths[bit];
1461 if (qubitTimes[bit] > maxTime) maxTime = qubitTimes[bit];
1462 if (qubitDepths[bit] > maxDepth) maxDepth = qubitDepths[bit];
1463 }
1464
1466 const auto condMeas =
1467 std::static_pointer_cast<ConditionalMeasurement<Time>>(op);
1468 const auto meas = condMeas->GetOperation();
1469 const auto measQubits = meas->AffectedQubits();
1470 const auto measBits = meas->AffectedBits();
1471
1472 for (size_t i = 0; i < measQubits.size(); ++i) {
1473 if (i < measBits.size())
1474 fromQubits[measBits[i]] = measQubits[i];
1475 else
1476 fromQubits[measQubits[i]] = measQubits[i];
1477 }
1478 }
1479 } else if (t == OperationType::kMeasurement) {
1480 condbits = op->AffectedBits();
1481
1482 for (size_t i = 0; i < qbits.size(); ++i) {
1483 if (i < condbits.size())
1484 fromQubits[condbits[i]] = qbits[i];
1485 else
1486 fromQubits[qbits[i]] = qbits[i];
1487 }
1488 }
1489
1490 for (auto q : qbits) {
1491 qubitTimes[q] = maxTime;
1492 qubitDepths[q] = maxDepth;
1493 }
1494
1495 for (auto bit : condbits) {
1496 qubitTimes[bit] = maxTime;
1497 qubitDepths[bit] = maxDepth;
1498 }
1499 }
1500
1501 return std::make_pair(qubitDepths, qubitTimes);
1502 }
1503
1513 std::pair<size_t, Time> GetMaxDepth() const {
1514 auto [qubitDepths, qubitTimes] = GetDepth();
1515
1516 Time maxTime = 0;
1517 size_t maxDepth = 0;
1518 for (size_t qubit = 0; qubit < qubitDepths.size(); ++qubit) {
1519 if (qubitTimes[qubit] > maxTime) maxTime = qubitTimes[qubit];
1520 if (qubitDepths[qubit] > maxDepth) maxDepth = qubitDepths[qubit];
1521 }
1522
1523 return std::make_pair(maxDepth, maxTime);
1524 }
1525
1532 size_t GetNumberOfOperations() const { return operations.size(); }
1533
1542 OperationPtr GetOperation(size_t pos) const {
1543 if (pos >= operations.size()) return nullptr;
1544
1545 return operations[pos];
1546 }
1547
1561 std::shared_ptr<Circuit<Time>> GetCircuitCut(Types::qubit_t startQubit,
1562 Types::qubit_t endQubit) const {
1563 OperationsVector newops;
1564 newops.reserve(operations.size());
1565
1566 for (const auto &op : operations) {
1567 const auto qubits = op->AffectedQubits();
1568 bool containsOutsideQubits = false;
1569 bool containsInsideQubits = false;
1570 for (const auto q : qubits) {
1571 if (q < startQubit || q > endQubit) {
1572 containsOutsideQubits = true;
1573 if (containsInsideQubits) break;
1574 } else {
1575 containsInsideQubits = true;
1576 if (containsOutsideQubits) break;
1577 }
1578 }
1579
1580 if (containsInsideQubits) {
1581 if (containsOutsideQubits)
1582 throw std::runtime_error(
1583 "Cannot cut the circuit with the specified interval");
1584 newops.emplace_back(op->Clone());
1585 }
1586 }
1587
1588 return std::make_shared<Circuit<Time>>(newops);
1589 }
1590
1602 std::unordered_set<Types::qubit_t> measuredQubits;
1603 std::unordered_set<Types::qubit_t> affectedQubits;
1604 std::unordered_set<Types::qubit_t> resetQubits;
1605
1606 for (const auto &op : operations) {
1607 const auto qubits = op->AffectedQubits();
1608
1609 if (op->GetType() == OperationType::kMeasurement) {
1610 for (const auto qbit : qubits)
1611 if (resetQubits.find(qbit) !=
1612 resetQubits.end()) // there is a reset on this qubit already and
1613 // it's not at the beginning of the circuit
1614 return true;
1615
1616 /*
1617 const auto bits = op->AffectedBits();
1618 if (bits.size() != qubits.size())
1619 return true;
1620
1621 for (size_t b = 0; b < bits.size(); ++b)
1622 if (bits[b] != qubits[b])
1623 return true;
1624 */
1625 measuredQubits.insert(qubits.begin(), qubits.end());
1626 } else if (op->GetType() == OperationType::kConditionalGate ||
1627 op->GetType() == OperationType::kConditionalMeasurement ||
1628 op->GetType() == OperationType::kRandomGen ||
1629 op->GetType() == OperationType::kConditionalRandomGen)
1630 return true;
1631 else if (op->GetType() == OperationType::kReset) {
1632 // resets in the middle of the circuit are treated as measurements
1633 for (const auto qbit : qubits) {
1634 // if there is already a gate applied on the qubit but no measurement
1635 // yet, it's considered in the middle if there is no gate applied,
1636 // then it's the first operation on the qubit
1637 if (affectedQubits.find(qbit) != affectedQubits.end() ||
1638 measuredQubits.find(qbit) != measuredQubits.end())
1639 resetQubits.insert(qbit);
1640
1641 affectedQubits.insert(qbit);
1642 }
1643 } else {
1644 for (const auto qbit : qubits) {
1645 if (measuredQubits.find(qbit) !=
1646 measuredQubits
1647 .end()) // there is a measurement on this qubit already
1648 return true;
1649
1650 if (resetQubits.find(qbit) !=
1651 resetQubits.end()) // there is a reset on this qubit already and
1652 // it's not at the beginning of the circuit
1653 return true;
1654
1655 affectedQubits.insert(qbit);
1656 }
1657 }
1658 }
1659
1660 return false;
1661 }
1662
1675 std::vector<bool> ExecuteNonMeasurements(
1676 const std::shared_ptr<Simulators::ISimulator> &sim,
1677 OperationState &state) const {
1678 std::vector<bool> executedOps;
1679 executedOps.reserve(operations.size());
1680
1681 std::unordered_set<Types::qubit_t> measuredQubits;
1682 std::unordered_set<Types::qubit_t> affectedQubits;
1683
1684 bool executionStopped = false;
1685
1686 for (size_t i = 0; i < operations.size(); ++i) {
1687 auto &op = operations[i];
1688 const auto qubits = op->AffectedQubits();
1689
1690 bool executed = false;
1691
1692 if (op->GetType() == OperationType::kMeasurement ||
1693 op->GetType() == OperationType::kConditionalMeasurement ||
1694 op->GetType() == OperationType::kRandomGen ||
1695 op->GetType() == OperationType::kConditionalRandomGen)
1696 measuredQubits.insert(qubits.begin(), qubits.end());
1697 else if (op->GetType() == OperationType::kReset) {
1698 // if it's the first op on qubit(s), execute it, otherwise treat it as a
1699 // measurement
1700 executed = true;
1701 for (auto qubit : qubits)
1702 if (affectedQubits.find(qubit) != affectedQubits.end()) {
1703 executed = false;
1704 break;
1705 }
1706
1707 if (executed) {
1708 if (sim) op->Execute(sim, state);
1709 } else
1710 measuredQubits.insert(qubits.begin(), qubits.end());
1711 } else // regular gate or conditional gate
1712 {
1713 const auto bits = op->AffectedBits();
1714
1715 // a measurement on a qubit prevents execution of any following gate
1716 // than affects the same qubit also a gate that's not executed and it
1717 // would affect certain qubits will prevent the execution of any
1718 // following gate that affects those qubits
1719
1720 bool canExecute = op->GetType() == OperationType::kGate;
1721
1722 if (canExecute) // a conditional gate cannot be executed, it needs
1723 // something executed at each shot, either a
1724 // measurement or a random number generated
1725 {
1726 for (auto bit : bits)
1727 if (measuredQubits.find(bit) != measuredQubits.end()) {
1728 canExecute = false;
1729 break;
1730 }
1731
1732 for (auto qubit : qubits)
1733 if (measuredQubits.find(qubit) != measuredQubits.end()) {
1734 canExecute = false;
1735 break;
1736 }
1737 }
1738
1739 if (canExecute) {
1740 if (sim) op->Execute(sim, state);
1741 executed = true;
1742 } else {
1743 // this is a 'trick', if it cannot execute, then neither can any
1744 // following gate that affects any of the already involved qubits
1745 measuredQubits.insert(bits.begin(), bits.end());
1746 measuredQubits.insert(qubits.begin(), qubits.end());
1747 }
1748 }
1749
1750 affectedQubits.insert(qubits.begin(), qubits.end());
1751
1752 if (!executed) executionStopped = true;
1753 if (executionStopped) executedOps.emplace_back(executed);
1754 }
1755
1756 // if (sim) sim->Flush();
1757
1758 return executedOps;
1759 }
1760
1772 void ExecuteMeasurements(const std::shared_ptr<Simulators::ISimulator> &sim,
1773 OperationState &state,
1774 const std::vector<bool> &executedOps) const {
1775 state.Reset();
1776 if (!sim) return;
1777
1778 // if (executedOps.empty() && !operations.empty()) throw
1779 // std::runtime_error("The executed operations vector is empty");
1780
1781 const size_t dif = operations.size() - executedOps.size();
1782
1783 for (size_t i = dif; i < operations.size(); ++i)
1784 if (!executedOps[i - dif]) operations[i]->Execute(sim, state);
1785
1786 // sim->Flush();
1787 }
1788
1789 // used internally to optimize measurements in the case of having measurements
1790 // only at the end of the circuit
1791 std::shared_ptr<MeasurementOperation<Time>> GetLastMeasurements(
1792 const std::vector<bool> &executedOps, bool sort = true) const {
1793 const size_t dif = operations.size() - executedOps.size();
1794 std::vector<std::pair<Types::qubit_t, size_t>> measurements;
1795 measurements.reserve(dif);
1796
1797 for (size_t i = dif; i < operations.size(); ++i)
1798 if (!executedOps[i - dif] &&
1799 operations[i]->GetType() == OperationType::kMeasurement) {
1800 auto measOp =
1801 std::static_pointer_cast<MeasurementOperation<Time>>(operations[i]);
1802 const auto &qubits = measOp->GetQubits();
1803 const auto &bits = measOp->GetBitsIndices();
1804
1805 for (size_t j = 0; j < qubits.size(); ++j)
1806 measurements.emplace_back(qubits[j], bits[j]);
1807 }
1808
1809 // qiskit aer expects sometimes to have them in sorted order, so...
1810 if (sort)
1811 std::sort(
1812 measurements.begin(), measurements.end(),
1813 [](const auto &p1, const auto &p2) { return p1.first < p2.first; });
1814
1815 return std::make_shared<MeasurementOperation<Time>>(measurements);
1816 }
1817
1827 for (const auto &op : operations)
1828 if (op->GetType() == OperationType::kConditionalGate ||
1829 op->GetType() == OperationType::kConditionalMeasurement ||
1830 op->GetType() == OperationType::kConditionalRandomGen)
1831 return true;
1832
1833 return false;
1834 }
1835
1850 for (const auto &op : operations) {
1851 const auto qubits = op->AffectedQubits();
1852 if (qubits.size() <= 1) continue;
1853
1854 if (qubits.size() == 2) {
1855 if (std::abs(qubits[0] - qubits[1]) != 1) return false;
1856 } else {
1857 Types::qubit_t minQubit = qubits[0];
1858 Types::qubit_t maxQubit = qubits[0];
1859
1860 for (size_t i = 1; i < qubits.size(); ++i) {
1861 if (qubits[i] < minQubit)
1862 minQubit = qubits[i];
1863 else if (qubits[i] > maxQubit)
1864 maxQubit = qubits[i];
1865 }
1866
1867 if (maxQubit - minQubit >= qubits.size()) return false;
1868 }
1869 }
1870
1871 return true;
1872 }
1873
1881 bool IsForest() const {
1882 std::unordered_map<Types::qubit_t, size_t> qubits;
1883 std::unordered_map<Types::qubit_t, Types::qubits_vector> lastQubits;
1884
1885 for (const auto &op : operations) {
1886 const auto q = op->AffectedQubits();
1887 // one qubit gates or other operations that do not affect qubits do not
1888 // change anything
1889 if (q.size() <= 1) continue;
1890
1891 bool allInTheLastQubits = true;
1892
1893 for (const auto qubit : q) {
1894 if (lastQubits.find(qubit) == lastQubits.end()) {
1895 allInTheLastQubits = false;
1896 break;
1897 } else {
1898 const auto &lastQ = lastQubits[qubit];
1899
1900 for (const auto q1 : q)
1901 if (std::find(lastQ.cbegin(), lastQ.cend(), q1) == lastQ.cend()) {
1902 allInTheLastQubits = false;
1903 break;
1904 }
1905
1906 if (!allInTheLastQubits) break;
1907 }
1908 }
1909
1910 if (allInTheLastQubits) continue;
1911
1912 for (const auto qubit : q) {
1913 if (qubits[qubit] > 1) // if the qubit is affected again...
1914 return false;
1915
1916 ++qubits[qubit];
1917
1918 lastQubits[qubit] = q;
1919 }
1920 }
1921
1922 return true;
1923 }
1924
1934 bool IsClifford() const override {
1935 for (const auto &op : operations)
1936 if (!op->IsClifford()) return false;
1937
1938 return true;
1939 }
1940
1950 double CliffordPercentage() const {
1951 size_t cliffordOps = 0;
1952 for (const auto &op : operations)
1953 if (op->IsClifford()) ++cliffordOps;
1954
1955 return static_cast<double>(cliffordOps) / operations.size();
1956 }
1957
1967 std::unordered_set<Types::qubit_t> GetCliffordQubits() const {
1968 std::unordered_set<Types::qubit_t> cliffordQubits;
1969 std::unordered_set<Types::qubit_t> nonCliffordQubits;
1970
1971 for (const auto &op : operations) {
1972 const auto qubits = op->AffectedQubits();
1973 if (op->IsClifford()) {
1974 for (const auto q : qubits) cliffordQubits.insert(q);
1975 } else {
1976 for (const auto q : qubits) nonCliffordQubits.insert(q);
1977 }
1978 }
1979
1980 for (const auto q : nonCliffordQubits) cliffordQubits.erase(q);
1981
1982 return cliffordQubits;
1983 }
1984
1994 std::vector<std::shared_ptr<Circuit<Time>>> SplitCircuit() const {
1995 std::vector<std::shared_ptr<Circuit<Time>>> circuits;
1996
1997 // find how many disjoint circuits we have in this circuit
1998
1999 std::unordered_map<Types::qubit_t, std::unordered_set<Types::qubit_t>>
2000 circuitsMap;
2001 auto allQubits = GetQubits();
2002 std::unordered_map<Types::qubit_t, Types::qubit_t> qubitCircuitMap;
2003
2004 // start with a bunch of disjoint sets of qubits, containing each a single
2005 // qubit
2006
2007 for (auto qubit : allQubits) {
2008 circuitsMap[qubit] = std::unordered_set<Types::qubit_t>{qubit};
2009 qubitCircuitMap[qubit] = qubit;
2010 }
2011
2012 // then the gates will join them together into circuits
2013
2014 for (const auto &op : operations) {
2015 const auto qubits = op->AffectedQubits();
2016
2017 if (qubits.empty()) continue;
2018
2019 auto qubitIt = qubits.cbegin();
2020 auto firstQubit = *qubitIt;
2021 // where is the first qubit in the disjoint sets?
2022 auto firstQubitCircuit = qubitCircuitMap[firstQubit];
2023
2024 ++qubitIt;
2025
2026 for (; qubitIt != qubits.cend(); ++qubitIt) {
2027 auto qubit = *qubitIt;
2028
2029 // where is the qubit in the disjoint sets?
2030 auto qubitCircuit = qubitCircuitMap[qubit];
2031
2032 // join the circuits / qubits sets
2033
2034 if (firstQubitCircuit != qubitCircuit) {
2035 // join the circuits
2036 circuitsMap[firstQubitCircuit].insert(
2037 circuitsMap[qubitCircuit].begin(),
2038 circuitsMap[qubitCircuit].end());
2039
2040 // update the qubit to circuit map
2041 for (auto q : circuitsMap[qubitCircuit])
2042 qubitCircuitMap[q] = firstQubitCircuit;
2043
2044 // remove the joined circuit
2045 circuitsMap.erase(qubitCircuit);
2046 }
2047 }
2048 }
2049
2050 size_t circSize = 1ULL;
2051 circSize = std::max(circSize, circuitsMap.size());
2052 circuits.resize(circSize);
2053
2054 for (size_t i = 0; i < circuits.size(); ++i)
2055 circuits[i] = std::make_shared<Circuit<Time>>();
2056
2057 std::unordered_map<Types::qubit_t, size_t> qubitsSetsToCircuit;
2058
2059 size_t circuitNo = 0;
2060 for (const auto &[id, qubitSet] : circuitsMap) {
2061 qubitsSetsToCircuit[id] = circuitNo;
2062
2063 ++circuitNo;
2064 }
2065
2066 // now fill them up with the operations
2067
2068 for (const auto &op : operations) {
2069 const auto qubits = op->AffectedQubits();
2070
2071 if (qubits.empty()) {
2072 circuits[0]->AddOperation(op->Clone());
2073 continue;
2074 }
2075
2076 const auto circ = qubitsSetsToCircuit[qubitCircuitMap[*qubits.cbegin()]];
2077
2078 circuits[circ]->AddOperation(op->Clone());
2079 }
2080
2081 return circuits;
2082 }
2083
2090 iterator begin() noexcept { return operations.begin(); }
2091
2098 iterator end() noexcept { return operations.end(); }
2099
2106 const_iterator cbegin() const noexcept { return operations.cbegin(); }
2107
2114 const_iterator cend() const noexcept { return operations.cend(); }
2115
2122 reverse_iterator rbegin() noexcept { return operations.rbegin(); }
2123
2130 reverse_iterator rend() noexcept { return operations.rend(); }
2131
2139 return operations.crbegin();
2140 }
2141
2148 const_reverse_iterator crend() const noexcept { return operations.crend(); }
2149
2156 auto size() const { return operations.size(); }
2157
2164 auto empty() const { return operations.empty(); }
2165
2173 auto &operator[](size_t pos) { return operations[pos]; }
2174
2182 const auto &operator[](size_t pos) const { return operations[pos]; }
2183
2191 void resize(size_t size) {
2192 if (size < operations.size()) operations.resize(size);
2193 }
2194
2195 private:
2206 void ReplaceThreeQubitAndSwapGates(bool onlyThreeQubits = false) {
2207 // just replace all three qubit gates(just ccnot and cswap will exist in the
2208 // first phase) with several gates on less qubits also replace swap gates
2209 // with three cnots (in the first phase) the controlled ones must be
2210 // replaced as well
2211
2212 // TODO: if composite operations will be implemented, those need to be
2213 // optimized as well
2214
2215 std::vector<std::shared_ptr<IOperation<Time>>> newops;
2216 newops.reserve(operations.size());
2217
2218 for (std::shared_ptr<IOperation<Time>> op : operations) {
2219 if (op->GetType() == OperationType::kGate) {
2220 std::shared_ptr<IQuantumGate<Time>> gate =
2221 std::static_pointer_cast<IQuantumGate<Time>>(op);
2222
2223 if (NeedsConversion(gate, onlyThreeQubits)) {
2224 std::vector<std::shared_ptr<IGateOperation<Time>>> newgates =
2225 ConvertGate(gate, onlyThreeQubits);
2226 newops.insert(newops.end(), newgates.begin(), newgates.end());
2227 } else
2228 newops.push_back(op);
2229 } else if (op->GetType() == OperationType::kConditionalGate) {
2230 std::shared_ptr<ConditionalGate<Time>> condgate =
2231 std::static_pointer_cast<ConditionalGate<Time>>(op);
2232 std::shared_ptr<IQuantumGate<Time>> gate =
2233 std::static_pointer_cast<IQuantumGate<Time>>(
2234 condgate->GetOperation());
2235
2236 if (NeedsConversion(gate, onlyThreeQubits)) {
2237 std::vector<std::shared_ptr<IGateOperation<Time>>> newgates =
2238 ConvertGate(gate, onlyThreeQubits);
2239 std::shared_ptr<ICondition> cond = condgate->GetCondition();
2240
2241 for (auto gate : newgates)
2242 newops.push_back(
2243 std::make_shared<ConditionalGate<Time>>(gate, cond));
2244 } else
2245 newops.push_back(op);
2246 } else
2247 newops.push_back(op);
2248 }
2249
2250 operations.swap(newops);
2251 }
2252
2262 static bool NeedsConversion(const std::shared_ptr<IQuantumGate<Time>> &gate,
2263 bool onlyThreeQubits = false) {
2264 const bool hasThreeQubits = gate->GetNumQubits() == 3;
2265 if (onlyThreeQubits) return hasThreeQubits;
2266
2267 return hasThreeQubits ||
2268 gate->GetGateType() == QuantumGateType::kSwapGateType;
2269 }
2270
2283 static std::vector<std::shared_ptr<IGateOperation<Time>>> ConvertGate(
2284 std::shared_ptr<IQuantumGate<Time>> &gate, bool onlyThreeQubits = false) {
2285 // TODO: if delays are used, how to transfer delays from the converted gate
2286 // to the resulting gates?
2287 std::vector<std::shared_ptr<IGateOperation<Time>>> newops;
2288
2289 if (gate->GetNumQubits() == 3) {
2290 // must be converted no matter what
2291 if (gate->GetGateType() == QuantumGateType::kCCXGateType) {
2292 const size_t q1 = gate->GetQubit(0); // control 1
2293 const size_t q2 = gate->GetQubit(1); // control 2
2294 const size_t q3 = gate->GetQubit(2); // target
2295
2296 // Sleator-Weinfurter decomposition
2297 newops.push_back(std::make_shared<CSxGate<Time>>(q2, q3));
2298 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
2299 newops.push_back(std::make_shared<CSxDagGate<Time>>(q2, q3));
2300 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
2301 newops.push_back(std::make_shared<CSxGate<Time>>(q1, q3));
2302 } else if (gate->GetGateType() == QuantumGateType::kCSwapGateType) {
2303 const size_t q1 = gate->GetQubit(0); // control 1
2304 const size_t q2 = gate->GetQubit(1); // control 2
2305 const size_t q3 = gate->GetQubit(2); // target
2306
2307 // TODO: find a better decomposition
2308 // this one I've got with the qiskit transpiler
2309 newops.push_back(std::make_shared<CXGate<Time>>(q3, q2));
2310
2311 newops.push_back(std::make_shared<CSxGate<Time>>(q2, q3));
2312 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
2313 newops.push_back(std::make_shared<PhaseGate<Time>>(q3, M_PI));
2314
2315 newops.push_back(std::make_shared<PhaseGate<Time>>(q2, -M_PI_2));
2316
2317 newops.push_back(std::make_shared<CSxGate<Time>>(q2, q3));
2318 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
2319 newops.push_back(std::make_shared<PhaseGate<Time>>(q3, M_PI));
2320
2321 newops.push_back(std::make_shared<CSxGate<Time>>(q1, q3));
2322
2323 newops.push_back(std::make_shared<CXGate<Time>>(q3, q2));
2324 } else
2325 newops.push_back(gate);
2326 } else if (!onlyThreeQubits &&
2327 gate->GetGateType() == QuantumGateType::kSwapGateType) {
2328 // must be converted no matter what
2329 const size_t q1 = gate->GetQubit(0);
2330 const size_t q2 = gate->GetQubit(1);
2331
2332 // for now replace it with three cnots, but maybe later make it
2333 // configurable there are other possibilities, for example three cy gates
2334 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
2335 newops.push_back(std::make_shared<CXGate<Time>>(q2, q1));
2336 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
2337 } else
2338 newops.push_back(gate);
2339
2340 return newops;
2341 }
2342
2343 OperationsVector operations;
2344};
2345
2359template <typename Time = Types::time_type>
2360class ComparableCircuit : public Circuit<Time> {
2361 public:
2364 using OperationPtr = std::shared_ptr<Operation>;
2367 std::vector<OperationPtr>;
2377
2387
2397
2398 return *this;
2399 }
2400
2407 bool operator==(const BaseClass &rhs) const {
2408 if (BaseClass::GetOperations().size() != rhs.GetOperations().size())
2409 return false;
2410
2411 for (size_t i = 0; i < BaseClass::GetOperations().size(); ++i) {
2412 if (BaseClass::GetOperations()[i]->GetType() !=
2413 rhs.GetOperations()[i]->GetType())
2414 return false;
2415
2416 switch (BaseClass::GetOperations()[i]->GetType()) {
2418 if (std::static_pointer_cast<IQuantumGate<Time>>(
2420 ->GetGateType() !=
2421 std::static_pointer_cast<IQuantumGate<Time>>(
2422 rhs.GetOperations()[i])
2423 ->GetGateType() ||
2424 BaseClass::GetOperations()[i]->AffectedBits() !=
2425 rhs.GetOperations()[i]->AffectedBits())
2426 return false;
2427 if (approximateParamsCheck) {
2428 const auto params1 = std::static_pointer_cast<IQuantumGate<Time>>(
2430 ->GetParams();
2431 const auto params2 = std::static_pointer_cast<IQuantumGate<Time>>(
2432 rhs.GetOperations()[i])
2433 ->GetParams();
2434 if (params1.size() != params2.size()) return false;
2435
2436 for (size_t j = 0; j < params1.size(); ++j)
2437 if (std::abs(params1[j] - params2[j]) > paramsEpsilon)
2438 return false;
2439 } else if (std::static_pointer_cast<IQuantumGate<Time>>(
2441 ->GetParams() !=
2442 std::static_pointer_cast<IQuantumGate<Time>>(
2443 rhs.GetOperations()[i])
2444 ->GetParams())
2445 return false;
2446 break;
2449 rhs.GetOperations()[i]->AffectedQubits() ||
2450 BaseClass::GetOperations()[i]->AffectedBits() !=
2451 rhs.GetOperations()[i]->AffectedBits())
2452 return false;
2453 break;
2456 rhs.GetOperations()[i]->AffectedBits())
2457 return false;
2458 break;
2462 // first, check the conditions
2463 const auto leftCondition =
2464 std::static_pointer_cast<IConditionalOperation<Time>>(
2466 ->GetCondition();
2467 const auto rightCondition =
2468 std::static_pointer_cast<IConditionalOperation<Time>>(
2469 rhs.GetOperations()[i])
2470 ->GetCondition();
2471 if (leftCondition->GetBitsIndices() !=
2472 rightCondition->GetBitsIndices())
2473 return false;
2474
2475 const auto leftEqCondition =
2476 std::static_pointer_cast<EqualCondition>(leftCondition);
2477 const auto rightEqCondition =
2478 std::static_pointer_cast<EqualCondition>(rightCondition);
2479 if (!leftEqCondition || !rightEqCondition) return false;
2480
2481 if (leftEqCondition->GetAllBits() != rightEqCondition->GetAllBits())
2482 return false;
2483
2484 // now check the operations
2485 const auto leftOp =
2486 std::static_pointer_cast<IConditionalOperation<Time>>(
2488 ->GetOperation();
2489 const auto rightOp =
2490 std::static_pointer_cast<IConditionalOperation<Time>>(
2491 rhs.GetOperations()[i])
2492 ->GetOperation();
2493
2494 ComparableCircuit<Time> leftCircuit;
2495 BaseClass rightCircuit;
2496 leftCircuit.SetApproximateParamsCheck(approximateParamsCheck);
2497 leftCircuit.AddOperation(leftOp);
2498 rightCircuit.AddOperation(rightOp);
2499
2500 if (leftCircuit != rightCircuit) return false;
2501 } break;
2504 rhs.GetOperations()[i]->AffectedQubits() ||
2505 std::static_pointer_cast<Reset<Time>>(
2507 ->GetResetTargets() !=
2508 std::static_pointer_cast<Reset<Time>>(rhs.GetOperations()[i])
2509 ->GetResetTargets())
2510 return false;
2511 break;
2513 break;
2514 default:
2515 return false;
2516 }
2517
2518 if (BaseClass::GetOperations()[i]->GetDelay() !=
2519 rhs.GetOperations()[i]->GetDelay())
2520 return false;
2521 }
2522
2523 return true;
2524 }
2525
2532 bool operator!=(const BaseClass &rhs) const { return !(*this == rhs); }
2533
2540 void SetApproximateParamsCheck(bool check) { approximateParamsCheck = check; }
2541
2548 bool GetApproximateParamsCheck() const { return approximateParamsCheck; }
2549
2558 void SetParamsEpsilon(double eps) { paramsEpsilon = eps; }
2559
2568 double GetParamsEpsilon() const { return paramsEpsilon; }
2569
2570 private:
2571 bool approximateParamsCheck =
2572 false;
2573 double paramsEpsilon = 1e-8;
2575};
2576
2577} // namespace Circuits
2578
2579#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:45
iterator begin() noexcept
Get the begin iterator for the operations.
Definition Circuit.h:2090
typename OperationsVector::const_reverse_iterator const_reverse_iterator
Definition Circuit.h:75
void ConvertForCutting()
Converts the circuit for distributed computing.
Definition Circuit.h:400
typename OperationsVector::reverse_iterator reverse_iterator
Definition Circuit.h:73
void Execute(const std::shared_ptr< Simulators::ISimulator > &sim, OperationState &state) const override
Execute the circuit on the given simulator.
Definition Circuit.h:95
typename OperationsVector::allocator_type allocator_type
Definition Circuit.h:63
std::unordered_map< Types::qubit_t, Types::qubit_t > BitMapping
The (qu)bit mapping for remapping.
Definition Circuit.h:54
const_iterator cbegin() const noexcept
Get the const begin iterator for the operations.
Definition Circuit.h:2106
double CliffordPercentage() const
Get the percentage of Clifford operations in the circuit.
Definition Circuit.h:1950
auto size() const
Get the number of operations in the circuit.
Definition Circuit.h:2156
bool IsClifford() const override
Checks if the circuit is a Clifford circuit.
Definition Circuit.h:1934
iterator end() noexcept
Get the end iterator for the operations.
Definition Circuit.h:2098
std::unordered_set< Types::qubit_t > GetCliffordQubits() const
Get the qubits that are acted on by Clifford operations.
Definition Circuit.h:1967
typename OperationsVector::iterator iterator
Definition Circuit.h:71
void AddOperation(const OperationPtr &op)
Adds an operation to the circuit.
Definition Circuit.h:120
std::set< size_t > GetBits() const
Returns the classical bits affected by the operations.
Definition Circuit.h:597
void Optimize(bool optimizeRotationGates=true)
Circuit optimization.
Definition Circuit.h:755
void EnsureProperOrderForMeasurements()
Definition Circuit.h:419
std::pair< size_t, Time > GetMaxDepth() const
Get max circuit depth.
Definition Circuit.h:1513
std::set< size_t > GetQubits() const
Returns the qubits affected by the operations.
Definition Circuit.h:580
std::vector< OperationPtr > OperationsVector
The vector of operations.
Definition Circuit.h:60
void AddResetsAtBeginningIfNeeded(Time delay=0)
Add resets at the beginning of the circuit.
Definition Circuit.h:736
void Clear()
Clears the operations from the circuit.
Definition Circuit.h:179
typename OperationsVector::value_type value_type
Definition Circuit.h:62
std::shared_ptr< Operation > OperationPtr
The shared pointer to the operation type.
Definition Circuit.h:58
typename OperationsVector::reference reference
Definition Circuit.h:66
bool CanAffectQuantumState() const override
Find if the circuit can affect the quantum state.
Definition Circuit.h:664
auto & operator[](size_t pos)
Get the operation at a given position.
Definition Circuit.h:2173
typename OperationsVector::difference_type difference_type
Definition Circuit.h:69
std::pair< std::vector< size_t >, std::vector< Time > > GetDepth() const
Get circuit depth.
Definition Circuit.h:1413
std::unordered_map< size_t, OperationPtr > GetLastOperationsOnQubits() const
Returns the last operations on circuit's qubits.
Definition Circuit.h:678
const_reverse_iterator crend() const noexcept
Get the const reverse end iterator for the operations.
Definition Circuit.h:2148
std::shared_ptr< MeasurementOperation< Time > > GetLastMeasurements(const std::vector< bool > &executedOps, bool sort=true) const
Definition Circuit.h:1791
bool ActsOnlyOnAdjacentQubits() const
Checks if the circuit has only operations that act on adjacent qubits.
Definition Circuit.h:1849
Circuit(const OperationsVector &ops={})
Construct a new Circuit object.
Definition Circuit.h:84
typename OperationsVector::pointer pointer
Definition Circuit.h:64
std::unordered_map< std::vector< bool >, size_t > ExecuteResults
The results of the execution of the circuit.
Definition Circuit.h:50
typename OperationsVector::size_type size_type
Definition Circuit.h:68
auto empty() const
Check if the circuit is empty.
Definition Circuit.h:2164
OperationPtr Remap(const BitMapping &qubitsMap, const BitMapping &bitsMap={}) const override
Get a shared pointer to a circuit remapped.
Definition Circuit.h:220
static void AccumulateResults(ExecuteResults &results, const ExecuteResults &newResults)
Accumulate the results of a circuit execution to already existing results.
Definition Circuit.h:325
std::shared_ptr< Circuit< Time > > GetCircuitCut(Types::qubit_t startQubit, Types::qubit_t endQubit) const
Get the circuit cut.
Definition Circuit.h:1561
void ConvertForDistribution()
Converts the circuit for distributed computing.
Definition Circuit.h:385
Types::qubits_vector AffectedQubits() const override
Returns the affected qubits.
Definition Circuit.h:613
bool HasOpsAfterMeasurements() const
Checks if the circuit has measurements that are followed by operations that affect the measured qubit...
Definition Circuit.h:1601
IOperation< Time > Operation
The operation type.
Definition Circuit.h:56
OperationPtr CloneFlyweight() const
Get a shared pointer to a clone of this object, but without cloning the operations.
Definition Circuit.h:202
bool HasConditionalOperations() const
Checks if the circuit has clasically conditional operations.
Definition Circuit.h:1826
void AddResetsIfNeeded(Time delay=0)
Add resets at the end of the circuit.
Definition Circuit.h:717
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:344
const_reverse_iterator crbegin() const noexcept
Get the const reverse begin iterator for the operations.
Definition Circuit.h:2138
OperationPtr GetOperation(size_t pos) const
Get an operation at a given position.
Definition Circuit.h:1542
const_iterator cend() const noexcept
Get the const end iterator for the operations.
Definition Circuit.h:2114
void MoveMeasurementsAndResets()
Move the measurements and resets closer to the beginning of the circuit.
Definition Circuit.h:1277
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:241
typename OperationsVector::const_iterator const_iterator
Definition Circuit.h:72
bool NeedsEntanglementForDistribution() const override
Find if the circuit needs entanglement for distribution.
Definition Circuit.h:650
std::unordered_map< size_t, OperationPtr > GetFirstOperationsOnQubits() const
Returns the first operations on circuit's qubits.
Definition Circuit.h:696
reverse_iterator rbegin() noexcept
Get the reverse begin iterator for the operations.
Definition Circuit.h:2122
void SetOperations(const OperationsVector &ops)
Set the operations in the circuit.
Definition Circuit.h:142
typename OperationsVector::const_pointer const_pointer
Definition Circuit.h:65
void AddOperations(const OperationsVector &ops)
Adds operations to the circuit.
Definition Circuit.h:151
size_t GetMaxQubitIndex() const
Returns the max qubit id for all operations.
Definition Circuit.h:506
OperationPtr Clone() const override
Get a shared pointer to a clone of this object.
Definition Circuit.h:187
size_t GetMaxCbitIndex() const
Returns the max classical bit id for all operations.
Definition Circuit.h:542
void AddCircuit(const std::shared_ptr< Circuit< Time > > &circuit)
Adds operations from another circuit to the circuit.
Definition Circuit.h:161
typename OperationsVector::const_reference const_reference
Definition Circuit.h:67
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:293
const auto & operator[](size_t pos) const
Get the operation at a given position.
Definition Circuit.h:2182
size_t GetMinCbitIndex() const
Returns the min classical bit id for all operations.
Definition Circuit.h:560
size_t GetNumberOfOperations() const
Get the number of operations in the circuit.
Definition Circuit.h:1532
std::vector< size_t > AffectedBits() const override
Returns the affected bits.
Definition Circuit.h:630
size_t GetMinQubitIndex() const
Returns the min qubit id for all operations.
Definition Circuit.h:524
void resize(size_t size)
Resizes the circuit.
Definition Circuit.h:2191
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:1675
OperationType GetType() const override
Get the type of the circuit.
Definition Circuit.h:111
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:1772
void ReplaceOperation(size_t index, const OperationPtr &op)
Replaces an operation in the circuit.
Definition Circuit.h:130
reverse_iterator rend() noexcept
Get the reverse end iterator for the operations.
Definition Circuit.h:2130
bool IsForest() const
Checks if the circuit is a forest circuit.
Definition Circuit.h:1881
std::vector< std::shared_ptr< Circuit< Time > > > SplitCircuit() const
Splits a circuit that has disjoint subcircuits in it into separate circuits.
Definition Circuit.h:1994
const OperationsVector & GetOperations() const
Get the operations in the circuit.
Definition Circuit.h:172
Circuit class for holding the sequence of operations that can be compared with another circuit.
Definition Circuit.h:2360
std::shared_ptr< Operation > OperationPtr
The shared pointer to the operation type.
Definition Circuit.h:2365
double GetParamsEpsilon() const
Gets the epsilon used for checking approximate equality of gate parameters.
Definition Circuit.h:2568
std::vector< OperationPtr > OperationsVector
The vector of operations.
Definition Circuit.h:2367
void SetApproximateParamsCheck(bool check)
Sets whether to check approximate equality of gate parameters.
Definition Circuit.h:2540
ComparableCircuit & operator=(const BaseClass &circ)
Assignment operator.
Definition Circuit.h:2395
bool operator==(const BaseClass &rhs) const
Comparison operator.
Definition Circuit.h:2407
bool GetApproximateParamsCheck() const
Gets whether to check approximate equality of gate parameters.
Definition Circuit.h:2548
ComparableCircuit(const BaseClass &circ)
Construct a new ComparableCircuit object.
Definition Circuit.h:2386
bool operator!=(const BaseClass &rhs) const
Comparison operator.
Definition Circuit.h:2532
Circuit< Time > BaseClass
The base class type.
Definition Circuit.h:2362
ComparableCircuit(const OperationsVector &ops={})
Construct a new ComparableCircuit object.
Definition Circuit.h:2376
void SetParamsEpsilon(double eps)
Sets the epsilon used for checking approximate equality of gate parameters.
Definition Circuit.h:2558
The operation interface.
Definition Operations.h:357
Time GetDelay() const
Get the delay of the operation.
Definition Operations.h:496
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:62
void Reset(bool value=false)
Set the classical bits with the specified value.
Definition Operations.h:243
The phase gate.
Reset operation class.
Definition Reset.h:32
const std::vector< bool > & GetResetTargets() const
Get the values to reset the qubits to.
Definition Reset.h:110
The S gate.
The S dagger gate.
The X gate.
The Z gate.
OperationType
The type of operations.
Definition Operations.h:26
@ kConditionalGate
conditional gate, similar with gate, but conditioned on something from 'OperationState'
@ kNoOp
no operation, just a placeholder, could be used to erase some operation from a circuit
@ kComposite
a composite operation, contains other operations - should not be used in the beginning,...
@ kRandomGen
random classical bit generator, result in 'OperationState'
@ kConditionalRandomGen
conditional random generator, similar with random gen, but conditioned on something from 'OperationSt...
@ kConditionalMeasurement
conditional measurement, similar with measurement, but conditioned on something from 'OperationState'
@ kMeasurement
measurement, result in 'OperationState'
@ kGate
the usual quantum gate, result stays in simulator's state
@ kReset
reset, no result in 'state', just apply measurement, then apply not on all qubits that were measured ...