Maestro 0.3.11
Unified interface for quantum circuit simulation
Loading...
Searching...
No Matches
ImmediateComposite.h
Go to the documentation of this file.
1
14
15#pragma once
16
17#ifndef _IMMEDIATE_COMPOSITE_H_
18#define _IMMEDIATE_COMPOSITE_H_
19
20#ifdef INCLUDED_BY_FACTORY
21
22#include "Individual.h"
23
24#include <vector>
25
27
28namespace Simulators
29{
30
31// TODO: Maybe use the pimpl idiom
32// https://en.cppreference.com/w/cpp/language/pimpl to hide the implementation
33// for good but during development this should be good enough
34namespace Private
35{
36
46class ImmediateCompositeSimulator : public ISimulator
47{
48 public:
56 ImmediateCompositeSimulator(SimulatorType type =
57#ifdef NO_QISKIT_AER
59#else
61#endif
62 )
63 : type(type)
64 {
65 SeedAuxiliaryRng(samplingSeed);
66 // just in case somebody tries to use a composite simulator composed of
67 // composite simulators or a gpu simulator - this one would work, but until
68 // we implement some supporting functionality with cuda, especially for
69 // composite probably it shouldn't be used splitting and merging is slow and
70 // it should be done in the videocard memory instead of using the cpu, if
71 // the gpu simulators are used otherwise the transfer from videocard memory
72 // and host memory back and forth is going to slow down the simulation quite
73 // a bit
74 if (
75#ifndef NO_QISKIT_AER
76 type != SimulatorType::kQiskitAer &&
77#endif
78 type != SimulatorType::kQCSim)
79 type =
80#ifdef NO_QISKIT_AER
82#else
84#endif
85 }
86
95 void Initialize() override
96 {
97 simulators.clear();
98 qubitsMap.resize(nrQubits);
99 const uint64_t initializationSeed = nextInitialization++ == 0 ? samplingSeed : DeriveSeed(samplingSeed, 0x494E4954ULL + nextInitialization);
100
101 // start with one qubit simulators:
102 for (size_t q = 0; q < nrQubits; ++q)
103 {
104 qubitsMap[q] = q;
105 auto sim = std::make_unique<IndividualSimulator>(type);
106 sim->AllocateQubits(1);
107
108 // this is for tests when we default to matrix product state simulator
109#if 0
110 sim->Configure("method", "statevector");
111#endif
112
113 sim->SetMultithreading(enableMultithreading);
114 if (config.IsSet("precision"))
115 sim->Configure("precision", config.GetConfiguration("precision").c_str());
116 sim->Initialize();
117 for (const auto &[key, value] : config.GetConfigMap())
118 if (key != "seed" && key != "method")
119 sim->Configure(key.c_str(), value.c_str());
120 sim->SetSeed(DeriveSeed(initializationSeed, q));
121 sim->GetQubitsMap()[q] = 0;
122 simulators[q] = std::move(sim);
123 }
124 nextId = nrQubits;
125 }
126
133 void Reset() override
134 {
135 Initialize();
136 }
137
150 void InitializeState(size_t num_qubits, std::vector<std::complex<double>> &amplitudes) override
151 {
152 throw std::runtime_error("CompositeSimulator::InitializeState not supported");
153 }
154
167 /*
168 void InitializeState(size_t num_qubits, std::vector<std::complex<double>,
169 avoid_init_allocator<std::complex<double>>>& amplitudes) override
170 {
171 throw std::runtime_error("CompositeSimulator::InitializeState not
172 supported");
173 }
174 */
175
188#ifndef NO_QISKIT_AER
189 void InitializeState(size_t num_qubits, AER::Vector<std::complex<double>> &amplitudes) override
190 {
191 throw std::runtime_error("CompositeSimulator::InitializeState not supported");
192 }
193#endif
194
207 void InitializeState(size_t num_qubits, Eigen::VectorXcd &amplitudes) override
208 {
209 throw std::runtime_error("CompositeSimulator::InitializeState not supported");
210 }
211
222 void Configure(const char *key, const char *value) override
223 {
225 // don't allow chaning the method, it should stay statevector
226 if (std::string("method") == key)
227 return;
228
229 if (std::string(key) == "precision" || std::string(key) == "use_double_precision")
230 {
231 Configuration::ParsePrecision(key, value);
232 // Fixed once the children exist; a reapplied precision is ignored until
233 // Clear, so later children cannot end up with a different one.
234 if (!simulators.empty())
235 return;
236 config.SetConfiguration(key, value);
237 return;
238 }
239
240 config.SetConfiguration(key, value);
241
242 if (std::string("seed") == key)
243 {
244 const uint64_t seed = std::stoull(value);
245 samplingSeed = seed;
246 nextCloneStream = nextRestoreStream = nextInitialization = nextSplitStream = 0;
247 SeedAuxiliaryRng(seed);
248 for (auto &[id, simulator] : simulators)
249 simulator->SetSeed(DeriveSeed(seed, id));
250 }
251 else
252 {
253 for (auto &[id, simulator] : simulators)
254 simulator->Configure(key, value);
255 }
256 }
257
265 std::string GetConfiguration(const char *key) const override
266 {
267 if (std::string(key) == "sampling_seed")
268 return std::to_string(samplingSeed);
269 if (std::string(key) == "sampling_policy" && type == SimulatorType::kQCSim && !config.IsSet(key))
270 return "reproducible_v1";
271 if (std::string(key) == "precision" || std::string(key) == "use_double_precision")
272 {
273 if (!simulators.empty())
274 return simulators.begin()->second->GetConfiguration(key);
275 const bool useDouble = type == SimulatorType::kQCSim || !config.IsSet("precision") || config.GetConfiguration("precision") == "double";
276 return std::string(key) == "precision" ? (useDouble ? "double" : "single") : (useDouble ? "1" : "0");
277 }
278 if (simulators.empty())
279 return "";
280
281 return config.GetConfiguration(key);
282 }
283
292 size_t AllocateQubits(size_t num_qubits) override
293 {
294 if (!simulators.empty())
295 return 0;
296
297 const size_t oldNrQubits = nrQubits;
298 nrQubits += num_qubits;
299 return oldNrQubits;
300 }
301
308 size_t GetNumberOfQubits() const override
309 {
310 return nrQubits;
311 }
312
320 void Clear() override
321 {
322 simulators.clear();
323 qubitsMap.clear();
324 nrQubits = 0;
325 }
326
332 void SaveStateToInternalDestructive() override
333 {
334 for (auto &[id, simulator] : simulators)
335 simulator->SaveStateToInternalDestructive();
336 }
337
345 {
346 for (auto &[id, simulator] : simulators)
347 simulator->RestoreInternalDestructiveSavedState();
348 }
349
358 std::complex<double> AmplitudeRaw(Types::qubit_t outcome) override
359 {
360 std::complex<double> res = 1.0;
361
362 for (auto &[id, simulator] : simulators)
363 res *= simulator->AmplitudeRaw(simulator->ConvertOutcomeFromGlobal(outcome));
364
365 return res;
366 }
367
378 size_t Measure(const Types::qubits_vector &qubits) override
379 {
380 if (qubits.size() > sizeof(size_t) * 8)
381 std::cerr << "Warning: The number of qubits to measure is larger than the "
382 "number of bits in the size_t type, the outcome will be undefined"
383 << std::endl;
384
385 size_t res = 0;
386 size_t mask = 1ULL;
387
388 DontNotify();
389 for (Types::qubit_t qubit : qubits)
390 {
391 const bool outcome = GetSimulator(qubit)->Measure({qubit}) != 0;
392 if (outcome)
393 res |= mask;
394 mask <<= 1;
395
396 Split(qubit, outcome);
397 }
398 Notify();
399
400 NotifyObservers(qubits);
401
402 return res;
403 }
404
411 std::vector<bool> MeasureMany(const Types::qubits_vector &qubits) override
412 {
413 std::vector<bool> res(qubits.size(), false);
414
415 DontNotify();
416 for (size_t q = 0; q < qubits.size(); ++q)
417 {
418 Types::qubit_t qubit = qubits[q];
419 const bool outcome = GetSimulator(qubit)->Measure({qubit}) != 0;
420 if (outcome)
421 res[q] = true;
422
423 Split(qubit, outcome);
424 }
425 Notify();
426
427 NotifyObservers(qubits);
428
429 return res;
430 }
431
438 void ApplyReset(const Types::qubits_vector &qubits) override
439 {
440 DontNotify();
441 for (Types::qubit_t qubit : qubits)
442 {
443 GetSimulator(qubit)->ApplyReset({qubit});
444 Split(qubit, false);
445 }
446 Notify();
447
448 NotifyObservers(qubits);
449 }
450
460 double Probability(Types::qubit_t outcome) override
461 {
462 return std::norm(Amplitude(outcome));
463 }
464
473 std::complex<double> Amplitude(Types::qubit_t outcome) override
474 {
475 std::complex<double> res = 1.0;
476
477 for (auto &[id, simulator] : simulators)
478 res *= simulator->Amplitude(outcome);
479
480 return res;
481 }
482
496 std::complex<double> ProjectOnZero() override
497 {
498 std::complex<double> res = 1.0;
499
500 for (auto &[id, simulator] : simulators)
501 res *= simulator->ProjectOnZero();
502
503 return res;
504 }
505
513 std::vector<double> AllProbabilities() override
514 {
515 const size_t nrBasisStates = 1ULL << nrQubits;
516 std::vector<double> result;
517
518 for (size_t i = 0; i < nrBasisStates; ++i)
519 result.emplace_back(Probability(i));
520
521 return result;
522 }
523
533 std::vector<double> Probabilities(const Types::qubits_vector &qubits) override
534 {
535 std::vector<double> result;
536
537 for (size_t i = 0; i < qubits.size(); ++i)
538 result.emplace_back(Probability(qubits[i]));
539
540 return result;
541 }
542
559 std::unordered_map<Types::qubit_t, Types::qubit_t> SampleCounts(const Types::qubits_vector &qubits, size_t shots = 1000) override
560 {
561 if (type == SimulatorType::kQCSim && config.GetConfiguration("sampling_policy") != "legacy")
562 return SamplePrepared<false>(qubits, shots);
563 if (qubits.empty() || !shots)
564 return {};
565 if (GetNumberOfQubits() > sizeof(Types::qubit_t) * 8)
566 std::cerr << "Warning: The number of qubits to measure is larger than the "
567 "number of bits in the Types::qubit_t type, the outcome will be "
568 "undefined"
569 << std::endl;
570 // TODO: improve it as for the qcsim statevector simulator case!
571 std::unordered_map<Types::qubit_t, Types::qubit_t> result;
572 DontNotify();
573
575
576 if (shots > 1)
577 {
578 InitializeAlias();
579
580 for (size_t shot = 0; shot < shots; ++shot)
581 {
582 size_t measRaw = 0;
583 for (auto &[id, simulator] : simulators)
584 measRaw |= simulator->SampleFromAlias();
585
586 size_t meas = 0;
587 size_t mask = 1ULL;
588 for (auto q : qubits)
589 {
590 const size_t qubitMask = 1ULL << q;
591 if ((measRaw & qubitMask) != 0)
592 meas |= mask;
593 mask <<= 1ULL;
594 }
595
596 ++result[meas];
597 }
598
599 ClearAlias();
600 }
601 else
602 for (size_t shot = 0; shot < shots; ++shot)
603 {
604 const auto measRaw = MeasureNoCollapse();
605
606 size_t meas = 0;
607 size_t mask = 1ULL;
608 for (auto q : qubits)
609 {
610 const size_t qubitMask = 1ULL << q;
611 if ((measRaw & qubitMask) != 0)
612 meas |= mask;
613 mask <<= 1ULL;
614 }
615
616 ++result[meas];
617 }
618
620
621 Notify();
622 NotifyObservers(qubits);
623
624 return result;
625 }
626
640 std::unordered_map<std::vector<bool>, Types::qubit_t> SampleCountsMany(const Types::qubits_vector &qubits, size_t shots = 1000) override
641 {
642 if (type == SimulatorType::kQCSim && config.GetConfiguration("sampling_policy") != "legacy")
643 return SamplePrepared<true>(qubits, shots);
644 if (qubits.empty() || !shots)
645 return {};
646 std::unordered_map<std::vector<bool>, Types::qubit_t> result;
647 DontNotify();
648
650 std::vector<bool> meas(qubits.size());
651 if (shots > 1)
652 {
653 InitializeAlias();
654
655 for (size_t shot = 0; shot < shots; ++shot)
656 {
657 size_t measRaw = 0;
658 for (auto &[id, simulator] : simulators)
659 measRaw |= simulator->SampleFromAlias();
660
661 for (size_t i = 0; i < qubits.size(); ++i)
662 meas[i] = ((measRaw >> qubits[i]) & 1) == 1;
663
664 ++result[meas];
665 }
666
667 ClearAlias();
668 }
669 else
670 {
671 for (size_t shot = 0; shot < shots; ++shot)
672 {
673 const auto measRaw = MeasureNoCollapseMany();
674
675 for (size_t i = 0; i < qubits.size(); ++i)
676 meas[i] = measRaw[qubits[i]];
677
678 ++result[meas];
679 }
680 }
681
683
684 Notify();
685 NotifyObservers(qubits);
686
687 return result;
688 }
689
701 double ExpectationValue(const std::string &pauliString) override
702 {
703 std::unordered_map<size_t, std::string> pauliStrings;
704
705 for (size_t q = 0; q < pauliString.size(); ++q)
706 {
707 const char op = toupper(pauliString[q]);
708 if (op == 'I')
709 continue;
710
711 const size_t simId = qubitsMap[q];
712 const size_t localQubit = simulators[simId]->GetQubitsMap().at(q);
713
714 if (pauliStrings[simId].size() <= localQubit)
715 pauliStrings[simId].resize(localQubit + 1, 'I');
716
717 pauliStrings[simId][localQubit] = op;
718 }
719
720 double result = 1.0;
721 for (auto &[id, localPauliString] : pauliStrings)
722 result *= simulators[id]->ExpectationValue(localPauliString);
723
724 return result;
725 }
726
734 SimulatorType GetType() const override
735 {
736#ifdef NO_QISKIT_AER
737 return SimulatorType::kCompositeQCSim;
738#else
739 if (type != SimulatorType::kQiskitAer)
740 return SimulatorType::kCompositeQCSim;
741
742 return SimulatorType::kCompositeQiskitAer;
743#endif
744 }
745
754 SimulationType GetSimulationType() const override
755 {
756 return SimulationType::kStatevector;
757 }
758
766 void Flush() override
767 {
768 for (auto &[id, simulator] : simulators)
769 simulator->Flush();
770 }
771
777 void ApplyGenericOneQubitGate(Types::qubit_t qubit, const Eigen::Matrix2cd &gate) override
778 {
779 GetSimulator(qubit)->ApplyGenericOneQubitGate(qubit, gate);
780 NotifyObservers({qubit});
781 }
782
789 void ApplyGenericTwoQubitGate(Types::qubit_t qubit0, Types::qubit_t qubit1, const Eigen::Matrix4cd &gate) override
790 {
791 JoinIfNeeded(qubit0, qubit1);
792 GetSimulator(qubit0)->ApplyGenericTwoQubitGate(qubit0, qubit1, gate);
793 NotifyObservers({qubit0, qubit1});
794 }
795
796 void ApplyGenericThreeQubitGate(Types::qubit_t q0, Types::qubit_t q1, Types::qubit_t q2, const Matrix8cd &gate) override
797 {
798 JoinIfNeeded(q0, q1);
799 JoinIfNeeded(q0, q2);
800 GetSimulator(q0)->ApplyGenericThreeQubitGate(q0, q1, q2, gate);
801 NotifyObservers({q0, q1, q2});
802 }
803
804 // YES, all one qubit gates are that easy:
805
813 void ApplyP(Types::qubit_t qubit, double lambda) override
814 {
815 GetSimulator(qubit)->ApplyP(qubit, lambda);
816 NotifyObservers({qubit});
817 }
818
825 void ApplyX(Types::qubit_t qubit) override
826 {
827 GetSimulator(qubit)->ApplyX(qubit);
828 NotifyObservers({qubit});
829 }
830
837 void ApplyY(Types::qubit_t qubit) override
838 {
839 GetSimulator(qubit)->ApplyY(qubit);
840 NotifyObservers({qubit});
841 }
842
849 void ApplyZ(Types::qubit_t qubit) override
850 {
851 GetSimulator(qubit)->ApplyZ(qubit);
852 NotifyObservers({qubit});
853 }
854
861 void ApplyH(Types::qubit_t qubit) override
862 {
863 GetSimulator(qubit)->ApplyH(qubit);
864 NotifyObservers({qubit});
865 }
866
873 void ApplyS(Types::qubit_t qubit) override
874 {
875 GetSimulator(qubit)->ApplyS(qubit);
876 NotifyObservers({qubit});
877 }
878
885 void ApplySDG(Types::qubit_t qubit) override
886 {
887 GetSimulator(qubit)->ApplySDG(qubit);
888 NotifyObservers({qubit});
889 }
890
897 void ApplyT(Types::qubit_t qubit) override
898 {
899 GetSimulator(qubit)->ApplyT(qubit);
900 NotifyObservers({qubit});
901 }
902
909 void ApplyTDG(Types::qubit_t qubit) override
910 {
911 GetSimulator(qubit)->ApplyTDG(qubit);
912 NotifyObservers({qubit});
913 }
914
921 void ApplySx(Types::qubit_t qubit) override
922 {
923 GetSimulator(qubit)->ApplySx(qubit);
924 NotifyObservers({qubit});
925 }
926
933 void ApplySxDAG(Types::qubit_t qubit) override
934 {
935 GetSimulator(qubit)->ApplySxDAG(qubit);
936 NotifyObservers({qubit});
937 }
938
945 void ApplyK(Types::qubit_t qubit) override
946 {
947 GetSimulator(qubit)->ApplyK(qubit);
948 NotifyObservers({qubit});
949 }
950
958 void ApplyRx(Types::qubit_t qubit, double theta) override
959 {
960 GetSimulator(qubit)->ApplyRx(qubit, theta);
961 NotifyObservers({qubit});
962 }
963
971 void ApplyRy(Types::qubit_t qubit, double theta) override
972 {
973 GetSimulator(qubit)->ApplyRy(qubit, theta);
974 NotifyObservers({qubit});
975 }
976
984 void ApplyRz(Types::qubit_t qubit, double theta) override
985 {
986 GetSimulator(qubit)->ApplyRz(qubit, theta);
987 NotifyObservers({qubit});
988 }
989
1000 void ApplyU(Types::qubit_t qubit, double theta, double phi, double lambda, double gamma) override
1001 {
1002 GetSimulator(qubit)->ApplyU(qubit, theta, phi, lambda, gamma);
1003 NotifyObservers({qubit});
1004 }
1005
1006 // the gates that operate on more than one qubit need joining of the
1007 // simulators, if the qubits are in different simulators
1008
1016 void ApplyCX(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit) override
1017 {
1018 JoinIfNeeded(ctrl_qubit, tgt_qubit);
1019 GetSimulator(ctrl_qubit)->ApplyCX(ctrl_qubit, tgt_qubit);
1020 NotifyObservers({tgt_qubit, ctrl_qubit});
1021 }
1022
1030 void ApplyCY(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit) override
1031 {
1032 JoinIfNeeded(ctrl_qubit, tgt_qubit);
1033 GetSimulator(ctrl_qubit)->ApplyCY(ctrl_qubit, tgt_qubit);
1034 NotifyObservers({tgt_qubit, ctrl_qubit});
1035 }
1036
1044 void ApplyCZ(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit) override
1045 {
1046 JoinIfNeeded(ctrl_qubit, tgt_qubit);
1047 GetSimulator(ctrl_qubit)->ApplyCZ(ctrl_qubit, tgt_qubit);
1048 NotifyObservers({tgt_qubit, ctrl_qubit});
1049 }
1050
1059 void ApplyCP(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit, double lambda) override
1060 {
1061 JoinIfNeeded(ctrl_qubit, tgt_qubit);
1062 GetSimulator(ctrl_qubit)->ApplyCP(ctrl_qubit, tgt_qubit, lambda);
1063 NotifyObservers({tgt_qubit, ctrl_qubit});
1064 }
1065
1074 void ApplyCRx(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit, double theta) override
1075 {
1076 JoinIfNeeded(ctrl_qubit, tgt_qubit);
1077 GetSimulator(ctrl_qubit)->ApplyCRx(ctrl_qubit, tgt_qubit, theta);
1078 NotifyObservers({tgt_qubit, ctrl_qubit});
1079 }
1080
1089 void ApplyCRy(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit, double theta) override
1090 {
1091 JoinIfNeeded(ctrl_qubit, tgt_qubit);
1092 GetSimulator(ctrl_qubit)->ApplyCRy(ctrl_qubit, tgt_qubit, theta);
1093 NotifyObservers({tgt_qubit, ctrl_qubit});
1094 }
1095
1104 void ApplyCRz(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit, double theta) override
1105 {
1106 JoinIfNeeded(ctrl_qubit, tgt_qubit);
1107 GetSimulator(ctrl_qubit)->ApplyCRz(ctrl_qubit, tgt_qubit, theta);
1108 NotifyObservers({tgt_qubit, ctrl_qubit});
1109 }
1110
1118 void ApplyCH(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit) override
1119 {
1120 JoinIfNeeded(ctrl_qubit, tgt_qubit);
1121 GetSimulator(ctrl_qubit)->ApplyCH(ctrl_qubit, tgt_qubit);
1122 NotifyObservers({tgt_qubit, ctrl_qubit});
1123 }
1124
1132 void ApplyCSx(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit) override
1133 {
1134 JoinIfNeeded(ctrl_qubit, tgt_qubit);
1135 GetSimulator(ctrl_qubit)->ApplyCSx(ctrl_qubit, tgt_qubit);
1136 NotifyObservers({tgt_qubit, ctrl_qubit});
1137 }
1138
1146 void ApplyCSxDAG(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit) override
1147 {
1148 JoinIfNeeded(ctrl_qubit, tgt_qubit);
1149 GetSimulator(ctrl_qubit)->ApplyCSxDAG(ctrl_qubit, tgt_qubit);
1150 NotifyObservers({tgt_qubit, ctrl_qubit});
1151 }
1152
1160 void ApplySwap(Types::qubit_t qubit0, Types::qubit_t qubit1) override
1161 {
1162 JoinIfNeeded(qubit0, qubit1);
1163 GetSimulator(qubit0)->ApplySwap(qubit0, qubit1);
1164 NotifyObservers({qubit1, qubit0});
1165 }
1166
1175 void ApplyCCX(Types::qubit_t qubit0, Types::qubit_t qubit1, Types::qubit_t qubit2) override
1176 {
1177 // TODO: See if it's worth optimizing to joing all three simulators at once,
1178 // if needed (that is, there is a different one for each qubit)
1179 JoinIfNeeded(qubit0, qubit1);
1180 JoinIfNeeded(qubit0, qubit2);
1181 GetSimulator(qubit0)->ApplyCCX(qubit0, qubit1, qubit2);
1182 NotifyObservers({qubit2, qubit1, qubit0});
1183 }
1184
1193 void ApplyCSwap(Types::qubit_t ctrl_qubit, Types::qubit_t qubit0, Types::qubit_t qubit1) override
1194 {
1195 // TODO: See if it's worth optimizing to joing all three simulators at once,
1196 // if needed (that is, there is a different one for each qubit)
1197 JoinIfNeeded(ctrl_qubit, qubit0);
1198 JoinIfNeeded(ctrl_qubit, qubit1);
1199 GetSimulator(ctrl_qubit)->ApplyCSwap(ctrl_qubit, qubit0, qubit1);
1200 NotifyObservers({qubit1, qubit0, ctrl_qubit});
1201 }
1202
1214 void ApplyCU(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit, double theta, double phi, double lambda, double gamma) override
1215 {
1216 JoinIfNeeded(ctrl_qubit, tgt_qubit);
1217 GetSimulator(ctrl_qubit)->ApplyCU(ctrl_qubit, tgt_qubit, theta, phi, lambda, gamma);
1218 NotifyObservers({tgt_qubit, ctrl_qubit});
1219 }
1220
1221 void ApplyNop() override
1222 {
1223 GetSimulator(0)->ApplyNop();
1224 }
1225
1234 void SetMultithreading(bool multithreading = true) override
1235 {
1236 enableMultithreading = multithreading;
1237 for (auto &[id, simulator] : simulators)
1238 simulator->SetMultithreading(multithreading);
1239 }
1240
1248 bool GetMultithreading() const override
1249 {
1250 return enableMultithreading;
1251 }
1252
1263 bool IsQcsim() const override
1264 {
1265 return type == Simulators::SimulatorType::kQCSim;
1266 }
1267
1282 void SaveState() override
1283 {
1284 savedState = CloneStateForExecution(DeriveSeed(samplingSeed, 0x53415645ULL), false);
1285 }
1286
1300 void RestoreState() override
1301 {
1302 if (savedState)
1303 {
1304 const ImmediateCompositeSimulator *savedStatePtr = static_cast<ImmediateCompositeSimulator *>(savedState.get());
1305
1306 type = savedStatePtr->type;
1307 qubitsMap = savedStatePtr->qubitsMap;
1309
1310 nrQubits = savedStatePtr->nrQubits;
1311 nextId = savedStatePtr->nextId;
1312 enableMultithreading = savedStatePtr->enableMultithreading;
1314
1315 simulators.clear();
1316 const uint64_t restoreSeed = DeriveSeed(samplingSeed, 0x52455354ULL + nextRestoreStream++);
1317 for (auto &[id, simulator] : savedStatePtr->simulators)
1318 {
1319 auto isim = simulator->CloneForExecution(DeriveSeed(restoreSeed, id));
1320 simulators[id] = std::unique_ptr<IndividualSimulator>(static_cast<IndividualSimulator *>(isim.release()));
1321 }
1322 }
1323 }
1324
1342 {
1343 Types::qubit_t res = 0;
1344
1345 if (GetNumberOfQubits() > sizeof(Types::qubit_t) * 8)
1346 std::cerr << "Warning: The number of qubits to measure is larger than the "
1347 "number of bits in the Types::qubit_t type, the outcome will be "
1348 "undefined"
1349 << std::endl;
1350
1351 for (auto &[id, simulator] : simulators)
1352 {
1353 const Types::qubit_t meas = simulator->MeasureNoCollapse();
1354 res |= meas;
1355 }
1356
1357 return res;
1358 }
1359
1374 std::vector<bool> MeasureNoCollapseMany() override
1375 {
1376 std::vector<bool> res(nrQubits, false);
1377 for (auto &[id, simulator] : simulators)
1378 {
1379 const std::vector<bool> meas = simulator->MeasureNoCollapseMany();
1380 for (size_t i = 0; i < meas.size(); ++i)
1381 if (meas[i])
1382 res[i] = true;
1383 }
1384 return res;
1385 }
1386
1397 std::unique_ptr<ISimulator> Clone() override
1398 {
1399 if (nextCloneStream == std::numeric_limits<uint64_t>::max())
1400 throw std::overflow_error("Composite clone stream exhausted");
1401 return CloneForExecution(DeriveSeed(samplingSeed, nextCloneStream++));
1402 }
1403
1404 std::unique_ptr<ISimulator> CloneForExecution(uint64_t seed) override
1405 {
1406 return CloneStateForExecution(seed, true);
1407 }
1408
1409 private:
1410 std::unique_ptr<ISimulator> CloneStateForExecution(uint64_t seed, bool includeSnapshot)
1411 {
1412 auto clone = std::make_unique<ImmediateCompositeSimulator>(type);
1413
1414 clone->type = type;
1415 clone->qubitsMap = qubitsMap;
1417
1418 clone->nrQubits = nrQubits;
1419 clone->nextId = nextId;
1420 clone->enableMultithreading = enableMultithreading;
1422
1423 clone->config = config;
1424
1425 for (auto &[id, simulator] : simulators)
1426 {
1427 auto isim = simulator->CloneForExecution(DeriveSeed(seed, id));
1428 clone->simulators[id] = std::unique_ptr<IndividualSimulator>(static_cast<IndividualSimulator *>(isim.release()));
1429 }
1430
1431 if (includeSnapshot && savedState)
1432 clone->savedState = static_cast<ImmediateCompositeSimulator *>(savedState.get())->CloneStateForExecution(DeriveSeed(seed, 0x53415645ULL), false);
1433
1434 clone->SetSeed(seed);
1435 return clone;
1436 }
1437
1438 public:
1439 const Configuration &GetConfiguration() const
1440 {
1441 return config;
1442 }
1443
1444 const std::unordered_map<std::string, std::string> &GetConfigMap() const override
1445 {
1446 return config.GetConfigMap();
1447 }
1448
1449 private:
1450 template <bool Many> Utils::Sampling::Counts<Many> SamplePrepared(const Types::qubits_vector &qubits, size_t shots)
1451 {
1452 using namespace Utils::Sampling;
1453 using Plan = QCSimStatevectorSimulator::SamplingPlan;
1454 Counts<Many> result;
1455 if (qubits.empty() || !shots)
1456 return result;
1457 ValidateQubits(qubits, nrQubits, Many);
1458 if (simulators.size() == 1)
1459 {
1460 auto &component = *simulators.begin()->second;
1461 if constexpr (Many)
1462 result = component.SampleCountsMany(qubits, shots);
1463 else
1464 result = component.SampleCounts(qubits, shots);
1465 NotifyObservers(qubits);
1466 return result;
1467 }
1468
1469 // Duplicate selections share a joint bit; output positions are expanded
1470 // only when inserting counts. No global qubit ID is used as a shift.
1471 std::unordered_map<size_t, size_t> uniqueBits;
1472 std::vector<size_t> outputBits;
1473 for (const auto qubit : qubits)
1474 outputBits.push_back(uniqueBits.emplace(qubit, uniqueBits.size()).first->second);
1475 constexpr size_t wordBits = std::numeric_limits<size_t>::digits;
1476 const size_t words = (uniqueBits.size() + wordBits - 1) / wordBits;
1477
1478 struct Component
1479 {
1480 size_t id;
1481 IndividualSimulator *simulator;
1482 std::vector<std::pair<size_t, size_t>> bits; // local bit -> joint bit
1483 std::unique_ptr<Plan> plan;
1484 std::vector<size_t> projection;
1485 size_t minimumBytes = 0;
1486 };
1487
1488 std::vector<Component> components;
1489 components.reserve(simulators.size());
1490 for (auto &[id, simulator] : simulators)
1491 {
1492 Component component{id, simulator.get()};
1493 for (auto [qubit, local] : simulator->GetQubitsMap())
1494 if (const auto found = uniqueBits.find(qubit); found != uniqueBits.end())
1495 component.bits.emplace_back(local, found->second);
1496 components.push_back(std::move(component));
1497 }
1498 // Stable budget assignment; neither hash iteration order nor worker
1499 // count is allowed to choose a component's sampling algorithm.
1500 std::sort(components.begin(), components.end(), [](const auto &a, const auto &b) { return a.id < b.id; });
1501 auto project = [&](const Component &component, size_t row, size_t *joint) {
1502 if (!component.projection.empty())
1503 joint[0] |= component.projection[row];
1504 else
1505 for (const auto [local, output] : component.bits)
1506 joint[output / wordBits] |= ((row >> local) & size_t{1}) << (output % wordBits);
1507 };
1508 std::vector<bool> key;
1509 if constexpr (Many)
1510 key.resize(qubits.size());
1511 auto insert = [&](const size_t *joint, size_t count) {
1512 if constexpr (Many)
1513 {
1514 for (size_t bit = 0; bit < outputBits.size(); ++bit)
1515 key[bit] = ((joint[outputBits[bit] / wordBits] >> (outputBits[bit] % wordBits)) & 1) != 0;
1516 result[key] += count;
1517 }
1518 else
1519 {
1520 Types::qubit_t packed = 0;
1521 for (size_t bit = 0; bit < outputBits.size(); ++bit)
1522 packed |= Types::qubit_t((joint[outputBits[bit] / wordBits] >> (outputBits[bit] % wordBits)) & 1) << bit;
1523 result[packed] += count;
1524 }
1525 };
1526 if (shots == 1)
1527 {
1528 std::vector<size_t> joint(words, 0);
1529 for (auto &component : components)
1530 if (component.bits.empty())
1531 component.simulator->DiscardLocalMeasurementDraw();
1532 else
1533 project(component, component.simulator->SampleLocalOnce(), joint.data());
1534 insert(joint.data(), 1);
1535 NotifyObservers(qubits);
1536 return result;
1537 }
1538
1539 const Options options = components.front().simulator->SamplingOptions();
1540 const size_t sharedReserve = options.memoryBytes / 4;
1541 size_t remaining = options.memoryBytes - sharedReserve, reserved = 0;
1542 const size_t selected = std::count_if(components.begin(), components.end(), [](const auto &c) { return !c.bits.empty(); });
1543 const size_t componentBudget = remaining / std::max<size_t>(1, selected);
1544 for (auto &component : components)
1545 if (!component.bits.empty())
1546 {
1547 component.minimumBytes = Plan::MinimumMemoryBytes(component.simulator->SamplingRows(), shots, componentBudget);
1548 if (component.minimumBytes > remaining - reserved)
1549 throw std::length_error("Composite probability indexes exceed the sampling scratch budget");
1550 reserved += component.minimumBytes;
1551 }
1552 for (auto &component : components)
1553 if (!component.bits.empty())
1554 {
1555 reserved -= component.minimumBytes;
1556 auto localOptions = options;
1557 localOptions.memoryBytes = remaining - reserved;
1558 component.plan = std::make_unique<Plan>(component.simulator->PrepareSampling(shots, localOptions));
1559 remaining -= localOptions.memoryBytes - component.plan->ScratchAvailable();
1560 }
1561 // Small local projection tables avoid per-shot bit permutation work.
1562 // Their memory comes from the same shared budget as sampling tables.
1563 for (auto &component : components)
1564 if (words == 1 && component.bits.size() > 1 && component.plan && component.plan->Size() <= 65536 &&
1565 shots >= std::max<size_t>(256, component.plan->Size()) && component.plan->Size() <= remaining / sizeof(size_t))
1566 {
1567 std::vector<size_t> table(component.plan->Size(), 0);
1568 for (size_t row = 0; row < table.size(); ++row)
1569 project(component, row, &table[row]);
1570 remaining -= table.size() * sizeof(size_t);
1571 component.projection = std::move(table);
1572 }
1573 remaining += sharedReserve;
1574 const size_t categories = uniqueBits.size() <= 20 ? size_t{1} << uniqueBits.size() : 0;
1575 const bool histogram = categories && shots >= std::max<size_t>(1024, categories / 4) && categories <= remaining / (2 * sizeof(size_t));
1576 std::vector<size_t> bins(histogram ? categories : 0, 0);
1577 remaining -= bins.size() * sizeof(size_t);
1578 // Uniforms, sampled rows, sorted-CDF scratch, and joint output words.
1579 if (words > (std::numeric_limits<size_t>::max() - 32) / sizeof(size_t))
1580 throw std::length_error("Composite sampling output is too wide");
1581 const size_t bytesPerShot = 32 + words * sizeof(size_t);
1582 const size_t batch = std::min({shots, options.batchSize, remaining / bytesPerShot});
1583 if (!batch)
1584 throw std::length_error("Composite sampling batch exceeds the scratch budget");
1585 std::vector<size_t> joint(batch * words), sampled;
1586 std::vector<double> uniforms;
1587 // Unselected components still consume their original one word per shot.
1588 // Omitting their probability preparation cannot restart later streams.
1589 for (auto &component : components)
1590 if (component.bits.empty())
1591 component.simulator->SamplingRng().discard(shots);
1592 for (size_t begin = 0; begin < shots;)
1593 {
1594 const size_t count = std::min(batch, shots - begin);
1595 std::fill(joint.begin(), joint.begin() + count * words, 0);
1596 for (auto &component : components)
1597 {
1598 if (!component.plan)
1599 continue;
1600 const auto &plan = *component.plan;
1601 auto &rng = component.simulator->SamplingRng();
1602 const int threads = plan.Categories() <= 64 && plan.Method() == Family::Alias ? 1 : Threads(options.multithreading, count);
1603 if (threads == 1 && (plan.Method() == Family::Alias || plan.Size() <= 65536 || count < 128))
1604 {
1605 for (size_t shot = 0; shot < count; ++shot)
1606 project(component, plan.Sample(Utils::RandomStream::Uniform(rng)), &joint[shot * words]);
1607 }
1608 else
1609 {
1610 uniforms.resize(count);
1611 for (double &value : uniforms)
1612 value = Utils::RandomStream::Uniform(rng);
1613 plan.Draw(uniforms, sampled, threads);
1614#ifdef _OPENMP
1615#pragma omp parallel for if (threads > 1) num_threads(threads) schedule(static)
1616#endif
1617 for (int64_t shot = 0; shot < static_cast<int64_t>(count); ++shot)
1618 project(component, sampled[shot], &joint[size_t(shot) * words]);
1619 }
1620 }
1621 for (size_t shot = 0; shot < count; ++shot)
1622 if (histogram)
1623 ++bins[joint[shot]];
1624 else
1625 insert(&joint[shot * words], 1);
1626 begin += count;
1627 }
1628 for (size_t row = 0; row < bins.size(); ++row)
1629 if (bins[row])
1630 insert(&row, bins[row]);
1631 NotifyObservers(qubits);
1632 return result;
1633 }
1634
1635 void InitializeAlias()
1636 {
1637 for (auto &[id, simulator] : simulators)
1638 simulator->InitializeAlias();
1639 }
1640
1641 void ClearAlias()
1642 {
1643 for (auto &[id, simulator] : simulators)
1644 simulator->ClearAlias();
1645 }
1646
1655 inline std::unique_ptr<IndividualSimulator> &GetSimulator(size_t qubit)
1656 {
1657 assert(qubitsMap.size() > qubit);
1658 assert(simulators.find(qubitsMap[qubit]) != simulators.end());
1659
1660 // assume it was called with a valid qubit
1661 return simulators[qubitsMap[qubit]];
1662 }
1663
1664 // if executing a gate on two or three qubits, use this to see if it can be
1665 // executed directly if it returns true, join the two simulators together and
1666 // execute the gate (for three qubits, do that once again for the third qubit,
1667 // before execution)
1678 inline bool JoinNeeded(size_t qubit1, size_t qubit2)
1679 {
1680 // TODO: Not really needed for all gates that act on multiple qubits, is
1681 // this worth pursuing? the obvious example is the identity gate, which does
1682 // nothing, so a join is not needed
1683 return qubitsMap[qubit1] != qubitsMap[qubit2];
1684 }
1685
1694 inline void JoinIfNeeded(size_t qubit1, size_t qubit2)
1695 {
1696 if (JoinNeeded(qubit1, qubit2))
1697 {
1698 const size_t simId = qubitsMap[qubit1];
1699 const size_t eraseSimId = qubitsMap[qubit2];
1700 auto &sim1 = GetSimulator(qubit1);
1701 const auto &sim2 = GetSimulator(qubit2);
1702
1703 sim1->Join(simId, sim2, qubitsMap, enableMultithreading);
1704
1705 simulators.erase(eraseSimId);
1706 }
1707 }
1708
1719 inline void Split(size_t qubit, bool qubitOutcome = false)
1720 {
1721 auto &sim = GetSimulator(qubit); // get the simulator for the qubit, this one will be split
1722 if (sim->GetNumberOfQubits() == 1) // no need to split it, it's already for a single qubit
1723 return;
1724
1725 if (nextSplitStream == std::numeric_limits<uint64_t>::max())
1726 throw std::overflow_error("Composite split stream exhausted");
1727 // A detached component gets its own stream. Restores and register
1728 // rebuilds must not rewind this ordinal and reuse a child's seed.
1729 const uint64_t splitSeed = DeriveSeed(DeriveSeed(samplingSeed, 0x53504C4954ULL), nextSplitStream++);
1730 qubitsMap[qubit] = nextId; // the qubit will be in the new simulator
1731 simulators[nextId] = sim->Split(qubit, qubitOutcome, enableMultithreading, splitSeed);
1732
1733 ++nextId;
1734 }
1735
1736 SimulatorType type;
1737 std::vector<size_t> qubitsMap;
1739 std::unordered_map<size_t, std::unique_ptr<IndividualSimulator>> simulators;
1740 size_t nrQubits = 0;
1741 size_t nextId = 0;
1742 bool enableMultithreading = true;
1743
1744 std::unique_ptr<ISimulator> savedState;
1745
1746 Configuration config;
1747 uint64_t samplingSeed = Utils::RandomStream::FreshSeed();
1748 uint64_t nextCloneStream = 0, nextRestoreStream = 0, nextInitialization = 0, nextSplitStream = 0;
1749};
1750
1751} // namespace Private
1752} // namespace Simulators
1753
1754#endif
1755#endif
int ApplyK(void *sim, int qubit)
double Probability(void *sim, unsigned long long int outcome)
char * GetConfiguration(void *sim, const char *key)
int RestoreState(void *sim)
int ApplyRx(void *sim, int qubit, double theta)
int ApplyReset(void *sim, const unsigned long int *qubits, unsigned long int nrQubits)
int ApplyX(void *sim, int qubit)
int ApplyU(void *sim, int qubit, double theta, double phi, double lambda, double gamma)
int ApplyCRy(void *sim, int controlQubit, int targetQubit, double theta)
int ApplyTDG(void *sim, int qubit)
int ApplyS(void *sim, int qubit)
int ApplyCX(void *sim, int controlQubit, int targetQubit)
unsigned long int AllocateQubits(void *sim, unsigned long int nrQubits)
int ApplyCRz(void *sim, int controlQubit, int targetQubit, double theta)
unsigned long int GetNumberOfQubits(void *sim)
double * AllProbabilities(void *sim)
unsigned long long int MeasureNoCollapse(void *sim)
int ApplyCP(void *sim, int controlQubit, int targetQubit, double theta)
int GetMultithreading(void *sim)
int ApplySDG(void *sim, int qubit)
unsigned long long int Measure(void *sim, const unsigned long int *qubits, unsigned long int nrQubits)
int ApplyCSwap(void *sim, int controlQubit, int qubit1, int qubit2)
int ApplyCCX(void *sim, int controlQubit1, int controlQubit2, int targetQubit)
int ApplyY(void *sim, int qubit)
double * Amplitude(void *sim, unsigned long long int outcome)
int ApplyZ(void *sim, int qubit)
int ApplyH(void *sim, int qubit)
int ApplyGenericThreeQubitGate(void *sim, unsigned long q0, unsigned long q1, unsigned long q2, const double *buffer)
int ApplyCY(void *sim, int controlQubit, int targetQubit)
double * Probabilities(void *sim, const unsigned long long int *qubits, unsigned long int nrQubits)
int SetMultithreading(void *sim, int multithreading)
int ApplyCU(void *sim, int controlQubit, int targetQubit, double theta, double phi, double lambda, double gamma)
int ApplySwap(void *sim, int qubit1, int qubit2)
void * GetSimulator(unsigned long int simHandle)
int ApplyRy(void *sim, int qubit, double theta)
int ApplyP(void *sim, int qubit, double theta)
int SaveStateToInternalDestructive(void *sim)
int ApplyCH(void *sim, int controlQubit, int targetQubit)
int GetSimulationType(void *sim)
unsigned long long int * SampleCounts(void *sim, const unsigned long long int *qubits, unsigned long int nrQubits, unsigned long int shots)
int ApplyCZ(void *sim, int controlQubit, int targetQubit)
int ApplyRz(void *sim, int qubit, double theta)
int RestoreInternalDestructiveSavedState(void *sim)
int ApplyT(void *sim, int qubit)
int ApplyCRx(void *sim, int controlQubit, int targetQubit, double theta)
int ApplyGenericOneQubitGate(void *sim, unsigned long q0, const double *buffer)
int ApplyGenericTwoQubitGate(void *sim, unsigned long q0, unsigned long q1, const double *buffer)
int IsQcsim(void *sim)
int SaveState(void *sim)
Bounded online routing based on current physical bond dimensions.
@ kQCSim
qcsim simulator type
Definition State.h:83
@ kQiskitAer
qiskit aer simulator type
Definition State.h:81
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
double Uniform(std::mt19937_64 &engine)
bool ValidateSetting(std::string_view name, std::string_view setting)
Definition Sampling.h:45
int Threads(bool permitted, size_t work)
Definition Sampling.h:65
void ValidateQubits(const Qubits &qubits, size_t width, bool many)
Definition Sampling.h:394
std::unordered_map< std::conditional_t< Many, std::vector< bool >, Types::qubit_t >, Types::qubit_t > Counts
Definition Sampling.h:373