Maestro 0.3.11
Unified interface for quantum circuit simulation
Loading...
Searching...
No Matches
Circuit.h
Go to the documentation of this file.
1
16
17#pragma once
18
19#ifndef _CIRCUIT_H_
20#define _CIRCUIT_H_
21
22#define _USE_MATH_DEFINES
23#include <algorithm>
24#include <math.h>
25#include <numeric>
26#include <set>
27
29#include "Operations/Delay.h"
33#include "Operations/Reset.h"
34#include <vector>
35
36namespace Circuits
37{
38
50template <typename Time = Types::time_type> class Circuit : public IOperation<Time>
51{
52 public:
53 using ExecuteResults = std::unordered_map<std::vector<bool>, size_t>;
55 using BitMapping = std::unordered_map<Types::qubit_t, Types::qubit_t>;
57
59 using OperationPtr = std::shared_ptr<Operation>;
61 using OperationsVector = std::vector<OperationPtr>;
62
63 using value_type = typename OperationsVector::value_type;
64 using allocator_type = typename OperationsVector::allocator_type;
65 using pointer = typename OperationsVector::pointer;
66 using const_pointer = typename OperationsVector::const_pointer;
67 using reference = typename OperationsVector::reference;
68 using const_reference = typename OperationsVector::const_reference;
69 using size_type = typename OperationsVector::size_type;
70 using difference_type = typename OperationsVector::difference_type;
71
72 using iterator = typename OperationsVector::iterator;
73 using const_iterator = typename OperationsVector::const_iterator;
74 using reverse_iterator = typename OperationsVector::reverse_iterator;
75 using const_reverse_iterator = typename OperationsVector::const_reverse_iterator;
76
84 Circuit(const OperationsVector &ops = {}) : Operation(), operations(ops)
85 {
86 }
87
97 void Execute(const std::shared_ptr<Simulators::ISimulator> &sim, OperationState &state) const override
98 {
99 ExecuteBD(sim, state);
100 }
101
113 void ExecuteBD(const std::shared_ptr<Simulators::ISimulator> &sim, OperationState &state, size_t *curMaxBondDim = nullptr) const
114 {
115 state.Reset();
116 if (!sim)
117 return;
118
119 for (const auto &op : operations)
120 ExecuteOperation(op, sim, state, curMaxBondDim);
121 if (sim->IsGateFusionEnabled())
122 sim->FlushPendingGates();
123 if (curMaxBondDim)
124 *curMaxBondDim = std::max(*curMaxBondDim, sim->GetExecutedMaxBondDimension());
125 }
126
134 OperationType GetType() const override
135 {
137 }
138
147 {
148 operations.push_back(op);
149 }
150
156 void Delay(Types::qubit_t qubit, Time duration)
157 {
158 operations.push_back(std::make_shared<Circuits::Delay<Time>>(qubit, duration));
159 }
160
169 void ReplaceOperation(size_t index, const OperationPtr &op)
170 {
171 if (index >= operations.size())
172 return;
173 operations[index] = op;
174 }
175
184 {
185 operations = ops;
186 }
187
196 {
197 operations.insert(operations.end(), ops.begin(), ops.end());
198 }
199
206 void AddCircuit(const std::shared_ptr<Circuit<Time>> &circuit)
207 {
208 AddOperations(circuit->GetOperations());
209 }
210
219 {
220 return operations;
221 }
222
228 void Clear()
229 {
230 operations.clear();
231 }
232
239 OperationPtr Clone() const override
240 {
241 OperationsVector newops;
242
243 for (auto &op : operations)
244 newops.emplace_back(op->Clone());
245
246 return std::make_shared<Circuit<Time>>(newops);
247 }
248
253 {
254 for (const auto &op : operations)
255 {
256 if (op->GetType() == OperationType::kRandomGen && !std::static_pointer_cast<Random<Time>>(op)->HasExplicitSeed())
257 return true;
258 if (op->GetType() == OperationType::kConditionalRandomGen &&
259 !std::static_pointer_cast<Random<Time>>(std::static_pointer_cast<ConditionalRandomGen<Time>>(op)->GetOperation())->HasExplicitSeed())
260 return true;
261 if (op->GetType() == OperationType::kComposite && std::static_pointer_cast<Circuit<Time>>(op)->HasUnseededRandomOperations())
262 return true;
263 }
264 return false;
265 }
266
274 std::shared_ptr<Circuit<Time>> CloneForExecution(uint64_t stream, uint64_t defaultSeed = 0) const
275 {
276 OperationsVector newops;
277 newops.reserve(operations.size());
278 for (size_t i = 0; i < operations.size(); ++i)
279 {
280 const auto &op = operations[i];
281 switch (op->GetType())
282 {
284 newops.push_back(std::static_pointer_cast<Random<Time>>(op)->CloneForExecution(Simulators::IState::DeriveSeed(stream, i), defaultSeed));
285 break;
287 auto copy = std::static_pointer_cast<ConditionalRandomGen<Time>>(op->Clone());
288 copy->SetOperation(
289 std::static_pointer_cast<Random<Time>>(copy->GetOperation())->CloneForExecution(Simulators::IState::DeriveSeed(stream, i), defaultSeed));
290 newops.push_back(std::move(copy));
291 break;
292 }
294 newops.push_back(std::static_pointer_cast<Circuit<Time>>(op)->CloneForExecution(Simulators::IState::DeriveSeed(stream, i), defaultSeed));
295 break;
296 default:
297 newops.push_back(op);
298 break;
299 }
300 }
301 return std::make_shared<Circuit<Time>>(newops);
302 }
303
312 {
313 OperationsVector newops;
314
315 for (auto &op : operations)
316 newops.push_back(op);
317
318 return std::make_shared<Circuit<Time>>(newops);
319 }
320
334 std::shared_ptr<Circuit<Time>> RemoveEndMeasurements() const
335 {
336 OperationsVector retained;
337 retained.reserve(operations.size());
338
339 std::unordered_set<Types::qubit_t> activeQubits;
340 std::unordered_set<size_t> neededBits;
341
342 for (auto it = operations.rbegin(); it != operations.rend(); ++it)
343 {
344 const auto &op = *it;
345 if (!op)
346 continue;
347
348 const auto type = op->GetType();
350 {
351 std::shared_ptr<MeasurementOperation<Time>> meas;
352 std::shared_ptr<ConditionalMeasurement<Time>> condMeas;
353
354 if (type == OperationType::kMeasurement)
355 {
356 meas = std::static_pointer_cast<MeasurementOperation<Time>>(op);
357 }
358 else
359 {
360 condMeas = std::static_pointer_cast<ConditionalMeasurement<Time>>(op);
361 meas = std::static_pointer_cast<MeasurementOperation<Time>>(condMeas->GetOperation());
362 }
363
364 if (!meas)
365 continue;
366
367 const auto &qubits = meas->GetQubits();
368 const auto &bits = meas->GetBitsIndices();
369 const size_t count = std::min(qubits.size(), bits.size());
370 if (count == 0)
371 continue;
372
373 std::vector<bool> keepFlags(count, false);
374 size_t keptCount = 0;
375
376 for (size_t idx = count; idx-- > 0;)
377 {
378 const auto q = qubits[idx];
379 const auto b = bits[idx];
380 if (activeQubits.count(q) > 0 || neededBits.count(b) > 0)
381 {
382 keepFlags[idx] = true;
383 ++keptCount;
384 if (type == OperationType::kMeasurement)
385 neededBits.erase(b);
386 }
387 }
388
389 if (keptCount == 0)
390 continue;
391
393 {
394 const auto condBits = condMeas->AffectedBits();
395 neededBits.insert(condBits.begin(), condBits.end());
396 }
397
398 if (keptCount == count)
399 {
400 retained.push_back(op);
401 }
402 else
403 {
404 std::vector<std::pair<Types::qubit_t, size_t>> keptPairs;
405 keptPairs.reserve(keptCount);
406 for (size_t i = 0; i < count; ++i)
407 {
408 if (keepFlags[i])
409 keptPairs.emplace_back(qubits[i], bits[i]);
410 }
411
412 auto filteredMeas = std::make_shared<MeasurementOperation<Time>>(keptPairs, meas->GetDelay());
413 if (meas->HasReadout())
414 {
415 const auto &rates = meas->GetReadout();
416 std::vector<ReadoutRates> keptRates;
417 keptRates.reserve(keptCount);
418 for (size_t i = 0; i < count; ++i)
419 {
420 if (keepFlags[i])
421 keptRates.push_back(i < rates.size() ? rates[i] : ReadoutRates{});
422 }
423 filteredMeas->SetReadout(std::move(keptRates));
424 }
425
427 {
428 retained.push_back(std::make_shared<ConditionalMeasurement<Time>>(filteredMeas, condMeas->GetCondition(), condMeas->GetDelay()));
429 }
430 else
431 {
432 retained.push_back(std::move(filteredMeas));
433 }
434 }
435 }
436 else
437 {
438 retained.push_back(op);
439
440 switch (type)
441 {
445 const auto qs = op->AffectedQubits();
446 activeQubits.insert(qs.begin(), qs.end());
447 break;
448 }
450 const auto qs = op->AffectedQubits();
451 activeQubits.insert(qs.begin(), qs.end());
452 const auto bs = op->AffectedBits();
453 neededBits.insert(bs.begin(), bs.end());
454 break;
455 }
457 const auto bs = op->AffectedBits();
458 for (const auto b : bs)
459 neededBits.erase(b);
460 break;
461 }
463 const auto bs = op->AffectedBits();
464 neededBits.insert(bs.begin(), bs.end());
465 break;
466 }
468 const auto qs = op->AffectedQubits();
469 activeQubits.insert(qs.begin(), qs.end());
470 const auto bs = GetReferencedBits(op);
471 neededBits.insert(bs.begin(), bs.end());
472 break;
473 }
476 default:
477 break;
478 }
479 }
480 }
481
482 std::reverse(retained.begin(), retained.end());
483 return std::make_shared<Circuit<Time>>(retained);
484 }
485
496 OperationPtr Remap(const BitMapping &qubitsMap, const BitMapping &bitsMap = {}) const override
497 {
498 OperationsVector newops;
499
500 for (const auto &op : operations)
501 newops.emplace_back(op->Remap(qubitsMap, bitsMap));
502
503 return std::make_shared<Circuit<Time>>(newops);
504 }
505
517 std::shared_ptr<Circuit<Time>> RemapToContinuous(BitMapping &newQubitsMap, BitMapping &reverseBitsMap, size_t &nrQubits, size_t &nrCbits) const
518 {
519 OperationsVector newops;
520
521 BitMapping newBitsMap;
522
523 nrQubits = 0;
524 nrCbits = 0;
525
526 // Number qubits in ascending order, not in order of first use: chain
527 // simulators (MPS/MPO) take the numbering as their site order, and a
528 // reordered gate sequence would otherwise scramble it.
529 std::vector<Types::qubit_t> usedQubits;
530 for (const auto &op : operations)
531 for (const auto qubit : op->AffectedQubits())
532 usedQubits.push_back(qubit);
533 std::sort(usedQubits.begin(), usedQubits.end());
534 usedQubits.erase(std::unique(usedQubits.begin(), usedQubits.end()), usedQubits.end());
535 for (const auto qubit : usedQubits)
536 if (newQubitsMap.find(qubit) == newQubitsMap.end())
537 {
538 newQubitsMap[qubit] = nrQubits;
539 ++nrQubits;
540 }
541
542 for (const auto &op : operations)
543 {
544 const auto affectedBits = GetReferencedBits(op);
545
546 for (const auto bit : affectedBits)
547 {
548 const auto it = newBitsMap.find(bit);
549 if (it == newBitsMap.end())
550 {
551 newBitsMap[bit] = nrCbits;
552 reverseBitsMap[nrCbits] = bit;
553 ++nrCbits;
554 }
555 }
556
557 newops.emplace_back(op->Remap(newQubitsMap, newBitsMap));
558 }
559
560 return std::make_shared<Circuit<Time>>(newops);
561 }
562
577 static ExecuteResults RemapResultsBack(const ExecuteResults &results, const BitMapping &bitsMap = {}, bool ignoreNotMapped = false, size_t sz = 0)
578 {
579 ExecuteResults newResults;
580
581 if (!ignoreNotMapped && sz == 0)
582 {
583 for (const auto &[from, to] : bitsMap)
584 if (to > sz)
585 sz = to;
586
587 ++sz;
588 }
589
590 for (const auto &res : results)
591 {
592 Circuits::OperationState mappedState(res.first);
593
594 mappedState.Remap(bitsMap, ignoreNotMapped, ignoreNotMapped ? bitsMap.size() : sz);
595 newResults[mappedState.GetAllBits()] += res.second;
596 }
597
598 return newResults;
599 }
600
609 static void AccumulateResults(ExecuteResults &results, const ExecuteResults &newResults)
610 {
611 for (const auto &res : newResults)
612 results[res.first] += res.second;
613 }
614
629 static void AccumulateResultsWithRemapBack(ExecuteResults &results, const ExecuteResults &newResults, const BitMapping &bitsMap = {},
630 bool ignoreNotMapped = true, size_t sz = 0)
631 {
632 if (!ignoreNotMapped && sz == 0)
633 {
634 for (const auto &[from, to] : bitsMap)
635 if (to > sz)
636 sz = to;
637
638 ++sz;
639 }
640
641 for (const auto &res : newResults)
642 {
643 Circuits::OperationState mappedState(res.first);
644
645 mappedState.Remap(bitsMap, ignoreNotMapped, ignoreNotMapped ? bitsMap.size() : sz);
646 results[mappedState.GetAllBits()] += res.second;
647 /*
648 const auto it = bitsMap.find(res.first);
649 if (it != bitsMap.end())
650 results[it->second] += res.second;
651 */
652 }
653 }
654
655 // TODO: This converts all swap, cswap and ccnot gates
656 // it's not really needed to convert them all,
657 // only those that are not applied locally, that is, on a single host
658 // the local ones can remain as they are
659 // so use network topology to decide which ones to convert
660 // that's for later, when we have the network part implemented
661 // and also the code that splits the circuit
662
671 {
672 // this will make the circuit better for distributed computing
673 // TODO: if composite operations will be implemented, those need to be
674 // optimized as well
675
676 ReplaceThreeQubitAndSwapGates();
677 }
678
687 {
688 ReplaceThreeQubitAndSwapGates(true);
689 }
690
691 /*
692 * @brief Splits the measurements to measurements on individual qubits and
693 * tries to order them as needed by the following clasically conditional
694 * gates.
695 *
696 * Splits the measurements to measurements on individual qubits and tries to
697 * order them as needed by the following clasically conditional gates. This is
698 * needed for netqasm, which requires the measurements to be in the right
699 * order, if the following clasically conditional gates need sending to
700 * another host. This wouldn't be needed if they are local, but we don't know
701 * that at this point.
702 *
703 * So in short, all measurements on more than one qubit are converted to one
704 * qubit measurements, and then all measurements that are grouped together
705 * (not separated by some other operation) are ordered in the same order as
706 * the conditions on the following clasically conditional gates.
707 */
709 {
710 // TODO: Maybe this should be moved at the netqasm level circuit conversion,
711 // since it's not needed for all kinds of distributed computing currently
712 // there is a virtual function in controller that does nothing in the base
713 // class, but for netqasm it calls this method
714 OperationsVector newops;
715
716 for (size_t i = 0; i < operations.size(); ++i)
717 {
718 const auto op = operations[i];
719 if (op->GetType() != OperationType::kMeasurement)
720 {
721 newops.emplace_back(op);
722 continue;
723 }
724
725 // ok, if it's a measurement, look ahead, accumulate all measurements and
726 // then add them in the right order
727 std::unordered_set<size_t> bits;
728 std::unordered_map<size_t, Types::qubit_t> measQubits;
729 std::unordered_map<size_t, Time> measDelays;
730 // Only for bits whose source measurement carried readout rates.
731 std::unordered_map<size_t, ReadoutRates> measReadout;
732
733 auto affectedBits = op->AffectedBits();
734 auto affectedQubits = op->AffectedQubits();
735
736 const auto firstMeas = std::static_pointer_cast<MeasurementOperation<Time>>(op);
737 const auto &firstRates = firstMeas->GetReadout();
738
739 for (size_t q = 0; q < affectedQubits.size(); ++q)
740 {
741 bits.insert(affectedBits[q]);
742 measQubits[affectedBits[q]] = affectedQubits[q];
743 measDelays[affectedBits[q]] = op->GetDelay();
744 if (q < firstRates.size())
745 measReadout[affectedBits[q]] = firstRates[q];
746 }
747
748 size_t j = i + 1;
749 for (; j < operations.size(); ++j)
750 {
751 const auto op2 = operations[j];
752
753 if (op2->GetType() != OperationType::kMeasurement)
754 break;
755
756 affectedQubits = op2->AffectedQubits();
757
758 const auto meas = std::static_pointer_cast<MeasurementOperation<Time>>(op2);
759 affectedBits = meas->GetBitsIndices();
760 const auto &rates = meas->GetReadout();
761 for (size_t q = 0; q < affectedBits.size(); ++q)
762 {
763 bits.insert(affectedBits[q]);
764 measQubits[affectedBits[q]] = affectedQubits[q];
765 measDelays[affectedBits[q]] = op2->GetDelay();
766 if (q < rates.size())
767 measReadout[affectedBits[q]] = rates[q];
768 else
769 measReadout.erase(affectedBits[q]);
770 }
771 }
772
773 i = j - 1;
774
775 // the right order is the one following in the classically controlled
776 // gates
777 for (; j < operations.size(); ++j)
778 {
779 const auto op2 = operations[j];
780 if (op2->GetType() == OperationType::kConditionalGate || op2->GetType() == OperationType::kConditionalMeasurement ||
781 op2->GetType() == OperationType::kConditionalRandomGen)
782 {
783 auto condop = std::static_pointer_cast<IConditionalOperation<Time>>(op2);
784 const auto condbits = condop->AffectedBits();
785 for (const auto bit : condbits)
786 if (bits.find(bit) != bits.end())
787 {
788 auto rebuilt = std::make_shared<MeasurementOperation<Time>>(std::vector{std::make_pair(measQubits[bit], bit)}, measDelays[bit]);
789 const auto rateit = measReadout.find(bit);
790 if (rateit != measReadout.end())
791 rebuilt->SetReadout({rateit->second});
792 newops.emplace_back(rebuilt);
793 bits.erase(bit);
794 }
795 }
796 if (bits.empty())
797 break;
798 }
799
800 // now add the measurements that were left in any order
801 for (auto bit : bits)
802 {
803 auto rebuilt = std::make_shared<MeasurementOperation<Time>>(std::vector{std::make_pair(measQubits[bit], bit)}, measDelays[bit]);
804 const auto rateit = measReadout.find(bit);
805 if (rateit != measReadout.end())
806 rebuilt->SetReadout({rateit->second});
807 newops.emplace_back(rebuilt);
808 }
809 }
810
811 operations.swap(newops);
812 }
813
821 size_t GetMaxQubitIndex() const
822 {
823 size_t mx = 0;
824 for (const auto &op : operations)
825 {
826 const auto qbits = op->AffectedQubits();
827 for (auto q : qbits)
828 if (q > mx)
829 mx = q;
830 }
831
832 return mx;
833 }
834
842 size_t GetMinQubitIndex() const
843 {
844 size_t mn = std::numeric_limits<size_t>::max();
845 for (const auto &op : operations)
846 {
847 const auto qbits = op->AffectedQubits();
848 for (auto q : qbits)
849 if (q < mn)
850 mn = q;
851 }
852
853 return mn;
854 }
855
863 size_t GetMaxCbitIndex() const
864 {
865 size_t mx = 0;
866 for (const auto &op : operations)
867 {
868 const auto cbits = GetReferencedBits(op);
869 for (auto q : cbits)
870 if (q > mx)
871 mx = q;
872 }
873
874 return mx;
875 }
876
884 size_t GetMinCbitIndex() const
885 {
886 size_t mn = std::numeric_limits<size_t>::max();
887 for (const auto &op : operations)
888 {
889 const auto cbits = GetReferencedBits(op);
890 for (auto q : cbits)
891 if (q < mn)
892 mn = q;
893 }
894
895 return mn;
896 }
897
907 std::set<size_t> GetQubits() const
908 {
909 std::set<size_t> qubits;
910 for (const auto &op : operations)
911 {
912 const auto qbits = op->AffectedQubits();
913 qubits.insert(qbits.begin(), qbits.end());
914 }
915
916 return qubits;
917 }
918
926 std::set<size_t> GetBits() const
927 {
928 std::set<size_t> cbits;
929 for (const auto &op : operations)
930 {
931 const auto bits = GetReferencedBits(op);
932 cbits.insert(bits.begin(), bits.end());
933 }
934
935 return cbits;
936 }
937
945 {
946 auto qubits = GetQubits();
947
948 Types::qubits_vector qubitsVec;
949 qubitsVec.reserve(qubits.size());
950
951 for (auto q : qubits)
952 qubitsVec.emplace_back(q);
953
954 return qubitsVec;
955 }
956
963 std::vector<size_t> AffectedBits() const override
964 {
965 auto bits = GetBits();
966
967 std::vector<size_t> bitsVec;
968 bitsVec.reserve(bits.size());
969
970 for (auto b : bits)
971 bitsVec.emplace_back(b);
972
973 return bitsVec;
974 }
975
986 {
987 for (const auto &op : operations)
988 if (op->NeedsEntanglementForDistribution())
989 return true;
990
991 return false;
992 }
993
1001 bool CanAffectQuantumState() const override
1002 {
1003 for (const auto &op : operations)
1004 if (op->CanAffectQuantumState())
1005 return true;
1006
1007 return false;
1008 }
1009
1017 std::unordered_map<size_t, OperationPtr> GetLastOperationsOnQubits() const
1018 {
1019 std::unordered_map<size_t, OperationPtr> lastOps;
1020
1021 for (const auto &op : operations)
1022 {
1023 const auto qbits = op->AffectedQubits();
1024 for (auto q : qbits)
1025 lastOps[q] = op;
1026 }
1027
1028 return lastOps;
1029 }
1030
1038 std::unordered_map<size_t, OperationPtr> GetFirstOperationsOnQubits() const
1039 {
1040 std::unordered_map<size_t, OperationPtr> firstOps;
1041
1042 for (const auto &op : operations)
1043 {
1044 const auto qbits = op->AffectedQubits();
1045 for (auto q : qbits)
1046 {
1047 if (firstOps.find(q) == firstOps.end())
1048 firstOps[q] = op;
1049 }
1050 }
1051
1052 return firstOps;
1053 }
1054
1063 void AddResetsIfNeeded(Time delay = 0)
1064 {
1065 const auto GetLastOps = GetLastOperationsOnQubits();
1066
1067 for (const auto &[q, op] : GetLastOps)
1068 if (op->GetType() != OperationType::kReset) // don't add it if there is already a reset
1069 // operation on the qubit
1070 operations.emplace_back(std::make_shared<Reset<Time>>(Types::qubits_vector{q}, delay));
1071 }
1072
1081 void AddResetsAtBeginningIfNeeded(Time delay = 0)
1082 {
1083 const auto GetFirstOps = GetFirstOperationsOnQubits();
1084
1085 for (const auto &[q, op] : GetFirstOps)
1086 if (op->GetType() != OperationType::kReset) // don't add it if there is already a reset
1087 // operation on the qubit
1088 operations.insert(operations.begin(), std::make_shared<Reset<Time>>(Types::qubits_vector{q}, delay));
1089 }
1090
1098 void Optimize(bool optimizeRotationGates = true)
1099 {
1100 // Some ideas, from simple to more complex:
1101 //
1102 // IMPORTANT: Focus on the gates that are added for distributed computing,
1103 // either for the one with entanglement or the one with cutting the reason
1104 // is that maybe the provided circuit is not that bad, but due of the
1105 // supplementary gates added, duplicates (for example) will occur the most
1106 // important one qubit ones added are hadamard then X, S, Sdag and Z
1107 //
1108 // 1. First, one qubit gates can be combined into a single gate or even no
1109 // gate a) Straightforward for those that are their own inverse (that is,
1110 // hermitian/involutory): Hadamard and Pauli gates for example, if one finds
1111 // two of them in sequence, they can be removed other ones are the one that
1112 // are followed by their 'dag' (inverse, since the gates are unitary) in the
1113 // circuit, those can be removed, too
1114
1115 // several resets can be also changed into a single one, also repeated
1116 // measurements of the same qubit, with result in the same cbit can be
1117 // replaced by a single measurement
1118
1119 // b) other ones can be combined into a single gate, for example phase shift
1120 // gates or rotation gates two phase gates (not the general phase shift we
1121 // have, but the one with 1, i on the diagonal) can be combined into a Z
1122 // gate, for example, or two sqrtNot gates can be combined into a single X
1123 // gate combinations of phase gates and hadamard can be replaced by pauli
1124 // gates in some cases, and so on even the U gate could be used to join
1125 // together several one qubit gates
1126
1127 // c) even more complex... three or more one qubit gates could be combined
1128 // into a single gate... an example is HXH = Z other examples SXS^t = Y,
1129 // SZS^t = Z
1130
1131 // 2. Two qubit gates can be optimized, too
1132 // for example two CNOT gates in sequence can be removed if the control
1133 // qubit is the same, the same goes for two CZ or CY or SWAP gates (this
1134 // goes for the three qubit gates, CCX, CCY, CCZ, CSWAP, too)
1135
1136 // 3. Some gates commute, the reorder can give some opportunities for more
1137 // optimization
1138
1139 // 4. Groups of gates
1140 // the possibilities are endless, but might be easier to focus first on
1141 // Clifford gates for example a X sandwiched between CNOTs can be replaced
1142 // with two X on each qubit if the original X is on the control qubit or
1143 // with an X on the target qubit if the original X is on the target qubit a
1144 // similar thing happens if Z is sandwiched between CNOTs, but this time Z x
1145 // Z appears if the original Z is on the target qubit and if original Z is
1146 // on the control qubit, then the CNOTs dissapear and the Z remains on the
1147 // control qubit
1148
1149 // three CNOT gates with the one in the middle turned upside down compare
1150 // with the other two can be replaced by a single swap gate
1151
1152 // first stage, take out duplicates of H, X, Y, Z
1153
1154 bool changed;
1155
1156 do
1157 {
1158 changed = false;
1159
1160 std::vector<std::shared_ptr<IOperation<Time>>> newops;
1161 newops.reserve(operations.size());
1162
1163 for (int i = 0; i < static_cast<int>(operations.size()); ++i)
1164 {
1165 const std::shared_ptr<IOperation<Time>> &op = operations[i];
1166
1167 const auto type = op->GetType();
1168 if (type == OperationType::kNoOp)
1169 continue;
1170 else if (type == OperationType::kGate)
1171 {
1172 std::shared_ptr<IQuantumGate<Time>> gate = std::static_pointer_cast<IQuantumGate<Time>>(op);
1173 const auto qubits = gate->AffectedQubits();
1174
1175 if (qubits.size() == 1)
1176 {
1177 // TODO: HXH = Z, SXS^t = Y, SZS^t = Z ????
1178
1179 auto gateType = gate->GetGateType();
1180 bool replace = false;
1181
1182 // if it's one of the interesting gates, look ahead to see if it's
1183 // followed by the same gate on the same qubit if yes, replace the
1184 // next one with a nop and skip the current one (or replace the pair
1185 // with a single gate, depending on the type) set changed to true if
1186 // something was changed
1187 switch (gateType)
1188 {
1190 [[fallthrough]];
1192 [[fallthrough]];
1194 if (!optimizeRotationGates)
1195 {
1196 newops.push_back(op);
1197 break;
1198 }
1199 [[fallthrough]];
1201 replace = true;
1202 [[fallthrough]];
1203 // those above will be replaced the pair with a single gate, all
1204 // the following are the ones that get removed
1206 [[fallthrough]];
1208 [[fallthrough]];
1210 [[fallthrough]];
1212 [[fallthrough]];
1214 [[fallthrough]];
1216 [[fallthrough]];
1218 [[fallthrough]];
1220 [[fallthrough]];
1222 [[fallthrough]];
1224 [[fallthrough]];
1226 bool found = false;
1227
1228 if (gateType == QuantumGateType::kSGateType)
1230 else if (gateType == QuantumGateType::kSdgGateType)
1231 gateType = QuantumGateType::kSGateType;
1232 else if (gateType == QuantumGateType::kTGateType)
1234 else if (gateType == QuantumGateType::kTdgGateType)
1235 gateType = QuantumGateType::kTGateType;
1236 else if (gateType == QuantumGateType::kSxGateType)
1238 else if (gateType == QuantumGateType::kSxDagGateType)
1240
1241 for (size_t j = i + 1; j < operations.size(); ++j)
1242 {
1243 auto &nextOp = operations[j];
1244 if (!nextOp->CanAffectQuantumState())
1245 continue;
1246
1247 const auto nextQubits = nextOp->AffectedQubits();
1248 bool hasQubit = false;
1249
1250 for (auto q : nextQubits)
1251 if (q == qubits[0])
1252 {
1253 hasQubit = true;
1254 break;
1255 }
1256
1257 if (!hasQubit)
1258 continue; // an op that does not touch the current qubit
1259 // can be skipped
1260 else if (nextQubits.size() != 1)
1261 break; // if it touches the current qubit and it's
1262 // something else than a single qubit gate, stop
1263
1264 const auto nextType = nextOp->GetType();
1265 if (nextType != OperationType::kGate)
1266 break; // could be a classically conditioned gate, stop
1267
1268 const auto &nextGate = std::static_pointer_cast<SingleQubitGate<Time>>(nextOp);
1269 if (nextGate->GetGateType() == gateType)
1270 {
1271 if (replace)
1272 {
1273 const auto params1 = gate->GetParams();
1274 const auto params2 = nextGate->GetParams();
1275
1276 const double param = params1[0] + params2[0];
1277 const auto delay = gate->GetDelay() + nextGate->GetDelay();
1278
1279 if (gateType == QuantumGateType::kPhaseGateType)
1280 newops.push_back(std::make_shared<PhaseGate<Time>>(qubits[0], param, delay));
1281 else if (gateType == QuantumGateType::kRxGateType)
1282 newops.push_back(std::make_shared<RxGate<Time>>(qubits[0], param, delay));
1283 else if (gateType == QuantumGateType::kRyGateType)
1284 newops.push_back(std::make_shared<RyGate<Time>>(qubits[0], param, delay));
1285 else
1286 newops.push_back(std::make_shared<RzGate<Time>>(qubits[0], param, delay));
1287 }
1288 nextOp = std::make_shared<NoOperation<Time>>();
1289 changed = true;
1290 found = true;
1291 break;
1292 }
1293 else if ((gateType == QuantumGateType::kSGateType && nextGate->GetGateType() == QuantumGateType::kSdgGateType) ||
1294 (gateType == QuantumGateType::kSdgGateType && nextGate->GetGateType() == QuantumGateType::kSGateType))
1295 {
1296 // if expecting an S gate (or a Sdg gate) and found the
1297 // original one instead, replace the pair with a Z gate (S *
1298 // S = Z, Sdag * Sdag = Z)
1299 const auto delay = gate->GetDelay() + nextGate->GetDelay();
1300 newops.push_back(std::make_shared<ZGate<Time>>(qubits[0], delay));
1301 nextOp = std::make_shared<NoOperation<Time>>();
1302 changed = true;
1303 found = true;
1304 break;
1305 }
1306 else if ((gateType == QuantumGateType::kSxGateType && nextGate->GetGateType() == QuantumGateType::kSxDagGateType) ||
1307 (gateType == QuantumGateType::kSxDagGateType && nextGate->GetGateType() == QuantumGateType::kSxGateType))
1308 {
1309 // if expecting an S gate (or a Sdg gate) and found the
1310 // original one instead, replace the pair with a X gate (Sx
1311 // * Sx = X, SXdag * SXdag = X)
1312 const auto delay = gate->GetDelay() + nextGate->GetDelay();
1313 newops.push_back(std::make_shared<XGate<Time>>(qubits[0], delay));
1314 nextOp = std::make_shared<NoOperation<Time>>();
1315 changed = true;
1316 found = true;
1317 break;
1318 }
1319 else if (gateType == QuantumGateType::kTGateType && nextGate->GetGateType() == QuantumGateType::kTdgGateType)
1320 {
1321 // if expecting a T gate and found the Tdgate instead,
1322 // replace the pair with a Sdag gate (Tdg * Tdg = Sdag)
1323 const auto delay = gate->GetDelay() + nextGate->GetDelay();
1324 newops.push_back(std::make_shared<SdgGate<Time>>(qubits[0], delay));
1325 nextOp = std::make_shared<NoOperation<Time>>();
1326 changed = true;
1327 found = true;
1328 break;
1329 }
1330 else if (gateType == QuantumGateType::kTdgGateType && nextGate->GetGateType() == QuantumGateType::kTGateType)
1331 {
1332 // if expecting a Tdg gate and found the T gate instead,
1333 // replace the pair with a S gate (T * T = S)
1334 const auto delay = gate->GetDelay() + nextGate->GetDelay();
1335 newops.push_back(std::make_shared<SGate<Time>>(qubits[0], delay));
1336 nextOp = std::make_shared<NoOperation<Time>>();
1337 changed = true;
1338 found = true;
1339 break;
1340 }
1341 else if (gateType == QuantumGateType::kPhaseGateType &&
1342 (nextGate->GetGateType() == QuantumGateType::kSGateType || nextGate->GetGateType() == QuantumGateType::kSdgGateType ||
1343 nextGate->GetGateType() == QuantumGateType::kTGateType || nextGate->GetGateType() == QuantumGateType::kTdgGateType))
1344 {
1345 const auto delay = gate->GetDelay() + nextGate->GetDelay();
1346 double param2;
1347 if (nextGate->GetGateType() == QuantumGateType::kSGateType)
1348 param2 = 0.5 * M_PI;
1349 else if (nextGate->GetGateType() == QuantumGateType::kSdgGateType)
1350 param2 = -0.5 * M_PI;
1351 else if (nextGate->GetGateType() == QuantumGateType::kTGateType)
1352 param2 = 0.25 * M_PI;
1353 else
1354 param2 = -0.25 * M_PI;
1355
1356 const auto param = gate->GetParams()[0] + param2;
1357 newops.push_back(std::make_shared<PhaseGate<Time>>(qubits[0], param, delay));
1358 nextOp = std::make_shared<NoOperation<Time>>();
1359 changed = true;
1360 found = true;
1361 break;
1362 }
1363 else if (nextGate->GetGateType() == QuantumGateType::kPhaseGateType &&
1364 (gateType == QuantumGateType::kSGateType || gateType == QuantumGateType::kSdgGateType ||
1366 {
1367 const auto delay = gate->GetDelay() + nextGate->GetDelay();
1368 double param1;
1369 if (gateType == QuantumGateType::kSGateType)
1370 param1 = -0.5 * M_PI;
1371 else if (gateType == QuantumGateType::kSdgGateType)
1372 param1 = 0.5 * M_PI;
1373 else if (gateType == QuantumGateType::kTGateType)
1374 param1 = -0.25 * M_PI;
1375 else
1376 param1 = 0.25 * M_PI;
1377
1378 const auto param = nextGate->GetParams()[0] + param1;
1379 newops.push_back(std::make_shared<PhaseGate<Time>>(qubits[0], param, delay));
1380 nextOp = std::make_shared<NoOperation<Time>>();
1381 changed = true;
1382 found = true;
1383 break;
1384 }
1385 else
1386 break; // not the expected gate, acting on same qubit, bail
1387 // out
1388 }
1389
1390 if (!found)
1391 newops.push_back(op);
1392 }
1393 break;
1394 default:
1395 // if no, just add it
1396 newops.push_back(op);
1397 break;
1398 }
1399 }
1400 else if (qubits.size() == 2)
1401 {
1402 auto gateType = gate->GetGateType();
1403 bool replace = false;
1404
1405 // if it's one of the interesting gates, look ahead to see if it's
1406 // followed by the same gate on the same qubit if yes, replace the
1407 // next one with a nop and skip the current one (or replace the pair
1408 // with a single gate, depending on the type) set changed to true if
1409 // something was changed
1410 switch (gateType)
1411 {
1413 [[fallthrough]];
1415 [[fallthrough]];
1417 if (!optimizeRotationGates)
1418 {
1419 newops.push_back(op);
1420 break;
1421 }
1422 [[fallthrough]];
1424 replace = true;
1425 [[fallthrough]];
1426 // those above will be replaced the pair with a single gate, all
1427 // the following are the ones that get removed
1429 [[fallthrough]];
1431 [[fallthrough]];
1433 [[fallthrough]];
1435 [[fallthrough]];
1437 [[fallthrough]];
1439 [[fallthrough]];
1441 bool found = false;
1442
1443 if (gateType == QuantumGateType::kCSxGateType)
1445 else if (gateType == QuantumGateType::kCSxDagGateType)
1447
1448 // looking forward for the next operation that acts on the same
1449 // qubits
1450 for (size_t j = i + 1; j < operations.size(); ++j)
1451 {
1452 auto &nextOp = operations[j];
1453 if (!nextOp->CanAffectQuantumState())
1454 continue;
1455
1456 const auto nextQubits = nextOp->AffectedQubits();
1457
1458 bool hasQubit = false;
1459
1460 for (auto q : nextQubits)
1461 if (q == qubits[0] || q == qubits[1])
1462 {
1463 hasQubit = true;
1464 break;
1465 }
1466
1467 if (!hasQubit)
1468 continue; // an op that does not touch the current qubit
1469 // can be skipped
1470 else if (nextQubits.size() != 2)
1471 break; // if it touches a current qubit and it's something
1472 // else than a two qubits gate, stop
1473 // if it's not the same qubits, bail out
1474 else if (gateType == QuantumGateType::kSwapGateType && !((qubits[0] == nextQubits[0] && qubits[1] == nextQubits[1]) ||
1475 (qubits[0] == nextQubits[1] && qubits[1] == nextQubits[0])))
1476 break;
1477 else if (!(qubits[0] == nextQubits[0] && qubits[1] == nextQubits[1]))
1478 break;
1479
1480 const auto nextType = nextOp->GetType();
1481 if (nextType != OperationType::kGate)
1482 break; // could be a classically conditioned gate, stop
1483
1484 const auto &nextGate = std::static_pointer_cast<TwoQubitsGate<Time>>(nextOp);
1485 if (nextGate->GetGateType() == gateType)
1486 {
1487 if (replace)
1488 {
1489 const auto params1 = gate->GetParams();
1490 const auto params2 = nextGate->GetParams();
1491 const double param = params1[0] + params2[0];
1492 const auto delay = gate->GetDelay() + nextGate->GetDelay();
1493
1494 if (gateType == QuantumGateType::kCPGateType)
1495 newops.push_back(std::make_shared<CPGate<Time>>(qubits[0], qubits[1], param, delay));
1496 else if (gateType == QuantumGateType::kCRxGateType)
1497 newops.push_back(std::make_shared<CRxGate<Time>>(qubits[0], qubits[1], param, delay));
1498 else if (gateType == QuantumGateType::kCRyGateType)
1499 newops.push_back(std::make_shared<CRyGate<Time>>(qubits[0], qubits[1], param, delay));
1500 else
1501 newops.push_back(std::make_shared<CRzGate<Time>>(qubits[0], qubits[1], param, delay));
1502 }
1503 nextOp = std::make_shared<NoOperation<Time>>();
1504 changed = true; // continue merging gates, we found one
1505 // that was merged/removed
1506 found = true; // don't put op in the new operations, we
1507 // handled it
1508 break;
1509 }
1510 else
1511 break; // not the expected gate, acting on same qubits,
1512 // bail out
1513 } // end for of looking forward
1514
1515 if (!found)
1516 newops.push_back(op);
1517 }
1518 break;
1519 default:
1520 // if no, just add it
1521 newops.push_back(op);
1522 break;
1523 }
1524 }
1525 else if (qubits.size() == 3)
1526 {
1527 auto gateType = gate->GetGateType();
1528
1529 // if it's one of the interesting gates, look ahead to see if it's
1530 // followed by the same gate on the same qubit if yes, replace the
1531 // next one with a nop and skip the current one (or replace the pair
1532 // with a single gate, depending on the type) set changed to true if
1533 // something was changed
1534 switch (gateType)
1535 {
1537 [[fallthrough]];
1539 bool found = false;
1540
1541 for (size_t j = i + 1; j < operations.size(); ++j)
1542 {
1543 auto &nextOp = operations[j];
1544 if (!nextOp->CanAffectQuantumState())
1545 continue;
1546
1547 const auto nextQubits = nextOp->AffectedQubits();
1548
1549 bool hasQubit = false;
1550
1551 for (auto q : nextQubits)
1552 if (q == qubits[0] || q == qubits[1] || q == qubits[2])
1553 {
1554 hasQubit = true;
1555 break;
1556 }
1557
1558 if (!hasQubit)
1559 continue; // an op that does not touch the current qubit
1560 // can be skipped
1561 else if (nextQubits.size() != 3)
1562 break; // if it touches a current qubit and it's something
1563 // else than a three qubits gate, stop
1564 // if it's not the same qubits, bail out
1565 else if (gateType == QuantumGateType::kCSwapGateType &&
1566 (qubits[0] != nextQubits[0] || !((qubits[1] == nextQubits[1] && qubits[2] == nextQubits[2]) ||
1567 (qubits[1] == nextQubits[2] && qubits[2] == nextQubits[1]))))
1568 break;
1569 else if (gateType == QuantumGateType::kCCXGateType &&
1570 (qubits[2] != nextQubits[2] || !(qubits[1] == nextQubits[1] && qubits[2] == nextQubits[2]) ||
1571 !(qubits[1] == nextQubits[2] && qubits[2] == nextQubits[1])))
1572 break;
1573
1574 const auto nextType = nextOp->GetType();
1575 if (nextType != OperationType::kGate)
1576 break; // could be a classically conditioned gate, stop
1577
1578 const auto &nextGate = std::static_pointer_cast<ThreeQubitsGate<Time>>(nextOp);
1579 if (nextGate->GetGateType() == gateType)
1580 {
1581 nextOp = std::make_shared<NoOperation<Time>>();
1582 changed = true;
1583 found = true;
1584 break;
1585 }
1586 else
1587 break; // not the expected gate, acting on same qubits,
1588 // bail out
1589 }
1590
1591 if (!found)
1592 newops.push_back(op);
1593 }
1594 break;
1595 default:
1596 // if no, just add it
1597 newops.push_back(op);
1598 break;
1599 }
1600 }
1601 else
1602 newops.push_back(op);
1603 }
1604 else
1605 newops.push_back(op);
1606 } // end for on circuit operations
1607
1608 operations.swap(newops);
1609 } while (changed);
1610 }
1611
1619 {
1620 // The dependency planner does not inspect nested controls or measurements.
1621 // In particular, a classical-only composite has no qubit queue to drain.
1623 return;
1624 OperationsVector newops;
1625 newops.reserve(operations.size());
1626
1627 size_t qubitsNo = std::max(GetMaxQubitIndex(), GetMaxCbitIndex()) + 1;
1628
1629 std::unordered_map<Types::qubit_t, std::vector<OperationPtr>> qubitOps;
1630
1631 std::vector<OperationPtr> lastOps(qubitsNo);
1632
1633 std::unordered_map<OperationPtr, std::unordered_set<OperationPtr>> dependenciesMap;
1634
1635 for (const auto &op : operations)
1636 {
1637 std::unordered_set<OperationPtr> dependencies;
1638
1639 const auto cbits = GetReferencedBits(op);
1640 for (auto c : cbits)
1641 {
1642 const auto lastOp = lastOps[c];
1643 if (lastOp)
1644 dependencies.insert(lastOp);
1645 }
1646
1647 const auto qubits = op->AffectedQubits();
1648 for (auto q : qubits)
1649 {
1650 qubitOps[q].push_back(op);
1651
1652 const auto lastOp = lastOps[q];
1653 if (lastOp)
1654 dependencies.insert(lastOp);
1655
1656 lastOps[q] = op;
1657 }
1658
1659 for (auto c : cbits)
1660 lastOps[c] = op;
1661
1662 dependenciesMap[op] = dependencies;
1663 }
1664 lastOps.clear();
1665
1666 std::vector<Types::qubit_t> indices(qubitsNo, 0);
1667
1668 while (!dependenciesMap.empty())
1669 {
1670 OperationPtr nextOp;
1671
1672 // try to locate a 'next' gate for a qubit that is either a measurement or
1673 // a reset
1674 for (size_t q = 0; q < qubitsNo; ++q)
1675 {
1676 if (qubitOps.find(q) == qubitOps.end()) // no operation left on this qubit
1677 continue;
1678
1679 // grab the current operation for this qubit
1680 const auto &ops = qubitOps[q];
1681 const auto &op = ops[indices[q]];
1682
1683 // consider only measurements and resets
1684 if (op->GetType() == OperationType::kMeasurement || op->GetType() == OperationType::kReset)
1685 {
1686 bool hasDependencies = false;
1687
1688 for (const auto &opd : dependenciesMap[op])
1689 if (dependenciesMap.find(opd) != dependenciesMap.end())
1690 {
1691 hasDependencies = true;
1692 break;
1693 }
1694
1695 if (!hasDependencies)
1696 {
1697 nextOp = op;
1698 break;
1699 }
1700 }
1701 }
1702
1703 if (nextOp)
1704 {
1705 dependenciesMap.erase(nextOp);
1706
1707 const auto qubits = nextOp->AffectedQubits();
1708 for (auto q : qubits)
1709 {
1710 ++indices[q];
1711 if (indices[q] >= qubitOps[q].size())
1712 qubitOps.erase(q);
1713 }
1714
1715 newops.emplace_back(std::move(nextOp));
1716 continue;
1717 }
1718
1719 // if there is no measurement or reset, add the next gate
1720 for (Types::qubit_t q = 0; q < qubitsNo; ++q)
1721 {
1722 if (qubitOps.find(q) == qubitOps.end()) // no operation left on this qubit
1723 continue;
1724
1725 // grab the current operation for this qubit
1726 const auto &ops = qubitOps[q];
1727 const auto &op = ops[indices[q]];
1728
1729 bool hasDependencies = false;
1730
1731 for (const auto &opd : dependenciesMap[op])
1732 if (dependenciesMap.find(opd) != dependenciesMap.end())
1733 {
1734 hasDependencies = true;
1735 break;
1736 }
1737
1738 if (!hasDependencies)
1739 {
1740 nextOp = op;
1741 break;
1742 }
1743 }
1744
1745 if (nextOp)
1746 {
1747 dependenciesMap.erase(nextOp);
1748
1749 const auto qubits = nextOp->AffectedQubits();
1750 for (auto q : qubits)
1751 {
1752 ++indices[q];
1753 if (indices[q] >= qubitOps[q].size())
1754 qubitOps.erase(q);
1755 }
1756
1757 newops.emplace_back(std::move(nextOp));
1758 }
1759 }
1760
1761 assert(newops.size() == operations.size());
1762
1763 operations.swap(newops);
1764 }
1765
1775 std::pair<std::vector<size_t>, std::vector<Time>> GetDepth() const
1776 {
1777 size_t maxDepth;
1778 Time maxTime;
1779
1780 size_t qubitsNo = GetMaxQubitIndex() + 1;
1781 std::vector<Time> qubitTimes(qubitsNo, 0);
1782 std::vector<size_t> qubitDepths(qubitsNo, 0);
1783
1784 std::unordered_map<size_t, size_t> fromQubits;
1785
1786 for (const auto &op : operations)
1787 {
1788 const auto qbits = op->AffectedQubits();
1789 const auto delay = op->GetDelay();
1790
1791 maxTime = 0;
1792 maxDepth = 0;
1793 for (auto q : qbits)
1794 {
1795 qubitTimes[q] += delay;
1796 ++qubitDepths[q];
1797 if (qubitTimes[q] > maxTime)
1798 maxTime = qubitTimes[q];
1799 if (qubitDepths[q] > maxDepth)
1800 maxDepth = qubitDepths[q];
1801 }
1802
1803 const auto t = op->GetType();
1804 std::vector<size_t> condbits;
1805
1806 // TODO: deal with 'random gen' operations, those do not affect qubits
1807 // directly, but they do affect the classical bits and can be used in
1808 // conditional operations
1809
1811 {
1812 condbits = op->AffectedBits();
1813
1814 for (auto bit : condbits)
1815 {
1816 if (fromQubits.find(bit) != fromQubits.end())
1817 bit = fromQubits[bit];
1818
1819 bool found = false;
1820 for (auto q : qbits)
1821 {
1822 if (q == bit)
1823 {
1824 found = true;
1825 break;
1826 }
1827 }
1828 if (found || bit >= qubitsNo)
1829 continue;
1830
1831 qubitTimes[bit] += delay;
1832 ++qubitDepths[bit];
1833 if (qubitTimes[bit] > maxTime)
1834 maxTime = qubitTimes[bit];
1835 if (qubitDepths[bit] > maxDepth)
1836 maxDepth = qubitDepths[bit];
1837 }
1838
1840 {
1841 const auto condMeas = std::static_pointer_cast<ConditionalMeasurement<Time>>(op);
1842 const auto meas = condMeas->GetOperation();
1843 const auto measQubits = meas->AffectedQubits();
1844 const auto measBits = meas->AffectedBits();
1845
1846 for (size_t i = 0; i < measQubits.size(); ++i)
1847 {
1848 if (i < measBits.size())
1849 fromQubits[measBits[i]] = measQubits[i];
1850 else
1851 fromQubits[measQubits[i]] = measQubits[i];
1852 }
1853 }
1854 }
1855 else if (t == OperationType::kMeasurement)
1856 {
1857 condbits = op->AffectedBits();
1858
1859 for (size_t i = 0; i < qbits.size(); ++i)
1860 {
1861 if (i < condbits.size())
1862 fromQubits[condbits[i]] = qbits[i];
1863 else
1864 fromQubits[qbits[i]] = qbits[i];
1865 }
1866 }
1867
1868 for (auto q : qbits)
1869 {
1870 qubitTimes[q] = maxTime;
1871 qubitDepths[q] = maxDepth;
1872 }
1873
1874 for (auto bit : condbits)
1875 {
1876 qubitTimes[bit] = maxTime;
1877 qubitDepths[bit] = maxDepth;
1878 }
1879 }
1880
1881 return std::make_pair(qubitDepths, qubitTimes);
1882 }
1883
1893 std::pair<size_t, Time> GetMaxDepth() const
1894 {
1895 auto [qubitDepths, qubitTimes] = GetDepth();
1896
1897 Time maxTime = 0;
1898 size_t maxDepth = 0;
1899 for (size_t qubit = 0; qubit < qubitDepths.size(); ++qubit)
1900 {
1901 if (qubitTimes[qubit] > maxTime)
1902 maxTime = qubitTimes[qubit];
1903 if (qubitDepths[qubit] > maxDepth)
1904 maxDepth = qubitDepths[qubit];
1905 }
1906
1907 return std::make_pair(maxDepth, maxTime);
1908 }
1909
1917 {
1918 return operations.size();
1919 }
1920
1929 OperationPtr GetOperation(size_t pos) const
1930 {
1931 if (pos >= operations.size())
1932 return nullptr;
1933
1934 return operations[pos];
1935 }
1936
1950 std::shared_ptr<Circuit<Time>> GetCircuitCut(Types::qubit_t startQubit, Types::qubit_t endQubit) const
1951 {
1952 OperationsVector newops;
1953 newops.reserve(operations.size());
1954
1955 for (const auto &op : operations)
1956 {
1957 const auto qubits = op->AffectedQubits();
1958 bool containsOutsideQubits = false;
1959 bool containsInsideQubits = false;
1960 for (const auto q : qubits)
1961 {
1962 if (q < startQubit || q > endQubit)
1963 {
1964 containsOutsideQubits = true;
1965 if (containsInsideQubits)
1966 break;
1967 }
1968 else
1969 {
1970 containsInsideQubits = true;
1971 if (containsOutsideQubits)
1972 break;
1973 }
1974 }
1975
1976 if (containsInsideQubits)
1977 {
1978 if (containsOutsideQubits)
1979 throw std::runtime_error("Cannot cut the circuit with the specified interval");
1980 newops.emplace_back(op->Clone());
1981 }
1982 }
1983
1984 return std::make_shared<Circuit<Time>>(newops);
1985 }
1986
1988 {
1989 return std::any_of(operations.begin(), operations.end(), [](const auto &op) { return op->GetType() == OperationType::kComposite; });
1990 }
1991
2004 {
2005 std::unordered_set<Types::qubit_t> measuredQubits;
2006 std::unordered_set<Types::qubit_t> affectedQubits;
2007 std::unordered_set<Types::qubit_t> resetQubits;
2008
2009 for (const auto &op : operations)
2010 {
2011 // Prefix execution and terminal-measurement extraction treat composites
2012 // as opaque operations. Run them per shot, including gate-only
2013 // composites, until those optimizations can inspect their contents
2014 // consistently.
2015 if (op->GetType() == OperationType::kComposite)
2016 return true;
2017 const auto qubits = op->AffectedQubits();
2018
2019 if (op->GetType() == OperationType::kMeasurement)
2020 {
2021 for (const auto qbit : qubits)
2022 if (resetQubits.find(qbit) != resetQubits.end()) // there is a reset on this qubit already and
2023 // it's not at the beginning of the circuit
2024 return true;
2025
2026 /*
2027 const auto bits = op->AffectedBits();
2028 if (bits.size() != qubits.size())
2029 return true;
2030
2031 for (size_t b = 0; b < bits.size(); ++b)
2032 if (bits[b] != qubits[b])
2033 return true;
2034 */
2035 measuredQubits.insert(qubits.begin(), qubits.end());
2036 }
2037 else if (op->GetType() == OperationType::kConditionalGate || op->GetType() == OperationType::kConditionalMeasurement ||
2038 op->GetType() == OperationType::kRandomGen || op->GetType() == OperationType::kConditionalRandomGen)
2039 return true;
2040 else if (op->GetType() == OperationType::kReset)
2041 {
2042 // resets in the middle of the circuit are treated as measurements
2043 for (const auto qbit : qubits)
2044 {
2045 // if there is already a gate applied on the qubit but no measurement
2046 // yet, it's considered in the middle
2047 // if there is no gate applied,
2048 // then it's the first operation on the qubit
2049 if (affectedQubits.find(qbit) != affectedQubits.end() || measuredQubits.find(qbit) != measuredQubits.end())
2050 resetQubits.insert(qbit);
2051
2052 affectedQubits.insert(qbit);
2053 }
2054 }
2055 else
2056 {
2057 for (const auto qbit : qubits)
2058 {
2059 if (measuredQubits.find(qbit) != measuredQubits.end()) // there is a measurement on this qubit already
2060 return true;
2061
2062 if (resetQubits.find(qbit) != resetQubits.end()) // there is a reset on this qubit already and
2063 // it's not at the beginning of the circuit
2064 return true;
2065
2066 affectedQubits.insert(qbit);
2067 }
2068 }
2069 }
2070
2071 // A trailing mid-circuit reset still has to run. ExecuteNonMeasurements
2072 // defers it like a measurement, and the terminal-measurement path would
2073 // drop it (e.g. sampled T1 noise before an expectation value).
2074 return !resetQubits.empty();
2075 }
2076
2091 std::vector<bool> ExecuteNonMeasurements(const std::shared_ptr<Simulators::ISimulator> &sim, OperationState &state, size_t *curMaxBondDim = nullptr) const
2092 {
2093 // With gate fusion, the look-ahead routing of an MPS/MPO follows the
2094 // simulator's upcoming operations in order, and stepping over an operation
2095 // that is deferred to the shots breaks that plan for the rest of this
2096 // prefix. Plan the order that actually runs instead: the operations
2097 // executed here first, then the deferred ones.
2098 bool planExecutionOrder = sim && sim->IsGateFusionEnabled() && sim->IsRoutingLookaheadEnabled() && sim->GetGatesCounter() == 0;
2099 std::vector<bool> mask;
2100 if (planExecutionOrder)
2101 {
2102 OperationState dryState(state);
2103 mask = ExecuteNonMeasurements(nullptr, dryState);
2104 planExecutionOrder = std::find(mask.begin(), mask.end(), true) != mask.end() && std::find(mask.begin(), mask.end(), false) != mask.end();
2105 }
2106 if (planExecutionOrder)
2107 {
2108 const size_t dif = operations.size() - mask.size();
2109 OperationsVector ordered(operations.begin(), operations.begin() + dif);
2110 ordered.reserve(operations.size());
2111 for (size_t i = dif; i < operations.size(); ++i)
2112 if (mask[i - dif])
2113 ordered.emplace_back(operations[i]);
2114 for (size_t i = dif; i < operations.size(); ++i)
2115 if (!mask[i - dif])
2116 ordered.emplace_back(operations[i]);
2117 sim->SetUpcomingGates(ordered);
2118 }
2119
2120 std::vector<bool> executedOps;
2121 executedOps.reserve(operations.size());
2122
2123 std::unordered_set<Types::qubit_t> measuredQubits;
2124 std::unordered_set<Types::qubit_t> affectedQubits;
2125
2126 bool executionStopped = false;
2127
2128 for (size_t i = 0; i < operations.size(); ++i)
2129 {
2130 auto &op = operations[i];
2131 const auto qubits = op->AffectedQubits();
2132
2133 bool executed = false;
2134
2135 if (op->GetType() == OperationType::kMeasurement || op->GetType() == OperationType::kConditionalMeasurement ||
2136 op->GetType() == OperationType::kRandomGen || op->GetType() == OperationType::kConditionalRandomGen)
2137 measuredQubits.insert(qubits.begin(), qubits.end());
2138 else if (op->GetType() == OperationType::kReset)
2139 {
2140 // if it's the first op on qubit(s), execute it, otherwise treat it as a
2141 // measurement
2142 executed = true;
2143 for (auto qubit : qubits)
2144 if (affectedQubits.find(qubit) != affectedQubits.end())
2145 {
2146 executed = false;
2147 break;
2148 }
2149
2150 if (executed)
2151 {
2152 if (sim)
2153 {
2154 ExecuteOperation(op, sim, state, curMaxBondDim);
2155 }
2156 }
2157 else
2158 measuredQubits.insert(qubits.begin(), qubits.end());
2159 }
2160 else // regular gate or conditional gate
2161 {
2162 const auto bits = op->AffectedBits();
2163
2164 // a measurement on a qubit prevents execution of any following gate
2165 // than affects the same qubit also a gate that's not executed and it
2166 // would affect certain qubits will prevent the execution of any
2167 // following gate that affects those qubits
2168
2169 bool canExecute =
2170 op->GetType() == OperationType::kGate || op->GetType() == OperationType::kQuantumChannel || op->GetType() == OperationType::kDelay;
2171
2172 if (canExecute) // a conditional gate cannot be executed, it needs
2173 // something executed at each shot, either a
2174 // measurement or a random number generated
2175 {
2176 for (auto bit : bits)
2177 if (measuredQubits.find(bit) != measuredQubits.end())
2178 {
2179 canExecute = false;
2180 break;
2181 }
2182
2183 for (auto qubit : qubits)
2184 if (measuredQubits.find(qubit) != measuredQubits.end())
2185 {
2186 canExecute = false;
2187 break;
2188 }
2189 }
2190
2191 if (canExecute)
2192 {
2193 if (sim)
2194 {
2195 ExecuteOperation(op, sim, state, curMaxBondDim);
2196 }
2197 executed = true;
2198 }
2199 else
2200 {
2201 // this is a 'trick', if it cannot execute, then neither can any
2202 // following gate that affects any of the already involved qubits
2203 measuredQubits.insert(bits.begin(), bits.end());
2204 measuredQubits.insert(qubits.begin(), qubits.end());
2205 }
2206 }
2207
2208 affectedQubits.insert(qubits.begin(), qubits.end());
2209
2210 if (!executed)
2211 {
2212 executionStopped = true;
2213 if (sim && sim->IsGateFusionEnabled())
2214 {
2215 // with the execution order planned, deferred operations are at the
2216 // end of the upcoming operations and are not stepped over here
2217 if (!planExecutionOrder)
2218 sim->IncrementGatesCounter();
2219 }
2220 else if (sim &&
2221 (sim->GetSimulationType() == Simulators::SimulationType::kMatrixProductState ||
2222 sim->GetSimulationType() == Simulators::SimulationType::kMatrixProductOperator) &&
2223 sim->SupportsMPSSwapOptimization() && op->GetType() != OperationType::kRandomGen &&
2224 op->GetType() != OperationType::kConditionalRandomGen && op->GetType() != OperationType::kNoOp)
2225 sim->SetGatesCounter(sim->GetGatesCounter() + 1);
2226 }
2227 if (executionStopped)
2228 executedOps.emplace_back(executed);
2229 }
2230
2231 if (sim)
2232 {
2233 if (sim->IsGateFusionEnabled())
2234 sim->FlushPendingGates();
2235 if (curMaxBondDim)
2236 *curMaxBondDim = std::max(*curMaxBondDim, sim->GetExecutedMaxBondDimension());
2237 }
2238
2239 return executedOps;
2240 }
2241
2255 void ExecuteMeasurements(const std::shared_ptr<Simulators::ISimulator> &sim, OperationState &state, const std::vector<bool> &executedOps,
2256 size_t *curMaxBondDim = nullptr) const
2257 {
2258 state.Reset();
2259 if (!sim)
2260 return;
2261
2262 // if (executedOps.empty() && !operations.empty()) throw
2263 // std::runtime_error("The executed operations vector is empty");
2264
2265 const size_t dif = operations.size() - executedOps.size();
2266
2267 for (size_t i = dif; i < operations.size(); ++i)
2268 if (!executedOps[i - dif])
2269 ExecuteOperation(operations[i], sim, state, curMaxBondDim);
2270
2271 if (sim->IsGateFusionEnabled())
2272 sim->FlushPendingGates();
2273 if (curMaxBondDim)
2274 *curMaxBondDim = std::max(*curMaxBondDim, sim->GetExecutedMaxBondDimension());
2275 }
2276
2288 std::shared_ptr<Circuits::Circuit<Time>> RemoveExecutedOperations(std::vector<bool> &executedOps) const
2289 {
2290 if (executedOps.empty())
2291 {
2292 executedOps.resize(size(), false);
2293 return std::make_shared<Circuit<Time>>(operations);
2294 }
2295
2296 OperationsVector newops;
2297 newops.reserve(operations.size());
2298
2299 const size_t dif = operations.size() - executedOps.size();
2300 for (size_t i = dif; i < operations.size(); ++i)
2301 if (!executedOps[i - dif])
2302 newops.emplace_back(operations[i]);
2303
2304 std::vector<bool> newExecutedOps(newops.size(), false);
2305 executedOps.swap(newExecutedOps);
2306
2307 return std::make_shared<Circuit<Time>>(newops);
2308 }
2309
2310 // used internally to optimize measurements in the case of having measurements
2311 // only at the end of the circuit
2312 std::shared_ptr<MeasurementOperation<Time>> GetLastMeasurements(const std::vector<bool> &executedOps, bool sort = true) const
2313 {
2314 const size_t dif = operations.size() - executedOps.size();
2315 std::vector<std::pair<Types::qubit_t, size_t>> measurements;
2316 std::vector<ReadoutRates> readout;
2317 std::unordered_map<size_t, size_t> lastWrite;
2318 bool anyReadout = false;
2319 measurements.reserve(dif);
2320 readout.reserve(dif);
2321
2322 for (size_t i = dif; i < operations.size(); ++i)
2323 if (!executedOps[i - dif] && operations[i]->GetType() == OperationType::kMeasurement)
2324 {
2325 auto measOp = std::static_pointer_cast<MeasurementOperation<Time>>(operations[i]);
2326 const auto &qubits = measOp->GetQubits();
2327 const auto &bits = measOp->GetBitsIndices();
2328 const auto &rates = measOp->GetReadout();
2329
2330 for (size_t j = 0; j < qubits.size(); ++j)
2331 {
2332 lastWrite[bits[j]] = measurements.size();
2333 measurements.emplace_back(qubits[j], bits[j]);
2334 readout.push_back(j < rates.size() ? rates[j] : ReadoutRates{});
2335 }
2336
2337 anyReadout = anyReadout || measOp->HasReadout();
2338 }
2339
2340 // Only the final write to each classical bit is observable in a terminal
2341 // measurement batch. Drop overwritten writes before sorting for a backend,
2342 // otherwise sorting can change which qubit (and readout rate) wins.
2343 std::vector<size_t> order;
2344 order.reserve(lastWrite.size());
2345 for (size_t i = 0; i < measurements.size(); ++i)
2346 if (lastWrite.at(measurements[i].second) == i)
2347 order.push_back(i);
2348
2349 // Keep each surviving readout rate attached to its measurement pair.
2350 if (sort)
2351 std::sort(order.begin(), order.end(), [&](size_t a, size_t b) { return measurements[a].first < measurements[b].first; });
2352
2353 if (sort || order.size() != measurements.size())
2354 {
2355 std::vector<std::pair<Types::qubit_t, size_t>> sortedMeas;
2356 std::vector<ReadoutRates> sortedReadout;
2357 sortedMeas.reserve(order.size());
2358 sortedReadout.reserve(order.size());
2359 for (const auto idx : order)
2360 {
2361 sortedMeas.push_back(measurements[idx]);
2362 sortedReadout.push_back(readout[idx]);
2363 }
2364 measurements.swap(sortedMeas);
2365 readout.swap(sortedReadout);
2366 }
2367
2368 auto merged = std::make_shared<MeasurementOperation<Time>>(measurements);
2369 if (anyReadout)
2370 merged->SetReadout(std::move(readout));
2371
2372 return merged;
2373 }
2374
2384 {
2385 for (const auto &op : operations)
2386 if (op->GetType() == OperationType::kConditionalGate || op->GetType() == OperationType::kConditionalMeasurement ||
2387 op->GetType() == OperationType::kConditionalRandomGen)
2388 return true;
2389
2390 return false;
2391 }
2392
2407 {
2408 for (const auto &op : operations)
2409 {
2410 const auto qubits = op->AffectedQubits();
2411 if (qubits.size() <= 1)
2412 continue;
2413
2414 if (qubits.size() == 2)
2415 {
2416 if (std::abs(qubits[0] - qubits[1]) != 1)
2417 return false;
2418 }
2419 else
2420 {
2421 Types::qubit_t minQubit = qubits[0];
2422 Types::qubit_t maxQubit = qubits[0];
2423
2424 for (size_t i = 1; i < qubits.size(); ++i)
2425 {
2426 if (qubits[i] < minQubit)
2427 minQubit = qubits[i];
2428 else if (qubits[i] > maxQubit)
2429 maxQubit = qubits[i];
2430 }
2431
2432 if (maxQubit - minQubit >= qubits.size())
2433 return false;
2434 }
2435 }
2436
2437 return true;
2438 }
2439
2447 bool IsForest() const
2448 {
2449 std::unordered_map<Types::qubit_t, size_t> qubits;
2450 std::unordered_map<Types::qubit_t, Types::qubits_vector> lastQubits;
2451
2452 for (const auto &op : operations)
2453 {
2454 const auto q = op->AffectedQubits();
2455 // one qubit gates or other operations that do not affect qubits do not
2456 // change anything
2457 if (q.size() <= 1)
2458 continue;
2459
2460 bool allInTheLastQubits = true;
2461
2462 for (const auto qubit : q)
2463 {
2464 if (lastQubits.find(qubit) == lastQubits.end())
2465 {
2466 allInTheLastQubits = false;
2467 break;
2468 }
2469 else
2470 {
2471 const auto &lastQ = lastQubits[qubit];
2472
2473 for (const auto q1 : q)
2474 if (std::find(lastQ.cbegin(), lastQ.cend(), q1) == lastQ.cend())
2475 {
2476 allInTheLastQubits = false;
2477 break;
2478 }
2479
2480 if (!allInTheLastQubits)
2481 break;
2482 }
2483 }
2484
2485 if (allInTheLastQubits)
2486 continue;
2487
2488 for (const auto qubit : q)
2489 {
2490 if (qubits[qubit] > 1) // if the qubit is affected again...
2491 return false;
2492
2493 ++qubits[qubit];
2494
2495 lastQubits[qubit] = q;
2496 }
2497 }
2498
2499 return true;
2500 }
2501
2511 bool IsClifford() const override
2512 {
2513 for (const auto &op : operations)
2514 if (!op->IsClifford())
2515 return false;
2516
2517 return true;
2518 }
2519
2529 double CliffordPercentage() const
2530 {
2531 size_t cliffordOps = 0;
2532 for (const auto &op : operations)
2533 if (op->IsClifford())
2534 ++cliffordOps;
2535
2536 return static_cast<double>(cliffordOps) / operations.size();
2537 }
2538
2548 std::unordered_set<Types::qubit_t> GetCliffordQubits() const
2549 {
2550 std::unordered_set<Types::qubit_t> cliffordQubits;
2551 std::unordered_set<Types::qubit_t> nonCliffordQubits;
2552
2553 for (const auto &op : operations)
2554 {
2555 const auto qubits = op->AffectedQubits();
2556 if (op->IsClifford())
2557 {
2558 for (const auto q : qubits)
2559 cliffordQubits.insert(q);
2560 }
2561 else
2562 {
2563 for (const auto q : qubits)
2564 nonCliffordQubits.insert(q);
2565 }
2566 }
2567
2568 for (const auto q : nonCliffordQubits)
2569 cliffordQubits.erase(q);
2570
2571 return cliffordQubits;
2572 }
2573
2583 std::vector<std::shared_ptr<Circuit<Time>>> SplitCircuit() const
2584 {
2585 std::vector<std::shared_ptr<Circuit<Time>>> circuits;
2586
2587 // find how many disjoint circuits we have in this circuit
2588
2589 std::unordered_map<Types::qubit_t, std::unordered_set<Types::qubit_t>> circuitsMap;
2590 auto allQubits = GetQubits();
2591 std::unordered_map<Types::qubit_t, Types::qubit_t> qubitCircuitMap;
2592
2593 // start with a bunch of disjoint sets of qubits, containing each a single
2594 // qubit
2595
2596 for (auto qubit : allQubits)
2597 {
2598 circuitsMap[qubit] = std::unordered_set<Types::qubit_t>{qubit};
2599 qubitCircuitMap[qubit] = qubit;
2600 }
2601
2602 // then the gates will join them together into circuits
2603
2604 for (const auto &op : operations)
2605 {
2606 const auto qubits = op->AffectedQubits();
2607
2608 if (qubits.empty())
2609 continue;
2610
2611 auto qubitIt = qubits.cbegin();
2612 auto firstQubit = *qubitIt;
2613 // where is the first qubit in the disjoint sets?
2614 auto firstQubitCircuit = qubitCircuitMap[firstQubit];
2615
2616 ++qubitIt;
2617
2618 for (; qubitIt != qubits.cend(); ++qubitIt)
2619 {
2620 auto qubit = *qubitIt;
2621
2622 // where is the qubit in the disjoint sets?
2623 auto qubitCircuit = qubitCircuitMap[qubit];
2624
2625 // join the circuits / qubits sets
2626
2627 if (firstQubitCircuit != qubitCircuit)
2628 {
2629 // join the circuits
2630 circuitsMap[firstQubitCircuit].insert(circuitsMap[qubitCircuit].begin(), circuitsMap[qubitCircuit].end());
2631
2632 // update the qubit to circuit map
2633 for (auto q : circuitsMap[qubitCircuit])
2634 qubitCircuitMap[q] = firstQubitCircuit;
2635
2636 // remove the joined circuit
2637 circuitsMap.erase(qubitCircuit);
2638 }
2639 }
2640 }
2641
2642 size_t circSize = 1ULL;
2643 // static cast added to prevent compiler error on MacOS
2644 circSize = std::max(circSize, static_cast<size_t>(circuitsMap.size()));
2645 circuits.resize(circSize);
2646
2647 for (size_t i = 0; i < circuits.size(); ++i)
2648 circuits[i] = std::make_shared<Circuit<Time>>();
2649
2650 std::unordered_map<Types::qubit_t, size_t> qubitsSetsToCircuit;
2651
2652 size_t circuitNo = 0;
2653 for (const auto &[id, qubitSet] : circuitsMap)
2654 {
2655 qubitsSetsToCircuit[id] = circuitNo;
2656
2657 ++circuitNo;
2658 }
2659
2660 // now fill them up with the operations
2661
2662 for (const auto &op : operations)
2663 {
2664 const auto qubits = op->AffectedQubits();
2665
2666 if (qubits.empty())
2667 {
2668 circuits[0]->AddOperation(op->Clone());
2669 continue;
2670 }
2671
2672 const auto circ = qubitsSetsToCircuit[qubitCircuitMap[*qubits.cbegin()]];
2673
2674 circuits[circ]->AddOperation(op->Clone());
2675 }
2676
2677 return circuits;
2678 }
2679
2687 std::vector<std::shared_ptr<Circuits::Circuit<Time>>> ToLayers() const
2688 {
2689 std::vector<std::shared_ptr<Circuits::Circuit<Time>>> layers;
2690 layers.emplace_back(std::make_shared<Circuits::Circuit<Time>>());
2691
2692 std::unordered_map<Types::qubit_t, Types::qubit_t> qubitsUsed;
2693 std::unordered_map<size_t, size_t> classicalBitLayer;
2694 // The last layer reading each bit: a later write (a measurement) must not
2695 // move ahead of a read of the old value or of an earlier write.
2696 std::unordered_map<size_t, size_t> classicalBitReadLayer;
2697
2698 for (const auto &op : GetOperations())
2699 {
2700 // check the instruction, see if a new layer is needed
2701
2702 // only qubits matter here, the others can be classicaly sent if they are
2703 // needed, even if they are shared... but that should be handled
2704 // somewhere, when not done implicitely (as for the 'simple network' case)
2705 if (op->CanAffectQuantumState())
2706 {
2707 const auto qubits = op->AffectedQubits();
2708 size_t maxLevel = 0;
2709
2710 for (Types::qubit_t qbit : qubits)
2711 {
2712 ++qubitsUsed[qbit];
2713 maxLevel = std::max(maxLevel, static_cast<size_t>(qubitsUsed[qbit]));
2714
2715 if (layers.size() < qubitsUsed[qbit])
2716 {
2717 auto circ = std::make_shared<Circuits::Circuit<Time>>();
2718 layers.push_back(std::move(circ));
2719 }
2720 }
2721
2722 if (op->IsConditional())
2723 {
2724 const auto bits = op->AffectedBits();
2725 for (const auto bit : bits)
2726 maxLevel = std::max(maxLevel, classicalBitLayer[bit]);
2727 }
2728 else
2729 {
2730 // the bits are written: keep the order of the writes and reads
2731 for (const auto bit : op->AffectedBits())
2732 maxLevel = std::max(maxLevel, std::max(classicalBitLayer[bit], classicalBitReadLayer[bit]));
2733 }
2734
2735 // now set all the qubits in the instruction to the max level
2736 for (Types::qubit_t qbit : qubits)
2737 qubitsUsed[qbit] = maxLevel;
2738
2739 const size_t layerIdx = maxLevel > 0 ? maxLevel - 1 : 0;
2740
2741 // ensure enough layers exist for the computed level
2742 while (layers.size() <= layerIdx)
2743 layers.push_back(std::make_shared<Circuits::Circuit<Time>>());
2744
2745 layers[layerIdx]->AddOperation(op->Clone());
2746
2747 const auto usedBits = op->AffectedBits();
2748 const size_t usedLevel = maxLevel > 0 ? maxLevel : 1;
2749 auto &bitLayers = op->IsConditional() ? classicalBitReadLayer : classicalBitLayer;
2750 for (const auto bit : usedBits)
2751 bitLayers[bit] = std::max(bitLayers[bit], usedLevel);
2752 }
2753 else
2754 // add the instruction to the last layer
2755 layers.back()->AddOperation(op->Clone());
2756 }
2757
2758 return layers;
2759 }
2760
2769 std::vector<std::shared_ptr<Circuits::Circuit<Time>>> ToLayersNoClone() const
2770 {
2771 std::vector<std::shared_ptr<Circuits::Circuit<Time>>> layers;
2772 layers.emplace_back(std::make_shared<Circuits::Circuit<Time>>());
2773
2774 std::unordered_map<Types::qubit_t, Types::qubit_t> qubitsUsed;
2775 std::unordered_map<size_t, size_t> classicalBitLayer;
2776 // The last layer reading each bit: a later write (a measurement) must not
2777 // move ahead of a read of the old value or of an earlier write.
2778 std::unordered_map<size_t, size_t> classicalBitReadLayer;
2779
2780 for (const auto &op : GetOperations())
2781 {
2782 // check the instruction, see if a new layer is needed
2783
2784 // only qubits matter here, the others can be classicaly sent if they are
2785 // needed, even if they are shared... but that should be handled
2786 // somewhere, when not done implicitely (as for the 'simple network' case)
2787 if (op->CanAffectQuantumState())
2788 {
2789 const auto qubits = op->AffectedQubits();
2790 size_t maxLevel = 0;
2791
2792 for (Types::qubit_t qbit : qubits)
2793 {
2794 ++qubitsUsed[qbit];
2795 maxLevel = std::max(maxLevel, static_cast<size_t>(qubitsUsed[qbit]));
2796
2797 if (layers.size() < qubitsUsed[qbit])
2798 {
2799 auto circ = std::make_shared<Circuits::Circuit<Time>>();
2800 layers.push_back(std::move(circ));
2801 }
2802 }
2803
2804 if (op->IsConditional())
2805 {
2806 const auto bits = op->AffectedBits();
2807 for (const auto bit : bits)
2808 maxLevel = std::max(maxLevel, classicalBitLayer[bit]);
2809 }
2810 else
2811 {
2812 // the bits are written: keep the order of the writes and reads
2813 for (const auto bit : op->AffectedBits())
2814 maxLevel = std::max(maxLevel, std::max(classicalBitLayer[bit], classicalBitReadLayer[bit]));
2815 }
2816
2817 // now set all the qubits in the instruction to the max level
2818 for (Types::qubit_t qbit : qubits)
2819 qubitsUsed[qbit] = maxLevel;
2820
2821 const size_t layerIdx = maxLevel > 0 ? maxLevel - 1 : 0;
2822
2823 // ensure enough layers exist for the computed level
2824 while (layers.size() <= layerIdx)
2825 layers.push_back(std::make_shared<Circuits::Circuit<Time>>());
2826
2827 layers[layerIdx]->AddOperation(op);
2828
2829 const auto usedBits = op->AffectedBits();
2830 const size_t usedLevel = maxLevel > 0 ? maxLevel : 1;
2831 auto &bitLayers = op->IsConditional() ? classicalBitReadLayer : classicalBitLayer;
2832 for (const auto bit : usedBits)
2833 bitLayers[bit] = std::max(bitLayers[bit], usedLevel);
2834 }
2835 else
2836 // add the instruction to the last layer
2837 layers.back()->AddOperation(op);
2838 }
2839
2840 return layers;
2841 }
2842
2853 std::vector<std::shared_ptr<Circuits::Circuit<Time>>> ToMultipleQubitsLayers() const
2854 {
2855 std::vector<std::shared_ptr<Circuits::Circuit<Time>>> layers;
2856 layers.emplace_back(std::make_shared<Circuits::Circuit<Time>>());
2857
2858 std::unordered_map<Types::qubit_t, Types::qubit_t> qubitsUsed;
2859 std::unordered_map<size_t, size_t> classicalBitLayer;
2860 // The last layer reading each bit: a later write (a measurement) must not
2861 // move ahead of a read of the old value or of an earlier write.
2862 std::unordered_map<size_t, size_t> classicalBitReadLayer;
2863
2864 for (const auto &op : GetOperations())
2865 {
2866 // check the instruction, see if a new layer is needed
2867
2868 // only qubits matter here, the others can be classicaly sent if they are
2869 // needed, even if they are shared... but that should be handled
2870 // somewhere, when not done implicitely (as for the 'simple network' case)
2871 if (op->CanAffectQuantumState())
2872 {
2873 const auto qubits = op->AffectedQubits();
2874 size_t maxLevel = 0;
2875
2876 for (Types::qubit_t qbit : qubits)
2877 {
2878 if (qubits.size() > 1)
2879 ++qubitsUsed[qbit];
2880 maxLevel = std::max(maxLevel, static_cast<size_t>(qubitsUsed[qbit]));
2881
2882 if (layers.size() < qubitsUsed[qbit])
2883 {
2884 auto circ = std::make_shared<Circuits::Circuit<Time>>();
2885 layers.push_back(std::move(circ));
2886 }
2887 }
2888
2889 if (op->IsConditional())
2890 {
2891 const auto bits = op->AffectedBits();
2892 for (const auto bit : bits)
2893 maxLevel = std::max(maxLevel, classicalBitLayer[bit]);
2894 }
2895 else
2896 {
2897 // the bits are written: keep the order of the writes and reads
2898 for (const auto bit : op->AffectedBits())
2899 maxLevel = std::max(maxLevel, std::max(classicalBitLayer[bit], classicalBitReadLayer[bit]));
2900 }
2901
2902 // now set all the qubits in the instruction to the max level
2903 for (Types::qubit_t qbit : qubits)
2904 qubitsUsed[qbit] = maxLevel;
2905
2906 const size_t layerIdx = maxLevel > 0 ? maxLevel - 1 : 0;
2907
2908 // ensure enough layers exist for the computed level
2909 while (layers.size() <= layerIdx)
2910 layers.push_back(std::make_shared<Circuits::Circuit<Time>>());
2911
2912 layers[layerIdx]->AddOperation(op->Clone());
2913
2914 const auto usedBits = op->AffectedBits();
2915 const size_t usedLevel = maxLevel > 0 ? maxLevel : 1;
2916 auto &bitLayers = op->IsConditional() ? classicalBitReadLayer : classicalBitLayer;
2917 for (const auto bit : usedBits)
2918 bitLayers[bit] = std::max(bitLayers[bit], usedLevel);
2919 }
2920 else
2921 // add the instruction to the last layer
2922 layers.back()->AddOperation(op->Clone());
2923 }
2924
2925 return layers;
2926 }
2927
2938 std::vector<std::shared_ptr<Circuits::Circuit<Time>>> ToMultipleQubitsLayersNoClone() const
2939 {
2940 std::vector<std::shared_ptr<Circuits::Circuit<Time>>> layers;
2941 layers.emplace_back(std::make_shared<Circuits::Circuit<Time>>());
2942
2943 std::unordered_map<Types::qubit_t, Types::qubit_t> qubitsUsed;
2944
2945 // Track which layer (1-based, like qubitsUsed) each classical bit was last
2946 // written to, so that conditional operations reading those bits are placed
2947 // in the same layer or later.
2948 std::unordered_map<size_t, size_t> classicalBitLayer;
2949 // Likewise the last layer reading each bit: a later write (a measurement)
2950 // must not move ahead of a read of the old value or of an earlier write.
2951 std::unordered_map<size_t, size_t> classicalBitReadLayer;
2952
2953 for (const auto &op : GetOperations())
2954 {
2955 // check the instruction, see if a new layer is needed
2956
2957 // only qubits matter here, the others can be classicaly sent if they are
2958 // needed, even if they are shared... but that should be handled
2959 // somewhere, when not done implicitely (as for the 'simple network' case)
2960 if (op->CanAffectQuantumState())
2961 {
2962 const auto qubits = op->AffectedQubits();
2963 size_t maxLevel = 0;
2964
2965 for (Types::qubit_t qbit : qubits)
2966 {
2967 if (qubits.size() > 1)
2968 ++qubitsUsed[qbit];
2969 maxLevel = std::max(maxLevel, static_cast<size_t>(qubitsUsed[qbit]));
2970
2971 if (layers.size() < qubitsUsed[qbit])
2972 {
2973 auto circ = std::make_shared<Circuits::Circuit<Time>>();
2974 layers.push_back(std::move(circ));
2975 }
2976 }
2977
2978 // For conditional operations, ensure this op is placed no earlier than
2979 // the layer where the classical bits it depends on were written.
2980 if (op->IsConditional())
2981 {
2982 const auto bits = op->AffectedBits();
2983 for (const auto bit : bits)
2984 maxLevel = std::max(maxLevel, classicalBitLayer[bit]);
2985 }
2986 else
2987 {
2988 // the bits are written: keep the order of the writes and reads
2989 for (const auto bit : op->AffectedBits())
2990 maxLevel = std::max(maxLevel, std::max(classicalBitLayer[bit], classicalBitReadLayer[bit]));
2991 }
2992
2993 // now set all the qubits in the instruction to the max level
2994 for (Types::qubit_t qbit : qubits)
2995 qubitsUsed[qbit] = maxLevel;
2996
2997 const size_t layerIdx = maxLevel > 0 ? maxLevel - 1 : 0;
2998
2999 // ensure enough layers exist for the computed level
3000 while (layers.size() <= layerIdx)
3001 layers.push_back(std::make_shared<Circuits::Circuit<Time>>());
3002
3003 layers[layerIdx]->AddOperation(op);
3004
3005 // Record the layer for classical bits written by measurements / resets
3006 // so that later conditional ops respect the dependency, and the layer
3007 // of the bits read by conditional ops so that later writes do.
3008 const auto usedBits = op->AffectedBits();
3009 const size_t usedLevel = maxLevel > 0 ? maxLevel : 1;
3010 auto &bitLayers = op->IsConditional() ? classicalBitReadLayer : classicalBitLayer;
3011 for (const auto bit : usedBits)
3012 bitLayers[bit] = std::max(bitLayers[bit], usedLevel);
3013 }
3014 else
3015 // add the instruction to the last layer
3016 layers.back()->AddOperation(op);
3017 }
3018
3019 return layers;
3020 }
3021
3031 static std::shared_ptr<Circuits::Circuit<Time>> LayersToCircuit(const std::vector<std::shared_ptr<Circuits::Circuit<Time>>> &layers)
3032 {
3033 auto circuit{std::make_shared<Circuits::Circuit<Time>>()};
3034
3035 for (const auto &layer : layers)
3036 circuit->AddOperations(layer->GetOperations());
3037
3038 return circuit;
3039 }
3040
3049 bool IsBranching() const override
3050 {
3051 for (const auto &op : GetOperations())
3052 if (op->IsBranching())
3053 return true;
3054
3055 return false;
3056 }
3057
3064 iterator begin() noexcept
3065 {
3066 return operations.begin();
3067 }
3068
3075 iterator end() noexcept
3076 {
3077 return operations.end();
3078 }
3079
3086 const_iterator begin() const noexcept
3087 {
3088 return operations.begin();
3089 }
3090
3097 const_iterator end() const noexcept
3098 {
3099 return operations.end();
3100 }
3101
3108 const_iterator cbegin() const noexcept
3109 {
3110 return operations.cbegin();
3111 }
3112
3119 const_iterator cend() const noexcept
3120 {
3121 return operations.cend();
3122 }
3123
3131 {
3132 return operations.rbegin();
3133 }
3134
3142 {
3143 return operations.rend();
3144 }
3145
3153 {
3154 return operations.crbegin();
3155 }
3156
3164 {
3165 return operations.crend();
3166 }
3167
3174 auto size() const
3175 {
3176 return operations.size();
3177 }
3178
3185 auto empty() const
3186 {
3187 return operations.empty();
3188 }
3189
3197 auto &operator[](size_t pos)
3198 {
3199 return operations[pos];
3200 }
3201
3209 const auto &operator[](size_t pos) const
3210 {
3211 return operations[pos];
3212 }
3213
3221 void resize(size_t size)
3222 {
3223 if (size < operations.size())
3224 operations.resize(size);
3225 }
3226
3227 private:
3228 // A conditional's AffectedBits() is deliberately predicate-only. Register
3229 // sizing, mapping and dependencies also need the wrapped operation's outputs.
3230 // Nested circuits collect their referenced bits through GetBits().
3231 static std::vector<size_t> GetReferencedBits(const OperationPtr &op)
3232 {
3233 auto bits = op->AffectedBits();
3234 if (op->IsConditional())
3235 {
3236 const auto conditional = std::static_pointer_cast<IConditionalOperation<Time>>(op);
3237 if (const auto operation = conditional->GetOperation())
3238 {
3239 const auto outputs = GetReferencedBits(operation);
3240 bits.insert(bits.end(), outputs.begin(), outputs.end());
3241 }
3242 }
3243 return bits;
3244 }
3245
3246 static void ExecuteOperation(const OperationPtr &op, const std::shared_ptr<Simulators::ISimulator> &sim, OperationState &state, size_t *curMaxBondDim)
3247 {
3248 const bool boundary = sim->IsGateFusionEnabled() && op->GetType() != OperationType::kGate;
3249 const auto sourcePosition = sim->GetGatesCounter();
3250 if (boundary)
3251 sim->FlushPendingGates();
3252 if (op->GetType() == OperationType::kComposite)
3253 {
3254 // Reset classical bits once at the start of the shot, not when entering
3255 // a nested circuit: its conditions and writes share the enclosing state.
3256 const auto circuit = std::static_pointer_cast<Circuit<Time>>(op);
3257 for (const auto &nested : circuit->GetOperations())
3258 ExecuteOperation(nested, sim, state, curMaxBondDim);
3259 if (boundary)
3260 {
3261 sim->FlushPendingGates();
3262 sim->SetGatesCounter(sourcePosition + 1);
3263 }
3264 return;
3265 }
3266 op->Execute(sim, state);
3267 // A circuit boundary is one source operation, even if it invokes no
3268 // simulator primitive (delay/classical op) or several (reset/conditional).
3269 if (boundary)
3270 {
3271 sim->FlushPendingGates();
3272 sim->SetGatesCounter(sourcePosition + 1);
3273 }
3274 if (curMaxBondDim)
3275 {
3276 const auto bondDim = sim->GetExecutedMaxBondDimension();
3277 if (bondDim > *curMaxBondDim)
3278 *curMaxBondDim = bondDim;
3279 }
3280 }
3281
3292 void ReplaceThreeQubitAndSwapGates(bool onlyThreeQubits = false)
3293 {
3294 // just replace all three qubit gates(just ccnot and cswap will exist in the
3295 // first phase) with several gates on less qubits also replace swap gates
3296 // with three cnots (in the first phase) the controlled ones must be
3297 // replaced as well
3298
3299 // TODO: if composite operations will be implemented, those need to be
3300 // optimized as well
3301
3302 std::vector<std::shared_ptr<IOperation<Time>>> newops;
3303 newops.reserve(operations.size());
3304
3305 for (std::shared_ptr<IOperation<Time>> op : operations)
3306 {
3307 if (op->GetType() == OperationType::kGate)
3308 {
3309 std::shared_ptr<IQuantumGate<Time>> gate = std::static_pointer_cast<IQuantumGate<Time>>(op);
3310
3311 if (NeedsConversion(gate, onlyThreeQubits))
3312 {
3313 std::vector<std::shared_ptr<IGateOperation<Time>>> newgates = ConvertGate(gate, onlyThreeQubits);
3314 newops.insert(newops.end(), newgates.begin(), newgates.end());
3315 }
3316 else
3317 newops.push_back(op);
3318 }
3319 else if (op->GetType() == OperationType::kConditionalGate)
3320 {
3321 std::shared_ptr<ConditionalGate<Time>> condgate = std::static_pointer_cast<ConditionalGate<Time>>(op);
3322 std::shared_ptr<IQuantumGate<Time>> gate = std::static_pointer_cast<IQuantumGate<Time>>(condgate->GetOperation());
3323
3324 if (NeedsConversion(gate, onlyThreeQubits))
3325 {
3326 std::vector<std::shared_ptr<IGateOperation<Time>>> newgates = ConvertGate(gate, onlyThreeQubits);
3327 std::shared_ptr<ICondition> cond = condgate->GetCondition();
3328
3329 for (auto gate : newgates)
3330 newops.push_back(std::make_shared<ConditionalGate<Time>>(gate, cond));
3331 }
3332 else
3333 newops.push_back(op);
3334 }
3335 else
3336 newops.push_back(op);
3337 }
3338
3339 operations.swap(newops);
3340 }
3341
3351 static bool NeedsConversion(const std::shared_ptr<IQuantumGate<Time>> &gate, bool onlyThreeQubits = false)
3352 {
3353 const bool hasThreeQubits = gate->GetNumQubits() == 3;
3354 if (onlyThreeQubits)
3355 return hasThreeQubits;
3356
3357 return hasThreeQubits || gate->GetGateType() == QuantumGateType::kSwapGateType;
3358 }
3359
3372 static std::vector<std::shared_ptr<IGateOperation<Time>>> ConvertGate(std::shared_ptr<IQuantumGate<Time>> &gate, bool onlyThreeQubits = false)
3373 {
3374 // TODO: if delays are used, how to transfer delays from the converted gate
3375 // to the resulting gates?
3376 std::vector<std::shared_ptr<IGateOperation<Time>>> newops;
3377
3378 if (gate->GetNumQubits() == 3)
3379 {
3380 // must be converted no matter what
3381 if (gate->GetGateType() == QuantumGateType::kCCXGateType)
3382 {
3383 const size_t q1 = gate->GetQubit(0); // control 1
3384 const size_t q2 = gate->GetQubit(1); // control 2
3385 const size_t q3 = gate->GetQubit(2); // target
3386
3387 // Sleator-Weinfurter decomposition
3388 newops.push_back(std::make_shared<CSxGate<Time>>(q2, q3));
3389 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
3390 newops.push_back(std::make_shared<CSxDagGate<Time>>(q2, q3));
3391 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
3392 newops.push_back(std::make_shared<CSxGate<Time>>(q1, q3));
3393 }
3394 else if (gate->GetGateType() == QuantumGateType::kCSwapGateType)
3395 {
3396 const size_t q1 = gate->GetQubit(0); // control 1
3397 const size_t q2 = gate->GetQubit(1); // control 2
3398 const size_t q3 = gate->GetQubit(2); // target
3399
3400 // TODO: find a better decomposition
3401 // this one I've got with the qiskit transpiler
3402 newops.push_back(std::make_shared<CXGate<Time>>(q3, q2));
3403
3404 newops.push_back(std::make_shared<CSxGate<Time>>(q2, q3));
3405 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
3406 newops.push_back(std::make_shared<PhaseGate<Time>>(q3, M_PI));
3407
3408 newops.push_back(std::make_shared<PhaseGate<Time>>(q2, -M_PI_2));
3409
3410 newops.push_back(std::make_shared<CSxGate<Time>>(q2, q3));
3411 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
3412 newops.push_back(std::make_shared<PhaseGate<Time>>(q3, M_PI));
3413
3414 newops.push_back(std::make_shared<CSxGate<Time>>(q1, q3));
3415
3416 newops.push_back(std::make_shared<CXGate<Time>>(q3, q2));
3417 }
3418 else
3419 newops.push_back(gate);
3420 }
3421 else if (!onlyThreeQubits && gate->GetGateType() == QuantumGateType::kSwapGateType)
3422 {
3423 // must be converted no matter what
3424 const size_t q1 = gate->GetQubit(0);
3425 const size_t q2 = gate->GetQubit(1);
3426
3427 // for now replace it with three cnots, but maybe later make it
3428 // configurable there are other possibilities, for example three cy gates
3429 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
3430 newops.push_back(std::make_shared<CXGate<Time>>(q2, q1));
3431 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
3432 }
3433 else
3434 newops.push_back(gate);
3435
3436 return newops;
3437 }
3438
3439 OperationsVector operations;
3440};
3441
3455template <typename Time = Types::time_type> class ComparableCircuit : public Circuit<Time>
3456{
3457 public:
3460 using OperationPtr = std::shared_ptr<Operation>;
3462 using OperationsVector = std::vector<OperationPtr>;
3463
3472 {
3473 }
3474
3484 {
3485 }
3486
3495 {
3497
3498 return *this;
3499 }
3500
3507 bool operator==(const BaseClass &rhs) const
3508 {
3509 if (BaseClass::GetOperations().size() != rhs.GetOperations().size())
3510 return false;
3511
3512 for (size_t i = 0; i < BaseClass::GetOperations().size(); ++i)
3513 {
3514 if (BaseClass::GetOperations()[i]->GetType() != rhs.GetOperations()[i]->GetType())
3515 return false;
3516
3517 switch (BaseClass::GetOperations()[i]->GetType())
3518 {
3520 if (std::static_pointer_cast<IQuantumGate<Time>>(BaseClass::GetOperations()[i])->GetGateType() !=
3521 std::static_pointer_cast<IQuantumGate<Time>>(rhs.GetOperations()[i])->GetGateType() ||
3522 BaseClass::GetOperations()[i]->AffectedBits() != rhs.GetOperations()[i]->AffectedBits())
3523 return false;
3524 if (approximateParamsCheck)
3525 {
3526 const auto params1 = std::static_pointer_cast<IQuantumGate<Time>>(BaseClass::GetOperations()[i])->GetParams();
3527 const auto params2 = std::static_pointer_cast<IQuantumGate<Time>>(rhs.GetOperations()[i])->GetParams();
3528 if (params1.size() != params2.size())
3529 return false;
3530
3531 for (size_t j = 0; j < params1.size(); ++j)
3532 if (std::abs(params1[j] - params2[j]) > paramsEpsilon)
3533 return false;
3534 }
3535 else if (std::static_pointer_cast<IQuantumGate<Time>>(BaseClass::GetOperations()[i])->GetParams() !=
3536 std::static_pointer_cast<IQuantumGate<Time>>(rhs.GetOperations()[i])->GetParams())
3537 return false;
3538 break;
3540 if (BaseClass::GetOperations()[i]->AffectedQubits() != rhs.GetOperations()[i]->AffectedQubits() ||
3541 BaseClass::GetOperations()[i]->AffectedBits() != rhs.GetOperations()[i]->AffectedBits())
3542 return false;
3543 break;
3545 if (BaseClass::GetOperations()[i]->AffectedBits() != rhs.GetOperations()[i]->AffectedBits())
3546 return false;
3547 break;
3551 // first, check the conditions
3552 const auto leftCondition = std::static_pointer_cast<IConditionalOperation<Time>>(BaseClass::GetOperations()[i])->GetCondition();
3553 const auto rightCondition = std::static_pointer_cast<IConditionalOperation<Time>>(rhs.GetOperations()[i])->GetCondition();
3554 if (leftCondition->GetBitsIndices() != rightCondition->GetBitsIndices())
3555 return false;
3556
3557 const auto leftEqCondition = std::static_pointer_cast<EqualCondition>(leftCondition);
3558 const auto rightEqCondition = std::static_pointer_cast<EqualCondition>(rightCondition);
3559 if (!leftEqCondition || !rightEqCondition)
3560 return false;
3561
3562 if (leftEqCondition->GetAllBits() != rightEqCondition->GetAllBits())
3563 return false;
3564
3565 // now check the operations
3566 const auto leftOp = std::static_pointer_cast<IConditionalOperation<Time>>(BaseClass::GetOperations()[i])->GetOperation();
3567 const auto rightOp = std::static_pointer_cast<IConditionalOperation<Time>>(rhs.GetOperations()[i])->GetOperation();
3568
3569 ComparableCircuit<Time> leftCircuit;
3570 BaseClass rightCircuit;
3571 leftCircuit.SetApproximateParamsCheck(approximateParamsCheck);
3572 leftCircuit.AddOperation(leftOp);
3573 rightCircuit.AddOperation(rightOp);
3574
3575 if (leftCircuit != rightCircuit)
3576 return false;
3577 }
3578 break;
3580 if (BaseClass::GetOperations()[i]->AffectedQubits() != rhs.GetOperations()[i]->AffectedQubits() ||
3581 std::static_pointer_cast<Reset<Time>>(BaseClass::GetOperations()[i])->GetResetTargets() !=
3582 std::static_pointer_cast<Reset<Time>>(rhs.GetOperations()[i])->GetResetTargets())
3583 return false;
3584 break;
3586 const auto left = std::static_pointer_cast<QuantumChannelOperation<Time>>(BaseClass::GetOperations()[i]);
3587 const auto right = std::static_pointer_cast<QuantumChannelOperation<Time>>(rhs.GetOperations()[i]);
3588 if (left->AffectedQubits() != right->AffectedQubits() ||
3589 !left->GetChannel().IsApprox(right->GetChannel(), approximateParamsCheck ? paramsEpsilon : 0.0))
3590 return false;
3591 }
3592 break;
3593 case OperationType::kDelay: {
3594 const auto left = std::static_pointer_cast<Circuits::Delay<Time>>(BaseClass::GetOperations()[i]);
3595 const auto right = std::static_pointer_cast<Circuits::Delay<Time>>(rhs.GetOperations()[i]);
3596 if (left->GetQubit() != right->GetQubit())
3597 return false;
3598 if (approximateParamsCheck)
3599 {
3600 if (std::abs(left->GetDuration() - right->GetDuration()) > paramsEpsilon)
3601 return false;
3602 }
3603 else if (left->GetDuration() != right->GetDuration())
3604 {
3605 return false;
3606 }
3607 }
3608 break;
3610 break;
3611 default:
3612 return false;
3613 }
3614
3615 if (BaseClass::GetOperations()[i]->GetDelay() != rhs.GetOperations()[i]->GetDelay())
3616 return false;
3617 }
3618
3619 return true;
3620 }
3621
3628 bool operator!=(const BaseClass &rhs) const
3629 {
3630 return !(*this == rhs);
3631 }
3632
3640 {
3641 approximateParamsCheck = check;
3642 }
3643
3651 {
3652 return approximateParamsCheck;
3653 }
3654
3663 void SetParamsEpsilon(double eps)
3664 {
3665 paramsEpsilon = eps;
3666 }
3667
3676 double GetParamsEpsilon() const
3677 {
3678 return paramsEpsilon;
3679 }
3680
3681 private:
3682 bool approximateParamsCheck = false;
3683 double paramsEpsilon = 1e-8;
3685};
3686
3687} // namespace Circuits
3688
3689#endif // !_CIRCUIT_H_
Idle / delay operation for quantum circuits.
Circuit operation for an exact local CPTP quantum channel.
The controlled P gate.
The controlled x rotation gate.
The controlled y rotation gate.
The controlled z rotation gate.
Circuit class for holding the sequence of operations.
Definition Circuit.h:51
iterator begin() noexcept
Get the begin iterator for the operations.
Definition Circuit.h:3064
typename OperationsVector::const_reverse_iterator const_reverse_iterator
Definition Circuit.h:75
void ConvertForCutting()
Converts the circuit for distributed computing.
Definition Circuit.h:686
typename OperationsVector::reverse_iterator reverse_iterator
Definition Circuit.h:74
void Execute(const std::shared_ptr< Simulators::ISimulator > &sim, OperationState &state) const override
Execute the circuit on the given simulator.
Definition Circuit.h:97
typename OperationsVector::allocator_type allocator_type
Definition Circuit.h:64
std::unordered_map< Types::qubit_t, Types::qubit_t > BitMapping
The (qu)bit mapping for remapping.
Definition Circuit.h:55
const_iterator cbegin() const noexcept
Get the const begin iterator for the operations.
Definition Circuit.h:3108
double CliffordPercentage() const
Get the percentage of Clifford operations in the circuit.
Definition Circuit.h:2529
auto size() const
Get the number of operations in the circuit.
Definition Circuit.h:3174
bool IsClifford() const override
Checks if the circuit is a Clifford circuit.
Definition Circuit.h:2511
iterator end() noexcept
Get the end iterator for the operations.
Definition Circuit.h:3075
std::unordered_set< Types::qubit_t > GetCliffordQubits() const
Get the qubits that are acted on by Clifford operations.
Definition Circuit.h:2548
typename OperationsVector::iterator iterator
Definition Circuit.h:72
void AddOperation(const OperationPtr &op)
Adds an operation to the circuit.
Definition Circuit.h:146
std::set< size_t > GetBits() const
Returns the classical bits affected by the operations.
Definition Circuit.h:926
void Optimize(bool optimizeRotationGates=true)
Circuit optimization.
Definition Circuit.h:1098
void EnsureProperOrderForMeasurements()
Definition Circuit.h:708
std::pair< size_t, Time > GetMaxDepth() const
Get max circuit depth.
Definition Circuit.h:1893
std::vector< std::shared_ptr< Circuits::Circuit< Time > > > ToMultipleQubitsLayersNoClone() const
Converts the circuit to layers oriented on multiple qubit gates.
Definition Circuit.h:2938
std::set< size_t > GetQubits() const
Returns the qubits affected by the operations.
Definition Circuit.h:907
std::vector< OperationPtr > OperationsVector
The vector of operations.
Definition Circuit.h:61
std::vector< bool > ExecuteNonMeasurements(const std::shared_ptr< Simulators::ISimulator > &sim, OperationState &state, size_t *curMaxBondDim=nullptr) const
Execute the non-measurements operations from the circuit on the given simulator.
Definition Circuit.h:2091
void AddResetsAtBeginningIfNeeded(Time delay=0)
Add resets at the beginning of the circuit.
Definition Circuit.h:1081
std::vector< std::shared_ptr< Circuits::Circuit< Time > > > ToMultipleQubitsLayers() const
Converts the circuit to layers oriented on multiple qubit gates.
Definition Circuit.h:2853
void Clear()
Clears the operations from the circuit.
Definition Circuit.h:228
typename OperationsVector::value_type value_type
Definition Circuit.h:63
void ExecuteMeasurements(const std::shared_ptr< Simulators::ISimulator > &sim, OperationState &state, const std::vector< bool > &executedOps, size_t *curMaxBondDim=nullptr) const
Execute the measurement operations from the circuit on the given simulator.
Definition Circuit.h:2255
std::shared_ptr< Operation > OperationPtr
The shared pointer to the operation type.
Definition Circuit.h:59
void Delay(Types::qubit_t qubit, Time duration)
Adds a delay (idle) operation on the specified qubit.
Definition Circuit.h:156
typename OperationsVector::reference reference
Definition Circuit.h:67
bool CanAffectQuantumState() const override
Find if the circuit can affect the quantum state.
Definition Circuit.h:1001
auto & operator[](size_t pos)
Get the operation at a given position.
Definition Circuit.h:3197
typename OperationsVector::difference_type difference_type
Definition Circuit.h:70
std::pair< std::vector< size_t >, std::vector< Time > > GetDepth() const
Get circuit depth.
Definition Circuit.h:1775
const_iterator begin() const noexcept
Get the begin iterator for the operations.
Definition Circuit.h:3086
std::unordered_map< size_t, OperationPtr > GetLastOperationsOnQubits() const
Returns the last operations on circuit's qubits.
Definition Circuit.h:1017
const_reverse_iterator crend() const noexcept
Get the const reverse end iterator for the operations.
Definition Circuit.h:3163
std::shared_ptr< MeasurementOperation< Time > > GetLastMeasurements(const std::vector< bool > &executedOps, bool sort=true) const
Definition Circuit.h:2312
bool ActsOnlyOnAdjacentQubits() const
Checks if the circuit has only operations that act on adjacent qubits.
Definition Circuit.h:2406
bool HasCompositeOperations() const
Definition Circuit.h:1987
Circuit(const OperationsVector &ops={})
Construct a new Circuit object.
Definition Circuit.h:84
bool IsBranching() const override
Checks if any operation is a branching one.
Definition Circuit.h:3049
typename OperationsVector::pointer pointer
Definition Circuit.h:65
std::unordered_map< std::vector< bool >, size_t > ExecuteResults
The results of the execution of the circuit.
Definition Circuit.h:53
std::shared_ptr< Circuits::Circuit< Time > > RemoveExecutedOperations(std::vector< bool > &executedOps) const
Returns a new circuit with the operations that were not yet executed.
Definition Circuit.h:2288
typename OperationsVector::size_type size_type
Definition Circuit.h:69
auto empty() const
Check if the circuit is empty.
Definition Circuit.h:3185
OperationPtr Remap(const BitMapping &qubitsMap, const BitMapping &bitsMap={}) const override
Get a shared pointer to a circuit remapped.
Definition Circuit.h:496
static void AccumulateResults(ExecuteResults &results, const ExecuteResults &newResults)
Accumulate the results of a circuit execution to already existing results.
Definition Circuit.h:609
std::shared_ptr< Circuit< Time > > GetCircuitCut(Types::qubit_t startQubit, Types::qubit_t endQubit) const
Get the circuit cut.
Definition Circuit.h:1950
void ConvertForDistribution()
Converts the circuit for distributed computing.
Definition Circuit.h:670
Types::qubits_vector AffectedQubits() const override
Returns the affected qubits.
Definition Circuit.h:944
bool HasOpsAfterMeasurements() const
Checks if the circuit has measurements that are followed by operations that affect the measured qubit...
Definition Circuit.h:2003
IOperation< Time > Operation
The operation type.
Definition Circuit.h:58
OperationPtr CloneFlyweight() const
Get a shared pointer to a clone of this object, but without cloning the operations.
Definition Circuit.h:311
bool HasConditionalOperations() const
Checks if the circuit has clasically conditional operations.
Definition Circuit.h:2383
void AddResetsIfNeeded(Time delay=0)
Add resets at the end of the circuit.
Definition Circuit.h:1063
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:629
bool HasUnseededRandomOperations() const
Whether an execution needs entropy for classical random operations.
Definition Circuit.h:252
const_reverse_iterator crbegin() const noexcept
Get the const reverse begin iterator for the operations.
Definition Circuit.h:3152
OperationPtr GetOperation(size_t pos) const
Get an operation at a given position.
Definition Circuit.h:1929
const_iterator cend() const noexcept
Get the const end iterator for the operations.
Definition Circuit.h:3119
const_iterator end() const noexcept
Get the end iterator for the operations.
Definition Circuit.h:3097
void MoveMeasurementsAndResets()
Move the measurements and resets closer to the beginning of the circuit.
Definition Circuit.h:1618
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:517
typename OperationsVector::const_iterator const_iterator
Definition Circuit.h:73
std::vector< std::shared_ptr< Circuits::Circuit< Time > > > ToLayers() const
Converts the circuit to layers.
Definition Circuit.h:2687
bool NeedsEntanglementForDistribution() const override
Find if the circuit needs entanglement for distribution.
Definition Circuit.h:985
std::unordered_map< size_t, OperationPtr > GetFirstOperationsOnQubits() const
Returns the first operations on circuit's qubits.
Definition Circuit.h:1038
void ExecuteBD(const std::shared_ptr< Simulators::ISimulator > &sim, OperationState &state, size_t *curMaxBondDim=nullptr) const
Execute the circuit on the given simulator.
Definition Circuit.h:113
reverse_iterator rbegin() noexcept
Get the reverse begin iterator for the operations.
Definition Circuit.h:3130
void SetOperations(const OperationsVector &ops)
Set the operations in the circuit.
Definition Circuit.h:183
typename OperationsVector::const_pointer const_pointer
Definition Circuit.h:66
void AddOperations(const OperationsVector &ops)
Adds operations to the circuit.
Definition Circuit.h:195
std::shared_ptr< Circuit< Time > > CloneForExecution(uint64_t stream, uint64_t defaultSeed=0) const
Copy a circuit for one execution job, isolating classical RNGs.
Definition Circuit.h:274
size_t GetMaxQubitIndex() const
Returns the max qubit id for all operations.
Definition Circuit.h:821
OperationPtr Clone() const override
Get a shared pointer to a clone of this object.
Definition Circuit.h:239
size_t GetMaxCbitIndex() const
Returns the max classical bit id for all operations.
Definition Circuit.h:863
void AddCircuit(const std::shared_ptr< Circuit< Time > > &circuit)
Adds operations from another circuit to the circuit.
Definition Circuit.h:206
typename OperationsVector::const_reference const_reference
Definition Circuit.h:68
static ExecuteResults RemapResultsBack(const ExecuteResults &results, const BitMapping &bitsMap={}, bool ignoreNotMapped=false, size_t sz=0)
Map back the results for a remapped circuit.
Definition Circuit.h:577
static std::shared_ptr< Circuits::Circuit< Time > > LayersToCircuit(const std::vector< std::shared_ptr< Circuits::Circuit< Time > > > &layers)
Converts the layers back to a circuit.
Definition Circuit.h:3031
const auto & operator[](size_t pos) const
Get the operation at a given position.
Definition Circuit.h:3209
std::vector< std::shared_ptr< Circuits::Circuit< Time > > > ToLayersNoClone() const
Converts the circuit to layers.
Definition Circuit.h:2769
std::shared_ptr< Circuit< Time > > RemoveEndMeasurements() const
Creates a new circuit with terminal measurements removed.
Definition Circuit.h:334
size_t GetMinCbitIndex() const
Returns the min classical bit id for all operations.
Definition Circuit.h:884
size_t GetNumberOfOperations() const
Get the number of operations in the circuit.
Definition Circuit.h:1916
std::vector< size_t > AffectedBits() const override
Returns the affected bits.
Definition Circuit.h:963
size_t GetMinQubitIndex() const
Returns the min qubit id for all operations.
Definition Circuit.h:842
void resize(size_t size)
Resizes the circuit.
Definition Circuit.h:3221
OperationType GetType() const override
Get the type of the circuit.
Definition Circuit.h:134
void ReplaceOperation(size_t index, const OperationPtr &op)
Replaces an operation in the circuit.
Definition Circuit.h:169
reverse_iterator rend() noexcept
Get the reverse end iterator for the operations.
Definition Circuit.h:3141
bool IsForest() const
Checks if the circuit is a forest circuit.
Definition Circuit.h:2447
std::vector< std::shared_ptr< Circuit< Time > > > SplitCircuit() const
Splits a circuit that has disjoint subcircuits in it into separate circuits.
Definition Circuit.h:2583
const OperationsVector & GetOperations() const
Get the operations in the circuit.
Definition Circuit.h:218
std::shared_ptr< Operation > OperationPtr
The shared pointer to the operation type.
Definition Circuit.h:3460
double GetParamsEpsilon() const
Gets the epsilon used for checking approximate equality of gate parameters.
Definition Circuit.h:3676
std::vector< OperationPtr > OperationsVector
The vector of operations.
Definition Circuit.h:3462
void SetApproximateParamsCheck(bool check)
Sets whether to check approximate equality of gate parameters.
Definition Circuit.h:3639
ComparableCircuit & operator=(const BaseClass &circ)
Assignment operator.
Definition Circuit.h:3494
bool operator==(const BaseClass &rhs) const
Comparison operator.
Definition Circuit.h:3507
bool GetApproximateParamsCheck() const
Gets whether to check approximate equality of gate parameters.
Definition Circuit.h:3650
ComparableCircuit(const BaseClass &circ)
Construct a new ComparableCircuit object.
Definition Circuit.h:3483
IOperation< Time > Operation
The operation type.
Definition Circuit.h:3459
bool operator!=(const BaseClass &rhs) const
Comparison operator.
Definition Circuit.h:3628
Circuit< Time > BaseClass
The base class type.
Definition Circuit.h:3458
ComparableCircuit(const OperationsVector &ops={})
Construct a new ComparableCircuit object.
Definition Circuit.h:3471
void SetParamsEpsilon(double eps)
Sets the epsilon used for checking approximate equality of gate parameters.
Definition Circuit.h:3663
A conditional measurement.
A conditional random generator.
Delay (idle) operation class.
Definition Delay.h:31
std::shared_ptr< IOperation< Time > > GetOperation() const
Get the operation for the conditional operation.
The operation interface.
Definition Operations.h:402
virtual Types::qubits_vector AffectedQubits() const
Returns the affected qubits.
Definition Operations.h:512
Types::time_type GetDelay() const
Definition Operations.h:546
IOperation(Types::time_type delay=0)
Definition Operations.h:410
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.
The state class that stores the classical state of a quantum circuit execution.
Definition Operations.h:68
void Reset(bool value=false)
Set the classical bits with the specified value.
Definition Operations.h:282
The phase gate.
Random operation.
Definition RandomOp.h:35
bool HasExplicitSeed() const
Definition RandomOp.h:120
std::shared_ptr< Random< Time > > CloneForExecution(uint64_t stream, uint64_t defaultSeed=0) const
Clone the generator for an independent execution stream.
Definition RandomOp.h:146
Reset operation class.
Definition Reset.h:34
const std::vector< bool > & GetResetTargets() const
Get the values to reset the qubits to.
Definition Reset.h:129
The S gate.
The S dagger gate.
The X gate.
The Z gate.
static uint64_t DeriveSeed(uint64_t seed, uint64_t stream)
Definition State.h:162
OperationType
The type of operations.
Definition Operations.h:29
@ kConditionalGate
conditional gate, similar with gate, but conditioned on something from 'OperationState'
Definition Operations.h:33
@ kDelay
a delay or idle period on one or more qubits
Definition Operations.h:50
@ kNoOp
no operation, just a placeholder, could be used to erase some operation from a circuit
Definition Operations.h:44
@ kComposite
a composite operation, contains other operations - should not be used in the beginning,...
Definition Operations.h:46
@ kRandomGen
random classical bit generator, result in 'OperationState'
Definition Operations.h:32
@ kConditionalRandomGen
conditional random generator, similar with random gen, but conditioned on something from 'OperationSt...
Definition Operations.h:38
@ kConditionalMeasurement
conditional measurement, similar with measurement, but conditioned on something from 'OperationState'
Definition Operations.h:35
@ kMeasurement
measurement, result in 'OperationState'
Definition Operations.h:31
@ kGate
the usual quantum gate, result stays in simulator's state
Definition Operations.h:30
@ kReset
reset, no result in 'state', just apply measurement, then apply not on all qubits that were measured ...
Definition Operations.h:41
@ kQuantumChannel
a non-unitary CPTP operation on the simulator state
Definition Operations.h:49
Classical readout-error rates for one measured qubit.
@ kMatrixProductState
matrix product state simulation type
Definition State.h:111
@ kMatrixProductOperator
Matrix product operator simulation type.
Definition State.h:118
std::vector< qubit_t > qubits_vector
The type of a vector of qubits.
Definition Types.h:24
uint_fast64_t qubit_t
The type of a qubit.
Definition Types.h:23