Maestro 0.1.0
Unified interface for quantum circuit simulation
Loading...
Searching...
No Matches
Circuit.h
Go to the documentation of this file.
1
15
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> {
46public:
48 std::unordered_map<std::vector<bool>,
49 size_t>;
51 using BitMapping =
52 std::unordered_map<Types::qubit_t,
55
57 using OperationPtr = std::shared_ptr<Operation>;
60 std::vector<OperationPtr>;
61
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)
99 return;
100
101 for (const auto &op : operations)
102 op->Execute(sim, state);
103 // sim->Flush();
104 }
105
114
122 void AddOperation(const OperationPtr &op) { operations.push_back(op); }
123
132 void ReplaceOperation(size_t index, const OperationPtr &op) {
133 if (index >= operations.size())
134 return;
135 operations[index] = op;
136 }
137
145 void SetOperations(const OperationsVector &ops) { operations = ops; }
146
155 operations.insert(operations.end(), ops.begin(), ops.end());
156 }
157
164 void AddCircuit(const std::shared_ptr<Circuit<Time>> &circuit) {
165 AddOperations(circuit->GetOperations());
166 }
167
175 const OperationsVector &GetOperations() const { return operations; }
176
182 void Clear() { operations.clear(); }
183
190 OperationPtr Clone() const override {
191 OperationsVector newops;
192
193 for (auto &op : operations)
194 newops.emplace_back(op->Clone());
195
196 return std::make_shared<Circuit<Time>>(newops);
197 }
198
207 OperationsVector newops;
208
209 for (auto &op : operations)
210 newops.push_back(op);
211
212 return std::make_shared<Circuit<Time>>(newops);
213 }
214
225 OperationPtr Remap(const BitMapping &qubitsMap,
226 const BitMapping &bitsMap = {}) const override {
227 OperationsVector newops;
228
229 for (const auto &op : operations)
230 newops.emplace_back(op->Remap(qubitsMap, bitsMap));
231
232 return std::make_shared<Circuit<Time>>(newops);
233 }
234
246 std::shared_ptr<Circuit<Time>> RemapToContinuous(BitMapping &newQubitsMap,
247 BitMapping &reverseBitsMap,
248 size_t &nrQubits,
249 size_t &nrCbits) const {
250 OperationsVector newops;
251
252 BitMapping newBitsMap;
253
254 nrQubits = 0;
255 nrCbits = 0;
256
257 for (const auto &op : operations) {
258 const auto affectedBits = op->AffectedBits();
259 const auto affectedQubits = op->AffectedQubits();
260
261 for (const auto qubit : affectedQubits) {
262 const auto it = newQubitsMap.find(qubit);
263 if (it == newQubitsMap.end()) {
264 newQubitsMap[qubit] = nrQubits;
265 ++nrQubits;
266 }
267 }
268
269 for (const auto bit : affectedBits) {
270 const auto it = newBitsMap.find(bit);
271 if (it == newBitsMap.end()) {
272 newBitsMap[bit] = nrCbits;
273 reverseBitsMap[nrCbits] = bit;
274 ++nrCbits;
275 }
276 }
277
278 newops.emplace_back(op->Remap(newQubitsMap, newBitsMap));
279 }
280
281 return std::make_shared<Circuit<Time>>(newops);
282 }
283
299 const BitMapping &bitsMap = {},
300 bool ignoreNotMapped = false,
301 size_t sz = 0) {
302 ExecuteResults newResults;
303
304 if (!ignoreNotMapped && sz == 0) {
305 for (const auto &[from, to] : bitsMap)
306 if (to > sz)
307 sz = to;
308
309 ++sz;
310 }
311
312 for (const auto &res : results) {
313 Circuits::OperationState mappedState(res.first);
314
315 mappedState.Remap(bitsMap, ignoreNotMapped,
316 ignoreNotMapped ? bitsMap.size() : sz);
317 newResults[mappedState.GetAllBits()] += res.second;
318 }
319
320 return newResults;
321 }
322
331 static void AccumulateResults(ExecuteResults &results,
332 const ExecuteResults &newResults) {
333 for (const auto &res : newResults)
334 results[res.first] += res.second;
335 }
336
352 const ExecuteResults &newResults,
353 const BitMapping &bitsMap = {},
354 bool ignoreNotMapped = true,
355 size_t sz = 0) {
356 if (!ignoreNotMapped && sz == 0) {
357 for (const auto &[from, to] : bitsMap)
358 if (to > sz)
359 sz = to;
360
361 ++sz;
362 }
363
364 for (const auto &res : newResults) {
365 Circuits::OperationState mappedState(res.first);
366
367 mappedState.Remap(bitsMap, ignoreNotMapped,
368 ignoreNotMapped ? bitsMap.size() : sz);
369 results[mappedState.GetAllBits()] += res.second;
370 /*
371 const auto it = bitsMap.find(res.first);
372 if (it != bitsMap.end())
373 results[it->second] += res.second;
374 */
375 }
376 }
377
378 // TODO: This converts all swap, cswap and ccnot gates
379 // it's not really needed to convert them all,
380 // only those that are not applied locally, that is, on a single host
381 // the local ones can remain as they are
382 // so use network topology to decide which ones to convert
383 // that's for later, when we have the network part implemented
384 // and also the code that splits the circuit
385
394 // this will make the circuit better for distributed computing
395 // TODO: if composite operations will be implemented, those need to be
396 // optimized as well
397
398 ReplaceThreeQubitAndSwapGates();
399 }
400
408 void ConvertForCutting() { ReplaceThreeQubitAndSwapGates(true); }
409
410 /*
411 * @brief Splits the measurements to measurements on individual qubits and
412 * tries to order them as needed by the following clasically conditional
413 * gates.
414 *
415 * Splits the measurements to measurements on individual qubits and tries to
416 * order them as needed by the following clasically conditional gates. This is
417 * needed for netqasm, which requires the measurements to be in the right
418 * order, if the following clasically conditional gates need sending to
419 * another host. This wouldn't be needed if they are local, but we don't know
420 * that at this point.
421 *
422 * So in short, all measurements on more than one qubit are converted to one
423 * qubit measurements, and then all measurements that are grouped together
424 * (not separated by some other operation) are ordered in the same order as
425 * the conditions on the following clasically conditional gates.
426 */
428 // TODO: Maybe this should be moved at the netqasm level circuit conversion,
429 // since it's not needed for all kinds of distributed computing currently
430 // there is a virtual function in controller that does nothing in the base
431 // class, but for netqasm it calls this method
432 OperationsVector newops;
433
434 for (size_t i = 0; i < operations.size(); ++i) {
435 const auto op = operations[i];
436 if (op->GetType() != OperationType::kMeasurement) {
437 newops.emplace_back(op);
438 continue;
439 }
440
441 // ok, if it's a measurement, look ahead, accumulate all measurements and
442 // then add them in the right order
443 std::unordered_set<size_t> bits;
444 std::unordered_map<size_t, Types::qubit_t> measQubits;
445 std::unordered_map<size_t, Time> measDelays;
446
447 auto affectedBits = op->AffectedBits();
448 auto affectedQubits = op->AffectedQubits();
449
450 for (size_t q = 0; q < affectedQubits.size(); ++q) {
451 bits.insert(affectedBits[q]);
452 measQubits[affectedBits[q]] = affectedQubits[q];
453 measDelays[affectedBits[q]] = op->GetDelay();
454 }
455
456 size_t j = i + 1;
457 for (; j < operations.size(); ++j) {
458 const auto op2 = operations[j];
459
460 if (op2->GetType() != OperationType::kMeasurement)
461 break;
462
463 affectedQubits = op2->AffectedQubits();
464
465 const auto meas =
466 std::static_pointer_cast<MeasurementOperation<Time>>(op2);
467 affectedBits = meas->GetBitsIndices();
468 for (size_t q = 0; q < affectedBits.size(); ++q) {
469 bits.insert(affectedBits[q]);
470 measQubits[affectedBits[q]] = affectedQubits[q];
471 measDelays[affectedBits[q]] = op2->GetDelay();
472 }
473 }
474
475 i = j - 1;
476
477 // the right order is the one following in the classically controlled
478 // gates
479 for (; j < operations.size(); ++j) {
480 const auto op2 = operations[j];
481 if (op2->GetType() == OperationType::kConditionalGate ||
482 op2->GetType() == OperationType::kConditionalMeasurement ||
483 op2->GetType() == OperationType::kConditionalRandomGen) {
484 auto condop =
485 std::static_pointer_cast<IConditionalOperation<Time>>(op2);
486 const auto condbits = condop->AffectedBits();
487 for (const auto bit : condbits)
488 if (bits.find(bit) != bits.end()) {
489 newops.emplace_back(std::make_shared<MeasurementOperation<Time>>(
490 std::vector{std::make_pair(measQubits[bit], bit)},
491 measDelays[bit]));
492 bits.erase(bit);
493 }
494 }
495 if (bits.empty())
496 break;
497 }
498
499 // now add the measurements that were left in any order
500 for (auto bit : bits)
501 newops.emplace_back(std::make_shared<MeasurementOperation<Time>>(
502 std::vector{std::make_pair(measQubits[bit], bit)},
503 measDelays[bit]));
504 }
505
506 operations.swap(newops);
507 }
508
516 size_t GetMaxQubitIndex() const {
517 size_t mx = 0;
518 for (const auto &op : operations) {
519 const auto qbits = op->AffectedQubits();
520 for (auto q : qbits)
521 if (q > mx)
522 mx = q;
523 }
524
525 return mx;
526 }
527
535 size_t GetMinQubitIndex() const {
536 size_t mn = std::numeric_limits<size_t>::max();
537 for (const auto &op : operations) {
538 const auto qbits = op->AffectedQubits();
539 for (auto q : qbits)
540 if (q < mn)
541 mn = q;
542 }
543
544 return mn;
545 }
546
554 size_t GetMaxCbitIndex() const {
555 size_t mx = 0;
556 for (const auto &op : operations) {
557 const auto cbits = op->AffectedBits();
558 for (auto q : cbits)
559 if (q > mx)
560 mx = q;
561 }
562
563 return mx;
564 }
565
573 size_t GetMinCbitIndex() const {
574 size_t mn = std::numeric_limits<size_t>::max();
575 for (const auto &op : operations) {
576 const auto cbits = op->AffectedBits();
577 for (auto q : cbits)
578 if (q < mn)
579 mn = q;
580 }
581
582 return mn;
583 }
584
594 std::set<size_t> GetQubits() const {
595 std::set<size_t> qubits;
596 for (const auto &op : operations) {
597 const auto qbits = op->AffectedQubits();
598 qubits.insert(qbits.begin(), qbits.end());
599 }
600
601 return qubits;
602 }
603
611 std::set<size_t> GetBits() const {
612 std::set<size_t> cbits;
613 for (const auto &op : operations) {
614 const auto bits = op->AffectedBits();
615 cbits.insert(bits.begin(), bits.end());
616 }
617
618 return cbits;
619 }
620
628 auto qubits = GetQubits();
629
630 Types::qubits_vector qubitsVec;
631 qubitsVec.reserve(qubits.size());
632
633 for (auto q : qubits)
634 qubitsVec.emplace_back(q);
635
636 return qubitsVec;
637 }
638
645 std::vector<size_t> AffectedBits() const override {
646 auto bits = GetBits();
647
648 std::vector<size_t> bitsVec;
649 bitsVec.reserve(bits.size());
650
651 for (auto b : bits)
652 bitsVec.emplace_back(b);
653
654 return bitsVec;
655 }
656
666 bool NeedsEntanglementForDistribution() const override {
667 for (const auto &op : operations)
668 if (op->NeedsEntanglementForDistribution())
669 return true;
670
671 return false;
672 }
673
681 bool CanAffectQuantumState() const override {
682 for (const auto &op : operations)
683 if (op->CanAffectQuantumState())
684 return true;
685
686 return false;
687 }
688
696 std::unordered_map<size_t, OperationPtr> GetLastOperationsOnQubits() const {
697 std::unordered_map<size_t, OperationPtr> lastOps;
698
699 for (const auto &op : operations) {
700 const auto qbits = op->AffectedQubits();
701 for (auto q : qbits)
702 lastOps[q] = op;
703 }
704
705 return lastOps;
706 }
707
715 std::unordered_map<size_t, OperationPtr> GetFirstOperationsOnQubits() const {
716 std::unordered_map<size_t, OperationPtr> firstOps;
717
718 for (const auto &op : operations) {
719 const auto qbits = op->AffectedQubits();
720 for (auto q : qbits) {
721 if (firstOps.find(q) == firstOps.end())
722 firstOps[q] = op;
723 }
724 }
725
726 return firstOps;
727 }
728
737 void AddResetsIfNeeded(Time delay = 0) {
738 const auto GetLastOps = GetLastOperationsOnQubits();
739
740 for (const auto &[q, op] : GetLastOps)
741 if (op->GetType() !=
742 OperationType::kReset) // don't add it if there is already a reset
743 // operation on the qubit
744 operations.emplace_back(
745 std::make_shared<Reset<Time>>(Types::qubits_vector{q}, delay));
746 }
747
756 void AddResetsAtBeginningIfNeeded(Time delay = 0) {
757 const auto GetFirstOps = GetFirstOperationsOnQubits();
758
759 for (const auto &[q, op] : GetFirstOps)
760 if (op->GetType() !=
761 OperationType::kReset) // don't add it if there is already a reset
762 // operation on the qubit
763 operations.insert(
764 operations.begin(),
765 std::make_shared<Reset<Time>>(Types::qubits_vector{q}, delay));
766 }
767
775 void Optimize(bool optimizeRotationGates = true) {
776 // Some ideas, from simple to more complex:
777 //
778 // IMPORTANT: Focus on the gates that are added for distributed computing,
779 // either for the one with entanglement or the one with cutting the reason
780 // is that maybe the provided circuit is not that bad, but due of the
781 // supplementary gates added, duplicates (for example) will occur the most
782 // important one qubit ones added are hadamard then X, S, Sdag and Z
783 //
784 // 1. First, one qubit gates can be combined into a single gate or even no
785 // gate a) Straightforward for those that are their own inverse (that is,
786 // hermitian/involutory): Hadamard and Pauli gates for example, if one finds
787 // two of them in sequence, they can be removed other ones are the one that
788 // are followed by their 'dag' (inverse, since the gates are unitary) in the
789 // circuit, those can be removed, too
790
791 // several resets can be also changed into a single one, also repeated
792 // measurements of the same qubit, with result in the same cbit can be
793 // replaced by a single measurement
794
795 // b) other ones can be combined into a single gate, for example phase shift
796 // gates or rotation gates two phase gates (not the general phase shift we
797 // have, but the one with 1, i on the diagonal) can be combined into a Z
798 // gate, for example, or two sqrtNot gates can be combined into a single X
799 // gate combinations of phase gates and hadamard can be replaced by pauli
800 // gates in some cases, and so on even the U gate could be used to join
801 // together several one qubit gates
802
803 // c) even more complex... three or more one qubit gates could be combined
804 // into a single gate... an example is HXH = Z other examples SXS^t = Y,
805 // SZS^t = Z
806
807 // 2. Two qubit gates can be optimized, too
808 // for example two CNOT gates in sequence can be removed if the control
809 // qubit is the same, the same goes for two CZ or CY or SWAP gates (this
810 // goes for the three qubit gates, CCX, CCY, CCZ, CSWAP, too)
811
812 // 3. Some gates commute, the reorder can give some opportunities for more
813 // optimization
814
815 // 4. Groups of gates
816 // the possibilities are endless, but might be easier to focus first on
817 // Clifford gates for example a X sandwiched between CNOTs can be replaced
818 // with two X on each qubit if the original X is on the control qubit or
819 // with an X on the target qubit if the original X is on the target qubit a
820 // similar thing happens if Z is sandwiched between CNOTs, but this time Z x
821 // Z appears if the original Z is on the target qubit and if original Z is
822 // on the control qubit, then the CNOTs dissapear and the Z remains on the
823 // control qubit
824
825 // three CNOT gates with the one in the middle turned upside down compare
826 // with the other two can be replaced by a single swap gate
827
828 // first stage, take out duplicates of H, X, Y, Z
829
830 bool changed;
831
832 do {
833 changed = false;
834
835 std::vector<std::shared_ptr<IOperation<Time>>> newops;
836 newops.reserve(operations.size());
837
838 for (int i = 0; i < static_cast<int>(operations.size()); ++i) {
839 const std::shared_ptr<IOperation<Time>> &op = operations[i];
840
841 const auto type = op->GetType();
842 if (type == OperationType::kNoOp)
843 continue;
844 else if (type == OperationType::kGate) {
845 std::shared_ptr<IQuantumGate<Time>> gate =
846 std::static_pointer_cast<IQuantumGate<Time>>(op);
847 const auto qubits = gate->AffectedQubits();
848
849 if (qubits.size() == 1) {
850 // TODO: HXH = Z, SXS^t = Y, SZS^t = Z ????
851
852 auto gateType = gate->GetGateType();
853 bool replace = false;
854
855 // if it's one of the interesting gates, look ahead to see if it's
856 // followed by the same gate on the same qubit if yes, replace the
857 // next one with a nop and skip the current one (or replace the pair
858 // with a single gate, depending on the type) set changed to true if
859 // something was changed
860 switch (gateType) {
862 [[fallthrough]];
864 [[fallthrough]];
866 if (!optimizeRotationGates) {
867 newops.push_back(op);
868 break;
869 }
870 [[fallthrough]];
872 replace = true;
873 [[fallthrough]];
874 // those above will be replaced the pair with a single gate, all the
875 // following are the ones that get removed
877 [[fallthrough]];
879 [[fallthrough]];
881 [[fallthrough]];
883 [[fallthrough]];
885 [[fallthrough]];
887 [[fallthrough]];
889 [[fallthrough]];
891 [[fallthrough]];
893 [[fallthrough]];
895 [[fallthrough]];
897 bool found = false;
898
899 if (gateType == QuantumGateType::kSGateType)
901 else if (gateType == QuantumGateType::kSdgGateType)
903 else if (gateType == QuantumGateType::kTGateType)
905 else if (gateType == QuantumGateType::kTdgGateType)
907 else if (gateType == QuantumGateType::kSxGateType)
909 else if (gateType == QuantumGateType::kSxDagGateType)
911
912 for (size_t j = i + 1; j < operations.size(); ++j) {
913 auto &nextOp = operations[j];
914 if (!nextOp->CanAffectQuantumState())
915 continue;
916
917 const auto nextQubits = nextOp->AffectedQubits();
918 bool hasQubit = false;
919
920 for (auto q : nextQubits)
921 if (q == qubits[0]) {
922 hasQubit = true;
923 break;
924 }
925
926 if (!hasQubit)
927 continue; // an op that does not touch the current qubit can
928 // be skipped
929 else if (nextQubits.size() != 1)
930 break; // if it touches the current qubit and it's something
931 // else than a single qubit gate, stop
932
933 const auto nextType = nextOp->GetType();
934 if (nextType != OperationType::kGate)
935 break; // could be a classically conditioned gate, stop
936
937 const auto &nextGate =
938 std::static_pointer_cast<SingleQubitGate<Time>>(nextOp);
939 if (nextGate->GetGateType() == gateType) {
940 if (replace) {
941 const auto params1 = gate->GetParams();
942 const auto params2 = nextGate->GetParams();
943
944 const double param = params1[0] + params2[0];
945 const auto delay = gate->GetDelay() + nextGate->GetDelay();
946
947 if (gateType == QuantumGateType::kPhaseGateType)
948 newops.push_back(std::make_shared<PhaseGate<Time>>(
949 qubits[0], param, delay));
950 else if (gateType == QuantumGateType::kRxGateType)
951 newops.push_back(std::make_shared<RxGate<Time>>(
952 qubits[0], param, delay));
953 else if (gateType == QuantumGateType::kRyGateType)
954 newops.push_back(std::make_shared<RyGate<Time>>(
955 qubits[0], param, delay));
956 else
957 newops.push_back(std::make_shared<RzGate<Time>>(
958 qubits[0], param, delay));
959 }
960 nextOp = std::make_shared<NoOperation<Time>>();
961 changed = true;
962 found = true;
963 break;
964 } else if ((gateType == QuantumGateType::kSGateType &&
965 nextGate->GetGateType() ==
967 (gateType == QuantumGateType::kSdgGateType &&
968 nextGate->GetGateType() ==
970 // if expecting an S gate (or a Sdg gate) and found the
971 // original one instead, replace the pair with a Z gate (S * S
972 // = Z, Sdag * Sdag = Z)
973 const auto delay = gate->GetDelay() + nextGate->GetDelay();
974 newops.push_back(
975 std::make_shared<ZGate<Time>>(qubits[0], delay));
976 nextOp = std::make_shared<NoOperation<Time>>();
977 changed = true;
978 found = true;
979 break;
980 } else if ((gateType == QuantumGateType::kSxGateType &&
981 nextGate->GetGateType() ==
983 (gateType == QuantumGateType::kSxDagGateType &&
984 nextGate->GetGateType() ==
986 // if expecting an S gate (or a Sdg gate) and found the
987 // original one instead, replace the pair with a X gate (Sx *
988 // Sx = X, SXdag * SXdag = X)
989 const auto delay = gate->GetDelay() + nextGate->GetDelay();
990 newops.push_back(
991 std::make_shared<XGate<Time>>(qubits[0], delay));
992 nextOp = std::make_shared<NoOperation<Time>>();
993 changed = true;
994 found = true;
995 break;
996 } else if (gateType == QuantumGateType::kTGateType &&
997 nextGate->GetGateType() ==
999 // if expecting a T gate and found the Tdgate instead, replace
1000 // the pair with a Sdag gate (Tdg * Tdg = Sdag)
1001 const auto delay = gate->GetDelay() + nextGate->GetDelay();
1002 newops.push_back(
1003 std::make_shared<SdgGate<Time>>(qubits[0], delay));
1004 nextOp = std::make_shared<NoOperation<Time>>();
1005 changed = true;
1006 found = true;
1007 break;
1008 } else if (gateType == QuantumGateType::kTdgGateType &&
1009 nextGate->GetGateType() ==
1011 // if expecting a Tdg gate and found the T gate instead,
1012 // replace the pair with a S gate (T * T = S)
1013 const auto delay = gate->GetDelay() + nextGate->GetDelay();
1014 newops.push_back(
1015 std::make_shared<SGate<Time>>(qubits[0], delay));
1016 nextOp = std::make_shared<NoOperation<Time>>();
1017 changed = true;
1018 found = true;
1019 break;
1020 } else if (gateType == QuantumGateType::kPhaseGateType &&
1021 (nextGate->GetGateType() ==
1023 nextGate->GetGateType() ==
1025 nextGate->GetGateType() ==
1027 nextGate->GetGateType() ==
1029 const auto delay = gate->GetDelay() + nextGate->GetDelay();
1030 double param2;
1031 if (nextGate->GetGateType() == QuantumGateType::kSGateType)
1032 param2 = 0.5 * M_PI;
1033 else if (nextGate->GetGateType() ==
1035 param2 = -0.5 * M_PI;
1036 else if (nextGate->GetGateType() ==
1038 param2 = 0.25 * M_PI;
1039 else
1040 param2 = -0.25 * M_PI;
1041
1042 const auto param = gate->GetParams()[0] + param2;
1043 newops.push_back(std::make_shared<PhaseGate<Time>>(
1044 qubits[0], param, delay));
1045 nextOp = std::make_shared<NoOperation<Time>>();
1046 changed = true;
1047 found = true;
1048 break;
1049 } else if (nextGate->GetGateType() ==
1051 (gateType == QuantumGateType::kSGateType ||
1052 gateType == QuantumGateType::kSdgGateType ||
1053 gateType == QuantumGateType::kTGateType ||
1054 gateType == QuantumGateType::kTdgGateType)) {
1055 const auto delay = gate->GetDelay() + nextGate->GetDelay();
1056 double param1;
1057 if (gateType == QuantumGateType::kSGateType)
1058 param1 = -0.5 * M_PI;
1059 else if (gateType == QuantumGateType::kSdgGateType)
1060 param1 = 0.5 * M_PI;
1061 else if (gateType == QuantumGateType::kTGateType)
1062 param1 = -0.25 * M_PI;
1063 else
1064 param1 = 0.25 * M_PI;
1065
1066 const auto param = nextGate->GetParams()[0] + param1;
1067 newops.push_back(std::make_shared<PhaseGate<Time>>(
1068 qubits[0], param, delay));
1069 nextOp = std::make_shared<NoOperation<Time>>();
1070 changed = true;
1071 found = true;
1072 break;
1073 } else
1074 break; // not the expected gate, acting on same qubit, bail
1075 // out
1076 }
1077
1078 if (!found)
1079 newops.push_back(op);
1080 } break;
1081 default:
1082 // if no, just add it
1083 newops.push_back(op);
1084 break;
1085 }
1086 } else if (qubits.size() == 2) {
1087 auto gateType = gate->GetGateType();
1088 bool replace = false;
1089
1090 // if it's one of the interesting gates, look ahead to see if it's
1091 // followed by the same gate on the same qubit if yes, replace the
1092 // next one with a nop and skip the current one (or replace the pair
1093 // with a single gate, depending on the type) set changed to true if
1094 // something was changed
1095 switch (gateType) {
1097 [[fallthrough]];
1099 [[fallthrough]];
1101 if (!optimizeRotationGates) {
1102 newops.push_back(op);
1103 break;
1104 }
1105 [[fallthrough]];
1107 replace = true;
1108 [[fallthrough]];
1109 // those above will be replaced the pair with a single gate, all
1110 // the following are the ones that get removed
1112 [[fallthrough]];
1114 [[fallthrough]];
1116 [[fallthrough]];
1118 [[fallthrough]];
1120 [[fallthrough]];
1122 [[fallthrough]];
1124 bool found = false;
1125
1126 if (gateType == QuantumGateType::kCSxGateType)
1128 else if (gateType == QuantumGateType::kCSxDagGateType)
1130
1131 // looking forward for the next operation that acts on the same
1132 // qubits
1133 for (size_t j = i + 1; j < operations.size(); ++j) {
1134 auto &nextOp = operations[j];
1135 if (!nextOp->CanAffectQuantumState())
1136 continue;
1137
1138 const auto nextQubits = nextOp->AffectedQubits();
1139
1140 bool hasQubit = false;
1141
1142 for (auto q : nextQubits)
1143 if (q == qubits[0] || q == qubits[1]) {
1144 hasQubit = true;
1145 break;
1146 }
1147
1148 if (!hasQubit)
1149 continue; // an op that does not touch the current qubit can
1150 // be skipped
1151 else if (nextQubits.size() != 2)
1152 break; // if it touches a current qubit and it's something
1153 // else than a two qubits gate, stop
1154 // if it's not the same qubits, bail out
1155 else if (gateType == QuantumGateType::kSwapGateType &&
1156 !((qubits[0] == nextQubits[0] &&
1157 qubits[1] == nextQubits[1]) ||
1158 (qubits[0] == nextQubits[1] &&
1159 qubits[1] == nextQubits[0])))
1160 break;
1161 else if (!(qubits[0] == nextQubits[0] &&
1162 qubits[1] == nextQubits[1]))
1163 break;
1164
1165 const auto nextType = nextOp->GetType();
1166 if (nextType != OperationType::kGate)
1167 break; // could be a classically conditioned gate, stop
1168
1169 const auto &nextGate =
1170 std::static_pointer_cast<TwoQubitsGate<Time>>(nextOp);
1171 if (nextGate->GetGateType() == gateType) {
1172 if (replace) {
1173 const auto params1 = gate->GetParams();
1174 const auto params2 = nextGate->GetParams();
1175 const double param = params1[0] + params2[0];
1176 const auto delay = gate->GetDelay() + nextGate->GetDelay();
1177
1178 if (gateType == QuantumGateType::kCPGateType)
1179 newops.push_back(std::make_shared<CPGate<Time>>(
1180 qubits[0], qubits[1], param, delay));
1181 else if (gateType == QuantumGateType::kCRxGateType)
1182 newops.push_back(std::make_shared<CRxGate<Time>>(
1183 qubits[0], qubits[1], param, delay));
1184 else if (gateType == QuantumGateType::kCRyGateType)
1185 newops.push_back(std::make_shared<CRyGate<Time>>(
1186 qubits[0], qubits[1], param, delay));
1187 else
1188 newops.push_back(std::make_shared<CRzGate<Time>>(
1189 qubits[0], qubits[1], param, delay));
1190 }
1191 nextOp = std::make_shared<NoOperation<Time>>();
1192 changed = true; // continue merging gates, we found one that
1193 // was merged/removed
1194 found =
1195 true; // don't put op in the new operations, we handled it
1196 break;
1197 } else
1198 break; // not the expected gate, acting on same qubits, bail
1199 // out
1200 } // end for of looking forward
1201
1202 if (!found)
1203 newops.push_back(op);
1204 } break;
1205 default:
1206 // if no, just add it
1207 newops.push_back(op);
1208 break;
1209 }
1210 } else if (qubits.size() == 3) {
1211 auto gateType = gate->GetGateType();
1212
1213 // if it's one of the interesting gates, look ahead to see if it's
1214 // followed by the same gate on the same qubit if yes, replace the
1215 // next one with a nop and skip the current one (or replace the pair
1216 // with a single gate, depending on the type) set changed to true if
1217 // something was changed
1218 switch (gateType) {
1220 [[fallthrough]];
1222 bool found = false;
1223
1224 for (size_t j = i + 1; j < operations.size(); ++j) {
1225 auto &nextOp = operations[j];
1226 if (!nextOp->CanAffectQuantumState())
1227 continue;
1228
1229 const auto nextQubits = nextOp->AffectedQubits();
1230
1231 bool hasQubit = false;
1232
1233 for (auto q : nextQubits)
1234 if (q == qubits[0] || q == qubits[1] || q == qubits[2]) {
1235 hasQubit = true;
1236 break;
1237 }
1238
1239 if (!hasQubit)
1240 continue; // an op that does not touch the current qubit can
1241 // be skipped
1242 else if (nextQubits.size() != 3)
1243 break; // if it touches a current qubit and it's something
1244 // else than a three qubits gate, stop
1245 // if it's not the same qubits, bail out
1246 else if (gateType == QuantumGateType::kCSwapGateType &&
1247 (qubits[0] != nextQubits[0] ||
1248 !((qubits[1] == nextQubits[1] &&
1249 qubits[2] == nextQubits[2]) ||
1250 (qubits[1] == nextQubits[2] &&
1251 qubits[2] == nextQubits[1]))))
1252 break;
1253 else if (gateType == QuantumGateType::kCCXGateType &&
1254 (qubits[2] != nextQubits[2] ||
1255 !(qubits[1] == nextQubits[1] &&
1256 qubits[2] == nextQubits[2]) ||
1257 !(qubits[1] == nextQubits[2] &&
1258 qubits[2] == nextQubits[1])))
1259 break;
1260
1261 const auto nextType = nextOp->GetType();
1262 if (nextType != OperationType::kGate)
1263 break; // could be a classically conditioned gate, stop
1264
1265 const auto &nextGate =
1266 std::static_pointer_cast<ThreeQubitsGate<Time>>(nextOp);
1267 if (nextGate->GetGateType() == gateType) {
1268 nextOp = std::make_shared<NoOperation<Time>>();
1269 changed = true;
1270 found = true;
1271 break;
1272 } else
1273 break; // not the expected gate, acting on same qubits, bail
1274 // out
1275 }
1276
1277 if (!found)
1278 newops.push_back(op);
1279 } break;
1280 default:
1281 // if no, just add it
1282 newops.push_back(op);
1283 break;
1284 }
1285 } else
1286 newops.push_back(op);
1287 } else
1288 newops.push_back(op);
1289 } // end for on circuit operations
1290
1291 operations.swap(newops);
1292 } while (changed);
1293 }
1294
1302 OperationsVector newops;
1303 newops.reserve(operations.size());
1304
1305 size_t qubitsNo = std::max(GetMaxQubitIndex(), GetMaxCbitIndex()) + 1;
1306
1307 std::unordered_map<Types::qubit_t, std::vector<OperationPtr>> qubitOps;
1308
1309 std::vector<OperationPtr> lastOps(qubitsNo);
1310
1311 std::unordered_map<OperationPtr, std::unordered_set<OperationPtr>>
1312 dependenciesMap;
1313
1314 for (const auto &op : operations) {
1315 std::unordered_set<OperationPtr> dependencies;
1316
1317 const auto cbits = op->AffectedBits();
1318 for (auto c : cbits) {
1319 const auto lastOp = lastOps[c];
1320 if (lastOp)
1321 dependencies.insert(lastOp);
1322 }
1323
1324 const auto qubits = op->AffectedQubits();
1325 for (auto q : qubits) {
1326 qubitOps[q].push_back(op);
1327
1328 const auto lastOp = lastOps[q];
1329 if (lastOp)
1330 dependencies.insert(lastOp);
1331
1332 lastOps[q] = op;
1333 }
1334
1335 for (auto c : cbits)
1336 lastOps[c] = op;
1337
1338 dependenciesMap[op] = dependencies;
1339 }
1340 lastOps.clear();
1341
1342 std::vector<Types::qubit_t> indices(qubitsNo, 0);
1343
1344 while (!dependenciesMap.empty()) {
1345 OperationPtr nextOp;
1346
1347 // try to locate a 'next' gate for a qubit that is either a measurement or
1348 // a reset
1349 for (size_t q = 0; q < qubitsNo; ++q) {
1350 if (qubitOps.find(q) ==
1351 qubitOps.end()) // no operation left on this qubit
1352 continue;
1353
1354 // grab the current operation for this qubit
1355 const auto &ops = qubitOps[q];
1356 const auto &op = ops[indices[q]];
1357
1358 // consider only measurements and resets
1359 if (op->GetType() == OperationType::kMeasurement ||
1360 op->GetType() == OperationType::kReset) {
1361 bool hasDependencies = false;
1362
1363 for (const auto &opd : dependenciesMap[op])
1364 if (dependenciesMap.find(opd) != dependenciesMap.end()) {
1365 hasDependencies = true;
1366 break;
1367 }
1368
1369 if (!hasDependencies) {
1370 nextOp = op;
1371 break;
1372 }
1373 }
1374 }
1375
1376 if (nextOp) {
1377 dependenciesMap.erase(nextOp);
1378
1379 const auto qubits = nextOp->AffectedQubits();
1380 for (auto q : qubits) {
1381 ++indices[q];
1382 if (indices[q] >= qubitOps[q].size())
1383 qubitOps.erase(q);
1384 }
1385
1386 newops.emplace_back(std::move(nextOp));
1387 continue;
1388 }
1389
1390 // if there is no measurement or reset, add the next gate
1391 for (Types::qubit_t q = 0; q < qubitsNo; ++q) {
1392 if (qubitOps.find(q) ==
1393 qubitOps.end()) // no operation left on this qubit
1394 continue;
1395
1396 // grab the current operation for this qubit
1397 const auto &ops = qubitOps[q];
1398 const auto &op = ops[indices[q]];
1399
1400 bool hasDependencies = false;
1401
1402 for (const auto &opd : dependenciesMap[op])
1403 if (dependenciesMap.find(opd) != dependenciesMap.end()) {
1404 hasDependencies = true;
1405 break;
1406 }
1407
1408 if (!hasDependencies) {
1409 nextOp = op;
1410 break;
1411 }
1412 }
1413
1414 if (nextOp) {
1415 dependenciesMap.erase(nextOp);
1416
1417 const auto qubits = nextOp->AffectedQubits();
1418 for (auto q : qubits) {
1419 ++indices[q];
1420 if (indices[q] >= qubitOps[q].size())
1421 qubitOps.erase(q);
1422 }
1423
1424 newops.emplace_back(std::move(nextOp));
1425 }
1426 }
1427
1428 assert(newops.size() == operations.size());
1429
1430 operations.swap(newops);
1431 }
1432
1442 std::pair<std::vector<size_t>, std::vector<Time>> GetDepth() const {
1443 size_t maxDepth;
1444 Time maxTime;
1445
1446 size_t qubitsNo = GetMaxQubitIndex() + 1;
1447 std::vector<Time> qubitTimes(qubitsNo, 0);
1448 std::vector<size_t> qubitDepths(qubitsNo, 0);
1449
1450 std::unordered_map<size_t, size_t> fromQubits;
1451
1452 for (const auto &op : operations) {
1453 const auto qbits = op->AffectedQubits();
1454 const auto delay = op->GetDelay();
1455
1456 maxTime = 0;
1457 maxDepth = 0;
1458 for (auto q : qbits) {
1459 qubitTimes[q] += delay;
1460 ++qubitDepths[q];
1461 if (qubitTimes[q] > maxTime)
1462 maxTime = qubitTimes[q];
1463 if (qubitDepths[q] > maxDepth)
1464 maxDepth = qubitDepths[q];
1465 }
1466
1467 const auto t = op->GetType();
1468 std::vector<size_t> condbits;
1469
1470 // TODO: deal with 'random gen' operations, those do not affect qubits
1471 // directly, but they do affect the classical bits and can be used in
1472 // conditional operations
1473
1476 condbits = op->AffectedBits();
1477
1478 for (auto bit : condbits) {
1479 if (fromQubits.find(bit) != fromQubits.end())
1480 bit = fromQubits[bit];
1481
1482 bool found = false;
1483 for (auto q : qbits) {
1484 if (q == bit) {
1485 found = true;
1486 break;
1487 }
1488 }
1489 if (found || bit >= qubitsNo)
1490 continue;
1491
1492 qubitTimes[bit] += delay;
1493 ++qubitDepths[bit];
1494 if (qubitTimes[bit] > maxTime)
1495 maxTime = qubitTimes[bit];
1496 if (qubitDepths[bit] > maxDepth)
1497 maxDepth = qubitDepths[bit];
1498 }
1499
1501 const auto condMeas =
1502 std::static_pointer_cast<ConditionalMeasurement<Time>>(op);
1503 const auto meas = condMeas->GetOperation();
1504 const auto measQubits = meas->AffectedQubits();
1505 const auto measBits = meas->AffectedBits();
1506
1507 for (size_t i = 0; i < measQubits.size(); ++i) {
1508 if (i < measBits.size())
1509 fromQubits[measBits[i]] = measQubits[i];
1510 else
1511 fromQubits[measQubits[i]] = measQubits[i];
1512 }
1513 }
1514 } else if (t == OperationType::kMeasurement) {
1515 condbits = op->AffectedBits();
1516
1517 for (size_t i = 0; i < qbits.size(); ++i) {
1518 if (i < condbits.size())
1519 fromQubits[condbits[i]] = qbits[i];
1520 else
1521 fromQubits[qbits[i]] = qbits[i];
1522 }
1523 }
1524
1525 for (auto q : qbits) {
1526 qubitTimes[q] = maxTime;
1527 qubitDepths[q] = maxDepth;
1528 }
1529
1530 for (auto bit : condbits) {
1531 qubitTimes[bit] = maxTime;
1532 qubitDepths[bit] = maxDepth;
1533 }
1534 }
1535
1536 return std::make_pair(qubitDepths, qubitTimes);
1537 }
1538
1548 std::pair<size_t, Time> GetMaxDepth() const {
1549 auto [qubitDepths, qubitTimes] = GetDepth();
1550
1551 Time maxTime = 0;
1552 size_t maxDepth = 0;
1553 for (size_t qubit = 0; qubit < qubitDepths.size(); ++qubit) {
1554 if (qubitTimes[qubit] > maxTime)
1555 maxTime = qubitTimes[qubit];
1556 if (qubitDepths[qubit] > maxDepth)
1557 maxDepth = qubitDepths[qubit];
1558 }
1559
1560 return std::make_pair(maxDepth, maxTime);
1561 }
1562
1569 size_t GetNumberOfOperations() const { return operations.size(); }
1570
1579 OperationPtr GetOperation(size_t pos) const {
1580 if (pos >= operations.size())
1581 return nullptr;
1582
1583 return operations[pos];
1584 }
1585
1599 std::shared_ptr<Circuit<Time>> GetCircuitCut(Types::qubit_t startQubit,
1600 Types::qubit_t endQubit) const {
1601 OperationsVector newops;
1602 newops.reserve(operations.size());
1603
1604 for (const auto &op : operations) {
1605 const auto qubits = op->AffectedQubits();
1606 bool containsOutsideQubits = false;
1607 bool containsInsideQubits = false;
1608 for (const auto q : qubits) {
1609 if (q < startQubit || q > endQubit) {
1610 containsOutsideQubits = true;
1611 if (containsInsideQubits)
1612 break;
1613 } else {
1614 containsInsideQubits = true;
1615 if (containsOutsideQubits)
1616 break;
1617 }
1618 }
1619
1620 if (containsInsideQubits) {
1621 if (containsOutsideQubits)
1622 throw std::runtime_error(
1623 "Cannot cut the circuit with the specified interval");
1624 newops.emplace_back(op->Clone());
1625 }
1626 }
1627
1628 return std::make_shared<Circuit<Time>>(newops);
1629 }
1630
1642 std::unordered_set<Types::qubit_t> measuredQubits;
1643 std::unordered_set<Types::qubit_t> affectedQubits;
1644 std::unordered_set<Types::qubit_t> resetQubits;
1645
1646 for (const auto &op : operations) {
1647 const auto qubits = op->AffectedQubits();
1648
1649 if (op->GetType() == OperationType::kMeasurement) {
1650 for (const auto qbit : qubits)
1651 if (resetQubits.find(qbit) !=
1652 resetQubits.end()) // there is a reset on this qubit already and
1653 // it's not at the beginning of the circuit
1654 return true;
1655
1656 /*
1657 const auto bits = op->AffectedBits();
1658 if (bits.size() != qubits.size())
1659 return true;
1660
1661 for (size_t b = 0; b < bits.size(); ++b)
1662 if (bits[b] != qubits[b])
1663 return true;
1664 */
1665 measuredQubits.insert(qubits.begin(), qubits.end());
1666 } else if (op->GetType() == OperationType::kConditionalGate ||
1667 op->GetType() == OperationType::kConditionalMeasurement ||
1668 op->GetType() == OperationType::kRandomGen ||
1669 op->GetType() == OperationType::kConditionalRandomGen)
1670 return true;
1671 else if (op->GetType() == OperationType::kReset) {
1672 // resets in the middle of the circuit are treated as measurements
1673 for (const auto qbit : qubits) {
1674 // if there is already a gate applied on the qubit but no measurement
1675 // yet, it's considered in the middle if there is no gate applied,
1676 // then it's the first operation on the qubit
1677 if (affectedQubits.find(qbit) != affectedQubits.end() ||
1678 measuredQubits.find(qbit) != measuredQubits.end())
1679 resetQubits.insert(qbit);
1680
1681 affectedQubits.insert(qbit);
1682 }
1683 } else {
1684 for (const auto qbit : qubits) {
1685 if (measuredQubits.find(qbit) !=
1686 measuredQubits
1687 .end()) // there is a measurement on this qubit already
1688 return true;
1689
1690 if (resetQubits.find(qbit) !=
1691 resetQubits.end()) // there is a reset on this qubit already and
1692 // it's not at the beginning of the circuit
1693 return true;
1694
1695 affectedQubits.insert(qbit);
1696 }
1697 }
1698 }
1699
1700 return false;
1701 }
1702
1715 std::vector<bool>
1716 ExecuteNonMeasurements(const std::shared_ptr<Simulators::ISimulator> &sim,
1717 OperationState &state) const {
1718 std::vector<bool> executedOps;
1719 executedOps.reserve(operations.size());
1720
1721 std::unordered_set<Types::qubit_t> measuredQubits;
1722 std::unordered_set<Types::qubit_t> affectedQubits;
1723
1724 bool executionStopped = false;
1725
1726 for (size_t i = 0; i < operations.size(); ++i) {
1727 auto &op = operations[i];
1728 const auto qubits = op->AffectedQubits();
1729
1730 bool executed = false;
1731
1732 if (op->GetType() == OperationType::kMeasurement ||
1733 op->GetType() == OperationType::kConditionalMeasurement ||
1734 op->GetType() == OperationType::kRandomGen ||
1735 op->GetType() == OperationType::kConditionalRandomGen)
1736 measuredQubits.insert(qubits.begin(), qubits.end());
1737 else if (op->GetType() == OperationType::kReset) {
1738 // if it's the first op on qubit(s), execute it, otherwise treat it as a
1739 // measurement
1740 executed = true;
1741 for (auto qubit : qubits)
1742 if (affectedQubits.find(qubit) != affectedQubits.end()) {
1743 executed = false;
1744 break;
1745 }
1746
1747 if (executed) {
1748 if (sim)
1749 op->Execute(sim, state);
1750 } else
1751 measuredQubits.insert(qubits.begin(), qubits.end());
1752 } else // regular gate or conditional gate
1753 {
1754 const auto bits = op->AffectedBits();
1755
1756 // a measurement on a qubit prevents execution of any following gate
1757 // than affects the same qubit also a gate that's not executed and it
1758 // would affect certain qubits will prevent the execution of any
1759 // following gate that affects those qubits
1760
1761 bool canExecute = op->GetType() == OperationType::kGate;
1762
1763 if (canExecute) // a conditional gate cannot be executed, it needs
1764 // something executed at each shot, either a measurement
1765 // or a random number generated
1766 {
1767 for (auto bit : bits)
1768 if (measuredQubits.find(bit) != measuredQubits.end()) {
1769 canExecute = false;
1770 break;
1771 }
1772
1773 for (auto qubit : qubits)
1774 if (measuredQubits.find(qubit) != measuredQubits.end()) {
1775 canExecute = false;
1776 break;
1777 }
1778 }
1779
1780 if (canExecute) {
1781 if (sim)
1782 op->Execute(sim, state);
1783 executed = true;
1784 } else {
1785 // this is a 'trick', if it cannot execute, then neither can any
1786 // following gate that affects any of the already involved qubits
1787 measuredQubits.insert(bits.begin(), bits.end());
1788 measuredQubits.insert(qubits.begin(), qubits.end());
1789 }
1790 }
1791
1792 affectedQubits.insert(qubits.begin(), qubits.end());
1793
1794 if (!executed)
1795 executionStopped = true;
1796 if (executionStopped)
1797 executedOps.emplace_back(executed);
1798 }
1799
1800 // if (sim) sim->Flush();
1801
1802 return executedOps;
1803 }
1804
1816 void ExecuteMeasurements(const std::shared_ptr<Simulators::ISimulator> &sim,
1817 OperationState &state,
1818 const std::vector<bool> &executedOps) const {
1819 state.Reset();
1820 if (!sim)
1821 return;
1822
1823 // if (executedOps.empty() && !operations.empty()) throw
1824 // std::runtime_error("The executed operations vector is empty");
1825
1826 const size_t dif = operations.size() - executedOps.size();
1827
1828 for (size_t i = dif; i < operations.size(); ++i)
1829 if (!executedOps[i - dif])
1830 operations[i]->Execute(sim, state);
1831
1832 // sim->Flush();
1833 }
1834
1835 // used internally to optimize measurements in the case of having measurements
1836 // only at the end of the circuit
1837 std::shared_ptr<MeasurementOperation<Time>>
1838 GetLastMeasurements(const std::vector<bool> &executedOps,
1839 bool sort = true) const {
1840 const size_t dif = operations.size() - executedOps.size();
1841 std::vector<std::pair<Types::qubit_t, size_t>> measurements;
1842 measurements.reserve(dif);
1843
1844 for (size_t i = dif; i < operations.size(); ++i)
1845 if (!executedOps[i - dif] &&
1846 operations[i]->GetType() == OperationType::kMeasurement) {
1847 auto measOp =
1848 std::static_pointer_cast<MeasurementOperation<Time>>(operations[i]);
1849 const auto &qubits = measOp->GetQubits();
1850 const auto &bits = measOp->GetBitsIndices();
1851
1852 for (size_t j = 0; j < qubits.size(); ++j)
1853 measurements.emplace_back(qubits[j], bits[j]);
1854 }
1855
1856 // qiskit aer expects sometimes to have them in sorted order, so...
1857 if (sort)
1858 std::sort(
1859 measurements.begin(), measurements.end(),
1860 [](const auto &p1, const auto &p2) { return p1.first < p2.first; });
1861
1862 return std::make_shared<MeasurementOperation<Time>>(measurements);
1863 }
1864
1874 for (const auto &op : operations)
1875 if (op->GetType() == OperationType::kConditionalGate ||
1876 op->GetType() == OperationType::kConditionalMeasurement ||
1877 op->GetType() == OperationType::kConditionalRandomGen)
1878 return true;
1879
1880 return false;
1881 }
1882
1897 for (const auto &op : operations) {
1898 const auto qubits = op->AffectedQubits();
1899 if (qubits.size() <= 1)
1900 continue;
1901
1902 if (qubits.size() == 2) {
1903 if (std::abs(qubits[0] - qubits[1]) != 1)
1904 return false;
1905 } else {
1906 Types::qubit_t minQubit = qubits[0];
1907 Types::qubit_t maxQubit = qubits[0];
1908
1909 for (size_t i = 1; i < qubits.size(); ++i) {
1910 if (qubits[i] < minQubit)
1911 minQubit = qubits[i];
1912 else if (qubits[i] > maxQubit)
1913 maxQubit = qubits[i];
1914 }
1915
1916 if (maxQubit - minQubit >= qubits.size())
1917 return false;
1918 }
1919 }
1920
1921 return true;
1922 }
1923
1931 bool IsForest() const {
1932 std::unordered_map<Types::qubit_t, size_t> qubits;
1933 std::unordered_map<Types::qubit_t, Types::qubits_vector> lastQubits;
1934
1935 for (const auto &op : operations) {
1936 const auto q = op->AffectedQubits();
1937 // one qubit gates or other operations that do not affect qubits do not
1938 // change anything
1939 if (q.size() <= 1)
1940 continue;
1941
1942 bool allInTheLastQubits = true;
1943
1944 for (const auto qubit : q) {
1945 if (lastQubits.find(qubit) == lastQubits.end()) {
1946 allInTheLastQubits = false;
1947 break;
1948 } else {
1949 const auto &lastQ = lastQubits[qubit];
1950
1951 for (const auto q1 : q)
1952 if (std::find(lastQ.cbegin(), lastQ.cend(), q1) == lastQ.cend()) {
1953 allInTheLastQubits = false;
1954 break;
1955 }
1956
1957 if (!allInTheLastQubits)
1958 break;
1959 }
1960 }
1961
1962 if (allInTheLastQubits)
1963 continue;
1964
1965 for (const auto qubit : q) {
1966 if (qubits[qubit] > 1) // if the qubit is affected again...
1967 return false;
1968
1969 ++qubits[qubit];
1970
1971 lastQubits[qubit] = q;
1972 }
1973 }
1974
1975 return true;
1976 }
1977
1987 bool IsClifford() const override {
1988 for (const auto &op : operations)
1989 if (!op->IsClifford())
1990 return false;
1991
1992 return true;
1993 }
1994
2004 double CliffordPercentage() const {
2005 size_t cliffordOps = 0;
2006 for (const auto &op : operations)
2007 if (op->IsClifford())
2008 ++cliffordOps;
2009
2010 return static_cast<double>(cliffordOps) / operations.size();
2011 }
2012
2022 std::unordered_set<Types::qubit_t> GetCliffordQubits() const {
2023 std::unordered_set<Types::qubit_t> cliffordQubits;
2024 std::unordered_set<Types::qubit_t> nonCliffordQubits;
2025
2026 for (const auto &op : operations) {
2027 const auto qubits = op->AffectedQubits();
2028 if (op->IsClifford()) {
2029 for (const auto q : qubits)
2030 cliffordQubits.insert(q);
2031 } else {
2032 for (const auto q : qubits)
2033 nonCliffordQubits.insert(q);
2034 }
2035 }
2036
2037 for (const auto q : nonCliffordQubits)
2038 cliffordQubits.erase(q);
2039
2040 return cliffordQubits;
2041 }
2042
2052 std::vector<std::shared_ptr<Circuit<Time>>> SplitCircuit() const {
2053 std::vector<std::shared_ptr<Circuit<Time>>> circuits;
2054
2055 // find how many disjoint circuits we have in this circuit
2056
2057 std::unordered_map<Types::qubit_t, std::unordered_set<Types::qubit_t>>
2058 circuitsMap;
2059 auto allQubits = GetQubits();
2060 std::unordered_map<Types::qubit_t, Types::qubit_t> qubitCircuitMap;
2061
2062 // start with a bunch of disjoint sets of qubits, containing each a single
2063 // qubit
2064
2065 for (auto qubit : allQubits) {
2066 circuitsMap[qubit] = std::unordered_set<Types::qubit_t>{qubit};
2067 qubitCircuitMap[qubit] = qubit;
2068 }
2069
2070 // then the gates will join them together into circuits
2071
2072 for (const auto &op : operations) {
2073 const auto qubits = op->AffectedQubits();
2074
2075 if (qubits.empty())
2076 continue;
2077
2078 auto qubitIt = qubits.cbegin();
2079 auto firstQubit = *qubitIt;
2080 // where is the first qubit in the disjoint sets?
2081 auto firstQubitCircuit = qubitCircuitMap[firstQubit];
2082
2083 ++qubitIt;
2084
2085 for (; qubitIt != qubits.cend(); ++qubitIt) {
2086 auto qubit = *qubitIt;
2087
2088 // where is the qubit in the disjoint sets?
2089 auto qubitCircuit = qubitCircuitMap[qubit];
2090
2091 // join the circuits / qubits sets
2092
2093 if (firstQubitCircuit != qubitCircuit) {
2094 // join the circuits
2095 circuitsMap[firstQubitCircuit].insert(
2096 circuitsMap[qubitCircuit].begin(),
2097 circuitsMap[qubitCircuit].end());
2098
2099 // update the qubit to circuit map
2100 for (auto q : circuitsMap[qubitCircuit])
2101 qubitCircuitMap[q] = firstQubitCircuit;
2102
2103 // remove the joined circuit
2104 circuitsMap.erase(qubitCircuit);
2105 }
2106 }
2107 }
2108
2109 size_t circSize = 1ULL;
2110 circSize = std::max(circSize, circuitsMap.size());
2111 circuits.resize(circSize);
2112
2113 for (size_t i = 0; i < circuits.size(); ++i)
2114 circuits[i] = std::make_shared<Circuit<Time>>();
2115
2116 std::unordered_map<Types::qubit_t, size_t> qubitsSetsToCircuit;
2117
2118 size_t circuitNo = 0;
2119 for (const auto &[id, qubitSet] : circuitsMap) {
2120 qubitsSetsToCircuit[id] = circuitNo;
2121
2122 ++circuitNo;
2123 }
2124
2125 // now fill them up with the operations
2126
2127 for (const auto &op : operations) {
2128 const auto qubits = op->AffectedQubits();
2129
2130 if (qubits.empty()) {
2131 circuits[0]->AddOperation(op->Clone());
2132 continue;
2133 }
2134
2135 const auto circ = qubitsSetsToCircuit[qubitCircuitMap[*qubits.cbegin()]];
2136
2137 circuits[circ]->AddOperation(op->Clone());
2138 }
2139
2140 return circuits;
2141 }
2142
2149 iterator begin() noexcept { return operations.begin(); }
2150
2157 iterator end() noexcept { return operations.end(); }
2158
2165 const_iterator cbegin() const noexcept { return operations.cbegin(); }
2166
2173 const_iterator cend() const noexcept { return operations.cend(); }
2174
2181 reverse_iterator rbegin() noexcept { return operations.rbegin(); }
2182
2189 reverse_iterator rend() noexcept { return operations.rend(); }
2190
2198 return operations.crbegin();
2199 }
2200
2207 const_reverse_iterator crend() const noexcept { return operations.crend(); }
2208
2215 auto size() const { return operations.size(); }
2216
2223 auto empty() const { return operations.empty(); }
2224
2232 auto &operator[](size_t pos) { return operations[pos]; }
2233
2241 const auto &operator[](size_t pos) const { return operations[pos]; }
2242
2250 void resize(size_t size) {
2251 if (size < operations.size())
2252 operations.resize(size);
2253 }
2254
2255private:
2266 void ReplaceThreeQubitAndSwapGates(bool onlyThreeQubits = false) {
2267 // just replace all three qubit gates(just ccnot and cswap will exist in the
2268 // first phase) with several gates on less qubits also replace swap gates
2269 // with three cnots (in the first phase) the controlled ones must be
2270 // replaced as well
2271
2272 // TODO: if composite operations will be implemented, those need to be
2273 // optimized as well
2274
2275 std::vector<std::shared_ptr<IOperation<Time>>> newops;
2276 newops.reserve(operations.size());
2277
2278 for (std::shared_ptr<IOperation<Time>> op : operations) {
2279 if (op->GetType() == OperationType::kGate) {
2280 std::shared_ptr<IQuantumGate<Time>> gate =
2281 std::static_pointer_cast<IQuantumGate<Time>>(op);
2282
2283 if (NeedsConversion(gate, onlyThreeQubits)) {
2284 std::vector<std::shared_ptr<IGateOperation<Time>>> newgates =
2285 ConvertGate(gate, onlyThreeQubits);
2286 newops.insert(newops.end(), newgates.begin(), newgates.end());
2287 } else
2288 newops.push_back(op);
2289 } else if (op->GetType() == OperationType::kConditionalGate) {
2290 std::shared_ptr<ConditionalGate<Time>> condgate =
2291 std::static_pointer_cast<ConditionalGate<Time>>(op);
2292 std::shared_ptr<IQuantumGate<Time>> gate =
2293 std::static_pointer_cast<IQuantumGate<Time>>(
2294 condgate->GetOperation());
2295
2296 if (NeedsConversion(gate, onlyThreeQubits)) {
2297 std::vector<std::shared_ptr<IGateOperation<Time>>> newgates =
2298 ConvertGate(gate, onlyThreeQubits);
2299 std::shared_ptr<ICondition> cond = condgate->GetCondition();
2300
2301 for (auto gate : newgates)
2302 newops.push_back(
2303 std::make_shared<ConditionalGate<Time>>(gate, cond));
2304 } else
2305 newops.push_back(op);
2306 } else
2307 newops.push_back(op);
2308 }
2309
2310 operations.swap(newops);
2311 }
2312
2322 static bool NeedsConversion(const std::shared_ptr<IQuantumGate<Time>> &gate,
2323 bool onlyThreeQubits = false) {
2324 const bool hasThreeQubits = gate->GetNumQubits() == 3;
2325 if (onlyThreeQubits)
2326 return hasThreeQubits;
2327
2328 return hasThreeQubits ||
2329 gate->GetGateType() == QuantumGateType::kSwapGateType;
2330 }
2331
2344 static std::vector<std::shared_ptr<IGateOperation<Time>>>
2345 ConvertGate(std::shared_ptr<IQuantumGate<Time>> &gate,
2346 bool onlyThreeQubits = false) {
2347 // TODO: if delays are used, how to transfer delays from the converted gate
2348 // to the resulting gates?
2349 std::vector<std::shared_ptr<IGateOperation<Time>>> newops;
2350
2351 if (gate->GetNumQubits() == 3) {
2352 // must be converted no matter what
2353 if (gate->GetGateType() == QuantumGateType::kCCXGateType) {
2354 const size_t q1 = gate->GetQubit(0); // control 1
2355 const size_t q2 = gate->GetQubit(1); // control 2
2356 const size_t q3 = gate->GetQubit(2); // target
2357
2358 // Sleator-Weinfurter decomposition
2359 newops.push_back(std::make_shared<CSxGate<Time>>(q2, q3));
2360 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
2361 newops.push_back(std::make_shared<CSxDagGate<Time>>(q2, q3));
2362 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
2363 newops.push_back(std::make_shared<CSxGate<Time>>(q1, q3));
2364 } else if (gate->GetGateType() == QuantumGateType::kCSwapGateType) {
2365 const size_t q1 = gate->GetQubit(0); // control 1
2366 const size_t q2 = gate->GetQubit(1); // control 2
2367 const size_t q3 = gate->GetQubit(2); // target
2368
2369 // TODO: find a better decomposition
2370 // this one I've got with the qiskit transpiler
2371 newops.push_back(std::make_shared<CXGate<Time>>(q3, q2));
2372
2373 newops.push_back(std::make_shared<CSxGate<Time>>(q2, q3));
2374 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
2375 newops.push_back(std::make_shared<PhaseGate<Time>>(q3, M_PI));
2376
2377 newops.push_back(std::make_shared<PhaseGate<Time>>(q2, -M_PI_2));
2378
2379 newops.push_back(std::make_shared<CSxGate<Time>>(q2, q3));
2380 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
2381 newops.push_back(std::make_shared<PhaseGate<Time>>(q3, M_PI));
2382
2383 newops.push_back(std::make_shared<CSxGate<Time>>(q1, q3));
2384
2385 newops.push_back(std::make_shared<CXGate<Time>>(q3, q2));
2386 } else
2387 newops.push_back(gate);
2388 } else if (!onlyThreeQubits &&
2389 gate->GetGateType() == QuantumGateType::kSwapGateType) {
2390 // must be converted no matter what
2391 const size_t q1 = gate->GetQubit(0);
2392 const size_t q2 = gate->GetQubit(1);
2393
2394 // for now replace it with three cnots, but maybe later make it
2395 // configurable there are other possibilities, for example three cy gates
2396 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
2397 newops.push_back(std::make_shared<CXGate<Time>>(q2, q1));
2398 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
2399 } else
2400 newops.push_back(gate);
2401
2402 return newops;
2403 }
2404
2405 OperationsVector operations;
2406};
2407
2421template <typename Time = Types::time_type>
2422class ComparableCircuit : public Circuit<Time> {
2423public:
2426 using OperationPtr = std::shared_ptr<Operation>;
2429 std::vector<OperationPtr>;
2430
2439
2449
2459
2460 return *this;
2461 }
2462
2469 bool operator==(const BaseClass &rhs) const {
2470 if (BaseClass::GetOperations().size() != rhs.GetOperations().size())
2471 return false;
2472
2473 for (size_t i = 0; i < BaseClass::GetOperations().size(); ++i) {
2474 if (BaseClass::GetOperations()[i]->GetType() !=
2475 rhs.GetOperations()[i]->GetType())
2476 return false;
2477
2478 switch (BaseClass::GetOperations()[i]->GetType()) {
2480 if (std::static_pointer_cast<IQuantumGate<Time>>(
2482 ->GetGateType() !=
2483 std::static_pointer_cast<IQuantumGate<Time>>(
2484 rhs.GetOperations()[i])
2485 ->GetGateType() ||
2486 BaseClass::GetOperations()[i]->AffectedBits() !=
2487 rhs.GetOperations()[i]->AffectedBits())
2488 return false;
2489 if (approximateParamsCheck) {
2490 const auto params1 = std::static_pointer_cast<IQuantumGate<Time>>(
2492 ->GetParams();
2493 const auto params2 = std::static_pointer_cast<IQuantumGate<Time>>(
2494 rhs.GetOperations()[i])
2495 ->GetParams();
2496 if (params1.size() != params2.size())
2497 return false;
2498
2499 for (size_t j = 0; j < params1.size(); ++j)
2500 if (std::abs(params1[j] - params2[j]) > paramsEpsilon)
2501 return false;
2502 } else if (std::static_pointer_cast<IQuantumGate<Time>>(
2504 ->GetParams() !=
2505 std::static_pointer_cast<IQuantumGate<Time>>(
2506 rhs.GetOperations()[i])
2507 ->GetParams())
2508 return false;
2509 break;
2512 rhs.GetOperations()[i]->AffectedQubits() ||
2513 BaseClass::GetOperations()[i]->AffectedBits() !=
2514 rhs.GetOperations()[i]->AffectedBits())
2515 return false;
2516 break;
2519 rhs.GetOperations()[i]->AffectedBits())
2520 return false;
2521 break;
2525 // first, check the conditions
2526 const auto leftCondition =
2527 std::static_pointer_cast<IConditionalOperation<Time>>(
2529 ->GetCondition();
2530 const auto rightCondition =
2531 std::static_pointer_cast<IConditionalOperation<Time>>(
2532 rhs.GetOperations()[i])
2533 ->GetCondition();
2534 if (leftCondition->GetBitsIndices() != rightCondition->GetBitsIndices())
2535 return false;
2536
2537 const auto leftEqCondition =
2538 std::static_pointer_cast<EqualCondition>(leftCondition);
2539 const auto rightEqCondition =
2540 std::static_pointer_cast<EqualCondition>(rightCondition);
2541 if (!leftEqCondition || !rightEqCondition)
2542 return false;
2543
2544 if (leftEqCondition->GetAllBits() != rightEqCondition->GetAllBits())
2545 return false;
2546
2547 // now check the operations
2548 const auto leftOp =
2549 std::static_pointer_cast<IConditionalOperation<Time>>(
2551 ->GetOperation();
2552 const auto rightOp =
2553 std::static_pointer_cast<IConditionalOperation<Time>>(
2554 rhs.GetOperations()[i])
2555 ->GetOperation();
2556
2557 ComparableCircuit<Time> leftCircuit;
2558 BaseClass rightCircuit;
2559 leftCircuit.SetApproximateParamsCheck(approximateParamsCheck);
2560 leftCircuit.AddOperation(leftOp);
2561 rightCircuit.AddOperation(rightOp);
2562
2563 if (leftCircuit != rightCircuit)
2564 return false;
2565 } break;
2568 rhs.GetOperations()[i]->AffectedQubits() ||
2569 std::static_pointer_cast<Reset<Time>>(BaseClass::GetOperations()[i])
2570 ->GetResetTargets() !=
2571 std::static_pointer_cast<Reset<Time>>(rhs.GetOperations()[i])
2572 ->GetResetTargets())
2573 return false;
2574 break;
2576 break;
2577 default:
2578 return false;
2579 }
2580
2581 if (BaseClass::GetOperations()[i]->GetDelay() !=
2582 rhs.GetOperations()[i]->GetDelay())
2583 return false;
2584 }
2585
2586 return true;
2587 }
2588
2595 bool operator!=(const BaseClass &rhs) const { return !(*this == rhs); }
2596
2603 void SetApproximateParamsCheck(bool check) { approximateParamsCheck = check; }
2604
2611 bool GetApproximateParamsCheck() const { return approximateParamsCheck; }
2612
2621 void SetParamsEpsilon(double eps) { paramsEpsilon = eps; }
2622
2631 double GetParamsEpsilon() const { return paramsEpsilon; }
2632
2633private:
2634 bool approximateParamsCheck =
2635 false;
2636 double paramsEpsilon = 1e-8;
2638};
2639
2640} // namespace Circuits
2641
2642#endif // !_CIRCUIT_H_
The controlled P gate.
The controlled x rotation gate.
The controlled y rotation gate.
The controlled z rotation gate.
iterator begin() noexcept
Get the begin iterator for the operations.
Definition Circuit.h:2149
void ConvertForCutting()
Converts the circuit for distributed computing.
Definition Circuit.h:408
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
const_iterator cbegin() const noexcept
Get the const begin iterator for the operations.
Definition Circuit.h:2165
double CliffordPercentage() const
Get the percentage of Clifford operations in the circuit.
Definition Circuit.h:2004
auto size() const
Get the number of operations in the circuit.
Definition Circuit.h:2215
bool IsClifford() const override
Checks if the circuit is a Clifford circuit.
Definition Circuit.h:1987
iterator end() noexcept
Get the end iterator for the operations.
Definition Circuit.h:2157
std::unordered_set< Types::qubit_t > GetCliffordQubits() const
Get the qubits that are acted on by Clifford operations.
Definition Circuit.h:2022
typename OperationsVector::iterator iterator
Definition Circuit.h:71
void AddOperation(const OperationPtr &op)
Adds an operation to the circuit.
Definition Circuit.h:122
std::set< size_t > GetBits() const
Returns the classical bits affected by the operations.
Definition Circuit.h:611
void Optimize(bool optimizeRotationGates=true)
Circuit optimization.
Definition Circuit.h:775
void EnsureProperOrderForMeasurements()
Definition Circuit.h:427
std::pair< size_t, Time > GetMaxDepth() const
Get max circuit depth.
Definition Circuit.h:1548
std::set< size_t > GetQubits() const
Returns the qubits affected by the operations.
Definition Circuit.h:594
void AddResetsAtBeginningIfNeeded(Time delay=0)
Add resets at the beginning of the circuit.
Definition Circuit.h:756
void Clear()
Clears the operations from the circuit.
Definition Circuit.h:182
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:57
typename OperationsVector::reference reference
Definition Circuit.h:66
bool CanAffectQuantumState() const override
Find if the circuit can affect the quantum state.
Definition Circuit.h:681
auto & operator[](size_t pos)
Get the operation at a given position.
Definition Circuit.h:2232
std::unordered_map< Types::qubit_t, Types::qubit_t > BitMapping
The (qu)bit mapping for remapping.
Definition Circuit.h:51
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:1442
std::unordered_map< size_t, OperationPtr > GetLastOperationsOnQubits() const
Returns the last operations on circuit's qubits.
Definition Circuit.h:696
const_reverse_iterator crend() const noexcept
Get the const reverse end iterator for the operations.
Definition Circuit.h:2207
std::shared_ptr< MeasurementOperation< Time > > GetLastMeasurements(const std::vector< bool > &executedOps, bool sort=true) const
Definition Circuit.h:1838
bool ActsOnlyOnAdjacentQubits() const
Checks if the circuit has only operations that act on adjacent qubits.
Definition Circuit.h:1896
Circuit(const OperationsVector &ops={})
Construct a new Circuit object.
Definition Circuit.h:84
typename OperationsVector::pointer pointer
Definition Circuit.h:64
typename OperationsVector::size_type size_type
Definition Circuit.h:68
auto empty() const
Check if the circuit is empty.
Definition Circuit.h:2223
OperationPtr Remap(const BitMapping &qubitsMap, const BitMapping &bitsMap={}) const override
Get a shared pointer to a circuit remapped.
Definition Circuit.h:225
static void AccumulateResults(ExecuteResults &results, const ExecuteResults &newResults)
Accumulate the results of a circuit execution to already existing results.
Definition Circuit.h:331
std::shared_ptr< Circuit< Time > > GetCircuitCut(Types::qubit_t startQubit, Types::qubit_t endQubit) const
Get the circuit cut.
Definition Circuit.h:1599
void ConvertForDistribution()
Converts the circuit for distributed computing.
Definition Circuit.h:393
Types::qubits_vector AffectedQubits() const override
Returns the affected qubits.
Definition Circuit.h:627
bool HasOpsAfterMeasurements() const
Checks if the circuit has measurements that are followed by operations that affect the measured qubit...
Definition Circuit.h:1641
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:206
bool HasConditionalOperations() const
Checks if the circuit has clasically conditional operations.
Definition Circuit.h:1873
void AddResetsIfNeeded(Time delay=0)
Add resets at the end of the circuit.
Definition Circuit.h:737
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:351
const_reverse_iterator crbegin() const noexcept
Get the const reverse begin iterator for the operations.
Definition Circuit.h:2197
OperationPtr GetOperation(size_t pos) const
Get an operation at a given position.
Definition Circuit.h:1579
const_iterator cend() const noexcept
Get the const end iterator for the operations.
Definition Circuit.h:2173
void MoveMeasurementsAndResets()
Move the measurements and resets closer to the beginning of the circuit.
Definition Circuit.h:1301
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:246
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:666
std::unordered_map< size_t, OperationPtr > GetFirstOperationsOnQubits() const
Returns the first operations on circuit's qubits.
Definition Circuit.h:715
reverse_iterator rbegin() noexcept
Get the reverse begin iterator for the operations.
Definition Circuit.h:2181
void SetOperations(const OperationsVector &ops)
Set the operations in the circuit.
Definition Circuit.h:145
typename OperationsVector::const_pointer const_pointer
Definition Circuit.h:65
void AddOperations(const OperationsVector &ops)
Adds operations to the circuit.
Definition Circuit.h:154
size_t GetMaxQubitIndex() const
Returns the max qubit id for all operations.
Definition Circuit.h:516
std::vector< OperationPtr > OperationsVector
The vector of operations.
Definition Circuit.h:59
OperationPtr Clone() const override
Get a shared pointer to a clone of this object.
Definition Circuit.h:190
size_t GetMaxCbitIndex() const
Returns the max classical bit id for all operations.
Definition Circuit.h:554
void AddCircuit(const std::shared_ptr< Circuit< Time > > &circuit)
Adds operations from another circuit to the circuit.
Definition Circuit.h:164
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:298
typename OperationsVector::const_reverse_iterator const_reverse_iterator
Definition Circuit.h:74
const auto & operator[](size_t pos) const
Get the operation at a given position.
Definition Circuit.h:2241
size_t GetMinCbitIndex() const
Returns the min classical bit id for all operations.
Definition Circuit.h:573
size_t GetNumberOfOperations() const
Get the number of operations in the circuit.
Definition Circuit.h:1569
std::vector< size_t > AffectedBits() const override
Returns the affected bits.
Definition Circuit.h:645
size_t GetMinQubitIndex() const
Returns the min qubit id for all operations.
Definition Circuit.h:535
void resize(size_t size)
Resizes the circuit.
Definition Circuit.h:2250
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:1716
OperationType GetType() const override
Get the type of the circuit.
Definition Circuit.h:113
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:1816
void ReplaceOperation(size_t index, const OperationPtr &op)
Replaces an operation in the circuit.
Definition Circuit.h:132
reverse_iterator rend() noexcept
Get the reverse end iterator for the operations.
Definition Circuit.h:2189
bool IsForest() const
Checks if the circuit is a forest circuit.
Definition Circuit.h:1931
std::unordered_map< std::vector< bool >, size_t > ExecuteResults
The results of the execution of the circuit.
Definition Circuit.h:47
std::vector< std::shared_ptr< Circuit< Time > > > SplitCircuit() const
Splits a circuit that has disjoint subcircuits in it into separate circuits.
Definition Circuit.h:2052
const OperationsVector & GetOperations() const
Get the operations in the circuit.
Definition Circuit.h:175
std::shared_ptr< Operation > OperationPtr
The shared pointer to the operation type.
Definition Circuit.h:2426
double GetParamsEpsilon() const
Gets the epsilon used for checking approximate equality of gate parameters.
Definition Circuit.h:2631
void SetApproximateParamsCheck(bool check)
Sets whether to check approximate equality of gate parameters.
Definition Circuit.h:2603
ComparableCircuit & operator=(const BaseClass &circ)
Assignment operator.
Definition Circuit.h:2457
bool operator==(const BaseClass &rhs) const
Comparison operator.
Definition Circuit.h:2469
bool GetApproximateParamsCheck() const
Gets whether to check approximate equality of gate parameters.
Definition Circuit.h:2611
ComparableCircuit(const BaseClass &circ)
Construct a new ComparableCircuit object.
Definition Circuit.h:2448
IOperation< Time > Operation
The operation type.
Definition Circuit.h:2425
bool operator!=(const BaseClass &rhs) const
Comparison operator.
Definition Circuit.h:2595
Circuit< Time > BaseClass
The base class type.
Definition Circuit.h:2424
ComparableCircuit(const OperationsVector &ops={})
Construct a new ComparableCircuit object.
Definition Circuit.h:2438
void SetParamsEpsilon(double eps)
Sets the epsilon used for checking approximate equality of gate parameters.
Definition Circuit.h:2621
std::vector< OperationPtr > OperationsVector
The vector of operations.
Definition Circuit.h:2428
The operation interface.
Definition Operations.h:361
virtual Types::qubits_vector AffectedQubits() const
Returns the affected qubits.
Definition Operations.h:473
Types::time_type GetDelay() const
Definition Operations.h:500
IOperation(Types::time_type delay=0)
Definition Operations.h:369
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:246
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'
Definition Operations.h:30
@ kNoOp
no operation, just a placeholder, could be used to erase some operation from a circuit
Definition Operations.h:41
@ kComposite
a composite operation, contains other operations - should not be used in the beginning,...
Definition Operations.h:43
@ kRandomGen
random classical bit generator, result in 'OperationState'
Definition Operations.h:29
@ kConditionalRandomGen
conditional random generator, similar with random gen, but conditioned on something from 'OperationSt...
Definition Operations.h:35
@ kConditionalMeasurement
conditional measurement, similar with measurement, but conditioned on something from 'OperationState'
Definition Operations.h:32
@ kMeasurement
measurement, result in 'OperationState'
Definition Operations.h:28
@ kGate
the usual quantum gate, result stays in simulator's state
Definition Operations.h:27
@ kReset
reset, no result in 'state', just apply measurement, then apply not on all qubits that were measured ...
Definition Operations.h:38
std::vector< qubit_t > qubits_vector
The type of a vector of qubits.
Definition Types.h:21
uint_fast64_t qubit_t
The type of a qubit.
Definition Types.h:20