Maestro 0.3.11
Unified interface for quantum circuit simulation
Loading...
Searching...
No Matches
QCSimDensityMatrixSimulator.h
Go to the documentation of this file.
1#pragma once
2#if defined(INCLUDED_BY_FACTORY)
3#include "DensityMatrix.h"
5#define _USE_MATH_DEFINES
6#include <math.h>
7
9{
10// Keep density sampling on its existing native engine. The adapter exposes an
11// immutable-table draw input without changing the external QCSim dependency.
12class SamplingDensityMatrix : public QC::DensityMatrix<>
13{
14 public:
15 using QC::DensityMatrix<>::DensityMatrix;
16
17 double SamplingUniform()
18 {
20 }
21
22 void SaveState()
23 {
24 snapshot = std::make_shared<const Eigen::MatrixXcd>(rho);
25 }
26
27 void RestoreState()
28 {
29 if (snapshot)
30 rho = *snapshot;
31 }
32
33 private:
34 std::shared_ptr<const Eigen::MatrixXcd> snapshot;
35};
36
37// density_matrix backend. Owns exactly one native QCSim implementation.
38class QCSimDensityMatrixSimulator : public QCSimGateSimulator
39{
40 public:
41 QCSimDensityMatrixSimulator()
42 {
43 configuration.SetConfiguration("method", "density_matrix");
44 }
45
46 const char *MethodName() const override
47 {
48 return "density_matrix";
49 }
50
51 void Initialize() override
52 {
53 if (nrQubits != 0)
54 {
55 InitializeSamplingSeed();
56 {
57 densityMatrix = std::make_unique<SamplingDensityMatrix>(nrQubits);
58 SeedBackend(densityMatrix.get(), samplingSeed);
59 }
60 SetMultithreading(enableMultithreading);
61 for (const auto &[key, value] : configuration.GetConfigMap())
62 if (key != "method")
63 Configure(key.c_str(), value.c_str());
64 }
65 }
66
67 void InitializeState(size_t num_qubits, std::vector<std::complex<double>> &amplitudes) override
68 {
69 if (num_qubits == 0)
70 return;
71 Clear();
72 nrQubits = num_qubits;
73 Initialize();
74 Eigen::VectorXcd amplitudesEigen(Eigen::Map<Eigen::VectorXcd, Eigen::Unaligned>(amplitudes.data(), amplitudes.size()));
75 densityMatrix->setFromStatevector(amplitudesEigen);
76 }
77
78#ifndef NO_QISKIT_AER
79 void InitializeState(size_t num_qubits, AER::Vector<std::complex<double>> &amplitudes) override
80 {
81 if (num_qubits == 0)
82 return;
83 Clear();
84 nrQubits = num_qubits;
85 Initialize();
86 Eigen::VectorXcd amplitudesEigen(Eigen::Map<Eigen::VectorXcd, Eigen::Unaligned>(amplitudes.data(), amplitudes.size()));
87 densityMatrix->setFromStatevector(amplitudesEigen);
88 }
89
90#endif
91
92 void InitializeState(size_t num_qubits, Eigen::VectorXcd &amplitudes) override
93 {
94 if (num_qubits == 0)
95 return;
96 Clear();
97 nrQubits = num_qubits;
98 Initialize();
99 densityMatrix->setFromStatevector(amplitudes);
100 }
101
102 void InitializeToBasisState(size_t num_qubits, Types::qubit_t basisState) override
103 {
104 if (num_qubits == 0)
105 return;
106 Clear();
107 nrQubits = num_qubits;
108 Initialize();
109 densityMatrix->setToBasisState(static_cast<size_t>(basisState));
110 }
111
112 void InitializeToMixtureOfBasisStates(size_t num_qubits, const std::vector<std::pair<Types::qubit_t, double>> &mixture) override
113 {
114 if (num_qubits == 0)
115 return;
116 Clear();
117 nrQubits = num_qubits;
118 Initialize();
119 std::vector<std::pair<size_t, double>> converted;
120 converted.reserve(mixture.size());
121 for (const auto &[basisState, weight] : mixture)
122 converted.emplace_back(static_cast<size_t>(basisState), weight);
123 densityMatrix->setToMixtureOfBasisStates(converted);
124 }
125
126 void Reset() override
127 {
128 if (densityMatrix)
129 densityMatrix->Reset();
130 upcomingGateIndex = 0;
131 }
132
133 void Configure(const char *key, const char *value) override
134 {
135 if (ConfigureSampling(key, value))
136 return;
137 if (std::string("method") == key)
138 {
139 ValidateMethod(value);
140 configuration.SetConfiguration(key, value);
141 return;
142 }
143 if (!configuration.WasApplied(key, value))
144 configuration.SetConfiguration(key, value);
145 if (std::string("seed") == key)
146 {
147 const uint64_t seed = std::stoull(value);
148 SeedAuxiliaryRng(seed);
149 SeedSampling(seed);
150 if (densityMatrix)
151 SeedBackend(densityMatrix.get(), seed);
152 return;
153 }
154 if (std::string(key) == "pauli_propagator_workers")
155 {
156 const auto workers = configuration.GetConfigurationAsUnsigned(key);
157 if (workers > 1024)
158 throw std::invalid_argument("pauli_propagator_workers exceeds 1024");
159 }
160 }
161
162 size_t AllocateQubits(size_t num_qubits) override
163 {
164 if (densityMatrix)
165 return 0;
166 const size_t oldNrQubits = nrQubits;
167 nrQubits += num_qubits;
168 return oldNrQubits;
169 }
170
171 void Clear() override
172 {
173 densityMatrix = nullptr;
174 nrQubits = 0;
175 upcomingGateIndex = 0;
176 upcomingGates.clear();
177 }
178
179 size_t Measure(const Types::qubits_vector &qubits) override
180 {
181 if (qubits.size() > sizeof(size_t) * 8)
182 std::cerr << "Warning: The number of qubits to measure is larger than the "
183 "number of bits in the size_t type, the outcome will be undefined"
184 << std::endl;
185 size_t res = 0;
186 size_t mask = 1ULL;
187 DontNotify();
188 {
189 for (size_t qubit : qubits)
190 {
191 if (densityMatrix->MeasureQubit(qubit))
192 res |= mask;
193 mask <<= 1;
194 }
195 }
196 Notify();
197 NotifyObservers(qubits);
198 return res;
199 }
200
201 std::vector<bool> MeasureMany(const Types::qubits_vector &qubits) override
202 {
203 std::vector<bool> res(qubits.size(), false);
204 DontNotify();
205 {
206 for (size_t q = 0; q < qubits.size(); ++q)
207 if (densityMatrix->MeasureQubit(qubits[q]))
208 res[q] = true;
209 }
210 Notify();
211 NotifyObservers(qubits);
212 return res;
213 }
214
215 void ApplyReset(const Types::qubits_vector &qubits) override
216 {
217 DontNotify();
218 {
219 for (size_t qubit : qubits)
220 densityMatrix->ApplyReset(qubit);
221 }
222 Notify();
223 NotifyObservers(qubits);
224 }
225
226 bool SupportsQuantumChannels() const override
227 {
228 return true;
229 }
230
231 void ApplyQuantumChannel(const Types::qubits_vector &targets, const QuantumChannel &channel) override
232 {
233 if (!SupportsQuantumChannels())
234 throw std::runtime_error("QCSim quantum channels require density_matrix or "
235 "matrix_product_operator simulation");
236 if (targets.size() != channel.GetNumberOfQubits())
237 throw std::invalid_argument("The number of channel targets does not match its Kraus operators");
238 if (targets.empty() || targets.size() > 2)
239 throw std::invalid_argument("QCSim supports only one- and two-qubit local channels");
240 std::unordered_set<Types::qubit_t> uniqueTargets;
241 for (const Types::qubit_t target : targets)
242 {
243 if (target >= nrQubits)
244 throw std::invalid_argument("Quantum-channel qubit is out of range");
245 if (!uniqueTargets.insert(target).second)
246 throw std::invalid_argument("Quantum-channel target qubits must be distinct");
247 }
248 const auto &krausOperators = channel.GetKrausOperators();
249 {
250 if (!densityMatrix)
251 throw std::runtime_error("QCSim density-matrix state is not initialized");
252 if (targets.size() == 1)
253 densityMatrix->ApplyChannel(krausOperators, targets[0]);
254 else
255 densityMatrix->ApplyChannel(krausOperators, targets[0], targets[1]);
256 }
257 NotifyObservers(targets);
258 }
259
260 std::complex<double> DensityMatrixTrace() const override
261 {
262 if (densityMatrix)
263 return densityMatrix->Trace();
264 throw std::runtime_error("Mixed-state diagnostics require density_matrix or "
265 "matrix_product_operator");
266 }
267
268 double DensityMatrixPurity() const override
269 {
270 if (densityMatrix)
271 return densityMatrix->Purity();
272 throw std::runtime_error("Mixed-state diagnostics require density_matrix or "
273 "matrix_product_operator");
274 }
275
276 std::complex<double> DensityMatrixTraceOfSquare() const override
277 {
278 if (densityMatrix)
279 {
280 const auto &rho = densityMatrix->getDensityMatrix();
281 return (rho * rho).trace();
282 }
283 throw std::runtime_error("Mixed-state diagnostics require density_matrix or "
284 "matrix_product_operator");
285 }
286
287 std::complex<double> DensityMatrixOverlap(const IState &other) const override
288 {
289 const auto *rhs = dynamic_cast<const QCSimDensityMatrixSimulator *>(&other);
290 if (!rhs)
291 throw std::invalid_argument("Density-matrix overlap requires matching QCSim backends");
292 if (densityMatrix && rhs->densityMatrix)
293 return densityMatrix->HilbertSchmidtOverlap(*rhs->densityMatrix);
294 throw std::invalid_argument("Density-matrix overlap requires two density matrices or two MPOs");
295 }
296
297 double DensityMatrixHermiticityResidual() const override
298 {
299 if (densityMatrix)
300 return (densityMatrix->getDensityMatrix() - densityMatrix->getDensityMatrix().adjoint()).norm();
301 throw std::runtime_error("Mixed-state diagnostics require density_matrix or "
302 "matrix_product_operator");
303 }
304
305 bool IsDensityMatrixHermitian(double eps = 1e-10) const override
306 {
307 if (densityMatrix)
308 return densityMatrix->IsHermitian(eps);
309 throw std::runtime_error("Mixed-state diagnostics require density_matrix or "
310 "matrix_product_operator");
311 }
312
313 Eigen::MatrixXcd PartialTrace(const Types::qubits_vector &qubits) const override
314 {
315 if (densityMatrix)
316 return densityMatrix->PartialTrace(std::vector<size_t>(qubits.begin(), qubits.end()));
317 throw std::runtime_error("Partial trace requires density_matrix or matrix_product_operator");
318 }
319
320 double FidelityWithStatevector(const Eigen::VectorXcd &psi) const override
321 {
322 if (densityMatrix)
323 return densityMatrix->FidelityWithStatevector(psi);
324 throw std::runtime_error("Mixed-state fidelity requires density_matrix or "
325 "matrix_product_operator");
326 }
327
328 double Probability(Types::qubit_t outcome) override
329 {
330 return densityMatrix->getBasisStateProbability(outcome);
331 }
332
333 std::complex<double> Amplitude(Types::qubit_t outcome) override
334 {
335 throw std::runtime_error("QCSimState::Amplitude: Amplitudes are not defined for the density "
336 "matrix simulator.");
337 }
338
339 std::vector<double> AllProbabilities() override
340 {
341 const size_t nrBasisStates = densityMatrix->getNrBasisStates();
342 std::vector<double> result(nrBasisStates);
343 for (size_t i = 0; i < nrBasisStates; ++i)
344 result[i] = densityMatrix->getBasisStateProbability(i);
345 return result;
346 }
347
348 std::vector<double> Probabilities(const Types::qubits_vector &qubits) override
349 {
350 std::vector<double> result(qubits.size());
351 {
352 for (int i = 0; i < static_cast<int>(qubits.size()); ++i)
353 result[i] = densityMatrix->getBasisStateProbability(qubits[i]);
354 }
355 return result;
356 }
357
358 std::unordered_map<Types::qubit_t, Types::qubit_t> SampleCounts(const Types::qubits_vector &qubits, size_t shots = 1000) override
359 {
360 if (!LegacySampling())
361 return SamplePrepared<false>(qubits, shots);
362 if (qubits.empty() || shots == 0)
363 return {};
364 if (qubits.size() > sizeof(size_t) * 8)
365 std::cerr << "Warning: The number of qubits to measure is larger than the "
366 "number of bits in the size_t type, the outcome will be undefined"
367 << std::endl;
368 std::unordered_map<Types::qubit_t, Types::qubit_t> result;
369 DontNotify();
370 {
371 const auto sampler = densityMatrix->PrepareSampler();
372 for (size_t shot = 0; shot < shots; ++shot)
373 {
374 const size_t measured = densityMatrix->MeasureNoCollapse(sampler);
375 Types::qubit_t packed = 0;
376 for (size_t i = 0; i < qubits.size(); ++i)
377 if ((measured & (1ULL << qubits[i])) != 0)
378 packed |= 1ULL << i;
379 ++result[packed];
380 }
381 }
382 Notify();
383 NotifyObservers(qubits);
384 return result;
385 }
386
387 std::unordered_map<std::vector<bool>, Types::qubit_t> SampleCountsMany(const Types::qubits_vector &qubits, size_t shots = 1000) override
388 {
389 if (!LegacySampling())
390 return SamplePrepared<true>(qubits, shots);
391 if (qubits.empty() || shots == 0)
392 return {};
393 std::unordered_map<std::vector<bool>, Types::qubit_t> result;
394 DontNotify();
395 {
396 const auto sampler = densityMatrix->PrepareSampler();
397 std::vector<bool> packed(qubits.size());
398 for (size_t shot = 0; shot < shots; ++shot)
399 {
400 const size_t measured = densityMatrix->MeasureNoCollapse(sampler);
401 for (size_t i = 0; i < qubits.size(); ++i)
402 packed[i] = (measured & (1ULL << qubits[i])) != 0;
403 ++result[packed];
404 }
405 }
406 Notify();
407 NotifyObservers(qubits);
408 return result;
409 }
410
411 double ExpectationValue(const std::string &pauliStringOrig) override
412 {
413 if (pauliStringOrig.empty())
414 return 1.0;
415 std::string pauliString = pauliStringOrig;
416 if (pauliString.size() > GetNumberOfQubits())
417 {
418 for (size_t i = GetNumberOfQubits(); i < pauliString.size(); ++i)
419 {
420 const auto pauliOp = toupper(pauliString[i]);
421 if (pauliOp != 'I' && pauliOp != 'Z')
422 return 0.0;
423 }
424 pauliString.resize(GetNumberOfQubits());
425 }
426 {
427 pauliString.resize(GetNumberOfQubits(), 'I');
428 return densityMatrix->ExpectationValue(pauliString).real();
429 }
430 }
431
432 SimulationType GetSimulationType() const override
433 {
434 return SimulationType::kDensityMatrix;
435 }
436
437 void SaveState() override
438 {
439 densityMatrix->SaveState();
440 }
441
442 void RestoreState() override
443 {
444 densityMatrix->RestoreState();
445 }
446
447 void SetMultithreading(bool multithreading = true) override
448 {
449 enableMultithreading = multithreading;
450 if (densityMatrix)
451 densityMatrix->SetMultithreading(multithreading);
452 }
453
455 {
456 if (GetNumberOfQubits() > sizeof(Types::qubit_t) * 8)
457 std::cerr << "Warning: The number of qubits to measure is larger than the "
458 "number of bits in the Types::qubit_t type, the outcome will be "
459 "undefined"
460 << std::endl;
461 return densityMatrix->MeasureNoCollapse();
462 }
463
464 std::vector<bool> MeasureNoCollapseMany() override
465 {
466 const auto measured = densityMatrix->MeasureNoCollapse();
467 std::vector<bool> res(nrQubits);
468 for (size_t i = 0; i < nrQubits; ++i)
469 res[i] = ((measured >> i) & 1) == 1;
470 return res;
471 }
472
473 void ApplyGenericOneQubitGate(Types::qubit_t qubit, const Eigen::Matrix2cd &gate) override
474 {
475 const QC::Gates::AppliedGate<> agate(gate, qubit);
476 ApplyNativeGate(agate);
477 NotifyObservers({qubit});
478 }
479
480 void ApplyGenericTwoQubitGate(Types::qubit_t qubit0, Types::qubit_t qubit1, const Eigen::Matrix4cd &gate) override
481 {
482 const QC::Gates::AppliedGate<> agate(gate, qubit0, qubit1);
483 ApplyNativeGate(agate);
484 NotifyObservers({qubit0, qubit1});
485 }
486
487 void ApplyGenericThreeQubitGate(Types::qubit_t q0, Types::qubit_t q1, Types::qubit_t q2, const Matrix8cd &gate) override
488 {
489 const QC::Gates::AppliedGate<> applied(gate, q0, q1, q2);
490 ApplyNativeGate(applied);
491 NotifyObservers({q0, q1, q2});
492 }
493
494 void ApplyP(Types::qubit_t qubit, double lambda) override
495 {
496 pgate.SetPhaseShift(lambda);
497 ApplyNativeGate(pgate, static_cast<unsigned int>(qubit));
498 NotifyObservers({qubit});
499 }
500
501 void ApplyX(Types::qubit_t qubit) override
502 {
503 ApplyNativeGate(xgate, static_cast<unsigned int>(qubit));
504 NotifyObservers({qubit});
505 }
506
507 void ApplyY(Types::qubit_t qubit) override
508 {
509 ApplyNativeGate(ygate, static_cast<unsigned int>(qubit));
510 NotifyObservers({qubit});
511 }
512
513 void ApplyZ(Types::qubit_t qubit) override
514 {
515 ApplyNativeGate(zgate, static_cast<unsigned int>(qubit));
516 NotifyObservers({qubit});
517 }
518
519 void ApplyH(Types::qubit_t qubit) override
520 {
521 ApplyNativeGate(h, static_cast<unsigned int>(qubit));
522 NotifyObservers({qubit});
523 }
524
525 void ApplyS(Types::qubit_t qubit) override
526 {
527 ApplyNativeGate(sgate, static_cast<unsigned int>(qubit));
528 NotifyObservers({qubit});
529 }
530
531 void ApplySDG(Types::qubit_t qubit) override
532 {
533 ApplyNativeGate(sdggate, static_cast<unsigned int>(qubit));
534 NotifyObservers({qubit});
535 }
536
537 void ApplyT(Types::qubit_t qubit) override
538 {
539 ApplyNativeGate(tgate, static_cast<unsigned int>(qubit));
540 NotifyObservers({qubit});
541 }
542
543 void ApplyTDG(Types::qubit_t qubit) override
544 {
545 ApplyNativeGate(tdggate, static_cast<unsigned int>(qubit));
546 NotifyObservers({qubit});
547 }
548
549 void ApplySx(Types::qubit_t qubit) override
550 {
551 ApplyNativeGate(sxgate, static_cast<unsigned int>(qubit));
552 NotifyObservers({qubit});
553 }
554
555 void ApplySxDAG(Types::qubit_t qubit) override
556 {
557 ApplyNativeGate(sxdaggate, static_cast<unsigned int>(qubit));
558 NotifyObservers({qubit});
559 }
560
561 void ApplyK(Types::qubit_t qubit) override
562 {
563 ApplyNativeGate(k, static_cast<unsigned int>(qubit));
564 NotifyObservers({qubit});
565 }
566
567 void ApplyRx(Types::qubit_t qubit, double theta) override
568 {
569 rxgate.SetTheta(theta);
570 ApplyNativeGate(rxgate, static_cast<unsigned int>(qubit));
571 NotifyObservers({qubit});
572 }
573
574 void ApplyRy(Types::qubit_t qubit, double theta) override
575 {
576 rygate.SetTheta(theta);
577 ApplyNativeGate(rygate, static_cast<unsigned int>(qubit));
578 NotifyObservers({qubit});
579 }
580
581 void ApplyRz(Types::qubit_t qubit, double theta) override
582 {
583 rzgate.SetTheta(theta);
584 ApplyNativeGate(rzgate, static_cast<unsigned int>(qubit));
585 NotifyObservers({qubit});
586 }
587
588 void ApplyU(Types::qubit_t qubit, double theta, double phi, double lambda, double gamma) override
589 {
590 ugate.SetParams(theta, phi, lambda, gamma);
591 ApplyNativeGate(ugate, static_cast<unsigned int>(qubit));
592 NotifyObservers({qubit});
593 }
594
595 void ApplyCX(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit) override
596 {
597 ApplyNativeGate(cxgate, static_cast<unsigned int>(tgt_qubit), static_cast<unsigned int>(ctrl_qubit));
598 NotifyObservers({tgt_qubit, ctrl_qubit});
599 }
600
601 void ApplyCY(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit) override
602 {
603 ApplyNativeGate(cygate, static_cast<unsigned int>(tgt_qubit), static_cast<unsigned int>(ctrl_qubit));
604 NotifyObservers({tgt_qubit, ctrl_qubit});
605 }
606
607 void ApplyCZ(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit) override
608 {
609 ApplyNativeGate(czgate, static_cast<unsigned int>(tgt_qubit), static_cast<unsigned int>(ctrl_qubit));
610 NotifyObservers({tgt_qubit, ctrl_qubit});
611 }
612
613 void ApplyCP(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit, double lambda) override
614 {
615 cpgate.SetPhaseShift(lambda);
616 ApplyNativeGate(cpgate, static_cast<unsigned int>(tgt_qubit), static_cast<unsigned int>(ctrl_qubit));
617 NotifyObservers({tgt_qubit, ctrl_qubit});
618 }
619
620 void ApplyCRx(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit, double theta) override
621 {
622 crxgate.SetTheta(theta);
623 ApplyNativeGate(crxgate, static_cast<unsigned int>(tgt_qubit), static_cast<unsigned int>(ctrl_qubit));
624 NotifyObservers({tgt_qubit, ctrl_qubit});
625 }
626
627 void ApplyCRy(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit, double theta) override
628 {
629 crygate.SetTheta(theta);
630 ApplyNativeGate(crygate, static_cast<unsigned int>(tgt_qubit), static_cast<unsigned int>(ctrl_qubit));
631 NotifyObservers({tgt_qubit, ctrl_qubit});
632 }
633
634 void ApplyCRz(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit, double theta) override
635 {
636 crzgate.SetTheta(theta);
637 ApplyNativeGate(crzgate, static_cast<unsigned int>(tgt_qubit), static_cast<unsigned int>(ctrl_qubit));
638 NotifyObservers({tgt_qubit, ctrl_qubit});
639 }
640
641 void ApplyCH(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit) override
642 {
643 ApplyNativeGate(ch, static_cast<unsigned int>(tgt_qubit), static_cast<unsigned int>(ctrl_qubit));
644 NotifyObservers({tgt_qubit, ctrl_qubit});
645 }
646
647 void ApplyCSx(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit) override
648 {
649 ApplyNativeGate(csx, static_cast<unsigned int>(tgt_qubit), static_cast<unsigned int>(ctrl_qubit));
650 NotifyObservers({tgt_qubit, ctrl_qubit});
651 }
652
653 void ApplyCSxDAG(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit) override
654 {
655 ApplyNativeGate(csxdag, static_cast<unsigned int>(tgt_qubit), static_cast<unsigned int>(ctrl_qubit));
656 NotifyObservers({tgt_qubit, ctrl_qubit});
657 }
658
659 void ApplySwap(Types::qubit_t qubit0, Types::qubit_t qubit1) override
660 {
661 ApplyNativeGate(swapgate, static_cast<unsigned int>(qubit1), static_cast<unsigned int>(qubit0));
662 NotifyObservers({qubit1, qubit0});
663 }
664
665 void ApplyCCX(Types::qubit_t qubit0, Types::qubit_t qubit1, Types::qubit_t qubit2) override
666 {
667 ApplyNativeGate(ccxgate, static_cast<unsigned int>(qubit2), static_cast<unsigned int>(qubit1), static_cast<unsigned int>(qubit0));
668 NotifyObservers({qubit2, qubit1, qubit0});
669 }
670
671 void ApplyCSwap(Types::qubit_t ctrl_qubit, Types::qubit_t qubit0, Types::qubit_t qubit1) override
672 {
673 ApplyNativeGate(cswapgate, static_cast<unsigned int>(qubit1), static_cast<unsigned int>(qubit0), static_cast<unsigned int>(ctrl_qubit));
674 NotifyObservers({qubit1, qubit0, ctrl_qubit});
675 }
676
677 void ApplyCU(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit, double theta, double phi, double lambda, double gamma) override
678 {
679 cugate.SetParams(theta, phi, lambda, gamma);
680 ApplyNativeGate(cugate, static_cast<unsigned int>(tgt_qubit), static_cast<unsigned int>(ctrl_qubit));
681 NotifyObservers({tgt_qubit, ctrl_qubit});
682 }
683
684 std::unique_ptr<ISimulator> Clone() override
685 {
686 return CloneForExecution(NextCloneSeed());
687 }
688
689 std::unique_ptr<ISimulator> CloneForExecution(uint64_t seed) override
690 {
691 auto cloned = std::make_unique<QCSimDensityMatrixSimulator>();
692 cloned->nrQubits = nrQubits;
693 cloned->enableMultithreading = enableMultithreading;
694 cloned->upcomingGates = upcomingGates;
695 cloned->upcomingGateIndex = upcomingGateIndex;
696 if (densityMatrix)
697 cloned->densityMatrix = std::make_unique<SamplingDensityMatrix>(*densityMatrix);
698 for (const auto &[key, value] : configuration.GetConfigMap())
699 cloned->Configure(key.c_str(), value.c_str());
700 // Native DensityMatrix copying includes its RNG. Replace every stream
701 // before publishing the clone, even when the parent was randomly seeded.
702 cloned->SetSeed(seed);
703 return cloned;
704 }
705
706 protected:
707 std::unique_ptr<SamplingDensityMatrix> densityMatrix;
708
709 private:
710 template <bool Many> Utils::Sampling::Counts<Many> SamplePrepared(const Types::qubits_vector &qubits, size_t shots)
711 {
712 if (qubits.empty() || !shots)
713 return {};
714 Utils::Sampling::ValidateQubits(qubits, nrQubits, Many);
715 const size_t states = densityMatrix->getNrBasisStates();
716 std::vector<size_t> selected, positions;
717 for (const auto q : qubits)
718 {
719 const auto found = std::find(selected.begin(), selected.end(), q);
720 positions.push_back(static_cast<size_t>(found - selected.begin()));
721 if (found == selected.end())
722 selected.push_back(q);
723 }
724 const bool marginal = selected.size() < nrQubits && selected.size() <= 12 && shots >= std::max<size_t>(256, states / 16);
725 const size_t outcomes = marginal ? size_t{1} << selected.size() : states;
726 auto options = SamplingOptions();
727 if (outcomes > (options.memoryBytes - 4096) / sizeof(double))
728 throw std::length_error("Density sampling snapshot exceeds the scratch budget");
729 options.memoryBytes -= outcomes * sizeof(double);
730 std::vector<double> probabilities(outcomes, 0.);
731 const auto &matrix = densityMatrix->getDensityMatrix();
732 double mass = 0.;
733 for (size_t state = 0; state < states; ++state)
734 {
735 const auto diagonal = matrix(state, state);
736 if (!std::isfinite(diagonal.real()) || !std::isfinite(diagonal.imag()) || std::abs(diagonal.imag()) > 1E-10 || diagonal.real() < -1E-12)
737 throw std::domain_error("Density-matrix populations must be finite, real and nonnegative");
738 const double probability = std::max(0., diagonal.real());
739 mass += probability;
740 size_t row = state;
741 if (marginal)
742 {
743 row = 0;
744 for (size_t bit = 0; bit < selected.size(); ++bit)
745 if ((state >> selected[bit]) & 1)
746 row |= size_t{1} << bit;
747 }
748 probabilities[row] += probability;
749 }
750 if (!std::isfinite(mass) || mass <= 1E-20)
751 throw std::domain_error("Cannot sample a density matrix with no probability mass");
752 const Utils::Sampling::Prepared plan(
753 outcomes, shots, [&](size_t row) { return probabilities[row]; }, options);
754 auto result = Utils::Sampling::Count<Many>(
755 plan, shots, qubits.size(), [&](size_t row, size_t bit) { return ((row >> (marginal ? positions[bit] : qubits[bit])) & 1) != 0; },
756 [&] { return densityMatrix->SamplingUniform(); });
757 NotifyObservers(qubits);
758 return result;
759 }
760
761 template <class Gate, class... Qubits> void ApplyNativeGate(const Gate &gate, Qubits... qubits)
762 {
763 // Native density diagonal loops can contract complex products
764 // differently when outlined by OpenMP. Preserve their serial rounding
765 // in reproducible trajectories; terminal preparation keeps its existing
766 // threading policy, and the other gate kernels remain parallel.
767 struct RestoreThreading
768 {
769 SamplingDensityMatrix &state;
770 bool enabled;
771
772 ~RestoreThreading()
773 {
774 state.SetMultithreading(enabled);
775 }
776 } restore{*densityMatrix, densityMatrix->GetMultithreading()};
777
778 bool hasTeam = false;
779#ifdef _OPENMP
780 hasTeam = !omp_in_parallel() && omp_get_max_threads() > 1;
781#endif
782 const bool trajectory = configuration.GetConfiguration("reproducible_trajectory") == "true";
783 if (!LegacySampling() && (!hasTeam || (trajectory && gate.getStructure().kind == QC::Gates::GateStructure::Kind::Diagonal)))
784 densityMatrix->SetMultithreading(false);
785 densityMatrix->ApplyGate(gate, static_cast<size_t>(qubits)...);
786 }
787};
788} // namespace Simulators::Private
789#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 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
double Uniform(std::mt19937_64 &engine)
Counts< Many > Count(const Plan &plan, size_t shots, size_t bits, const Bit &bit, Uniform &&uniform)
Definition Sampling.h:472
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