Maestro 0.3.11
Unified interface for quantum circuit simulation
Loading...
Searching...
No Matches
Individual.h
Go to the documentation of this file.
1
11
12#ifndef _INDIVIDUAL_H
13#define _INDIVIDUAL_H
14
15#ifdef INCLUDED_BY_FACTORY
16
18#include "../Aer/AerSimulator.h"
19#include "../Core/Factory.h"
21#include <unordered_map>
22
23namespace Simulators
24{
25
26// TODO: Maybe use the pimpl idiom
27// https://en.cppreference.com/w/cpp/language/pimpl to hide the implementation
28// for good but during development this should be good enough
29namespace Private
30{
31
32class ImmediateCompositeSimulator;
33
42class IndividualSimulator : public ISimulator
43{
44 friend class ImmediateCompositeSimulator;
45
46 public:
53 IndividualSimulator(SimulatorType type =
54#ifdef NO_QISKIT_AER
56#else
58#endif
59 ) noexcept
60 : simulator(SimulatorsFactory::CreateImmediateSimulatorUnique(type))
61 {
62 }
63
71 void Reset() override
72 {
73 simulator->Reset();
74 }
75
87 inline void Join(size_t simId, const std::unique_ptr<IndividualSimulator> &other, std::vector<size_t> &qubitsMapToSim, bool enableMultithreading)
88 {
89 // 1. grab the state of both simulators (in the first phase, using
90 // Amplitude, but maybe something faster can be done)
91 // 2. join the states by a tensor product
92 const size_t nrQubits1 = GetNumberOfQubits();
93 const size_t nrBasisStates1 = 1ULL << nrQubits1;
94 const size_t nrQubits2 = other->GetNumberOfQubits();
95 const size_t nrBasisStates2 = 1ULL << nrQubits2;
96
97 const size_t newNrQubits = nrQubits1 + nrQubits2;
98 const size_t nrBasisStates = 1ULL << newNrQubits;
99
101 other->SaveStateToInternalDestructive();
102
103 if (GetType() == SimulatorType::kQCSim)
104 {
105 if (enableMultithreading && nrBasisStates > OmpLimitJoin)
106 JoinOmpQcsim(nrQubits1, nrBasisStates1, nrBasisStates2, newNrQubits, nrBasisStates, other, enableMultithreading);
107 else
108 {
109 Eigen::VectorXcd newAmplitudes;
110 newAmplitudes.resize(nrBasisStates);
111
112 for (size_t state2 = 0; state2 < nrBasisStates2; ++state2)
113 {
114 const auto ampl2 = other->AmplitudeRaw(state2);
115 const size_t state2Mask = state2 << nrQubits1;
116 for (size_t state1 = 0; state1 < nrBasisStates1; ++state1)
117 newAmplitudes[state2Mask | state1] = AmplitudeRaw(state1) * ampl2;
118 }
119
120 // 3. set the state of the current simulator to the joined state
121 // the original qubits of this simulator get mapped as they are
122 // the other ones get shifted to the left by the number of qubits of
123 // this simulator so transfer mapping keeping this in mind
124
125 // simulator->SetMultithreading(enableMultithreading);
126 static_cast<QCSimStatevectorSimulator *>(simulator.get())
127 ->ReplaceState(newNrQubits,
128 newAmplitudes); // this will end up by swapping the data from
129 // newAmplitudes to the simulator, no allocation
130 // and copying is done
131 }
132 }
133#ifndef NO_QISKIT_AER
134 else
135 {
136 if (enableMultithreading && nrBasisStates > OmpLimitJoin)
137 JoinOmpAer(nrQubits1, nrBasisStates1, nrBasisStates2, newNrQubits, nrBasisStates, other, enableMultithreading);
138 else
139 {
140 AER::Vector<std::complex<double>> newAmplitudes(nrBasisStates, false); // the false here avoids data
141 // initialization, it will be set anyway
142
143 for (size_t state2 = 0; state2 < nrBasisStates2; ++state2)
144 {
145 const auto ampl2 = other->AmplitudeRaw(state2);
146 const size_t state2Mask = state2 << nrQubits1;
147 for (size_t state1 = 0; state1 < nrBasisStates1; ++state1)
148 newAmplitudes[state2Mask | state1] = AmplitudeRaw(state1) * ampl2;
149 }
150
151 // 3. set the state of the current simulator to the joined state
152 // the original qubits of this simulator get mapped as they are
153 // the other ones get shifted to the left by the number of qubits of
154 // this simulator so transfer mapping keeping this in mind
155 // simulator->SetMultithreading(enableMultithreading);
156 simulator->InitializeState(newNrQubits,
157 newAmplitudes); // this will move the data from newAmplitudes to
158 // the simulator, no allocation and copying is done
159 }
160 }
161#endif
162
163 for (auto [origq, mapq] : other->GetQubitsMap())
164 {
165 qubitsMap[origq] = mapq + nrQubits1;
166 qubitsMapToSim[origq] = simId;
167 }
168 }
169
183 inline std::unique_ptr<IndividualSimulator> Split(size_t qubit, bool qubitOutcome, bool enableMultithreading, uint64_t splitSeed)
184 {
185 const size_t oldNrQubits = GetNumberOfQubits();
186 const size_t newNrQubits = oldNrQubits - 1;
187 const size_t nrBasisStates = 1ULL << newNrQubits;
188 const size_t localQubit = qubitsMap[qubit];
189
190 // the new simulator split from this one
191 // a one qubit one, containing the qubit that was measured or reset
192 // TODO: this can be optimized a little bit by directly initializing with
193 // the proper amplitudes, but I'm not sure if it's worth it
194 auto newSimulator = std::make_unique<IndividualSimulator>(GetType());
195 newSimulator->AllocateQubits(1);
196 newSimulator->GetQubitsMap()[qubit] = 0; // the qubit is mapped to the only local qubit in the new simulator,
197 // which is 0
198 newSimulator->SetMultithreading(enableMultithreading);
199
200 for (const auto &[key, value] : GetConfigMap())
201 if (key != "seed")
202 newSimulator->Configure(key.c_str(), value.c_str());
203 newSimulator->SetSeed(splitSeed);
204
205 newSimulator->Initialize();
206 if (qubitOutcome)
207 {
208 newSimulator->ApplyX(qubit);
209 // newSimulator->Flush();
210 }
211
212 qubitsMap.erase(qubit); // the qubit is removed from the current simulator
213
215
216 if (GetType() == SimulatorType::kQCSim)
217 {
218 /*
219 if (nrBasisStates > OmpLimitSplit) // parallelization for assignment and
220 some bit manipulations, I must do some benchmarks to see if it's worth it
221 and find where the limit is SplitOmpQcsim(localQubit, newNrQubits,
222 nrBasisStates, qubitOutcome); else
223 */
224 {
225 // now the adjusted current simulator, without the removed qubit
226 Eigen::VectorXcd newAmplitudes;
227 newAmplitudes.resize(nrBasisStates);
228
229 // compute the new amplitudes
230
231 const size_t localQubitMask = 1ULL << localQubit;
232 const size_t maskLow = localQubitMask - 1ULL;
233 const size_t maskHigh = ~maskLow;
234 const size_t qubitMask = qubitOutcome ? localQubitMask : 0ULL;
235
236 for (size_t state = 0; state < nrBasisStates; ++state)
237 {
238 const size_t stateLow = state & maskLow;
239 const size_t stateHigh = (state & maskHigh) << 1ULL;
240
241 newAmplitudes[state] = AmplitudeRaw(stateLow | stateHigh | qubitMask);
242 }
243
244 // simulator->SetMultithreading(enableMultithreading);
245 static_cast<QCSimStatevectorSimulator *>(simulator.get())
246 ->ReplaceState(newNrQubits,
247 newAmplitudes); // this will end up by swapping the data from
248 // newAmplitudes to the simulator, no allocation
249 // and copying is done
250 }
251 }
252#ifndef NO_QISKIT_AER
253 else
254 {
255 /*
256 if (nrBasisStates > OmpLimitSplit) // parallelization for assignment and
257 some bit manipulations, I must do some benchmarks to see if it's worth it
258 and find where the limit is SplitOmpAer(localQubit, newNrQubits,
259 nrBasisStates, qubitOutcome); else
260 */
261 {
262 // now the adjusted current simulator, without the removed qubit
263 AER::Vector<std::complex<double>> newAmplitudes(nrBasisStates, false); // the false here avoids data
264 // initialization, it will be set anyway
265
266 // compute the new amplitudes
267 const size_t localQubitMask = 1ULL << localQubit;
268 const size_t maskLow = localQubitMask - 1ULL;
269 const size_t maskHigh = ~maskLow;
270 const size_t qubitMask = qubitOutcome ? localQubitMask : 0ULL;
271
272 for (size_t state = 0; state < nrBasisStates; ++state)
273 {
274 const size_t stateLow = state & maskLow;
275 const size_t stateHigh = (state & maskHigh) << 1ULL;
276
277 newAmplitudes[state] = AmplitudeRaw(stateLow | stateHigh | qubitMask);
278 }
279
280 // simulator->SetMultithreading(enableMultithreading);
281 simulator->InitializeState(newNrQubits,
282 newAmplitudes); // this will move the data from newAmplitudes to
283 // the simulator, no allocation and copying is done
284 }
285 }
286#endif
287
288 // now adjust the local qubits map
289 for (auto &mapped : qubitsMap)
290 if (mapped.second > localQubit)
291 --mapped.second;
292
293 return newSimulator;
294 }
295
305 std::complex<double> AmplitudeRaw(Types::qubit_t outcome) override
306 {
307 return simulator->AmplitudeRaw(outcome);
308 }
309
319 void SaveStateToInternalDestructive() override
320 {
321 simulator->SaveStateToInternalDestructive();
322 }
323
331 {
332 simulator->RestoreInternalDestructiveSavedState();
333 }
334
344 inline Types::qubits_vector ConvertQubits(const Types::qubits_vector &qubits)
345 {
346 Types::qubits_vector converted;
347 converted.reserve(qubits.size());
348
349 for (auto qubit : qubits)
350 if (HasQubit(qubit))
351 converted.emplace_back(qubitsMap[qubit]);
352
353 return converted;
354 }
355
364 inline bool HasQubit(Types::qubit_t qubit) const
365 {
366 return qubitsMap.find(qubit) != qubitsMap.end();
367 }
368
377 inline Types::qubit_t ConvertOutcomeFromLocal(Types::qubit_t outcome) const
378 {
379 Types::qubit_t res = 0;
380
381 for (auto [origQubit, localQubit] : qubitsMap)
382 if (outcome & (1ULL << localQubit))
383 res |= (1ULL << origQubit);
384
385 return res;
386 }
387
396 inline Types::qubit_t ConvertOutcomeFromGlobal(Types::qubit_t outcome) const
397 {
398 Types::qubit_t res = 0;
399
400 for (auto [origQubit, localQubit] : qubitsMap)
401 if (outcome & (1ULL << origQubit))
402 res |= (1ULL << localQubit);
403
404 return res;
405 }
406
415 inline std::vector<bool> ConvertOutcomeFromLocal(const std::vector<bool> &outcome) const
416 {
417 std::vector<bool> res;
418
419 size_t maxQubit = 0;
420 for (auto [origQubit, localQubit] : qubitsMap)
421 if (origQubit > maxQubit)
422 maxQubit = origQubit;
423 res.resize(maxQubit + 1, false);
424
425 for (auto [origQubit, localQubit] : qubitsMap)
426 if (outcome[localQubit])
427 res[origQubit] = true;
428
429 return res;
430 }
431
440 inline std::vector<bool> ConvertOutcomeFromGlobal(const std::vector<bool> &outcome) const
441 {
442 const size_t nrQubits = simulator->GetNumberOfQubits();
443 std::vector<bool> res(nrQubits, false);
444
445 for (auto [origQubit, localQubit] : qubitsMap)
446 if (outcome[origQubit])
447 res[localQubit] = true;
448
449 return res;
450 }
451
457 void SaveState() override
458 {
459 if (!simulator)
460 return;
461 const size_t nrBasisStates = 1ULL << simulator->GetNumberOfQubits();
462 savedState.resize(nrBasisStates);
463
464 for (Types::qubit_t state = 0; state < nrBasisStates; ++state)
465 savedState[state] = simulator->Amplitude(state);
466 }
467
473 void ClearSavedState()
474 {
475 savedState.clear();
476 }
477
483 void RestoreState() override
484 {
485 if (!simulator || savedState.empty())
486 return;
487 const size_t nrQubits = simulator->GetNumberOfQubits();
488
489 if (GetType() == SimulatorType::kQCSim)
490 {
491 Eigen::VectorXcd amplitudes(Eigen::Map<const Eigen::VectorXcd>(savedState.data(), savedState.size()));
492 static_cast<QCSimStatevectorSimulator *>(simulator.get())->ReplaceState(nrQubits, amplitudes);
493 }
494 else
495 {
496 simulator->Clear();
497 simulator->InitializeState(nrQubits, savedState);
498 }
499 ClearSavedState();
500 }
501
510 inline std::unordered_map<Types::qubit_t, Types::qubit_t> &GetQubitsMap()
511 {
512 return qubitsMap;
513 }
514
523 inline const std::unordered_map<Types::qubit_t, Types::qubit_t> &GetQubitsMap() const
524 {
525 return qubitsMap;
526 }
527
534 void Initialize() override
535 {
536 simulator->Initialize();
537 }
538
551 void InitializeState(size_t num_qubits, std::vector<std::complex<double>> &amplitudes) override
552 {
553 simulator->InitializeState(num_qubits, amplitudes);
554 }
555
568 /*
569 void InitializeState(size_t num_qubits, std::vector<std::complex<double>,
570 avoid_init_allocator<std::complex<double>>>& amplitudes) override
571 {
572 simulator->InitializeState(num_qubits, amplitudes);
573 }
574 */
575
588#ifndef NO_QISKIT_AER
589 void InitializeState(size_t num_qubits, AER::Vector<std::complex<double>> &amplitudes) override
590 {
591 simulator->InitializeState(num_qubits, amplitudes);
592 }
593#endif
594
607 void InitializeState(size_t num_qubits, Eigen::VectorXcd &amplitudes) override
608 {
609 simulator->InitializeState(num_qubits, amplitudes);
610 }
611
620 void Configure(const char *key, const char *value) override
621 {
622 if (std::string("seed") == key)
623 {
624 const uint64_t seed = std::stoull(value);
625 SeedAuxiliaryRng(seed);
626 simulator->SetSeed(seed);
627 }
628 else
629 {
630 simulator->Configure(key, value);
631 }
632 }
633
641 std::string GetConfiguration(const char *key) const override
642 {
643 if (!simulator)
644 return "";
645
646 return simulator->GetConfiguration(key);
647 }
648
656 size_t AllocateQubits(size_t num_qubits) override
657 {
658 return simulator->AllocateQubits(num_qubits);
659 }
660
667 size_t GetNumberOfQubits() const override
668 {
669 return simulator->GetNumberOfQubits();
670 }
671
679 void Clear() override
680 {
681 simulator->Clear();
682 }
683
695 size_t Measure(const Types::qubits_vector &qubits) override
696 {
697 return ConvertOutcomeFromLocal(simulator->Measure(ConvertQubits(qubits)));
698 }
699
708 std::vector<bool> MeasureMany(const Types::qubits_vector &qubits) override
709 {
710 return ConvertOutcomeFromLocal(simulator->MeasureMany(ConvertQubits(qubits)));
711 }
712
719 void ApplyReset(const Types::qubits_vector &qubits) override
720 {
721 simulator->ApplyReset(ConvertQubits(qubits));
722 }
723
735 double Probability(Types::qubit_t outcome) override
736 {
737 return simulator->Probability(ConvertOutcomeFromGlobal(outcome));
738 }
739
750 std::complex<double> Amplitude(Types::qubit_t outcome) override
751 {
752 return simulator->Amplitude(ConvertOutcomeFromGlobal(outcome));
753 }
754
768 std::complex<double> ProjectOnZero() override
769 {
770 return Amplitude(0);
771 }
772
782 std::vector<double> AllProbabilities() override
783 {
784 return simulator->AllProbabilities();
785 }
786
798 std::vector<double> Probabilities(const Types::qubits_vector &qubits) override
799 {
800 return simulator->Probabilities(ConvertQubits(qubits));
801 }
802
820 std::unordered_map<Types::qubit_t, Types::qubit_t> SampleCounts(const Types::qubits_vector &qubits, size_t shots = 1000) override
821 {
822 // Results are indexed by positions in the requested list, not by local
823 // register IDs. Convert the input IDs only; preserve the returned order.
824 return simulator->SampleCounts(ConvertQubits(qubits), shots);
825 }
826
840 std::unordered_map<std::vector<bool>, Types::qubit_t> SampleCountsMany(const Types::qubits_vector &qubits, size_t shots = 1000) override
841 {
842 return simulator->SampleCountsMany(ConvertQubits(qubits), shots);
843 }
844
856 double ExpectationValue(const std::string &pauliString) override
857 {
858 return simulator->ExpectationValue(pauliString);
859 }
860
868 SimulatorType GetType() const override
869 {
870 return simulator->GetType();
871 }
872
881 SimulationType GetSimulationType() const override
882 {
883 return SimulationType::kStatevector;
884 }
885
893 void Flush() override
894 {
895 simulator->Flush();
896 }
897
898 // WARNING: for all the following functions, the call is supposed to be made
899 // after the proper joining (or splitting)!
900
906 void ApplyGenericOneQubitGate(Types::qubit_t qubit, const Eigen::Matrix2cd &gate) override
907 {
908 simulator->ApplyGenericOneQubitGate(qubitsMap[qubit], gate);
909 }
910
917 void ApplyGenericTwoQubitGate(Types::qubit_t qubit0, Types::qubit_t qubit1, const Eigen::Matrix4cd &gate) override
918 {
919 simulator->ApplyGenericTwoQubitGate(qubitsMap[qubit0], qubitsMap[qubit1], gate);
920 }
921
922 void ApplyGenericThreeQubitGate(Types::qubit_t q0, Types::qubit_t q1, Types::qubit_t q2, const Matrix8cd &gate) override
923 {
924 simulator->ApplyGenericThreeQubitGate(qubitsMap.at(q0), qubitsMap.at(q1), qubitsMap.at(q2), gate);
925 }
926
934 void ApplyP(Types::qubit_t qubit, double lambda) override
935 {
936 simulator->ApplyP(qubitsMap[qubit], lambda);
937 }
938
945 void ApplyX(Types::qubit_t qubit) override
946 {
947 simulator->ApplyX(qubitsMap[qubit]);
948 }
949
956 void ApplyY(Types::qubit_t qubit) override
957 {
958 simulator->ApplyY(qubitsMap[qubit]);
959 }
960
967 void ApplyZ(Types::qubit_t qubit) override
968 {
969 simulator->ApplyZ(qubitsMap[qubit]);
970 }
971
978 void ApplyH(Types::qubit_t qubit) override
979 {
980 simulator->ApplyH(qubitsMap[qubit]);
981 }
982
989 void ApplyS(Types::qubit_t qubit) override
990 {
991 simulator->ApplyS(qubitsMap[qubit]);
992 }
993
1000 void ApplySDG(Types::qubit_t qubit) override
1001 {
1002 simulator->ApplySDG(qubitsMap[qubit]);
1003 }
1004
1011 void ApplyT(Types::qubit_t qubit) override
1012 {
1013 simulator->ApplyT(qubitsMap[qubit]);
1014 }
1015
1022 void ApplyTDG(Types::qubit_t qubit) override
1023 {
1024 simulator->ApplyTDG(qubitsMap[qubit]);
1025 }
1026
1033 void ApplySx(Types::qubit_t qubit) override
1034 {
1035 simulator->ApplySx(qubitsMap[qubit]);
1036 }
1037
1044 void ApplySxDAG(Types::qubit_t qubit) override
1045 {
1046 simulator->ApplySxDAG(qubitsMap[qubit]);
1047 }
1048
1055 void ApplyK(Types::qubit_t qubit) override
1056 {
1057 simulator->ApplyK(qubitsMap[qubit]);
1058 }
1059
1067 void ApplyRx(Types::qubit_t qubit, double theta) override
1068 {
1069 simulator->ApplyRx(qubitsMap[qubit], theta);
1070 }
1071
1079 void ApplyRy(Types::qubit_t qubit, double theta) override
1080 {
1081 simulator->ApplyRy(qubitsMap[qubit], theta);
1082 }
1083
1091 void ApplyRz(Types::qubit_t qubit, double theta) override
1092 {
1093 simulator->ApplyRz(qubitsMap[qubit], theta);
1094 }
1095
1106 void ApplyU(Types::qubit_t qubit, double theta, double phi, double lambda, double gamma) override
1107 {
1108 simulator->ApplyU(qubitsMap[qubit], theta, phi, lambda, gamma);
1109 }
1110
1118 void ApplyCX(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit) override
1119 {
1120 simulator->ApplyCX(qubitsMap[ctrl_qubit], qubitsMap[tgt_qubit]);
1121 }
1122
1130 void ApplyCY(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit) override
1131 {
1132 simulator->ApplyCY(qubitsMap[ctrl_qubit], qubitsMap[tgt_qubit]);
1133 }
1134
1142 void ApplyCZ(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit) override
1143 {
1144 simulator->ApplyCZ(qubitsMap[ctrl_qubit], qubitsMap[tgt_qubit]);
1145 }
1146
1155 void ApplyCP(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit, double lambda) override
1156 {
1157 simulator->ApplyCP(qubitsMap[ctrl_qubit], qubitsMap[tgt_qubit], lambda);
1158 }
1159
1168 void ApplyCRx(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit, double theta) override
1169 {
1170 simulator->ApplyCRx(qubitsMap[ctrl_qubit], qubitsMap[tgt_qubit], theta);
1171 }
1172
1181 void ApplyCRy(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit, double theta) override
1182 {
1183 simulator->ApplyCRy(qubitsMap[ctrl_qubit], qubitsMap[tgt_qubit], theta);
1184 }
1185
1194 void ApplyCRz(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit, double theta) override
1195 {
1196 simulator->ApplyCRz(qubitsMap[ctrl_qubit], qubitsMap[tgt_qubit], theta);
1197 }
1198
1206 void ApplyCH(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit) override
1207 {
1208 simulator->ApplyCH(qubitsMap[ctrl_qubit], qubitsMap[tgt_qubit]);
1209 }
1210
1218 void ApplyCSx(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit) override
1219 {
1220 simulator->ApplyCSx(qubitsMap[ctrl_qubit], qubitsMap[tgt_qubit]);
1221 }
1222
1230 void ApplyCSxDAG(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit) override
1231 {
1232 simulator->ApplyCSxDAG(qubitsMap[ctrl_qubit], qubitsMap[tgt_qubit]);
1233 }
1234
1242 void ApplySwap(Types::qubit_t qubit0, Types::qubit_t qubit1) override
1243 {
1244 simulator->ApplySwap(qubitsMap[qubit0], qubitsMap[qubit1]);
1245 }
1246
1255 void ApplyCCX(Types::qubit_t qubit0, Types::qubit_t qubit1, Types::qubit_t qubit2) override
1256 {
1257 simulator->ApplyCCX(qubitsMap[qubit0], qubitsMap[qubit1], qubitsMap[qubit2]);
1258 }
1259
1268 void ApplyCSwap(Types::qubit_t ctrl_qubit, Types::qubit_t qubit0, Types::qubit_t qubit1) override
1269 {
1270 simulator->ApplyCSwap(qubitsMap[ctrl_qubit], qubitsMap[qubit0], qubitsMap[qubit1]);
1271 }
1272
1284 void ApplyCU(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit, double theta, double phi, double lambda, double gamma) override
1285 {
1286 simulator->ApplyCU(qubitsMap[ctrl_qubit], qubitsMap[tgt_qubit], theta, phi, lambda, gamma);
1287 }
1288
1289 void ApplyNop() override
1290 {
1291 simulator->ApplyNop();
1292 }
1293
1302 void SetMultithreading(bool multithreading = true) override
1303 {
1304 if (simulator)
1305 simulator->SetMultithreading(multithreading);
1306
1307 processor_count = multithreading ? QC::QubitRegister<>::GetNumberOfThreads() : 1;
1308 }
1309
1317 bool GetMultithreading() const override
1318 {
1319 if (simulator)
1320 return simulator->GetMultithreading();
1321
1322 return false;
1323 }
1324
1335 bool IsQcsim() const override
1336 {
1337 return GetType() == Simulators::SimulatorType::kQCSim;
1338 }
1339
1357 {
1358 return ConvertOutcomeFromLocal(simulator->MeasureNoCollapse());
1359 }
1360
1375 std::vector<bool> MeasureNoCollapseMany() override
1376 {
1377 auto res = simulator->MeasureNoCollapseMany();
1378 return ConvertOutcomeFromLocal(res);
1379 }
1380
1391 std::unique_ptr<ISimulator> Clone() override
1392 {
1393 auto cloned = std::make_unique<IndividualSimulator>();
1394
1395 cloned->qubitsMap = qubitsMap;
1397 cloned->savedState = savedState;
1399 cloned->simulator = simulator->Clone();
1400
1401 return cloned;
1402 }
1403
1404 std::unique_ptr<ISimulator> CloneForExecution(uint64_t seed) override
1405 {
1406 auto cloned = std::make_unique<IndividualSimulator>();
1407 cloned->qubitsMap = qubitsMap;
1408 cloned->savedState = savedState;
1409 cloned->simulator = simulator->CloneForExecution(seed);
1410 return cloned;
1411 }
1412
1413 Types::qubit_t SampleFromAlias()
1414 {
1415 if (!alias || !simulator)
1416 return 0;
1417
1418 double prob = 0.0;
1419 if (GetType() == SimulatorType::kQCSim)
1420 {
1421 // qcsim - convert 'simulator' to qcsim simulator and access 'state' (from
1422 // there the statevector is accessible)
1423 QCSimStatevectorSimulator *qcsim = dynamic_cast<QCSimStatevectorSimulator *>(simulator.get());
1424 prob = 1. - qcsim->uniformZeroOne(qcsim->rng);
1425 }
1426#ifndef NO_QISKIT_AER
1427 else
1428 {
1429 // qiskit aer - convert 'simulator' to qiskit aer simulator and access
1430 // 'savedAmplitudes' (assumes destructive saving of the state)
1431#ifndef NO_QISKIT_AER
1432 AerSimulator *aer = dynamic_cast<AerSimulator *>(simulator.get());
1433 prob = 1 - aer->uniformZeroOne(aer->rng);
1434#else
1435 return 0;
1436#endif
1437 }
1438#endif
1439
1440 const size_t measRaw = alias->Sample(prob);
1441
1442 return ConvertOutcomeFromLocal(measRaw);
1443 }
1444
1445 const std::unordered_map<std::string, std::string> &GetConfigMap() const override
1446 {
1447 if (!simulator)
1448 {
1449 static const std::unordered_map<std::string, std::string> emptyMap;
1450 return emptyMap;
1451 }
1452
1453 return simulator->GetConfigMap();
1454 }
1455
1456 private:
1457 // These helpers operate on component-local rows. The composite projects
1458 // directly into requested output positions, including global IDs above 63.
1459 QCSimStatevectorSimulator &SamplingStatevector() const
1460 {
1461 return *static_cast<QCSimStatevectorSimulator *>(simulator.get());
1462 }
1463
1464 size_t SamplingRows() const
1465 {
1466 return size_t{1} << SamplingStatevector().samplingSupportQubits;
1467 }
1468
1469 Utils::Sampling::Options SamplingOptions() const
1470 {
1471 return SamplingStatevector().SamplingOptions();
1472 }
1473
1474 std::mt19937_64 &SamplingRng()
1475 {
1476 return SamplingStatevector().rng;
1477 }
1478
1479 QCSimStatevectorSimulator::SamplingPlan PrepareSampling(size_t shots, Utils::Sampling::Options options) const
1480 {
1481 return {SamplingRows(), shots, {&SamplingStatevector().state->getRegisterStorage()}, options};
1482 }
1483
1484 size_t SampleLocalOnce()
1485 {
1486 auto &backend = *SamplingStatevector().state;
1487
1488 struct RestoreThreading
1489 {
1490 QC::QubitRegister<> &backend;
1491 bool enabled;
1492
1493 ~RestoreThreading()
1494 {
1495 backend.SetMultithreading(enabled);
1496 }
1497 } restore{backend, simulator->GetMultithreading()};
1498
1499 backend.SetMultithreading(false);
1500 return backend.MeasureNoCollapse();
1501 }
1502
1503 void DiscardLocalMeasurementDraw()
1504 {
1505 SamplingStatevector().state->DiscardMeasurementDraw();
1506 }
1507
1508 void InitializeAlias()
1509 {
1510 if (GetType() == SimulatorType::kQCSim)
1511 {
1512 // qcsim - convert 'simulator' to qcsim simulator and access 'state' (from
1513 // there the statevector is accessible)
1514 QCSimStatevectorSimulator *qcsim = dynamic_cast<QCSimStatevectorSimulator *>(simulator.get());
1515 alias = std::make_unique<Utils::Alias>(qcsim->state->getRegisterStorage());
1516 }
1517#ifndef NO_QISKIT_AER
1518 else
1519 {
1520 // qiskit aer - convert 'simulator' to qiskit aer simulator and access
1521 // 'savedAmplitudes' (assumes destructive saving of the state)
1522#ifndef NO_QISKIT_AER
1523 AerSimulator *aer = dynamic_cast<AerSimulator *>(simulator.get());
1524 if (aer)
1525 {
1526 alias = std::unique_ptr<Utils::Alias>(new Utils::Alias(aer->savedAmplitudes));
1527 }
1528#else
1529 // If we are here, it means it's not QCSim, but Qiskit is disabled.
1530 throw std::runtime_error("Qiskit Aer is disabled in this build.");
1531#endif
1532 }
1533#endif
1534 }
1535
1536 void ClearAlias()
1537 {
1538 alias = nullptr;
1539 }
1540
1553#ifndef NO_QISKIT_AER
1554 inline void JoinOmpAer(size_t nrQubits1, size_t nrBasisStates1, size_t nrBasisStates2, size_t newNrQubits, size_t nrBasisStates,
1555 const std::unique_ptr<IndividualSimulator> &other, bool enableMultithreading)
1556 {
1557 AER::Vector<std::complex<double>> newAmplitudes(nrBasisStates, false); // the false here avoids data initialization, it
1558 // will be set anyway
1559
1560 /*
1561 const size_t state1Mask = nrBasisStates1 - 1ULL;
1562
1563 #pragma omp parallel for num_threads(processor_count) schedule(static,
1564 OmpLimitJoin / divSchedule) for (long long int state = 0; state <
1565 static_cast<long long int>(nrBasisStates); ++state) newAmplitudes[state] =
1566 AmplitudeRaw(state & state1Mask) * other->AmplitudeRaw(state >> nrQubits1);
1567 */
1568
1569 // TODO: check if this is better
1570#pragma omp parallel for num_threads(processor_count)
1571 for (long long int state2 = 0; state2 < static_cast<long long int>(nrBasisStates2); ++state2)
1572 {
1573 const auto ampl2 = other->AmplitudeRaw(state2);
1574 const size_t state2Mask = state2 << nrQubits1;
1575 for (size_t state1 = 0; state1 < nrBasisStates1; ++state1)
1576 newAmplitudes[state2Mask | state1] = AmplitudeRaw(state1) * ampl2;
1577 }
1578
1579 // 3. set the state of the current simulator to the joined state
1580 // the original qubits of this simulator get mapped as they are
1581 // the other ones get shifted to the left by the number of qubits of this
1582 // simulator so transfer mapping keeping this in mind
1583 // simulator->SetMultithreading(enableMultithreading);
1584 simulator->InitializeState(newNrQubits,
1585 newAmplitudes); // this will move the data from newAmplitudes to the
1586 // simulator, no allocation and copying is done
1587 }
1588#endif
1589
1600 /*
1601 inline void SplitOmpAer(size_t localQubit, size_t newNrQubits, size_t
1602 nrBasisStates, bool qubitOutcome = false)
1603 {
1604 // now the adjusted current simulator, without the removed qubit
1605 AER::Vector<std::complex<double>> newAmplitudes(nrBasisStates, false);
1606 // the false here avoids data initialization, it will be set anyway
1607
1608 // compute the new amplitudes
1609
1610 const size_t localQubitMask = 1ULL << localQubit;
1611 const size_t maskLow = localQubitMask - 1ULL;
1612 const size_t maskHigh = ~maskLow;
1613 const size_t qubitMask = qubitOutcome ? localQubitMask : 0ULL;
1614
1615 #pragma omp parallel for num_threads(processor_count) schedule(static,
1616 OmpLimitSplit / divSchedule) for (long long int state = 0; state <
1617 static_cast<long long int>(nrBasisStates); ++state)
1618 {
1619 const size_t stateLow = state & maskLow;
1620 const size_t stateHigh = (state & maskHigh) << 1ULL;
1621
1622 newAmplitudes[state] = AmplitudeRaw(stateLow | stateHigh |
1623 qubitMask);
1624 }
1625
1626 simulator->InitializeState(newNrQubits, newAmplitudes); // this will
1627 move the data from newAmplitudes to the simulator, no allocation and copying is
1628 done
1629 }
1630 */
1631
1644 inline void JoinOmpQcsim(size_t nrQubits1, size_t nrBasisStates1, size_t nrBasisStates2, size_t newNrQubits, size_t nrBasisStates,
1645 const std::unique_ptr<IndividualSimulator> &other, bool enableMultithreading)
1646 {
1647 Eigen::VectorXcd newAmplitudes;
1648 newAmplitudes.resize(nrBasisStates);
1649
1650 /*
1651 const size_t state1Mask = nrBasisStates1 - 1ULL;
1652
1653 #pragma omp parallel for num_threads(processor_count) schedule(static,
1654 OmpLimitJoin / divSchedule) for (long long int state = 0; state <
1655 static_cast<long long int>(nrBasisStates); ++state) newAmplitudes[state] =
1656 AmplitudeRaw(state & state1Mask) * other->AmplitudeRaw(state >> nrQubits1);
1657 */
1658
1659 // TODO: check if this is better
1660#pragma omp parallel for num_threads(processor_count)
1661 for (long long int state2 = 0; state2 < static_cast<long long int>(nrBasisStates2); ++state2)
1662 {
1663 const auto ampl2 = other->AmplitudeRaw(state2);
1664 const size_t state2Mask = state2 << nrQubits1;
1665 for (size_t state1 = 0; state1 < nrBasisStates1; ++state1)
1666 newAmplitudes[state2Mask | state1] = AmplitudeRaw(state1) * ampl2;
1667 }
1668
1669 // 3. set the state of the current simulator to the joined state
1670 // the original qubits of this simulator get mapped as they are
1671 // the other ones get shifted to the left by the number of qubits of this
1672 // simulator so transfer mapping keeping this in mind
1673 // simulator->SetMultithreading(enableMultithreading);
1674 static_cast<QCSimStatevectorSimulator *>(simulator.get())->ReplaceState(newNrQubits, newAmplitudes); // swaps the data
1675 // from newAmplitudes to the simulator, no
1676 // allocation and copying is done
1677 }
1678
1689 /*
1690 inline void SplitOmpQcsim(size_t localQubit, size_t newNrQubits, size_t
1691 nrBasisStates, bool qubitOutcome = false)
1692 {
1693 // now the adjusted current simulator, without the removed qubit
1694 Eigen::VectorXcd newAmplitudes;
1695 newAmplitudes.resize(nrBasisStates);
1696
1697 // compute the new amplitudes
1698 const size_t localQubitMask = 1ULL << localQubit;
1699 const size_t maskLow = localQubitMask - 1ULL;
1700 const size_t maskHigh = ~maskLow;
1701 const size_t qubitMask = qubitOutcome ? localQubitMask : 0ULL;
1702
1703 #pragma omp parallel for num_threads(processor_count) schedule(static,
1704 OmpLimitSplit / divSchedule) for (long long int state = 0; state <
1705 static_cast<long long int>(nrBasisStates); ++state)
1706 {
1707 const size_t stateLow = state & maskLow;
1708 const size_t stateHigh = (state & maskHigh) << 1ULL;
1709
1710 newAmplitudes[state] = AmplitudeRaw(stateLow | stateHigh |
1711 qubitMask);
1712 }
1713
1714 static_cast<QCSimStatevectorSimulator *>(simulator.get())->ReplaceState(newNrQubits, newAmplitudes); // this will
1715 end up by swapping the data from newAmplitudes to the simulator, no allocation
1716 and copying is done
1717 }
1718 */
1719
1720 std::unordered_map<Types::qubit_t, Types::qubit_t> qubitsMap;
1722 std::unique_ptr<ISimulator> simulator;
1723 std::vector<std::complex<double>> savedState;
1725
1726 std::unique_ptr<Utils::Alias> alias;
1727
1728 int processor_count = QC::QubitRegister<>::GetNumberOfThreads();
1731
1732 // constexpr static int divSchedule = 4;
1733 constexpr static size_t OmpLimitJoin = 4096 * 2;
1734 // constexpr static size_t OmpLimitSplit = OmpLimitJoin * 16;
1735};
1736
1737} // namespace Private
1738} // namespace Simulators
1739
1740#endif
1741#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)
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.
SimulationType
The type of simulation.
Definition State.h:109
SimulatorType
The type of simulator.
Definition State.h:79
@ 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