Maestro 0.3.1
Unified interface for quantum circuit simulation
Loading...
Searching...
No Matches
QCSimState.h
Go to the documentation of this file.
1
12
13#pragma once
14
15#ifndef _QCSIMSTATE_H_
16#define _QCSIMSTATE_H_
17
18#ifdef INCLUDED_BY_FACTORY
19
20#include <algorithm>
21#include <iomanip>
22#include <limits>
23#include <random>
24#include <sstream>
25#include <type_traits>
26#include <utility>
27
28#include "Simulator.h"
29
30#include "Clifford.h"
31#include "DensityMatrix.h"
32#include "MPOSimulator.h"
33#include "MPSSimulator.h"
34#include "QubitRegister.h"
38
41
42#include "../Utils/Alias.h"
43
44#include "MPSDummySimulator.h"
45#include "Configuration.h"
46
47namespace Simulators {
48// TODO: Maybe use the pimpl idiom
49// https://en.cppreference.com/w/cpp/language/pimpl to hide the implementation
50// for good but during development this should be good enough
51namespace Private {
52
53template <typename T, typename = void>
54struct HasSetSeed : std::false_type {};
55template <typename T>
56struct HasSetSeed<T, std::void_t<decltype(std::declval<T &>().SetSeed(
57 std::declval<uint64_t>()))>> : std::true_type {};
58
59template <typename T>
60void SeedBackend(T *backend, uint64_t seed) {
61 if constexpr (HasSetSeed<T>::value) backend->SetSeed(seed);
62}
63
74
75//#define LOG_CALLBACK_INFO 1
76
77class QCSimState : public ISimulator {
78 public:
79 QCSimState() : rng(std::random_device{}()), uniformZeroOne(0, 1) {
80 meetingPositionCallback = [this](/*const auto &qMap,*/ const auto& bondDims)
81 -> QC::TensorNetworks::MPSSimulatorInterface::IndexType {
82 if (lookaheadDepth <= 0 ||
83 lookaheadDepth == std::numeric_limits<int>::max())
84 return -1; // will fallback to default behavior
85
86 if (upcomingGates.empty() ||
87 upcomingGateIndex >=
88 static_cast<long long>(upcomingGates.size())) {
89 return -1; // will fallback to default behavior
90 }
91
92 const size_t nQ = bondDims.size() + 1;
93
94 if (!dummySim || dummySim->getNrQubits() != nQ) {
95 dummySim = std::make_unique<Simulators::MPSDummySimulator>(nQ);
96 dummySim->SetMaxBondDimension(
97 configuration.GetConfigurationAsInt(MaxBondDimensionConfigKey()));
98 dummySim->setGrowthFactorGate(growthFactorGate);
99 dummySim->setGrowthFactorSwap(growthFactorSwap);
100 }
101
102 // Seed dummy with current real simulator state
103 // std::vector<long long int> map64(qMap.begin(), qMap.end());
104 // dummySim->SetInitialQubitsMap(map64);
105 dummySim->setTotalSwappingCost(0);
106
107 // Convert actual bond dims to doubles
108 std::vector<double> bondDimsD(bondDims.begin(), bondDims.end());
109 dummySim->SetCurrentBondDimensions(bondDimsD);
110
111 // display bond dimensions for debugging
112#ifdef LOG_CALLBACK_INFO
113 std::cerr << "Bond dimensions before swapping and applying the gate:";
114 for (size_t i = 0; i < bondDims.size(); ++i) {
115 std::cerr << bondDims[i] << " ";
116 }
117 std::cerr << std::endl;
118#endif
119
120 const auto& op = upcomingGates[upcomingGateIndex];
121 const auto qbits = op->AffectedQubits();
122
123 if (qbits.size() != 2)
124 return -1; // will fallback
125
126#ifdef LOG_CALLBACK_INFO
127 const auto &qmap = dummySim->getQubitsMap();
128
129 std::cerr << "Applying 2-qubit gate on physical qubits " <<
130 qmap[qbits[0]] << " and "
131 << qmap[qbits[1]]
132 << std::endl;
133
134 std::cerr << "Finding best meeting position for upcoming gates starting at index "
135 << upcomingGateIndex << " with lookahead depth "
136 << lookaheadDepth << " and heuristic depth "
137 << lookaheadDepthWithHeuristic << std::endl;
138
139 std::cerr << "Affected qubits: ";
140 for (const auto &q : qbits) std::cerr << q << " ";
141 std::cerr << std::endl;
142#endif
143
144 double bestCost = std::numeric_limits<double>::infinity();
145 auto res = dummySim->FindBestMeetingPosition(
146 upcomingGates, upcomingGateIndex, lookaheadDepth,
147 lookaheadDepthWithHeuristic, 0, bestCost);
148
149#ifdef LOG_CALLBACK_INFO
150 std::cerr << "Swapping the two qubits on position: " << res << " and " << (res + 1) << std::endl;
151#endif
152
153 dummySim->SwapQubitsToPosition(qbits[0], qbits[1], res);
154 dummySim->ApplyGate(op);
155
156 // display the expected bond dimensions after applying the gate for
157 // debugging
158
159#ifdef LOG_CALLBACK_INFO
160 const auto &expectedBondDims = dummySim->getCurrentBondDimensions();
161 std::cerr << "Expected bond dimensions after swapping and applying "
162 "the gate: ";
163 for (size_t i = 0; i < expectedBondDims.size(); ++i) {
164 std::cerr << expectedBondDims[i] << " ";
165 }
166 std::cerr << std::endl;
167
168 std::cerr << "Best meeting position: " << res << " with estimated cost: " << bestCost << std::endl;
169#endif
170
171 return res;
172 };
173
174 bondDimensionCallback = [this](const auto& bondDims) {
175 for (int i = 0; i < static_cast<int>(bondDims.size()); ++i)
176 if (static_cast<size_t>(bondDims[i]) > curMaxBondDim)
177 curMaxBondDim = static_cast<size_t>(bondDims[i]);
178 };
179 }
180
188 void Initialize() override {
189 if (nrQubits != 0) {
190 if (simulationType == SimulationType::kMatrixProductState) {
191 mpsSimulator =
192 std::make_unique<QC::TensorNetworks::MPSSimulator>(nrQubits);
193
194 // default is true
195 if (!useOptimalMeetingPosition)
196 mpsSimulator->SetUseOptimalMeetingPosition(false);
197 mpsSimulator->SetBondDimensionCallback(bondDimensionCallback);
198
199 curMaxBondDim = 1;
200 } else if (simulationType == SimulationType::kMatrixProductOperator) {
201 mpoSimulator =
202 std::make_unique<QC::TensorNetworks::MPOSimulator>(nrQubits);
203 if (!useOptimalMeetingPosition)
204 mpoSimulator->SetUseOptimalMeetingPosition(false);
205 mpoSimulator->SetBondDimensionCallback(bondDimensionCallback);
206 curMaxBondDim = 1;
207 } else if (simulationType == SimulationType::kStabilizer)
208 cliffordSimulator =
209 std::make_unique<QC::Clifford::StabilizerSimulator>(nrQubits);
210 else if (simulationType == SimulationType::kTensorNetwork) {
211 tensorNetwork =
212 std::make_unique<TensorNetworks::TensorNetwork>(nrQubits);
213 // for now the only used contractor is the forest one, but we'll use
214 // more in the future
215 const auto tensorContractor =
216 std::make_shared<TensorNetworks::ForestContractor>();
217 tensorNetwork->SetContractor(tensorContractor);
218 } else if (simulationType == SimulationType::kPauliPropagator) {
219 pp = std::make_unique<Simulators::QcsimPauliPropagator>();
220 pp->SetNrQubits(static_cast<int>(nrQubits));
221 } else if (simulationType == SimulationType::kPathIntegral) {
222 pathIntegralSimulator = std::make_unique<PathIntegralSimulator>();
223 pathIntegralSimulator->SetStartZeroState(nrQubits);
224 } else if (simulationType == SimulationType::kDensityMatrix) {
225 densityMatrix = std::make_unique<QC::DensityMatrix<>>(nrQubits);
226 } else if (simulationType == SimulationType::kExtendedStabilizer) {
227 extendedStabilizer =
228 std::make_unique<Simulators::QCSimExtendedStabilizer>(nrQubits);
229 } else
230 state = std::make_unique<QC::QubitRegister<>>(nrQubits);
231
232 SetMultithreading(enableMultithreading);
233
234 // ensure the config settings are applied, they need to be applied after the simulator is created
235 for (const auto& [key, value] : configuration.GetConfigMap())
236 if (key != "method") Configure(key.c_str(), value.c_str());
237 }
238 }
239
251 void InitializeState(size_t num_qubits,
252 std::vector<std::complex<double>> &amplitudes) override {
253 if (num_qubits == 0) return;
254 Clear();
255 nrQubits = num_qubits;
256 Initialize();
257 if (simulationType != SimulationType::kStatevector &&
258 simulationType != SimulationType::kDensityMatrix)
259 throw std::runtime_error(
260 "QCSimState::InitializeState: Invalid "
261 "simulation type for initializing the state.");
262
263 Eigen::VectorXcd amplitudesEigen(
264 Eigen::Map<Eigen::VectorXcd, Eigen::Unaligned>(amplitudes.data(),
265 amplitudes.size()));
266 if (simulationType == SimulationType::kDensityMatrix)
267 densityMatrix->setFromStatevector(amplitudesEigen);
268 else
269 state->setRegisterStorageFastNoNormalize(amplitudesEigen);
270 }
271
283 /*
284 void InitializeState(size_t num_qubits, std::vector<std::complex<double>,
285 avoid_init_allocator<std::complex<double>>>& amplitudes) override
286 {
287 Clear();
288 nrQubits = num_qubits;
289 Initialize();
290 Eigen::VectorXcd amplitudesEigen(Eigen::Map<Eigen::VectorXcd,
291 Eigen::Unaligned>(amplitudes.data(), amplitudes.size()));
292 state->setRegisterStorageFastNoNormalize(amplitudesEigen);
293 }
294 */
295
307#ifndef NO_QISKIT_AER
308 void InitializeState(size_t num_qubits,
309 AER::Vector<std::complex<double>> &amplitudes) override {
310 if (num_qubits == 0) return;
311 Clear();
312 nrQubits = num_qubits;
313 Initialize();
314 if (simulationType != SimulationType::kStatevector &&
315 simulationType != SimulationType::kDensityMatrix)
316 throw std::runtime_error(
317 "QCSimState::InitializeState: Invalid "
318 "simulation type for initializing the state.");
319
320 Eigen::VectorXcd amplitudesEigen(
321 Eigen::Map<Eigen::VectorXcd, Eigen::Unaligned>(amplitudes.data(),
322 amplitudes.size()));
323 if (simulationType == SimulationType::kDensityMatrix)
324 densityMatrix->setFromStatevector(amplitudesEigen);
325 else
326 state->setRegisterStorageFastNoNormalize(amplitudesEigen);
327 }
328#endif
329
341 void InitializeState(size_t num_qubits,
342 Eigen::VectorXcd &amplitudes) override {
343 if (num_qubits == 0) return;
344 Clear();
345 nrQubits = num_qubits;
346 Initialize();
347
348 if (simulationType != SimulationType::kStatevector &&
349 simulationType != SimulationType::kDensityMatrix)
350 throw std::runtime_error(
351 "QCSimState::InitializeState: Invalid "
352 "simulation type for initializing the state.");
353
354 if (simulationType == SimulationType::kDensityMatrix)
355 densityMatrix->setFromStatevector(amplitudes);
356 else {
357 state = std::make_unique<QC::QubitRegister<>>(nrQubits, amplitudes);
358 state->SetMultithreading(enableMultithreading);
359 }
360 }
361
373 void InitializeToBasisState(size_t num_qubits,
374 Types::qubit_t basisState) override {
375 if (num_qubits == 0) return;
376 Clear();
377 nrQubits = num_qubits;
378 Initialize();
379
380 if (simulationType == SimulationType::kDensityMatrix)
381 densityMatrix->setToBasisState(static_cast<size_t>(basisState));
382 else if (simulationType == SimulationType::kMatrixProductOperator)
383 mpoSimulator->setToBasisState(static_cast<size_t>(basisState));
384 else if (simulationType == SimulationType::kMatrixProductState)
385 mpsSimulator->setToBasisState(static_cast<size_t>(basisState));
386 else if (simulationType == SimulationType::kStatevector)
387 state->setToBasisState(static_cast<size_t>(basisState));
388 else
389 for (size_t q = 0; q < num_qubits; ++q)
390 if ((basisState >> q) & 1ULL) ApplyX(static_cast<Types::qubit_t>(q));
391 }
392
406 void InitializeToBasisState(size_t num_qubits,
407 const std::vector<bool> &basisState) override {
408 if (num_qubits == 0) return;
409 Clear();
410 nrQubits = num_qubits;
411 Initialize();
412
413 if (simulationType == SimulationType::kMatrixProductOperator)
414 mpoSimulator->setToBasisState(basisState);
415 else if (simulationType == SimulationType::kMatrixProductState)
416 mpsSimulator->setToBasisState(basisState);
417 else
418 for (size_t q = 0; q < num_qubits && q < basisState.size(); ++q)
419 if (basisState[q]) ApplyX(static_cast<Types::qubit_t>(q));
420 }
421
433 void InitializeToMixtureOfBasisStates(
434 size_t num_qubits,
435 const std::vector<std::pair<Types::qubit_t, double>> &mixture)
436 override {
437 if (num_qubits == 0) return;
438 Clear();
439 nrQubits = num_qubits;
440 Initialize();
441
442 if (simulationType != SimulationType::kDensityMatrix &&
443 simulationType != SimulationType::kMatrixProductOperator)
444 throw std::runtime_error(
445 "QCSimState::InitializeToMixtureOfBasisStates: Invalid simulation "
446 "type for initializing to a mixture of basis states.");
447
448 std::vector<std::pair<size_t, double>> converted;
449 converted.reserve(mixture.size());
450 for (const auto &[basisState, weight] : mixture)
451 converted.emplace_back(static_cast<size_t>(basisState), weight);
452
453 if (simulationType == SimulationType::kDensityMatrix)
454 densityMatrix->setToMixtureOfBasisStates(converted);
455 else
456 mpoSimulator->setToMixtureOfBasisStates(converted);
457 }
458
469 void InitializeToMixtureOfBasisStates(
470 size_t num_qubits,
471 const std::vector<std::pair<std::vector<bool>, double>> &mixture)
472 override {
473 if (num_qubits == 0) return;
474 Clear();
475 nrQubits = num_qubits;
476 Initialize();
477
478 if (simulationType != SimulationType::kMatrixProductOperator)
479 throw std::runtime_error(
480 "QCSimState::InitializeToMixtureOfBasisStates: Invalid simulation "
481 "type for initializing to a mixture of basis states.");
482
483 mpoSimulator->setToMixtureOfBasisStates(mixture);
484 }
485
492 void Reset() override {
493 if (mpsSimulator) {
494 mpsSimulator->Clear();
495 curMaxBondDim = 1;
496 } else if (mpoSimulator) {
497 mpoSimulator->Clear();
498 curMaxBondDim = 1;
499 } else if (cliffordSimulator)
500 cliffordSimulator->Reset();
501 else if (tensorNetwork)
502 tensorNetwork->Clear();
503 else if (state)
504 state->Reset();
505 else if (pp)
506 pp->ClearOperations();
507 else if (pathIntegralSimulator) {
508 pathIntegralSimulator->Reset();
509 pathIntegralSimulator->SetStartZeroState(nrQubits);
510 } else if (densityMatrix)
511 densityMatrix->Reset();
512 else if (extendedStabilizer)
513 extendedStabilizer->Reset(nrQubits);
514
515 upcomingGateIndex = 0;
516 ResetDummySimulator();
517 }
518
526 bool SupportsMPSSwapOptimization() const override { return true; }
527
536 void SetInitialQubitsMap(
537 const std::vector<long long int> &initialMap) override {
538 if (mpsSimulator || mpoSimulator) {
539 if (mpsSimulator) mpsSimulator->SetInitialQubitsMap(initialMap);
540 else mpoSimulator->SetInitialQubitsMap(initialMap);
541
542 if (!dummySim || dummySim->getNrQubits() != initialMap.size()) {
543 dummySim =
544 std::make_unique<Simulators::MPSDummySimulator>(initialMap.size());
545 dummySim->SetMaxBondDimension(
546 configuration.GetConfigurationAsInt(MaxBondDimensionConfigKey()));
547 }
548 dummySim->setGrowthFactorGate(growthFactorGate);
549 dummySim->setGrowthFactorSwap(growthFactorSwap);
550 dummySim->SetInitialQubitsMap(initialMap);
551 }
552 }
553
554 void SetUseOptimalMeetingPosition(bool enable) override {
555 useOptimalMeetingPosition = enable;
556 if (mpsSimulator) mpsSimulator->SetUseOptimalMeetingPosition(enable);
557 else if (mpoSimulator)
558 mpoSimulator->SetUseOptimalMeetingPosition(enable);
559 }
560
561 void SetLookaheadDepth(int depth) override {
562 lookaheadDepth = depth;
563 if (depth > 0 && !useOptimalMeetingPosition) {
564 if (mpsSimulator) mpsSimulator->SetUseOptimalMeetingPosition(true);
565 else if (mpoSimulator)
566 mpoSimulator->SetUseOptimalMeetingPosition(true);
567 }
568 }
569
570 void SetLookaheadDepthWithHeuristic(int depth) override {
571 lookaheadDepthWithHeuristic = depth;
572 if (lookaheadDepth < depth) SetLookaheadDepth(depth);
573 }
574
575 void SetUpcomingGates(
576 const std::vector<std::shared_ptr<Circuits::IOperation<double>>> &gates)
577 override {
578 upcomingGates = gates;
579 upcomingGateIndex = 0;
580
581 if (!mpsSimulator && !mpoSimulator) return;
582
583 // Register an observer that advances the gate index
584 ClearObservers(); // for now we only have this observer, so this should be
585 // fine
586 gateCounterObserver =
587 std::make_shared<GateCounterObserver>(upcomingGateIndex);
588 RegisterObserver(gateCounterObserver);
589
590 // Set up a meeting position callback that uses MPSDummySimulator
591 // for lookahead evaluation with actual bond dimensions
592 // the callback is called only for two qubits gates and only if executing
593 // them would require a swap
594 if (mpsSimulator)
595 mpsSimulator->SetMeetingPositionCallback(meetingPositionCallback);
596 else
597 mpoSimulator->SetMeetingPositionCallback(meetingPositionCallback);
598 }
599
608 long long int GetGatesCounter() const override { return upcomingGateIndex; }
609
619 void SetGatesCounter(long long int counter) override {
620 upcomingGateIndex = counter;
621 }
622
631 void IncrementGatesCounter() override { ++upcomingGateIndex; }
632
633 double getGrowthFactorSwap() const override { return growthFactorSwap; }
634 double getGrowthFactorGate() const override { return growthFactorGate; }
635
636 void setGrowthFactorSwap(double factor) override {
637 growthFactorSwap = factor;
638 if (dummySim) dummySim->setGrowthFactorSwap(factor);
639 }
640
641 void setGrowthFactorGate(double factor) override {
642 growthFactorGate = factor;
643 if (dummySim) dummySim->setGrowthFactorGate(factor);
644 }
645
654 void Configure(const char *key, const char *value) override {
655 if (std::string("method") == key) {
656 if (std::string("statevector") == value)
657 simulationType = SimulationType::kStatevector;
658 else if (std::string("matrix_product_state") == value)
659 simulationType = SimulationType::kMatrixProductState;
660 else if (std::string("matrix_product_operator") == value)
661 simulationType = SimulationType::kMatrixProductOperator;
662 else if (std::string("stabilizer") == value)
663 simulationType = SimulationType::kStabilizer;
664 else if (std::string("tensor_network") == value)
665 simulationType = SimulationType::kTensorNetwork;
666 else if (std::string("pauli_propagator") == value)
667 simulationType = SimulationType::kPauliPropagator;
668 else if (std::string("path_integral") == value)
669 simulationType = SimulationType::kPathIntegral;
670 else if (std::string("density_matrix") == value)
671 simulationType = SimulationType::kDensityMatrix;
672 else if (std::string("extended_stabilizer") == value)
673 simulationType = SimulationType::kExtendedStabilizer;
674 }
675
676 if (!configuration.WasApplied(key, value))
677 configuration.SetConfiguration(key, value);
678
679 if (std::string("seed") == key) {
680 const uint64_t seed = std::stoull(value);
681 nextSeedStream = 0;
682 rng.seed(seed);
683 if (state) SeedBackend(state.get(), seed);
684 if (mpsSimulator) SeedBackend(mpsSimulator.get(), seed);
685 if (mpoSimulator) SeedBackend(mpoSimulator.get(), seed);
686 if (cliffordSimulator) SeedBackend(cliffordSimulator.get(), seed);
687 if (tensorNetwork) tensorNetwork->SetSeed(seed);
688 if (pp) SeedBackend(pp.get(), seed);
689 if (pathIntegralSimulator) pathIntegralSimulator->SetSeed(seed);
690 if (densityMatrix) SeedBackend(densityMatrix.get(), seed);
691 if (extendedStabilizer)
692 extendedStabilizer->SetRandomSeed(
693 static_cast<std::mt19937::result_type>(seed));
694 return;
695 }
696
697 if (mpsSimulator) {
698 if (std::string(key) == "matrix_product_state_max_bond_dimension") {
699 mpsSimulator->setLimitBondDimension(configuration.GetConfigurationAsInt(key));
700 } else if (std::string(key) == "matrix_product_state_truncation_threshold") {
701 const double threshold = configuration.GetConfigurationAsDouble(key);
702 if (threshold > 0.) mpsSimulator->setLimitEntanglement(threshold);
703 } else if (std::string(key) == "matrix_product_state_truncation_mode") {
704 // "relative_max" -> RelativeToMax, "discarded_weight" -> DiscardedWeight (the
705 // default -- see QC::TensorNetworks::MPSSimulatorInterface::TruncationMode).
706 if (std::string(value) == "relative_max")
707 mpsSimulator->setTruncationMode(
708 QC::TensorNetworks::MPSSimulator::TruncationMode::RelativeToMax);
709 else if (std::string(value) == "discarded_weight")
710 mpsSimulator->setTruncationMode(
711 QC::TensorNetworks::MPSSimulator::TruncationMode::DiscardedWeight);
712 }
713 }
714
715 if (mpoSimulator) {
716 if (std::string(key) == "matrix_product_state_max_bond_dimension" ||
717 std::string(key) == "matrix_product_operator_max_bond_dimension") {
718 mpoSimulator->setLimitBondDimension(
719 configuration.GetConfigurationAsInt(key));
720 } else if (
721 std::string(key) == "matrix_product_state_truncation_threshold" ||
722 std::string(key) == "matrix_product_operator_truncation_threshold") {
723 const double threshold = configuration.GetConfigurationAsDouble(key);
724 if (threshold > 0.) mpoSimulator->setLimitEntanglement(threshold);
725 } else if (
726 std::string(key) == "matrix_product_state_truncation_mode" ||
727 std::string(key) == "matrix_product_operator_truncation_mode") {
728 // See the mpsSimulator branch above for the value convention.
729 if (std::string(value) == "relative_max")
730 mpoSimulator->setTruncationMode(
731 QC::TensorNetworks::MPOSimulator::TruncationMode::RelativeToMax);
732 else if (std::string(value) == "discarded_weight")
733 mpoSimulator->setTruncationMode(
734 QC::TensorNetworks::MPOSimulator::TruncationMode::DiscardedWeight);
735 } else if (std::string(key) ==
736 "matrix_product_operator_kraus_completeness_check") {
737 using Check = QC::TensorNetworks::MPOSimulator::KrausCompletenessCheck;
738 if (std::string(value) == "ignore")
739 mpoSimulator->setKrausCompletenessCheck(Check::Ignore);
740 else if (std::string(value) == "warn")
741 mpoSimulator->setKrausCompletenessCheck(Check::Warn);
742 else if (std::string(value) == "strict")
743 mpoSimulator->setKrausCompletenessCheck(Check::Strict);
744 } else if (std::string(key) ==
745 "matrix_product_operator_restore_trace_after_truncation") {
746 mpoSimulator->setRestoreTraceAfterTruncation(
747 std::string(value) == "1" || std::string(value) == "true");
748 } else if (std::string(key) ==
749 "matrix_product_operator_hermitize_after_truncation") {
750 mpoSimulator->setHermitizeAfterTruncation(
751 std::string(value) == "1" || std::string(value) == "true");
752 }
753 }
754
755 if (pp) {
756 if (std::string(key) == "pauli_propagator_coefficient_threshold") {
757 pp->SetCoefficientThreshold(configuration.GetConfigurationAsDouble(key));
758 } else if (std::string(key) == "pauli_propagator_pauli_weight_threshold") {
759 pp->SetPauliWeightThreshold(
760 configuration.GetConfigurationAsUnsigned(key));
761 } else if (std::string(key) == "pauli_propagator_steps_between_trims") {
762 pp->SetStepsBetweenTrims(configuration.GetConfigurationAsInt(key));
763 } else if (std::string(key) ==
764 "pauli_propagator_num_gates_between_deduplications") {
765 pp->SetStepsBetweenDeduplication(
766 configuration.GetConfigurationAsInt(key));
767 }
768 }
769
770 if (pathIntegralSimulator) {
771 if (std::string(key) == "path_integral_threshold") {
772 pathIntegralSimulator->SetTrimValue(configuration.GetConfigurationAsDouble(key));
773 }
774 }
775 }
776
784 std::string GetConfiguration(const char *key) const override {
785 if (std::string("method") == key) {
786 switch (simulationType) {
787 case SimulationType::kStatevector:
788 return "statevector";
789 case SimulationType::kMatrixProductState:
790 return "matrix_product_state";
791 case SimulationType::kMatrixProductOperator:
792 return "matrix_product_operator";
793 case SimulationType::kStabilizer:
794 return "stabilizer";
795 case SimulationType::kTensorNetwork:
796 return "tensor_network";
797 case SimulationType::kPauliPropagator:
798 return "pauli_propagator";
799 case SimulationType::kPathIntegral:
800 return "path_integral";
801 case SimulationType::kDensityMatrix:
802 return "density_matrix";
803 case SimulationType::kExtendedStabilizer:
804 return "extended_stabilizer";
805 default:
806 return "other";
807 }
808 }
809
810 return configuration.GetConfiguration(key);
811 }
812
820 size_t AllocateQubits(size_t num_qubits) override {
821 if ((simulationType == SimulationType::kStatevector && state) ||
822 (simulationType == SimulationType::kMatrixProductState &&
823 mpsSimulator) ||
824 (simulationType == SimulationType::kMatrixProductOperator &&
825 mpoSimulator) ||
826 (simulationType == SimulationType::kStabilizer && cliffordSimulator) ||
827 (simulationType == SimulationType::kTensorNetwork && tensorNetwork) ||
828 (simulationType == SimulationType::kDensityMatrix && densityMatrix) ||
829 (simulationType == SimulationType::kExtendedStabilizer &&
830 extendedStabilizer))
831 return 0;
832
833 const size_t oldNrQubits = nrQubits;
834 nrQubits += num_qubits;
835 if (simulationType == SimulationType::kPauliPropagator)
836 if (pp) pp->SetNrQubits(static_cast<int>(nrQubits));
837
838 return oldNrQubits;
839 }
840
847 size_t GetNumberOfQubits() const override { return nrQubits; }
848
856 void Clear() override {
857 state = nullptr;
858 mpsSimulator = nullptr;
859 mpoSimulator = nullptr;
860 cliffordSimulator = nullptr;
861 tensorNetwork = nullptr;
862 pp = nullptr;
863 pathIntegralSimulator = nullptr;
864 densityMatrix = nullptr;
865 extendedStabilizer = nullptr;
866 dummySim = nullptr;
867 nrQubits = 0;
868 upcomingGateIndex = 0;
869 upcomingGates.clear();
870 }
871
882 size_t Measure(const Types::qubits_vector &qubits) override {
883 // TODO: this is inefficient, maybe implement it better in qcsim
884 // for now it has the possibility of measuring a qubits interval, but not a
885 // list of qubits
886 if (qubits.size() > sizeof(size_t) * 8)
887 std::cerr
888 << "Warning: The number of qubits to measure is larger than the "
889 "number of bits in the size_t type, the outcome will be undefined"
890 << std::endl;
891
892 size_t res = 0;
893 size_t mask = 1ULL;
894
895 DontNotify();
896 if (simulationType == SimulationType::kStatevector) {
897 for (size_t qubit : qubits) {
898 if (state->MeasureQubit(static_cast<unsigned int>(qubit))) res |= mask;
899 mask <<= 1;
900 }
901 } else if (simulationType == SimulationType::kDensityMatrix) {
902 for (size_t qubit : qubits) {
903 if (densityMatrix->MeasureQubit(qubit)) res |= mask;
904 mask <<= 1;
905 }
906 } else if (simulationType == SimulationType::kMatrixProductOperator) {
907 const std::set<Eigen::Index> qubitsSet(qubits.begin(), qubits.end());
908 const auto measured = mpoSimulator->MeasureQubits(qubitsSet);
909 for (Types::qubit_t qubit : qubits) {
910 if (measured.at(static_cast<Eigen::Index>(qubit))) res |= mask;
911 mask <<= 1;
912 }
913 } else if (simulationType == SimulationType::kExtendedStabilizer) {
914 for (size_t qubit : qubits) {
915 if (extendedStabilizer->Measure(qubit)) res |= mask;
916 mask <<= 1;
917 }
918 } else if (simulationType == SimulationType::kStabilizer) {
919 for (size_t qubit : qubits) {
920 if (cliffordSimulator->MeasureQubit(static_cast<unsigned int>(qubit)))
921 res |= mask;
922 mask <<= 1;
923 }
924 } else if (simulationType == SimulationType::kTensorNetwork) {
925 for (size_t qubit : qubits) {
926 if (tensorNetwork->Measure(static_cast<unsigned int>(qubit)))
927 res |= mask;
928 mask <<= 1;
929 }
930 } else if (simulationType == SimulationType::kPauliPropagator) {
931 std::vector<int> qubitsInt;
932 qubitsInt.reserve(qubits.size());
933 for (const auto q : qubits)
934 qubitsInt.push_back(static_cast<int>(q));
935 const auto res = pp->Measure(qubitsInt);
936 Types::qubit_t result = 0;
937 for (size_t i = 0; i < res.size(); ++i) {
938 if (res[i]) result |= mask;
939 mask <<= 1;
940 }
941 return result;
942 } else if (simulationType == SimulationType::kPathIntegral) {
943 for (size_t qubit : qubits) {
944 if (pathIntegralSimulator->MeasureQubit(qubit)) res |= mask;
945 mask <<= 1;
946 }
947 } else {
948 /*
949 for (size_t qubit : qubits)
950 {
951 if (mpsSimulator->MeasureQubit(static_cast<unsigned int>(qubit)))
952 res |= mask;
953 mask <<= 1;
954 }
955 */
956 const std::set<Eigen::Index> qubitsSet(qubits.begin(), qubits.end());
957 auto measured = mpsSimulator->MeasureQubits(qubitsSet);
958 for (Types::qubit_t qubit : qubits) {
959 if (measured[qubit]) res |= mask;
960 mask <<= 1;
961 }
962 }
963 Notify();
964
965 NotifyObservers(qubits);
966
967 return res;
968 }
969
976 std::vector<bool> MeasureMany(const Types::qubits_vector &qubits) override {
977 std::vector<bool> res(qubits.size(), false);
978 DontNotify();
979
980 if (simulationType == SimulationType::kStatevector) {
981 for (size_t q = 0; q < qubits.size(); ++q)
982 if (state->MeasureQubit(static_cast<unsigned int>(qubits[q])))
983 res[q] = true;
984 } else if (simulationType == SimulationType::kDensityMatrix) {
985 for (size_t q = 0; q < qubits.size(); ++q)
986 if (densityMatrix->MeasureQubit(qubits[q])) res[q] = true;
987 } else if (simulationType == SimulationType::kMatrixProductOperator) {
988 const std::set<Eigen::Index> qubitsSet(qubits.begin(), qubits.end());
989 const auto measured = mpoSimulator->MeasureQubits(qubitsSet);
990 for (size_t q = 0; q < qubits.size(); ++q)
991 res[q] = measured.at(static_cast<Eigen::Index>(qubits[q]));
992 } else if (simulationType == SimulationType::kExtendedStabilizer) {
993 for (size_t q = 0; q < qubits.size(); ++q)
994 if (extendedStabilizer->Measure(qubits[q])) res[q] = true;
995 } else if (simulationType == SimulationType::kStabilizer) {
996 for (size_t q = 0; q < qubits.size(); ++q)
997 if (cliffordSimulator->MeasureQubit(
998 static_cast<unsigned int>(qubits[q])))
999 res[q] = true;
1000 } else if (simulationType == SimulationType::kTensorNetwork) {
1001 for (size_t q = 0; q < qubits.size(); ++q)
1002 if (tensorNetwork->Measure(static_cast<unsigned int>(qubits[q])))
1003 res[q] = true;
1004 } else if (simulationType == SimulationType::kPauliPropagator) {
1005 std::vector<int> qubitsInt(qubits.begin(), qubits.end());
1006 res = pp->Measure(qubitsInt);
1007 } else if (simulationType == SimulationType::kPathIntegral) {
1008 for (size_t q = 0; q < qubits.size(); ++q)
1009 if (pathIntegralSimulator->MeasureQubit(qubits[q])) res[q] = true;
1010 } else {
1011 const std::set<Eigen::Index> qubitsSet(qubits.begin(), qubits.end());
1012 auto measured = mpsSimulator->MeasureQubits(qubitsSet);
1013 for (size_t q = 0; q < qubits.size(); ++q)
1014 if (measured[qubits[q]]) res[q] = true;
1015 }
1016 Notify();
1017 NotifyObservers(qubits);
1018
1019 return res;
1020 }
1021
1028 void ApplyReset(const Types::qubits_vector &qubits) override {
1029 QC::Gates::PauliXGate xGate;
1030
1031 DontNotify();
1032 if (simulationType == SimulationType::kStatevector) {
1033 for (size_t qubit : qubits)
1034 if (state->MeasureQubit(static_cast<unsigned int>(qubit)))
1035 state->ApplyGate(xGate, static_cast<unsigned int>(qubit));
1036 } else if (simulationType == SimulationType::kDensityMatrix) {
1037 for (size_t qubit : qubits) densityMatrix->ApplyReset(qubit);
1038 } else if (simulationType == SimulationType::kMatrixProductOperator) {
1039 for (size_t qubit : qubits)
1040 mpoSimulator->ApplyReset(static_cast<Eigen::Index>(qubit));
1041 } else if (simulationType == SimulationType::kExtendedStabilizer) {
1042 for (size_t qubit : qubits)
1043 if (extendedStabilizer->Measure(qubit))
1044 extendedStabilizer->ApplyX(qubit);
1045 } else if (simulationType == SimulationType::kStabilizer) {
1046 for (size_t qubit : qubits)
1047 if (cliffordSimulator->MeasureQubit(static_cast<unsigned int>(qubit)))
1048 cliffordSimulator->ApplyX(static_cast<unsigned int>(qubit));
1049 } else if (simulationType == SimulationType::kTensorNetwork) {
1050 for (size_t qubit : qubits)
1051 if (tensorNetwork->Measure(static_cast<unsigned int>(qubit)))
1052 tensorNetwork->AddGate(xGate, static_cast<unsigned int>(qubit));
1053 } else if (simulationType == SimulationType::kPauliPropagator) {
1054 std::vector<int> qubitsInt(qubits.begin(), qubits.end());
1055 const auto res = pp->Measure(qubitsInt);
1056 for (size_t i = 0; i < res.size(); ++i) {
1057 if (res[i]) pp->ApplyX(qubitsInt[i]);
1058 }
1059 } else if (simulationType == SimulationType::kPathIntegral) {
1060 for (size_t qubit : qubits)
1061 if (pathIntegralSimulator->MeasureQubit(qubit)) {
1062 QC::Gates::AppliedGate<> gate(xGate.getRawOperatorMatrix(), qubit);
1063 pathIntegralSimulator->PropagateStep(
1064 gate, pathIntegralSimulator->Amplitudes());
1065 }
1066 } else {
1067 for (size_t qubit : qubits)
1068 if (mpsSimulator->MeasureQubit(static_cast<unsigned int>(qubit)))
1069 mpsSimulator->ApplyGate(xGate, static_cast<unsigned int>(qubit));
1070 }
1071 Notify();
1072
1073 NotifyObservers(qubits);
1074 }
1075
1076 bool SupportsQuantumChannels() const override {
1077 return simulationType == SimulationType::kDensityMatrix ||
1078 simulationType == SimulationType::kMatrixProductOperator;
1079 }
1080
1090 void ApplyQuantumChannel(const Types::qubits_vector &targets,
1091 const QuantumChannel &channel) override {
1092 if (!SupportsQuantumChannels())
1093 throw std::runtime_error(
1094 "QCSim quantum channels require density_matrix or "
1095 "matrix_product_operator simulation");
1096 if (targets.size() != channel.GetNumberOfQubits())
1097 throw std::invalid_argument(
1098 "The number of channel targets does not match its Kraus operators");
1099 if (targets.empty() || targets.size() > 2)
1100 throw std::invalid_argument(
1101 "QCSim supports only one- and two-qubit local channels");
1102
1103 std::unordered_set<Types::qubit_t> uniqueTargets;
1104 for (const Types::qubit_t target : targets) {
1105 if (target >= nrQubits)
1106 throw std::invalid_argument("Quantum-channel qubit is out of range");
1107 if (!uniqueTargets.insert(target).second)
1108 throw std::invalid_argument(
1109 "Quantum-channel target qubits must be distinct");
1110 }
1111
1112 const auto &krausOperators = channel.GetKrausOperators();
1113 if (simulationType == SimulationType::kDensityMatrix) {
1114 if (!densityMatrix)
1115 throw std::runtime_error(
1116 "QCSim density-matrix state is not initialized");
1117 if (targets.size() == 1)
1118 densityMatrix->ApplyChannel(krausOperators, targets[0]);
1119 else
1120 densityMatrix->ApplyChannel(krausOperators, targets[0], targets[1]);
1121 } else {
1122 if (!mpoSimulator)
1123 throw std::runtime_error("QCSim MPO state is not initialized");
1124 if (targets.size() == 1)
1125 mpoSimulator->ApplyKrausOperators(
1126 krausOperators, static_cast<Eigen::Index>(targets[0]));
1127 else
1128 mpoSimulator->ApplyKrausOperators(
1129 krausOperators, static_cast<Eigen::Index>(targets[0]),
1130 static_cast<Eigen::Index>(targets[1]));
1131 }
1132
1133 NotifyObservers(targets);
1134 }
1135
1136 std::complex<double> DensityMatrixTrace() const override {
1137 if (densityMatrix) return densityMatrix->Trace();
1138 if (mpoSimulator) return mpoSimulator->Trace();
1139 throw std::runtime_error("Mixed-state diagnostics require density_matrix or matrix_product_operator");
1140 }
1141 double DensityMatrixPurity() const override {
1142 if (densityMatrix) return densityMatrix->Purity();
1143 if (mpoSimulator) return mpoSimulator->Purity();
1144 throw std::runtime_error("Mixed-state diagnostics require density_matrix or matrix_product_operator");
1145 }
1146 std::complex<double> DensityMatrixTraceOfSquare() const override {
1147 if (densityMatrix) {
1148 const auto &rho = densityMatrix->getDensityMatrix();
1149 return (rho * rho).trace();
1150 }
1151 if (mpoSimulator) return mpoSimulator->TraceOfSquare();
1152 throw std::runtime_error("Mixed-state diagnostics require density_matrix or matrix_product_operator");
1153 }
1154 std::complex<double> DensityMatrixOverlap(const IState &other) const override {
1155 const auto *rhs = dynamic_cast<const QCSimState *>(&other);
1156 if (!rhs) throw std::invalid_argument("Density-matrix overlap requires matching QCSim backends");
1157 if (densityMatrix && rhs->densityMatrix)
1158 return densityMatrix->HilbertSchmidtOverlap(*rhs->densityMatrix);
1159 if (mpoSimulator && rhs->mpoSimulator)
1160 return mpoSimulator->HilbertSchmidtOverlap(*rhs->mpoSimulator);
1161 throw std::invalid_argument("Density-matrix overlap requires two density matrices or two MPOs");
1162 }
1163 double DensityMatrixHermiticityResidual() const override {
1164 if (densityMatrix) return (densityMatrix->getDensityMatrix() - densityMatrix->getDensityMatrix().adjoint()).norm();
1165 if (mpoSimulator) return mpoSimulator->HermiticityResidual();
1166 throw std::runtime_error("Mixed-state diagnostics require density_matrix or matrix_product_operator");
1167 }
1168 bool IsDensityMatrixHermitian(double eps = 1e-10) const override {
1169 if (densityMatrix) return densityMatrix->IsHermitian(eps);
1170 if (mpoSimulator) return mpoSimulator->IsHermitian(eps);
1171 throw std::runtime_error("Mixed-state diagnostics require density_matrix or matrix_product_operator");
1172 }
1173 Eigen::MatrixXcd PartialTrace(const Types::qubits_vector &qubits) const override {
1174 if (densityMatrix) return densityMatrix->PartialTrace(std::vector<size_t>(qubits.begin(), qubits.end()));
1175 if (mpoSimulator) return mpoSimulator->PartialTrace(std::vector<Eigen::Index>(qubits.begin(), qubits.end()));
1176 throw std::runtime_error("Partial trace requires density_matrix or matrix_product_operator");
1177 }
1178 double FidelityWithStatevector(const Eigen::VectorXcd &psi) const override {
1179 if (densityMatrix) return densityMatrix->FidelityWithStatevector(psi);
1180 if (mpoSimulator) return mpoSimulator->FidelityWithStatevector(psi);
1181 throw std::runtime_error("Mixed-state fidelity requires density_matrix or matrix_product_operator");
1182 }
1183 void RestoreDensityMatrixTrace() override {
1184 if (!mpoSimulator) throw std::runtime_error("Trace restoration is only available for QCSim MPO");
1185 mpoSimulator->RestoreTrace();
1186 }
1187 void HermitizeDensityMatrix() override {
1188 if (!mpoSimulator) throw std::runtime_error("Hermitization is only available for QCSim MPO");
1189 mpoSimulator->Hermitize();
1190 }
1191 void TrimMatrixProductOperator() override {
1192 if (!mpoSimulator) throw std::runtime_error("QCSim MPO is not initialized");
1193 mpoSimulator->Trim();
1194 }
1195 void ReCanonicalizeMatrixProductOperator() override {
1196 if (!mpoSimulator) throw std::runtime_error("QCSim MPO is not initialized");
1197 mpoSimulator->ReCanonicalize();
1198 }
1199
1211 double Probability(Types::qubit_t outcome) override {
1212 if (simulationType == SimulationType::kMatrixProductState)
1213 return mpsSimulator->getBasisStateProbability(
1214 static_cast<unsigned int>(outcome));
1215 else if (simulationType == SimulationType::kStabilizer)
1216 return cliffordSimulator->getBasisStateProbability(
1217 static_cast<unsigned int>(outcome));
1218 else if (simulationType == SimulationType::kTensorNetwork)
1219 return tensorNetwork->getBasisStateProbability(outcome);
1220 else if (simulationType == SimulationType::kPauliPropagator)
1221 return pp->Probability(outcome);
1222 else if (simulationType == SimulationType::kPathIntegral)
1223 return pathIntegralSimulator->Probability(outcome);
1224 else if (simulationType == SimulationType::kDensityMatrix)
1225 return densityMatrix->getBasisStateProbability(outcome);
1226 else if (simulationType == SimulationType::kMatrixProductOperator)
1227 return mpoSimulator->getBasisStateProbability(outcome);
1228 else if (simulationType == SimulationType::kExtendedStabilizer)
1229 return ExtendedStabilizerBasisProbability(outcome);
1230
1231 return state->getBasisStateProbability(static_cast<unsigned int>(outcome));
1232 }
1233
1244 std::complex<double> Amplitude(Types::qubit_t outcome) override {
1245 if (simulationType == SimulationType::kMatrixProductState)
1246 return mpsSimulator->getBasisStateAmplitude(
1247 static_cast<unsigned int>(outcome));
1248 else if (simulationType == SimulationType::kPathIntegral)
1249 return pathIntegralSimulator->AmplitudeForOutcome(outcome);
1250 else if (simulationType == SimulationType::kStabilizer)
1251 throw std::runtime_error(
1252 "QCSimState::Amplitude: Invalid simulation type for obtaining the "
1253 "amplitude of the specified outcome.");
1254 else if (simulationType == SimulationType::kTensorNetwork)
1255 throw std::runtime_error(
1256 "QCSimState::Amplitude: Not supported for the "
1257 "tensor network simulator.");
1258 else if (simulationType == SimulationType::kPauliPropagator)
1259 throw std::runtime_error(
1260 "QCSimState::Amplitude: Invalid simulation type for obtaining the "
1261 "amplitude of the specified outcome.");
1262 else if (simulationType == SimulationType::kDensityMatrix)
1263 throw std::runtime_error(
1264 "QCSimState::Amplitude: Amplitudes are not defined for the density "
1265 "matrix simulator.");
1266 else if (simulationType == SimulationType::kMatrixProductOperator)
1267 throw std::runtime_error(
1268 "QCSimState::Amplitude: Amplitudes are not defined for the matrix "
1269 "product operator simulator.");
1270 else if (simulationType == SimulationType::kExtendedStabilizer)
1271 throw std::runtime_error(
1272 "QCSimState::Amplitude: Amplitudes are not exposed by the extended "
1273 "stabilizer simulator.");
1274
1275 return state->getBasisStateAmplitude(static_cast<unsigned int>(outcome));
1276 }
1277
1291 std::complex<double> ProjectOnZero() override {
1292 if (simulationType == SimulationType::kMatrixProductState)
1293 return mpsSimulator->ProjectOnZero();
1294
1295 return Amplitude(0);
1296 }
1297
1308 std::vector<double> AllProbabilities() override {
1309 // TODO: In principle this could be done, but why? It should be costly.
1310 if (simulationType == SimulationType::kTensorNetwork)
1311 throw std::runtime_error(
1312 "QCSimState::AllProbabilities: Invalid "
1313 "simulation type for obtaining probabilities.");
1314 else if (simulationType == SimulationType::kStabilizer)
1315 return cliffordSimulator->AllProbabilities();
1316 else if (simulationType == SimulationType::kPauliPropagator) {
1317 const size_t nrBasisStates = 1ULL << GetNumberOfQubits();
1318 std::vector<double> result(nrBasisStates);
1319 for (size_t i = 0; i < nrBasisStates; ++i) result[i] = pp->Probability(i);
1320 return result;
1321 } else if (simulationType == SimulationType::kPathIntegral) {
1322 const size_t nrBasisStates = 1ULL << GetNumberOfQubits();
1323 std::vector<double> result(nrBasisStates);
1324 for (size_t i = 0; i < nrBasisStates; ++i)
1325 result[i] = pathIntegralSimulator->Probability(i);
1326 return result;
1327 } else if (simulationType == SimulationType::kDensityMatrix) {
1328 const size_t nrBasisStates = densityMatrix->getNrBasisStates();
1329 std::vector<double> result(nrBasisStates);
1330 for (size_t i = 0; i < nrBasisStates; ++i)
1331 result[i] = densityMatrix->getBasisStateProbability(i);
1332 return result;
1333 } else if (simulationType == SimulationType::kMatrixProductOperator) {
1334 const size_t nrBasisStates = CheckedBasisStateCountForQueries();
1335 std::vector<double> result(nrBasisStates);
1336 for (size_t i = 0; i < nrBasisStates; ++i)
1337 result[i] = mpoSimulator->getBasisStateProbability(i);
1338 return result;
1339 } else if (simulationType == SimulationType::kExtendedStabilizer) {
1340 const size_t nrBasisStates = CheckedBasisStateCountForQueries();
1341 std::vector<double> result(nrBasisStates);
1342 for (size_t i = 0; i < nrBasisStates; ++i)
1343 result[i] = ExtendedStabilizerBasisProbability(i);
1344 return result;
1345 }
1346
1347 const Eigen::VectorXcd probs =
1348 simulationType == SimulationType::kMatrixProductState
1349 ? mpsSimulator->getRegisterStorage().cwiseAbs2()
1350 : state->getRegisterStorage().cwiseAbs2();
1351
1352 std::vector<double> result(probs.size());
1353
1354 for (int i = 0; i < probs.size(); ++i) result[i] = probs[i].real();
1355
1356 return result;
1357 }
1358
1370 std::vector<double> Probabilities(
1371 const Types::qubits_vector &qubits) override {
1372 if (simulationType == SimulationType::kStabilizer)
1373 throw std::runtime_error(
1374 "QCSimState::Probabilities: Invalid simulation "
1375 "type for obtaining probabilities.");
1376 else if (simulationType == SimulationType::kTensorNetwork) {
1377 // TODO: Implement this!!!
1378 throw std::runtime_error(
1379 "QCSimState::Probabilities: Not implemented yet "
1380 "for the tensor network simulator.");
1381 }
1382
1383 std::vector<double> result(qubits.size());
1384
1385 if (simulationType == SimulationType::kMatrixProductState) {
1386 for (int i = 0; i < static_cast<int>(qubits.size()); ++i)
1387 result[i] = mpsSimulator->getBasisStateProbability(qubits[i]);
1388 } else if (simulationType == SimulationType::kPauliPropagator) {
1389 for (int i = 0; i < static_cast<int>(qubits.size()); ++i)
1390 result[i] = pp->Probability(qubits[i]);
1391 } else if (simulationType == SimulationType::kPathIntegral) {
1392 for (int i = 0; i < static_cast<int>(qubits.size()); ++i)
1393 result[i] = pathIntegralSimulator->Probability(qubits[i]);
1394 } else if (simulationType == SimulationType::kDensityMatrix) {
1395 for (int i = 0; i < static_cast<int>(qubits.size()); ++i)
1396 result[i] = densityMatrix->getBasisStateProbability(qubits[i]);
1397 } else if (simulationType == SimulationType::kMatrixProductOperator) {
1398 for (int i = 0; i < static_cast<int>(qubits.size()); ++i)
1399 result[i] = mpoSimulator->getBasisStateProbability(qubits[i]);
1400 } else if (simulationType == SimulationType::kExtendedStabilizer) {
1401 for (int i = 0; i < static_cast<int>(qubits.size()); ++i)
1402 result[i] = ExtendedStabilizerBasisProbability(qubits[i]);
1403 } else {
1404 const Eigen::VectorXcd &reg = state->getRegisterStorage();
1405
1406 for (int i = 0; i < static_cast<int>(qubits.size()); ++i)
1407 result[i] = std::norm(reg[qubits[i]]);
1408 }
1409
1410 return result;
1411 }
1412
1429 std::unordered_map<Types::qubit_t, Types::qubit_t> SampleCounts(
1430 const Types::qubits_vector &qubits, size_t shots = 1000) override {
1431 if (qubits.empty() || shots == 0) return {};
1432
1433 if (qubits.size() > sizeof(size_t) * 8)
1434 std::cerr
1435 << "Warning: The number of qubits to measure is larger than the "
1436 "number of bits in the size_t type, the outcome will be undefined"
1437 << std::endl;
1438
1439 // TODO: this is inefficient, maybe implement it better in qcsim
1440 // for now it has the possibility of measuring a qubits interval, but not a
1441 // list of qubits
1442 std::unordered_map<Types::qubit_t, Types::qubit_t> result;
1443
1444 DontNotify();
1445
1446 if (simulationType == SimulationType::kMatrixProductState) {
1447 bool normal = true;
1448 if (!configuration.IsSet("mps_sample_measure_algorithm") || configuration.GetConfiguration("mps_sample_measure_algorithm") == "mps_probabilities") {
1449 // check to see if it can be used
1450 const std::set<Eigen::Index> qset(qubits.begin(), qubits.end());
1451 if (qset.size() == GetNumberOfQubits()) {
1452 // it can!
1453 normal = false;
1454 for (size_t shot = 0; shot < shots; ++shot) {
1455 const size_t measRaw = MeasureNoCollapse();
1456 size_t meas = 0;
1457 size_t mask = 1ULL;
1458
1459 // translate the measurement
1460 for (auto q : qubits) {
1461 const size_t qubitMask = 1ULL << q;
1462 if (measRaw & qubitMask) meas |= mask;
1463 mask <<= 1ULL;
1464 }
1465
1466 ++result[meas];
1467 }
1468 } else if (qset.size() > 1) {
1469 mpsSimulator->MoveAtBeginningOfChain(qset);
1470 // now sample
1471 normal = false;
1472 for (size_t shot = 0; shot < shots; ++shot) {
1473 const auto measRaw = mpsSimulator->MeasureNoCollapse(qset);
1474 size_t meas = 0;
1475 size_t mask = 1ULL;
1476
1477 // might not be in the requested order
1478 // translate the measurement
1479 for (auto q : qubits) {
1480 if (measRaw.at(q)) meas |= mask;
1481 mask <<= 1ULL;
1482 }
1483
1484 ++result[meas];
1485 }
1486
1487 } else if (qset.size() == 1) {
1488 // if only one qubit is measured, we can use the probability
1489 normal = false;
1490 const auto prob0 = mpsSimulator->GetProbability(qubits[0]);
1491 for (size_t shot = 0; shot < shots; ++shot) {
1492 const size_t meas = uniformZeroOne(rng) < prob0 ? 0ULL : 1ULL;
1493 size_t m = meas;
1494 // why would somebody set more than one time?
1495 for (size_t i = 1; i < qubits.size(); ++i) {
1496 m <<= 1ULL;
1497 m |= meas;
1498 }
1499 ++result[m];
1500 }
1501 }
1502 }
1503
1504 if (normal) {
1505 auto savedState = mpsSimulator->getState();
1506 for (size_t shot = 0; shot < shots; ++shot) {
1507 const size_t meas = Measure(qubits);
1508 ++result[meas];
1509 mpsSimulator->setState(savedState);
1510 }
1511 }
1512 } else if (simulationType == SimulationType::kStabilizer) {
1513 cliffordSimulator->SaveState();
1514 for (size_t shot = 0; shot < shots; ++shot) {
1515 const size_t meas = Measure(qubits);
1516 ++result[meas];
1517 cliffordSimulator->RestoreState();
1518 }
1519 cliffordSimulator->ClearSavedState();
1520 } else if (simulationType == SimulationType::kTensorNetwork) {
1521 tensorNetwork->SaveState();
1522 for (size_t shot = 0; shot < shots; ++shot) {
1523 const size_t meas = Measure(qubits);
1524 ++result[meas];
1525 tensorNetwork->RestoreState();
1526 }
1527 tensorNetwork->ClearSavedState();
1528 } else if (simulationType == SimulationType::kPauliPropagator) {
1529 std::vector<int> qubitsInt(qubits.begin(), qubits.end());
1530 for (size_t shot = 0; shot < shots; ++shot) {
1531 const auto res = pp->Sample(qubitsInt);
1532
1533 size_t meas = 0;
1534 for (size_t i = 0; i < qubits.size(); ++i) {
1535 if (res[i]) meas |= (1ULL << i);
1536 }
1537
1538 ++result[meas];
1539 }
1540 } else if (simulationType == SimulationType::kPathIntegral) {
1541 if (nrQubits < 64) {
1542 if (shots > 1) {
1543 const auto &amplitudes = pathIntegralSimulator->Amplitudes();
1544 const Utils::Alias alias(amplitudes);
1545
1546 for (size_t shot = 0; shot < shots; ++shot) {
1547 const double prob = 1. - uniformZeroOne(rng);
1548 const size_t measRaw = alias.Sample(prob);
1549
1550 size_t meas = 0;
1551 size_t mask = 1ULL;
1552 for (auto q : qubits) {
1553 const size_t qubitMask = 1ULL << q;
1554 if ((measRaw & qubitMask) != 0) meas |= mask;
1555 mask <<= 1ULL;
1556 }
1557
1558 ++result[meas];
1559 }
1560 } else {
1561 const size_t measRaw = MeasureNoCollapse();
1562 size_t meas = 0;
1563 size_t mask = 1ULL;
1564 for (auto q : qubits) {
1565 const size_t qubitMask = 1ULL << q;
1566 if ((measRaw & qubitMask) != 0) meas |= mask;
1567 mask <<= 1ULL;
1568 }
1569 ++result[meas];
1570 }
1571 } else {
1572 throw std::runtime_error(
1573 "QCSimState::SampleCounts: The path integral simulator does not "
1574 "support sampling for more than 63 qubits into 64 bits integers.");
1575 }
1576 } else if (simulationType == SimulationType::kDensityMatrix) {
1577 for (size_t shot = 0; shot < shots; ++shot) {
1578 const size_t measured = densityMatrix->MeasureNoCollapse();
1579 Types::qubit_t packed = 0;
1580 for (size_t i = 0; i < qubits.size(); ++i)
1581 if ((measured & (1ULL << qubits[i])) != 0) packed |= 1ULL << i;
1582 ++result[packed];
1583 }
1584 } else if (simulationType == SimulationType::kMatrixProductOperator) {
1585 const std::set<Eigen::Index> qubitsSet(qubits.begin(), qubits.end());
1586 for (size_t shot = 0; shot < shots; ++shot) {
1587 const auto measured = mpoSimulator->MeasureNoCollapse(qubitsSet);
1588 Types::qubit_t packed = 0;
1589 for (size_t i = 0; i < qubits.size(); ++i)
1590 if (measured.at(static_cast<Eigen::Index>(qubits[i])))
1591 packed |= 1ULL << i;
1592 ++result[packed];
1593 }
1594 } else if (simulationType == SimulationType::kExtendedStabilizer) {
1595 auto sampler = extendedStabilizer->Clone();
1596 sampler->SaveState();
1597 for (size_t shot = 0; shot < shots; ++shot) {
1598 sampler->RestoreState();
1599 Types::qubit_t packed = 0;
1600 for (size_t i = 0; i < qubits.size(); ++i)
1601 if (sampler->Measure(qubits[i])) packed |= 1ULL << i;
1602 ++result[packed];
1603 }
1604 } else {
1605 if (shots > 1) {
1606 const auto &statev = state->getRegisterStorage();
1607
1608 const Utils::Alias alias(statev);
1609
1610 for (size_t shot = 0; shot < shots; ++shot) {
1611 const double prob = 1. - uniformZeroOne(rng);
1612 const size_t measRaw = alias.Sample(prob);
1613
1614 size_t meas = 0;
1615 size_t mask = 1ULL;
1616 for (auto q : qubits) {
1617 const size_t qubitMask = 1ULL << q;
1618 if ((measRaw & qubitMask) != 0) meas |= mask;
1619 mask <<= 1ULL;
1620 }
1621
1622 ++result[meas];
1623 }
1624 } else {
1625 for (size_t shot = 0; shot < shots; ++shot) {
1626 const size_t measRaw = MeasureNoCollapse();
1627 size_t meas = 0;
1628 size_t mask = 1ULL;
1629
1630 for (auto q : qubits) {
1631 const size_t qubitMask = 1ULL << q;
1632 if ((measRaw & qubitMask) != 0) meas |= mask;
1633 mask <<= 1ULL;
1634 }
1635
1636 ++result[meas];
1637 }
1638 }
1639 }
1640
1641 Notify();
1642 NotifyObservers(qubits);
1643
1644 return result;
1645 }
1646
1660 std::unordered_map<std::vector<bool>, Types::qubit_t> SampleCountsMany(
1661 const Types::qubits_vector &qubits, size_t shots = 1000) override {
1662 if (qubits.empty() || shots == 0) return {};
1663
1664 std::unordered_map<std::vector<bool>, Types::qubit_t> result;
1665
1666 DontNotify();
1667
1668 if (simulationType == SimulationType::kMatrixProductState) {
1669 bool normal = true;
1670 if (!configuration.IsSet("mps_sample_measure_algorithm") || configuration.GetConfiguration("mps_sample_measure_algorithm") == "mps_probabilities") {
1671 // check to see if it can be used
1672 const std::set<Eigen::Index> qset(qubits.begin(), qubits.end());
1673 if (qset.size() == GetNumberOfQubits()) {
1674 // it can!
1675 normal = false;
1676 for (size_t shot = 0; shot < shots; ++shot) {
1677 const auto meas = MeasureNoCollapseMany();
1678
1679 // might not be in the requested order
1680 // translate the measurement
1681 std::vector<bool> measVec(qubits.size());
1682 for (size_t i = 0; i < qubits.size(); ++i)
1683 measVec[i] = meas[qubits[i]];
1684
1685 ++result[measVec];
1686 }
1687 } else if (qset.size() > 1) {
1688 mpsSimulator->MoveAtBeginningOfChain(qset);
1689 // now sample
1690 normal = false;
1691 for (size_t shot = 0; shot < shots; ++shot) {
1692 const auto meas = mpsSimulator->MeasureNoCollapse(qset);
1693
1694 // might not be in the requested order
1695 // translate the measurement
1696 std::vector<bool> measVec(qubits.size());
1697 for (size_t i = 0; i < qubits.size(); ++i)
1698 measVec[i] = meas.at(qubits[i]);
1699
1700 ++result[measVec];
1701 }
1702 } else if (qset.size() == 1) {
1703 // if only one qubit is measured, we can use the probability
1704 normal = false;
1705 const auto prob0 = mpsSimulator->GetProbability(qubits[0]);
1706 for (size_t shot = 0; shot < shots; ++shot) {
1707 const size_t meas = uniformZeroOne(rng) < prob0 ? 0ULL : 1ULL;
1708 const std::vector<bool> m(qubits.size(), meas);
1709 ++result[m];
1710 }
1711 }
1712 }
1713
1714 if (normal) {
1715 auto savedState = mpsSimulator->getState();
1716 for (size_t shot = 0; shot < shots; ++shot) {
1717 const auto meas = MeasureMany(qubits);
1718
1719 ++result[meas];
1720 mpsSimulator->setState(savedState);
1721 }
1722 }
1723 } else if (simulationType == SimulationType::kStabilizer) {
1724 cliffordSimulator->SaveState();
1725 for (size_t shot = 0; shot < shots; ++shot) {
1726 const auto meas = MeasureMany(qubits);
1727 ++result[meas];
1728 cliffordSimulator->RestoreState();
1729 }
1730 cliffordSimulator->ClearSavedState();
1731 } else if (simulationType == SimulationType::kTensorNetwork) {
1732 tensorNetwork->SaveState();
1733 for (size_t shot = 0; shot < shots; ++shot) {
1734 const auto meas = MeasureMany(qubits);
1735 ++result[meas];
1736 tensorNetwork->RestoreState();
1737 }
1738 tensorNetwork->ClearSavedState();
1739 } else if (simulationType == SimulationType::kPauliPropagator) {
1740 std::vector<int> qubitsInt(qubits.begin(), qubits.end());
1741 for (size_t shot = 0; shot < shots; ++shot) {
1742 const auto meas = pp->Sample(qubitsInt);
1743 ++result[meas];
1744 }
1745 } else if (simulationType == SimulationType::kPathIntegral) {
1746 if (nrQubits < 64) {
1747 if (shots > 1) {
1748 const auto &amplitudes = pathIntegralSimulator->Amplitudes();
1749 const Utils::Alias alias(amplitudes);
1750 for (size_t shot = 0; shot < shots; ++shot) {
1751 const double prob = 1. - uniformZeroOne(rng);
1752 const size_t measRaw = alias.Sample(prob);
1753 std::vector<bool> meas(qubits.size(), false);
1754 for (size_t i = 0; i < qubits.size(); ++i)
1755 if (((measRaw >> qubits[i]) & 1) == 1) meas[i] = true;
1756 ++result[meas];
1757 }
1758 } else {
1759 for (size_t shot = 0; shot < shots; ++shot) {
1760 const auto measRaw = MeasureNoCollapseMany();
1761 std::vector<bool> meas(qubits.size(), false);
1762
1763 for (size_t i = 0; i < qubits.size(); ++i)
1764 if (measRaw[qubits[i]]) meas[i] = true;
1765
1766 ++result[meas];
1767 }
1768 }
1769 } else {
1770 if (shots > 1) {
1771 const auto &amplitudes = pathIntegralSimulator->Amplitudes();
1772 const Utils::AliasBig alias(amplitudes);
1773
1774 for (size_t shot = 0; shot < shots; ++shot) {
1775 const double prob = 1. - uniformZeroOne(rng);
1776 const auto measRaw = alias.Sample(prob);
1777 std::vector<bool> meas(qubits.size(), false);
1778 for (size_t i = 0; i < qubits.size(); ++i)
1779 if (measRaw.get(qubits[i])) meas[i] = true;
1780 ++result[meas];
1781 }
1782 } else {
1783 for (size_t shot = 0; shot < shots; ++shot) {
1784 const auto measRaw = MeasureNoCollapseMany();
1785 std::vector<bool> meas(qubits.size(), false);
1786
1787 for (size_t i = 0; i < qubits.size(); ++i)
1788 if (measRaw[qubits[i]]) meas[i] = true;
1789
1790 ++result[meas];
1791 }
1792 }
1793 }
1794 } else if (simulationType == SimulationType::kDensityMatrix) {
1795 for (size_t shot = 0; shot < shots; ++shot) {
1796 const size_t measured = densityMatrix->MeasureNoCollapse();
1797 std::vector<bool> packed(qubits.size(), false);
1798 for (size_t i = 0; i < qubits.size(); ++i)
1799 packed[i] = (measured & (1ULL << qubits[i])) != 0;
1800 ++result[packed];
1801 }
1802 } else if (simulationType == SimulationType::kMatrixProductOperator) {
1803 const std::set<Eigen::Index> qubitsSet(qubits.begin(), qubits.end());
1804 for (size_t shot = 0; shot < shots; ++shot) {
1805 const auto measured = mpoSimulator->MeasureNoCollapse(qubitsSet);
1806 std::vector<bool> packed(qubits.size(), false);
1807 for (size_t i = 0; i < qubits.size(); ++i)
1808 packed[i] = measured.at(static_cast<Eigen::Index>(qubits[i]));
1809 ++result[packed];
1810 }
1811 } else if (simulationType == SimulationType::kExtendedStabilizer) {
1812 auto sampler = extendedStabilizer->Clone();
1813 sampler->SaveState();
1814 for (size_t shot = 0; shot < shots; ++shot) {
1815 sampler->RestoreState();
1816 std::vector<bool> packed(qubits.size(), false);
1817 for (size_t i = 0; i < qubits.size(); ++i)
1818 packed[i] = sampler->Measure(qubits[i]);
1819 ++result[packed];
1820 }
1821 } else {
1822 if (shots > 1) {
1823 const auto &statev = state->getRegisterStorage();
1824
1825 const Utils::Alias alias(statev);
1826
1827 for (size_t shot = 0; shot < shots; ++shot) {
1828 const double prob = 1. - uniformZeroOne(rng);
1829 const size_t measRaw = alias.Sample(prob);
1830
1831 std::vector<bool> meas(qubits.size(), false);
1832
1833 for (size_t i = 0; i < qubits.size(); ++i)
1834 if (((measRaw >> qubits[i]) & 1) == 1) meas[i] = true;
1835
1836 ++result[meas];
1837 }
1838 } else {
1839 for (size_t shot = 0; shot < shots; ++shot) {
1840 const auto measRaw = MeasureNoCollapseMany();
1841 std::vector<bool> meas(qubits.size(), false);
1842
1843 for (size_t i = 0; i < qubits.size(); ++i)
1844 if (measRaw[qubits[i]]) meas[i] = true;
1845
1846 ++result[meas];
1847 }
1848 }
1849 }
1850
1851 Notify();
1852 NotifyObservers(qubits);
1853
1854 return result;
1855 }
1856
1868 double ExpectationValue(const std::string &pauliStringOrig) override {
1869 if (pauliStringOrig.empty()) return 1.0;
1870
1871 std::string pauliString = pauliStringOrig;
1872 if (pauliString.size() > GetNumberOfQubits()) {
1873 for (size_t i = GetNumberOfQubits(); i < pauliString.size(); ++i) {
1874 const auto pauliOp = toupper(pauliString[i]);
1875 if (pauliOp != 'I' && pauliOp != 'Z') return 0.0;
1876 }
1877
1878 pauliString.resize(GetNumberOfQubits());
1879 }
1880
1881 if (simulationType == SimulationType::kStabilizer)
1882 return cliffordSimulator->ExpectationValue(pauliString);
1883 else if (simulationType == SimulationType::kTensorNetwork)
1884 return tensorNetwork->ExpectationValue(pauliString);
1885 else if (simulationType == SimulationType::kPauliPropagator)
1886 return pp->ExpectationValue(pauliString);
1887 else if (simulationType == SimulationType::kPathIntegral)
1888 return pathIntegralSimulator->ExpectationValue(pauliString);
1889 else if (simulationType == SimulationType::kDensityMatrix) {
1890 pauliString.resize(GetNumberOfQubits(), 'I');
1891 return densityMatrix->ExpectationValue(pauliString).real();
1892 }
1893 else if (simulationType == SimulationType::kMatrixProductOperator) {
1894 pauliString.resize(GetNumberOfQubits(), 'I');
1895 return mpoSimulator->ExpectationValue(pauliString).real();
1896 }
1897 else if (simulationType == SimulationType::kExtendedStabilizer)
1898 return extendedStabilizer->ExpectationValue(pauliString);
1899
1900 // statevector or mps
1901 static const QC::Gates::PauliXGate<> xgate;
1902 static const QC::Gates::PauliYGate<> ygate;
1903 static const QC::Gates::PauliZGate<> zgate;
1904
1905 std::vector<QC::Gates::AppliedGate<Eigen::MatrixXcd>> pauliStringVec;
1906 pauliStringVec.reserve(pauliString.size());
1907
1908 for (size_t q = 0; q < pauliString.size(); ++q) {
1909 switch (toupper(pauliString[q])) {
1910 case 'X': {
1911 QC::Gates::AppliedGate<Eigen::MatrixXcd> ag(
1912 xgate.getRawOperatorMatrix(), static_cast<Types::qubit_t>(q));
1913 pauliStringVec.emplace_back(std::move(ag));
1914 } break;
1915 case 'Y': {
1916 QC::Gates::AppliedGate<Eigen::MatrixXcd> ag(
1917 ygate.getRawOperatorMatrix(), static_cast<Types::qubit_t>(q));
1918 pauliStringVec.emplace_back(std::move(ag));
1919 } break;
1920 case 'Z': {
1921 QC::Gates::AppliedGate<Eigen::MatrixXcd> ag(
1922 zgate.getRawOperatorMatrix(), static_cast<Types::qubit_t>(q));
1923 pauliStringVec.emplace_back(std::move(ag));
1924 } break;
1925 case 'I':
1926 [[fallthrough]];
1927 default:
1928 break;
1929 }
1930 }
1931
1932 if (pauliStringVec.empty()) return 1.0;
1933
1934 if (simulationType == SimulationType::kMatrixProductState)
1935 return mpsSimulator->ExpectationValue(pauliStringVec).real();
1936
1937 return state->ExpectationValue(pauliStringVec).real();
1938 }
1939
1947 SimulatorType GetType() const override { return SimulatorType::kQCSim; }
1948
1957 SimulationType GetSimulationType() const override { return simulationType; }
1958
1967 void Flush() override {}
1968
1978 void SaveStateToInternalDestructive() override {}
1979
1986 void RestoreInternalDestructiveSavedState() override {}
1987
1997 void SaveState() override {
1998 if (simulationType == SimulationType::kMatrixProductState)
1999 mpsSimulator->SaveState();
2000 else if (simulationType == SimulationType::kStabilizer)
2001 cliffordSimulator->SaveState();
2002 else if (simulationType == SimulationType::kTensorNetwork)
2003 tensorNetwork->SaveState();
2004 else if (simulationType == SimulationType::kPauliPropagator)
2005 pp->SaveState();
2006 else if (simulationType == SimulationType::kPathIntegral)
2007 pathIntegralSimulator->SaveState();
2008 else if (simulationType == SimulationType::kDensityMatrix)
2009 densityMatrix->SaveState();
2010 else if (simulationType == SimulationType::kMatrixProductOperator)
2011 mpoSimulator->SaveState();
2012 else if (simulationType == SimulationType::kExtendedStabilizer)
2013 extendedStabilizer->SaveState();
2014 else
2015 state->SaveState();
2016 }
2017
2026 void RestoreState() override {
2027 if (simulationType == SimulationType::kMatrixProductState)
2028 mpsSimulator->RestoreState();
2029 else if (simulationType == SimulationType::kStabilizer)
2030 cliffordSimulator->RestoreState();
2031 else if (simulationType == SimulationType::kTensorNetwork)
2032 tensorNetwork->RestoreState();
2033 else if (simulationType == SimulationType::kPauliPropagator)
2034 pp->RestoreState();
2035 else if (simulationType == SimulationType::kPathIntegral)
2036 pathIntegralSimulator->RestoreState();
2037 else if (simulationType == SimulationType::kDensityMatrix)
2038 densityMatrix->RestoreState();
2039 else if (simulationType == SimulationType::kMatrixProductOperator)
2040 mpoSimulator->RestoreState();
2041 else if (simulationType == SimulationType::kExtendedStabilizer)
2042 extendedStabilizer->RestoreState();
2043 else
2044 state->RestoreState();
2045 }
2046
2054 std::complex<double> AmplitudeRaw(Types::qubit_t outcome) override {
2055 return Amplitude(outcome);
2056 }
2057
2066 void SetMultithreading(bool multithreading = true) override {
2067 enableMultithreading = multithreading;
2068 if (state) state->SetMultithreading(multithreading);
2069 if (cliffordSimulator) cliffordSimulator->SetMultithreading(multithreading);
2070 if (tensorNetwork) tensorNetwork->SetMultithreading(multithreading);
2071 if (densityMatrix) densityMatrix->SetMultithreading(multithreading);
2072 if (pp) {
2073 if (multithreading)
2074 pp->EnableParallel();
2075 else
2076 pp->DisableParallel();
2077 }
2078 if (pathIntegralSimulator) {
2079 enableMultithreading = false; // not supported for now
2080 }
2081 }
2082
2090 bool GetMultithreading() const override { return enableMultithreading; }
2091
2102 bool IsQcsim() const override { return true; }
2103
2122 if (GetNumberOfQubits() > sizeof(Types::qubit_t) * 8)
2123 std::cerr
2124 << "Warning: The number of qubits to measure is larger than the "
2125 "number of bits in the Types::qubit_t type, the outcome will be "
2126 "undefined"
2127 << std::endl;
2128
2129 if (simulationType == SimulationType::kStatevector)
2130 return state->MeasureNoCollapse();
2131 else if (simulationType == SimulationType::kDensityMatrix)
2132 return densityMatrix->MeasureNoCollapse();
2133 else if (simulationType == SimulationType::kMatrixProductOperator) {
2134 const auto measured = mpoSimulator->MeasureNoCollapse();
2135 Types::qubit_t result = 0;
2136 for (size_t qubit = 0; qubit < nrQubits; ++qubit)
2137 if (measured.at(static_cast<Eigen::Index>(qubit)))
2138 result |= 1ULL << qubit;
2139 return result;
2140 }
2141 else if (simulationType == SimulationType::kExtendedStabilizer) {
2142 auto sampler = extendedStabilizer->Clone();
2143 Types::qubit_t result = 0;
2144 for (size_t qubit = 0; qubit < nrQubits; ++qubit)
2145 if (sampler->Measure(qubit)) result |= 1ULL << qubit;
2146 return result;
2147 }
2148 else if (simulationType == SimulationType::kMatrixProductState) {
2149 const auto measured = mpsSimulator->MeasureNoCollapse();
2150 Types::qubit_t result = 0;
2151 Types::qubit_t mask = 1;
2152 for (Types::qubit_t q = 0; q < measured.size(); ++q) {
2153 if (measured.at(q)) result |= mask;
2154 mask <<= 1;
2155 }
2156 return result;
2157 } else if (simulationType == SimulationType::kPauliPropagator) {
2158 std::vector<int> qubitsInt(GetNumberOfQubits());
2159 std::iota(qubitsInt.begin(), qubitsInt.end(), 0);
2160 const auto res = pp->Sample(qubitsInt);
2161 Types::qubit_t result = 0;
2162 for (size_t i = 0; i < res.size(); ++i) {
2163 if (res[i]) result |= (1ULL << i);
2164 }
2165 return result;
2166 } else if (simulationType == SimulationType::kPathIntegral) {
2167 if (nrQubits < 64) {
2168 const auto measured = pathIntegralSimulator->MeasureNoCollapse();
2169 Types::qubit_t result = 0;
2170 Types::qubit_t mask = 1;
2171 for (Types::qubit_t q = 0; q < measured.size(); ++q) {
2172 if (measured.get(q)) result |= mask;
2173 mask <<= 1;
2174 }
2175 return result;
2176 } else {
2177 throw std::runtime_error(
2178 "QCSimState::MeasureNoCollapse: The path integral simulator does not "
2179 "support measuring more than 63 qubits into 64 bits integers.");
2180 }
2181 }
2182
2183 throw std::runtime_error(
2184 "QCSimState::MeasureNoCollapse: Invalid simulation type for "
2185 "measuring "
2186 "all the qubits without collapsing the state.");
2187
2188 return 0;
2189 }
2190
2206 std::vector<bool> MeasureNoCollapseMany() override {
2207 if (simulationType == SimulationType::kStatevector) {
2208 auto state = MeasureNoCollapse();
2209 std::vector<bool> res(nrQubits);
2210 for (size_t i = 0; i < nrQubits; ++i) res[i] = ((state >> i) & 1) == 1;
2211 return res;
2212 } else if (simulationType == SimulationType::kDensityMatrix) {
2213 const auto measured = densityMatrix->MeasureNoCollapse();
2214 std::vector<bool> res(nrQubits);
2215 for (size_t i = 0; i < nrQubits; ++i)
2216 res[i] = ((measured >> i) & 1) == 1;
2217 return res;
2218 } else if (simulationType == SimulationType::kMatrixProductOperator) {
2219 const auto measured = mpoSimulator->MeasureNoCollapse();
2220 std::vector<bool> res(nrQubits);
2221 for (size_t i = 0; i < nrQubits; ++i)
2222 res[i] = measured.at(static_cast<Eigen::Index>(i));
2223 return res;
2224 } else if (simulationType == SimulationType::kExtendedStabilizer) {
2225 auto sampler = extendedStabilizer->Clone();
2226 std::vector<bool> res(nrQubits);
2227 for (size_t i = 0; i < nrQubits; ++i) res[i] = sampler->Measure(i);
2228 return res;
2229 } else if (simulationType == SimulationType::kMatrixProductState) {
2230 const auto measured = mpsSimulator->MeasureNoCollapse();
2231 std::vector<bool> res(nrQubits);
2232 for (size_t i = 0; i < nrQubits; ++i) res[i] = measured.at(i);
2233 return res;
2234 } else if (simulationType == SimulationType::kPauliPropagator) {
2235 std::vector<int> qubitsInt(GetNumberOfQubits());
2236 std::iota(qubitsInt.begin(), qubitsInt.end(), 0);
2237 return pp->Sample(qubitsInt);
2238 } else if (simulationType == SimulationType::kPathIntegral) {
2239 const auto measured = pathIntegralSimulator->MeasureNoCollapse();
2240 std::vector<bool> res(nrQubits);
2241 for (size_t i = 0; i < nrQubits; ++i) res[i] = measured.get(i);
2242 return res;
2243 }
2244
2245 throw std::runtime_error(
2246 "QCSimState::MeasureNoCollapseMany: Invalid simulation type for "
2247 "measuring all the qubits without collapsing the state.");
2248
2249 return {};
2250 }
2251
2258 size_t GetCurrentMaxBondDimension() const override { return curMaxBondDim; }
2259
2260 const Configuration& GetConfiguration() const { return configuration; }
2261
2262 const std::unordered_map<std::string, std::string>& GetConfigMap()
2263 const override {
2264 return configuration.GetConfigMap();
2265 }
2266
2267 protected:
2268 const char* MaxBondDimensionConfigKey() const {
2269 return simulationType == SimulationType::kMatrixProductOperator &&
2270 configuration.IsSet(
2271 "matrix_product_operator_max_bond_dimension")
2272 ? "matrix_product_operator_max_bond_dimension"
2273 : "matrix_product_state_max_bond_dimension";
2274 }
2275
2276 void ResetDummySimulator() {
2277 if (!dummySim) return;
2278
2279 std::vector<long long int> identityMap(nrQubits);
2280 for (size_t qubit = 0; qubit < nrQubits; ++qubit)
2281 identityMap[qubit] = static_cast<long long int>(qubit);
2282 dummySim->SetInitialQubitsMap(identityMap);
2283 dummySim->setTotalSwappingCost(0.);
2284 if (nrQubits > 1)
2285 dummySim->SetCurrentBondDimensions(
2286 std::vector<double>(nrQubits - 1, 1.));
2287 }
2288
2289 size_t CheckedBasisStateCountForQueries() const {
2290 if (nrQubits >= std::numeric_limits<size_t>::digits)
2291 throw std::runtime_error(
2292 "QCSimState: Too many qubits for enumerating basis states.");
2293 return 1ULL << nrQubits;
2294 }
2295
2296 double ExtendedStabilizerBasisProbability(Types::qubit_t outcome) const {
2297 const size_t nrBasisStates = CheckedBasisStateCountForQueries();
2298 if (outcome >= nrBasisStates) return 0.0;
2299
2300 double probability = 0.0;
2301 std::string pauliString(nrQubits, 'I');
2302 for (size_t mask = 0; mask < nrBasisStates; ++mask) {
2303 double sign = 1.0;
2304 size_t parityBits = mask & static_cast<size_t>(outcome);
2305 while (parityBits != 0) {
2306 sign = -sign;
2307 parityBits &= parityBits - 1;
2308 }
2309
2310 for (size_t qubit = 0; qubit < nrQubits; ++qubit)
2311 pauliString[qubit] = ((mask >> qubit) & 1ULL) == 0 ? 'I' : 'Z';
2312 probability += sign * extendedStabilizer->ExpectationValue(pauliString);
2313 }
2314
2315 probability /= static_cast<double>(nrBasisStates);
2316 return std::max(0.0, std::min(1.0, probability));
2317 }
2318
2319 SimulationType simulationType =
2320 SimulationType::kStatevector;
2321
2322 std::unique_ptr<QC::QubitRegister<>> state;
2323 std::unique_ptr<QC::TensorNetworks::MPSSimulator>
2324 mpsSimulator;
2325 std::unique_ptr<QC::TensorNetworks::MPOSimulator>
2326 mpoSimulator;
2327 std::unique_ptr<QC::Clifford::StabilizerSimulator>
2328 cliffordSimulator;
2329 std::unique_ptr<TensorNetworks::TensorNetwork>
2330 tensorNetwork;
2331 std::unique_ptr<QcsimPauliPropagator> pp;
2332 std::unique_ptr<PathIntegralSimulator>
2333 pathIntegralSimulator;
2334 std::unique_ptr<QC::DensityMatrix<>>
2335 densityMatrix;
2336 std::unique_ptr<Simulators::QCSimExtendedStabilizer>
2337 extendedStabilizer;
2338
2339 size_t nrQubits = 0;
2340
2341 bool enableMultithreading = true;
2342
2343 int lookaheadDepth = 0;
2344 int lookaheadDepthWithHeuristic = 0;
2345 bool useOptimalMeetingPosition = true;
2346 std::vector<std::shared_ptr<Circuits::IOperation<>>> upcomingGates;
2347 long long int upcomingGateIndex = 0;
2348 double growthFactorSwap = 1.;
2349 double growthFactorGate = 0.65;
2350
2351 std::unique_ptr<Simulators::MPSDummySimulator> dummySim;
2352
2353 size_t curMaxBondDim = 0;
2354 QC::TensorNetworks::MPSSimulator::MeetingPositionCallback meetingPositionCallback = nullptr;
2355 QC::TensorNetworks::MPSSimulator::BondDimensionCallback bondDimensionCallback = nullptr;
2356
2357
2358 // Observer that counts applied gates to track position in upcomingGates
2359 class GateCounterObserver : public ISimulatorObserver {
2360 public:
2361 GateCounterObserver(long long int &indexRef) : index(indexRef) {}
2362 void Update(const Types::qubits_vector &) override { ++index; }
2363
2364 private:
2365 long long int &index;
2366 };
2367 std::shared_ptr<GateCounterObserver> gateCounterObserver;
2368
2369 std::mt19937_64 rng;
2370 uint64_t nextSeedStream = 0;
2371 std::uniform_real_distribution<double> uniformZeroOne;
2372
2373 Configuration configuration;
2374};
2375
2376} // namespace Private
2377} // namespace Simulators
2378
2379#endif
2380
2381#endif // !_QCSIMSTATE_H_
double Probability(void *sim, unsigned long long int outcome)
char * GetConfiguration(void *sim, const char *key)
int RestoreState(void *sim)
int ApplyReset(void *sim, const unsigned long int *qubits, unsigned long int nrQubits)
int ApplyX(void *sim, int qubit)
unsigned long int AllocateQubits(void *sim, unsigned long int nrQubits)
unsigned long int GetNumberOfQubits(void *sim)
double * AllProbabilities(void *sim)
unsigned long long int MeasureNoCollapse(void *sim)
int GetMultithreading(void *sim)
unsigned long long int Measure(void *sim, const unsigned long int *qubits, unsigned long int nrQubits)
double * Amplitude(void *sim, unsigned long long int outcome)
double * Probabilities(void *sim, const unsigned long long int *qubits, unsigned long int nrQubits)
int SetMultithreading(void *sim, int multithreading)
int SaveStateToInternalDestructive(void *sim)
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 RestoreInternalDestructiveSavedState(void *sim)
int IsQcsim(void *sim)
int SaveState(void *sim)
The operation interface.
Definition Operations.h:360
QC::PathIntegral::FastVectorBool Sample(double v) const
Definition Alias.h:248
size_t Sample(double v) const
Definition Alias.h:199
std::vector< qubit_t > qubits_vector
The type of a vector of qubits.
Definition Types.h:22
uint_fast64_t qubit_t
The type of a qubit.
Definition Types.h:21