Maestro 0.3.11
Unified interface for quantum circuit simulation
Loading...
Searching...
No Matches
QCSimStatevectorSimulator.h
Go to the documentation of this file.
1#pragma once
2#if defined(INCLUDED_BY_FACTORY)
4#include "QubitRegister.h"
5#define _USE_MATH_DEFINES
6#include <math.h>
7
9{
10// Composite joins/splits replace amplitudes within an existing execution.
11// Keeping the native register alive also keeps its measurement RNG position.
12class SamplingQubitRegister : public QC::QubitRegister<>
13{
14 public:
15 using QC::QubitRegister<>::QubitRegister;
16
17 size_t MeasureQubitReproducible(size_t qubit)
18 {
19 if (qubit >= NrQubits)
20 throw std::out_of_range("Measured qubit is outside the register");
21 if (measuredMask)
22 return MeasureRemainingQubit(qubit);
23 if (!UseMultithreading())
24 {
25 const size_t outcome = QC::QubitRegister<>::MeasureQubit(qubit);
26 RememberMeasurement(qubit, outcome);
27 return outcome;
28 }
29
30 // Match native serial MeasureRange exactly: one native RNG draw,
31 // the same cumulative scan, and the same outcome-probability sum.
32 // Only the independent amplitude updates are parallel. In particular,
33 // do not compute p(0) as 1-p(1), which changes normalization rounding.
34 const double draw = 1. - uniformZeroOne(rng);
35 const size_t mask = size_t{1} << qubit;
36 const size_t outcome = (BaseClass::SampleBasisState(NrBasisStates, registerStorage, draw, 0, false) >> qubit) & 1;
37 const size_t lowMask = mask - 1;
38 const size_t selected = outcome << qubit;
39 double probability = 0.;
40 for (size_t k = 0; k < NrBasisStates / 2; ++k)
41 probability += std::norm(registerStorage(((k & ~lowMask) << 1) | selected | (k & lowMask)));
42 const double norm = 1. / std::sqrt(probability);
43#ifdef _OPENMP
44#pragma omp parallel for schedule(static) if (!omp_in_parallel())
45#endif
46 for (long long i = 0; i < static_cast<long long>(NrBasisStates); ++i)
47 registerStorage(i) = (static_cast<size_t>(i) & mask) == selected ? registerStorage(i) * norm : std::complex<double>(0., 0.);
48 if (std::isfinite(norm))
49 RememberMeasurement(qubit, outcome);
50 return outcome;
51 }
52
53 void InvalidateMeasurements()
54 {
55 measuredMask = measuredValues = 0;
56 }
57
58 void SaveState()
59 {
60 // Execution clones share the immutable checkpoint, not live amplitudes.
61 snapshot = std::make_shared<const Eigen::VectorXcd>(registerStorage);
62 }
63
64 void RestoreState()
65 {
66 if (snapshot)
67 registerStorage = *snapshot;
68 InvalidateMeasurements();
69 }
70
71 void DiscardMeasurementDraw()
72 {
73 // Consume the native distribution, just as MeasureNoCollapse does.
74 // Its engine-word usage is an implementation detail of the library.
75 (void)uniformZeroOne(rng);
76 }
77
78 void ReplaceAmplitudes(size_t qubits, Eigen::VectorXcd &amplitudes)
79 {
80 NrQubits = qubits;
81 NrBasisStates = static_cast<size_t>(amplitudes.size());
82 registerStorage.swap(amplitudes);
83 savedStateStorage.resize(0);
84 snapshot.reset();
85 computeGates.clear();
86 recordGates = false;
87 InvalidateMeasurements();
88 }
89
90 private:
91 void RememberMeasurement(size_t qubit, size_t outcome)
92 {
93 measuredMask |= size_t{1} << qubit;
94 measuredValues = (measuredValues & ~(size_t{1} << qubit)) | (outcome << qubit);
95 }
96
97 // Insert fixed measured bits into an index in the remaining subspace.
98 // Called once per tile, not once per amplitude.
99 size_t Expand(size_t packed) const
100 {
101 size_t result = measuredValues;
102 for (size_t free = (NrBasisStates - 1) & ~measuredMask; packed; packed >>= 1)
103 {
104 const size_t bit = free & (~free + 1);
105 if (packed & 1)
106 result |= bit;
107 free &= free - 1;
108 }
109 return result;
110 }
111
112 size_t MeasureRemainingQubit(size_t qubit)
113 {
114 const double draw = 1. - uniformZeroOne(rng);
115 const size_t mask = size_t{1} << qubit;
116 double cumulative = 0.;
117 size_t sampled = 0;
118 // This is the native left-to-right sum with known zero terms skipped.
119 // Keep normalization and RNG consumption for duplicate measurements too.
120 for (size_t i = measuredValues; i < NrBasisStates; i = (((i | measuredMask) + 1) & ~measuredMask) | measuredValues)
121 {
122 cumulative += std::norm(registerStorage(i));
123 if (draw <= cumulative)
124 {
125 sampled = i;
126 break;
127 }
128 }
129 const size_t outcome = (sampled >> qubit) & 1;
130 const size_t selected = outcome << qubit;
131 const size_t fixed = measuredMask | mask;
132 const size_t values = (measuredValues & ~mask) | selected;
133 double probability = 0.;
134 if (!(measuredMask & mask) || (measuredValues & mask) == selected)
135 for (size_t i = values; i < NrBasisStates; i = (((i | fixed) + 1) & ~fixed) | values)
136 probability += std::norm(registerStorage(i));
137 const double norm = 1. / std::sqrt(probability);
138 // Preserve the native full update for exceptional, unnormalizable input.
139 if (!std::isfinite(norm))
140 {
141 for (size_t i = 0; i < NrBasisStates; ++i)
142 registerStorage(i) = (i & mask) == selected ? registerStorage(i) * norm : std::complex<double>(0., 0.);
143 InvalidateMeasurements();
144 return outcome;
145 }
146 size_t active = NrBasisStates;
147 for (size_t bits = measuredMask; bits; bits &= bits - 1)
148 active >>= 1;
149 constexpr size_t tileSize = 1024;
150 const size_t tiles = (active + tileSize - 1) / tileSize;
151 const int threads = Utils::Sampling::Threads(GetMultithreading(), active / 4);
152#ifdef _OPENMP
153#pragma omp parallel for schedule(static) if (threads > 1) num_threads(threads)
154#endif
155 for (long long tile = 0; tile < static_cast<long long>(tiles); ++tile)
156 {
157 size_t index = Expand(static_cast<size_t>(tile) * tileSize);
158 const size_t end = std::min(active, (static_cast<size_t>(tile) + 1) * tileSize);
159 for (size_t k = static_cast<size_t>(tile) * tileSize; k < end; ++k)
160 {
161 registerStorage(index) = (index & mask) == selected ? registerStorage(index) * norm : std::complex<double>(0., 0.);
162 index = (((index | measuredMask) + 1) & ~measuredMask) | measuredValues;
163 }
164 }
165 RememberMeasurement(qubit, outcome);
166 return outcome;
167 }
168
169 size_t measuredMask = 0, measuredValues = 0;
170 std::shared_ptr<const Eigen::VectorXcd> snapshot;
171};
172
173// statevector backend. Owns exactly one native QCSim implementation.
174class QCSimStatevectorSimulator : public QCSimGateSimulator
175{
176 public:
177 QCSimStatevectorSimulator()
178 {
179 configuration.SetConfiguration("method", "statevector");
180 }
181
182 const char *MethodName() const override
183 {
184 return "statevector";
185 }
186 friend class IndividualSimulator;
187
188 struct SamplingProbability
189 {
190 const Eigen::VectorXcd *storage;
191
192 double operator()(size_t row) const
193 {
194 return std::norm((*storage)[row]);
195 }
196 };
197
198 using SamplingPlan = Utils::Sampling::Prepared<SamplingProbability>;
199
200 SamplingPlan PrepareSampling(size_t shots) const
201 {
202 return SamplingPlan(size_t{1} << samplingSupportQubits, shots, SamplingProbability{&state->getRegisterStorage()}, SamplingOptions());
203 }
204
205 public:
206 void Initialize() override
207 {
208 if (nrQubits != 0)
209 {
210 InitializeSamplingSeed();
211 state = std::make_unique<SamplingQubitRegister>(nrQubits);
212 samplingSupportQubits = savedSamplingSupportQubits = 0;
213 SeedBackend(state.get(), samplingSeed);
214 SetMultithreading(enableMultithreading);
215 for (const auto &[key, value] : configuration.GetConfigMap())
216 if (key != "method")
217 Configure(key.c_str(), value.c_str());
218 }
219 }
220
221 void InitializeState(size_t num_qubits, std::vector<std::complex<double>> &amplitudes) override
222 {
223 if (num_qubits == 0)
224 return;
225 Clear();
226 nrQubits = num_qubits;
227 Initialize();
228 Eigen::VectorXcd amplitudesEigen(Eigen::Map<Eigen::VectorXcd, Eigen::Unaligned>(amplitudes.data(), amplitudes.size()));
229 state->setRegisterStorageFastNoNormalize(amplitudesEigen);
230 UpdateSamplingSupport();
231 }
232
233#ifndef NO_QISKIT_AER
234 void InitializeState(size_t num_qubits, AER::Vector<std::complex<double>> &amplitudes) override
235 {
236 if (num_qubits == 0)
237 return;
238 Clear();
239 nrQubits = num_qubits;
240 Initialize();
241 Eigen::VectorXcd amplitudesEigen(Eigen::Map<Eigen::VectorXcd, Eigen::Unaligned>(amplitudes.data(), amplitudes.size()));
242 state->setRegisterStorageFastNoNormalize(amplitudesEigen);
243 UpdateSamplingSupport();
244 }
245
246#endif
247
248 void InitializeState(size_t num_qubits, Eigen::VectorXcd &amplitudes) override
249 {
250 if (num_qubits == 0)
251 return;
252 Clear();
253 nrQubits = num_qubits;
254 Initialize();
255 {
256 state = std::make_unique<SamplingQubitRegister>(nrQubits, amplitudes);
257 state->SetMultithreading(enableMultithreading);
258 SeedBackend(state.get(), samplingSeed);
259 UpdateSamplingSupport();
260 }
261 }
262
263 void InitializeToBasisState(size_t num_qubits, Types::qubit_t basisState) override
264 {
265 if (num_qubits == 0)
266 return;
267 Clear();
268 nrQubits = num_qubits;
269 Initialize();
270 state->setToBasisState(static_cast<size_t>(basisState));
271 samplingSupportQubits = 0;
272 for (size_t value = basisState; value; value >>= 1)
273 ++samplingSupportQubits;
274 }
275
276 void Reset() override
277 {
278 if (state)
279 {
280 state->InvalidateMeasurements();
281 state->Reset();
282 }
283 samplingSupportQubits = 0;
284 upcomingGateIndex = 0;
285 }
286
287 void Configure(const char *key, const char *value) override
288 {
289 if (ConfigureSampling(key, value))
290 return;
291 if (std::string("method") == key)
292 {
293 ValidateMethod(value);
294 configuration.SetConfiguration(key, value);
295 return;
296 }
297 if (!configuration.WasApplied(key, value))
298 configuration.SetConfiguration(key, value);
299 if (std::string("seed") == key)
300 {
301 const uint64_t seed = std::stoull(value);
302 SeedAuxiliaryRng(seed);
303 SeedSampling(seed);
304 if (state)
305 SeedBackend(state.get(), seed);
306 return;
307 }
308 if (std::string(key) == "pauli_propagator_workers")
309 {
310 const auto workers = configuration.GetConfigurationAsUnsigned(key);
311 if (workers > 1024)
312 throw std::invalid_argument("pauli_propagator_workers exceeds 1024");
313 }
314 }
315
316 size_t AllocateQubits(size_t num_qubits) override
317 {
318 if (state)
319 return 0;
320 const size_t oldNrQubits = nrQubits;
321 nrQubits += num_qubits;
322 return oldNrQubits;
323 }
324
325 void Clear() override
326 {
327 state = nullptr;
328 samplingSupportQubits = savedSamplingSupportQubits = 0;
329 nrQubits = 0;
330 upcomingGateIndex = 0;
331 upcomingGates.clear();
332 }
333
334 size_t Measure(const Types::qubits_vector &qubits) override
335 {
336 if (qubits.size() > sizeof(size_t) * 8)
337 std::cerr << "Warning: The number of qubits to measure is larger than the "
338 "number of bits in the size_t type, the outcome will be undefined"
339 << std::endl;
340 size_t res = 0;
341 size_t mask = 1ULL;
342 DontNotify();
343 {
344 for (size_t qubit : qubits)
345 {
346 if (MeasureQubit(qubit))
347 res |= mask;
348 mask <<= 1;
349 }
350 }
351 Notify();
352 NotifyObservers(qubits);
353 return res;
354 }
355
356 std::vector<bool> MeasureMany(const Types::qubits_vector &qubits) override
357 {
358 std::vector<bool> res(qubits.size(), false);
359 DontNotify();
360 {
361 for (size_t q = 0; q < qubits.size(); ++q)
362 if (MeasureQubit(qubits[q]))
363 res[q] = true;
364 }
365 Notify();
366 NotifyObservers(qubits);
367 return res;
368 }
369
370 void ApplyReset(const Types::qubits_vector &qubits) override
371 {
372 DontNotify();
373 {
374 for (size_t qubit : qubits)
375 if (MeasureQubit(qubit))
376 {
377 state->InvalidateMeasurements();
378 state->ApplyGate(xgate, static_cast<unsigned int>(qubit));
379 }
380 }
381 Notify();
382 NotifyObservers(qubits);
383 }
384
385 std::vector<std::complex<double>> GetStateVector() override
386 {
387 if (!nrQubits)
388 return {};
389 const auto count = TensorQueries::DenseSize(nrQubits);
390 const auto values = state->getRegisterStorage();
391 if (static_cast<size_t>(values.size()) != count)
392 throw std::runtime_error("QCSim statevector query returned an invalid size");
393 return {values.data(), values.data() + values.size()};
394 }
395
396 double Probability(Types::qubit_t outcome) override
397 {
398 return state->getBasisStateProbability(static_cast<unsigned int>(outcome));
399 }
400
401 std::complex<double> Amplitude(Types::qubit_t outcome) override
402 {
403 return state->getBasisStateAmplitude(static_cast<unsigned int>(outcome));
404 }
405
406 std::vector<double> AllProbabilities() override
407 {
408 const Eigen::VectorXcd probs = state->getRegisterStorage().cwiseAbs2();
409 std::vector<double> result(probs.size());
410 for (int i = 0; i < probs.size(); ++i)
411 result[i] = probs[i].real();
412 return result;
413 }
414
415 std::vector<double> Probabilities(const Types::qubits_vector &qubits) override
416 {
417 std::vector<double> result(qubits.size());
418 {
419 const Eigen::VectorXcd &reg = state->getRegisterStorage();
420 for (int i = 0; i < static_cast<int>(qubits.size()); ++i)
421 result[i] = std::norm(reg[qubits[i]]);
422 }
423 return result;
424 }
425
426 std::unordered_map<Types::qubit_t, Types::qubit_t> SampleCounts(const Types::qubits_vector &qubits, size_t shots = 1000) override
427 {
428 if (!LegacySampling())
429 return SamplePrepared<false>(qubits, shots);
430 if (qubits.empty() || shots == 0)
431 return {};
432 if (qubits.size() > sizeof(size_t) * 8)
433 std::cerr << "Warning: The number of qubits to measure is larger than the "
434 "number of bits in the size_t type, the outcome will be undefined"
435 << std::endl;
436 std::unordered_map<Types::qubit_t, Types::qubit_t> result;
437 DontNotify();
438 {
439 if (shots > 1)
440 {
441 const auto &statev = state->getRegisterStorage();
442 const Utils::Alias alias(statev);
443 for (size_t shot = 0; shot < shots; ++shot)
444 {
445 const double prob = 1. - uniformZeroOne(rng);
446 const size_t measRaw = alias.Sample(prob);
447 size_t meas = 0;
448 size_t mask = 1ULL;
449 for (auto q : qubits)
450 {
451 const size_t qubitMask = 1ULL << q;
452 if ((measRaw & qubitMask) != 0)
453 meas |= mask;
454 mask <<= 1ULL;
455 }
456 ++result[meas];
457 }
458 }
459 else
460 {
461 for (size_t shot = 0; shot < shots; ++shot)
462 {
463 const size_t measRaw = MeasureNoCollapse();
464 size_t meas = 0;
465 size_t mask = 1ULL;
466 for (auto q : qubits)
467 {
468 const size_t qubitMask = 1ULL << q;
469 if ((measRaw & qubitMask) != 0)
470 meas |= mask;
471 mask <<= 1ULL;
472 }
473 ++result[meas];
474 }
475 }
476 }
477 Notify();
478 NotifyObservers(qubits);
479 return result;
480 }
481
482 std::unordered_map<std::vector<bool>, Types::qubit_t> SampleCountsMany(const Types::qubits_vector &qubits, size_t shots = 1000) override
483 {
484 if (!LegacySampling())
485 return SamplePrepared<true>(qubits, shots);
486 if (qubits.empty() || shots == 0)
487 return {};
488 std::unordered_map<std::vector<bool>, Types::qubit_t> result;
489 DontNotify();
490 {
491 if (shots > 1)
492 {
493 const auto &statev = state->getRegisterStorage();
494 const Utils::Alias alias(statev);
495 std::vector<bool> meas(qubits.size());
496 for (size_t shot = 0; shot < shots; ++shot)
497 {
498 const double prob = 1. - uniformZeroOne(rng);
499 const size_t measRaw = alias.Sample(prob);
500 for (size_t i = 0; i < qubits.size(); ++i)
501 meas[i] = ((measRaw >> qubits[i]) & 1) != 0;
502 ++result[meas];
503 }
504 }
505 else
506 {
507 for (size_t shot = 0; shot < shots; ++shot)
508 {
509 const auto measRaw = MeasureNoCollapseMany();
510 std::vector<bool> meas(qubits.size(), false);
511 for (size_t i = 0; i < qubits.size(); ++i)
512 if (measRaw[qubits[i]])
513 meas[i] = true;
514 ++result[meas];
515 }
516 }
517 }
518 Notify();
519 NotifyObservers(qubits);
520 return result;
521 }
522
523 double ExpectationValue(const std::string &pauliStringOrig) override
524 {
525 if (pauliStringOrig.empty())
526 return 1.0;
527 std::string pauliString = pauliStringOrig;
528 if (pauliString.size() > GetNumberOfQubits())
529 {
530 for (size_t i = GetNumberOfQubits(); i < pauliString.size(); ++i)
531 {
532 const auto pauliOp = toupper(pauliString[i]);
533 if (pauliOp != 'I' && pauliOp != 'Z')
534 return 0.0;
535 }
536 pauliString.resize(GetNumberOfQubits());
537 }
538 std::vector<QC::Gates::AppliedGate<Eigen::MatrixXcd>> pauliStringVec;
539 pauliStringVec.reserve(pauliString.size());
540 for (size_t q = 0; q < pauliString.size(); ++q)
541 {
542 switch (toupper(pauliString[q]))
543 {
544 case 'X': {
545 QC::Gates::AppliedGate<Eigen::MatrixXcd> ag(xgate.getRawOperatorMatrix(), static_cast<Types::qubit_t>(q));
546 pauliStringVec.emplace_back(std::move(ag));
547 }
548 break;
549 case 'Y': {
550 QC::Gates::AppliedGate<Eigen::MatrixXcd> ag(ygate.getRawOperatorMatrix(), static_cast<Types::qubit_t>(q));
551 pauliStringVec.emplace_back(std::move(ag));
552 }
553 break;
554 case 'Z': {
555 QC::Gates::AppliedGate<Eigen::MatrixXcd> ag(zgate.getRawOperatorMatrix(), static_cast<Types::qubit_t>(q));
556 pauliStringVec.emplace_back(std::move(ag));
557 }
558 break;
559 case 'I':
560 [[fallthrough]];
561 default:
562 break;
563 }
564 }
565 if (pauliStringVec.empty())
566 return 1.0;
567 return state->ExpectationValue(pauliStringVec).real();
568 }
569
570 SimulationType GetSimulationType() const override
571 {
572 return SimulationType::kStatevector;
573 }
574
575 void SaveState() override
576 {
577 state->SaveState();
578 savedSamplingSupportQubits = samplingSupportQubits;
579 }
580
581 void RestoreState() override
582 {
583 state->InvalidateMeasurements();
584 state->RestoreState();
585 samplingSupportQubits = std::max(samplingSupportQubits, savedSamplingSupportQubits);
586 }
587
588 void SetMultithreading(bool multithreading = true) override
589 {
590 enableMultithreading = multithreading;
591 if (state)
592 state->SetMultithreading(multithreading);
593 }
594
596 {
597 if (GetNumberOfQubits() > sizeof(Types::qubit_t) * 8)
598 std::cerr << "Warning: The number of qubits to measure is larger than the "
599 "number of bits in the Types::qubit_t type, the outcome will be "
600 "undefined"
601 << std::endl;
602 return state->MeasureNoCollapse();
603 }
604
605 std::vector<bool> MeasureNoCollapseMany() override
606 {
607 auto state = MeasureNoCollapse();
608 std::vector<bool> res(nrQubits);
609 for (size_t i = 0; i < nrQubits; ++i)
610 res[i] = ((state >> i) & 1) == 1;
611 return res;
612 }
613
614 void ApplyGenericOneQubitGate(Types::qubit_t qubit, const Eigen::Matrix2cd &gate) override
615 {
616 samplingSupportQubits = std::max(samplingSupportQubits, size_t(qubit) + 1);
617 const QC::Gates::AppliedGate<> agate(gate, qubit);
618 ApplyNativeGate(agate);
619 NotifyObservers({qubit});
620 }
621
622 void ApplyGenericTwoQubitGate(Types::qubit_t qubit0, Types::qubit_t qubit1, const Eigen::Matrix4cd &gate) override
623 {
624 samplingSupportQubits = std::max({samplingSupportQubits, size_t(qubit0) + 1, size_t(qubit1) + 1});
625 const QC::Gates::AppliedGate<> agate(gate, qubit0, qubit1);
626 ApplyNativeGate(agate);
627 NotifyObservers({qubit0, qubit1});
628 }
629
630 void ApplyGenericThreeQubitGate(Types::qubit_t q0, Types::qubit_t q1, Types::qubit_t q2, const Matrix8cd &gate) override
631 {
632 samplingSupportQubits = std::max({samplingSupportQubits, size_t(q0) + 1, size_t(q1) + 1, size_t(q2) + 1});
633 const QC::Gates::AppliedGate<> applied(gate, q0, q1, q2);
634 ApplyNativeGate(applied);
635 NotifyObservers({q0, q1, q2});
636 }
637
638 void ApplyP(Types::qubit_t qubit, double lambda) override
639 {
640 pgate.SetPhaseShift(lambda);
641 ApplyNativeGate(pgate, static_cast<unsigned int>(qubit));
642 NotifyObservers({qubit});
643 }
644
645 void ApplyX(Types::qubit_t qubit) override
646 {
647 ApplyNativeGate(xgate, static_cast<unsigned int>(qubit));
648 NotifyObservers({qubit});
649 }
650
651 void ApplyY(Types::qubit_t qubit) override
652 {
653 ApplyNativeGate(ygate, static_cast<unsigned int>(qubit));
654 NotifyObservers({qubit});
655 }
656
657 void ApplyZ(Types::qubit_t qubit) override
658 {
659 ApplyNativeGate(zgate, static_cast<unsigned int>(qubit));
660 NotifyObservers({qubit});
661 }
662
663 void ApplyH(Types::qubit_t qubit) override
664 {
665 ApplyNativeGate(h, static_cast<unsigned int>(qubit));
666 NotifyObservers({qubit});
667 }
668
669 void ApplyS(Types::qubit_t qubit) override
670 {
671 ApplyNativeGate(sgate, static_cast<unsigned int>(qubit));
672 NotifyObservers({qubit});
673 }
674
675 void ApplySDG(Types::qubit_t qubit) override
676 {
677 ApplyNativeGate(sdggate, static_cast<unsigned int>(qubit));
678 NotifyObservers({qubit});
679 }
680
681 void ApplyT(Types::qubit_t qubit) override
682 {
683 ApplyNativeGate(tgate, static_cast<unsigned int>(qubit));
684 NotifyObservers({qubit});
685 }
686
687 void ApplyTDG(Types::qubit_t qubit) override
688 {
689 ApplyNativeGate(tdggate, static_cast<unsigned int>(qubit));
690 NotifyObservers({qubit});
691 }
692
693 void ApplySx(Types::qubit_t qubit) override
694 {
695 ApplyNativeGate(sxgate, static_cast<unsigned int>(qubit));
696 NotifyObservers({qubit});
697 }
698
699 void ApplySxDAG(Types::qubit_t qubit) override
700 {
701 ApplyNativeGate(sxdaggate, static_cast<unsigned int>(qubit));
702 NotifyObservers({qubit});
703 }
704
705 void ApplyK(Types::qubit_t qubit) override
706 {
707 ApplyNativeGate(k, static_cast<unsigned int>(qubit));
708 NotifyObservers({qubit});
709 }
710
711 void ApplyRx(Types::qubit_t qubit, double theta) override
712 {
713 rxgate.SetTheta(theta);
714 ApplyNativeGate(rxgate, static_cast<unsigned int>(qubit));
715 NotifyObservers({qubit});
716 }
717
718 void ApplyRy(Types::qubit_t qubit, double theta) override
719 {
720 rygate.SetTheta(theta);
721 ApplyNativeGate(rygate, static_cast<unsigned int>(qubit));
722 NotifyObservers({qubit});
723 }
724
725 void ApplyRz(Types::qubit_t qubit, double theta) override
726 {
727 rzgate.SetTheta(theta);
728 ApplyNativeGate(rzgate, static_cast<unsigned int>(qubit));
729 NotifyObservers({qubit});
730 }
731
732 void ApplyU(Types::qubit_t qubit, double theta, double phi, double lambda, double gamma) override
733 {
734 ugate.SetParams(theta, phi, lambda, gamma);
735 ApplyNativeGate(ugate, static_cast<unsigned int>(qubit));
736 NotifyObservers({qubit});
737 }
738
739 void ApplyCX(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit) override
740 {
741 ApplyNativeGate(cxgate, static_cast<unsigned int>(tgt_qubit), static_cast<unsigned int>(ctrl_qubit));
742 NotifyObservers({tgt_qubit, ctrl_qubit});
743 }
744
745 void ApplyCY(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit) override
746 {
747 ApplyNativeGate(cygate, static_cast<unsigned int>(tgt_qubit), static_cast<unsigned int>(ctrl_qubit));
748 NotifyObservers({tgt_qubit, ctrl_qubit});
749 }
750
751 void ApplyCZ(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit) override
752 {
753 ApplyNativeGate(czgate, static_cast<unsigned int>(tgt_qubit), static_cast<unsigned int>(ctrl_qubit));
754 NotifyObservers({tgt_qubit, ctrl_qubit});
755 }
756
757 void ApplyCP(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit, double lambda) override
758 {
759 cpgate.SetPhaseShift(lambda);
760 ApplyNativeGate(cpgate, static_cast<unsigned int>(tgt_qubit), static_cast<unsigned int>(ctrl_qubit));
761 NotifyObservers({tgt_qubit, ctrl_qubit});
762 }
763
764 void ApplyCRx(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit, double theta) override
765 {
766 crxgate.SetTheta(theta);
767 ApplyNativeGate(crxgate, static_cast<unsigned int>(tgt_qubit), static_cast<unsigned int>(ctrl_qubit));
768 NotifyObservers({tgt_qubit, ctrl_qubit});
769 }
770
771 void ApplyCRy(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit, double theta) override
772 {
773 crygate.SetTheta(theta);
774 ApplyNativeGate(crygate, static_cast<unsigned int>(tgt_qubit), static_cast<unsigned int>(ctrl_qubit));
775 NotifyObservers({tgt_qubit, ctrl_qubit});
776 }
777
778 void ApplyCRz(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit, double theta) override
779 {
780 crzgate.SetTheta(theta);
781 ApplyNativeGate(crzgate, static_cast<unsigned int>(tgt_qubit), static_cast<unsigned int>(ctrl_qubit));
782 NotifyObservers({tgt_qubit, ctrl_qubit});
783 }
784
785 void ApplyCH(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit) override
786 {
787 ApplyNativeGate(ch, static_cast<unsigned int>(tgt_qubit), static_cast<unsigned int>(ctrl_qubit));
788 NotifyObservers({tgt_qubit, ctrl_qubit});
789 }
790
791 void ApplyCSx(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit) override
792 {
793 ApplyNativeGate(csx, static_cast<unsigned int>(tgt_qubit), static_cast<unsigned int>(ctrl_qubit));
794 NotifyObservers({tgt_qubit, ctrl_qubit});
795 }
796
797 void ApplyCSxDAG(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit) override
798 {
799 ApplyNativeGate(csxdag, static_cast<unsigned int>(tgt_qubit), static_cast<unsigned int>(ctrl_qubit));
800 NotifyObservers({tgt_qubit, ctrl_qubit});
801 }
802
803 void ApplySwap(Types::qubit_t qubit0, Types::qubit_t qubit1) override
804 {
805 ApplyNativeGate(swapgate, static_cast<unsigned int>(qubit1), static_cast<unsigned int>(qubit0));
806 NotifyObservers({qubit1, qubit0});
807 }
808
809 void ApplyCCX(Types::qubit_t qubit0, Types::qubit_t qubit1, Types::qubit_t qubit2) override
810 {
811 ApplyNativeGate(ccxgate, static_cast<unsigned int>(qubit2), static_cast<unsigned int>(qubit1), static_cast<unsigned int>(qubit0));
812 NotifyObservers({qubit2, qubit1, qubit0});
813 }
814
815 void ApplyCSwap(Types::qubit_t ctrl_qubit, Types::qubit_t qubit0, Types::qubit_t qubit1) override
816 {
817 ApplyNativeGate(cswapgate, static_cast<unsigned int>(qubit1), static_cast<unsigned int>(qubit0), static_cast<unsigned int>(ctrl_qubit));
818 NotifyObservers({qubit1, qubit0, ctrl_qubit});
819 }
820
821 void ApplyCU(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit, double theta, double phi, double lambda, double gamma) override
822 {
823 cugate.SetParams(theta, phi, lambda, gamma);
824 ApplyNativeGate(cugate, static_cast<unsigned int>(tgt_qubit), static_cast<unsigned int>(ctrl_qubit));
825 NotifyObservers({tgt_qubit, ctrl_qubit});
826 }
827
828 std::unique_ptr<ISimulator> Clone() override
829 {
830 return CloneForExecution(NextCloneSeed());
831 }
832
833 std::unique_ptr<ISimulator> CloneForExecution(uint64_t seed) override
834 {
835 auto cloned = std::make_unique<QCSimStatevectorSimulator>();
836 cloned->nrQubits = nrQubits;
837 cloned->enableMultithreading = enableMultithreading;
838 cloned->upcomingGates = upcomingGates;
839 cloned->upcomingGateIndex = upcomingGateIndex;
840 cloned->samplingSupportQubits = samplingSupportQubits;
841 cloned->savedSamplingSupportQubits = savedSamplingSupportQubits;
842 if (state)
843 cloned->state = std::make_unique<SamplingQubitRegister>(*state);
844 for (const auto &[key, value] : configuration.GetConfigMap())
845 cloned->Configure(key.c_str(), value.c_str());
846 cloned->SetSeed(seed);
847 return cloned;
848 }
849
850 protected:
851 std::unique_ptr<SamplingQubitRegister> state;
852
853 private:
854 size_t MeasureQubit(size_t qubit)
855 {
856 if (!LegacySampling())
857 return state->MeasureQubitReproducible(qubit);
858 state->InvalidateMeasurements();
859 return state->MeasureQubit(qubit);
860 }
861
862 // Internal state replacement, deliberately separate from public
863 // initialization: preserve native/batch/readout RNGs and fork ordinals.
864 void ReplaceState(size_t qubits, Eigen::VectorXcd &amplitudes)
865 {
866 if (!state || !qubits || qubits >= std::numeric_limits<size_t>::digits || static_cast<size_t>(amplitudes.size()) != (size_t{1} << qubits))
867 throw std::invalid_argument("Invalid composite replacement state");
868 state->ReplaceAmplitudes(qubits, amplitudes);
869 nrQubits = qubits;
870 savedSamplingSupportQubits = 0;
871 upcomingGateIndex = 0;
872 upcomingGates.clear();
873 UpdateSamplingSupport();
874 }
875
876 template <bool Many> Utils::Sampling::Counts<Many> SamplePrepared(const Types::qubits_vector &qubits, size_t shots)
877 {
878 if (qubits.empty() || !shots)
879 return {};
880 Utils::Sampling::ValidateQubits(qubits, nrQubits, Many);
881 if (shots == 1)
882 {
883 // Preserve the native one-shot fast path and its RNG stream. Fix
884 // its summation path too: native parallel reductions depend on P.
885 const size_t row = [&] {
886 struct RestoreThreading
887 {
888 QC::QubitRegister<> &state;
889 bool enabled;
890 ~RestoreThreading()
891 {
892 state.SetMultithreading(enabled);
893 }
894 } restore{*state, enableMultithreading};
895 state->SetMultithreading(false);
896 return state->MeasureNoCollapse();
897 }();
898 auto result = Utils::Sampling::Single<Many>(qubits.size(), [&](size_t bit) { return ((row >> qubits[bit]) & 1) != 0; });
899 NotifyObservers(qubits);
900 return result;
901 }
902 const auto plan = PrepareSampling(shots);
903 auto result = Utils::Sampling::Count<Many>(
904 plan, shots, qubits.size(), [&](size_t row, size_t bit) { return ((row >> qubits[bit]) & 1) != 0; },
905 [&] { return Utils::RandomStream::Uniform(rng); });
906 NotifyObservers(qubits);
907 return result;
908 }
909
910 void UpdateSamplingSupport()
911 {
912 // Imports already establish a new state. Inspect trailing entries once
913 // here, instead of scanning the whole register on every sampling call.
914 // This is exact support metadata: tiny/invalid nonzero entries are kept.
915 const auto &storage = state->getRegisterStorage();
916 size_t end = static_cast<size_t>(storage.size());
917 while (end > 1 && storage[end - 1] == std::complex<double>{})
918 --end;
919 samplingSupportQubits = 0;
920 for (size_t last = end ? end - 1 : 0; last; last >>= 1)
921 ++samplingSupportQubits;
922 }
923
924 // An exact upper bound on occupied basis bits, never inferred from a probe.
925 // Initialized/imported states set the bound; gates can only widen it.
926 size_t samplingSupportQubits = 0, savedSamplingSupportQubits = 0;
927
928 template <class Gate, class... Qubits> void ApplyNativeGate(const Gate &gate, Qubits... qubits)
929 {
930 state->InvalidateMeasurements();
931
932 // The native parallel dispatcher's one-thread fallback can contract
933 // complex products differently from its serial/actual-team kernels.
934 // Use the serial dispatcher when no team will run, including builds
935 // without OpenMP. Keep the public permission flag unchanged.
936 struct RestoreThreading
937 {
938 SamplingQubitRegister &state;
939 bool enabled;
940
941 ~RestoreThreading()
942 {
943 state.SetMultithreading(enabled);
944 }
945 } restore{*state, state->GetMultithreading()};
946
947 bool hasTeam = false;
948#ifdef _OPENMP
949 hasTeam = !omp_in_parallel() && omp_get_max_threads() > 1;
950#endif
951 if (!LegacySampling() && !hasTeam)
952 state->SetMultithreading(false);
953 state->ApplyGate(gate, qubits...);
954 ((samplingSupportQubits = std::max(samplingSupportQubits, size_t(qubits) + 1)), ...);
955 }
956};
957} // namespace Simulators::Private
958#endif
int ApplyK(void *sim, int qubit)
double Probability(void *sim, unsigned long long int outcome)
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 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 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 SaveState(void *sim)
std::vector< int > Qubits(const Types::qubits_vector &qubits, size_t width)
SimulationType
The type of simulation.
Definition State.h:109
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
Counts< Many > Count(const Plan &plan, size_t shots, size_t bits, const Bit &bit, Uniform &&uniform)
Definition Sampling.h:472
int Threads(bool permitted, size_t work)
Definition Sampling.h:65
Counts< Many > Single(size_t bits, const Bit &bit)
Definition Sampling.h:375
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