Maestro 0.3.11
Unified interface for quantum circuit simulation
Loading...
Searching...
No Matches
QCSimMPOSimulator.h
Go to the documentation of this file.
1#pragma once
2#if defined(INCLUDED_BY_FACTORY)
3#include "MPOSimulator.h"
5#define _USE_MATH_DEFINES
6#include <math.h>
7
9{
10// matrix_product_operator backend. Owns exactly one native QCSim implementation.
11class QCSimMPOSimulator : public QCSimTensorChainSimulator
12{
13 public:
14 QCSimMPOSimulator() : QCSimTensorChainSimulator(true)
15 {
16 configuration.SetConfiguration("method", "matrix_product_operator");
17 }
18
19 const char *MethodName() const override
20 {
21 return "matrix_product_operator";
22 }
23
24 void Initialize() override
25 {
26 InvalidateBoundedRouting(true);
27 RefreshRoutingSettings();
28 InvalidateDummyHistory();
29 if (nrQubits != 0)
30 {
31 {
32 mpoSimulator = std::make_unique<QC::TensorNetworks::MPOSimulator>(nrQubits);
33 if (!useOptimalMeetingPosition)
34 mpoSimulator->SetUseOptimalMeetingPosition(false);
35 InstallBondSummary(*mpoSimulator, bondDimensionCallback);
36 curMaxBondDim = 1;
37 }
38 SetMultithreading(enableMultithreading);
39 for (const auto &[key, value] : configuration.GetConfigMap())
40 if (key != "method")
41 Configure(key.c_str(), value.c_str());
42 }
43 RefreshRoutingCallback();
44 }
45
46 void InitializeToBasisState(size_t num_qubits, Types::qubit_t basisState) override
47 {
48 if (num_qubits == 0)
49 return;
50 Clear();
51 nrQubits = num_qubits;
52 Initialize();
53 mpoSimulator->setToBasisState(static_cast<size_t>(basisState));
54 }
55
56 void InitializeToBasisState(size_t num_qubits, const std::vector<bool> &basisState) override
57 {
58 if (num_qubits == 0)
59 return;
60 Clear();
61 nrQubits = num_qubits;
62 Initialize();
63 mpoSimulator->setToBasisState(basisState);
64 }
65
66 void InitializeToMixtureOfBasisStates(size_t num_qubits, const std::vector<std::pair<Types::qubit_t, double>> &mixture) override
67 {
68 if (num_qubits == 0)
69 return;
70 Clear();
71 nrQubits = num_qubits;
72 Initialize();
73 std::vector<std::pair<size_t, double>> converted;
74 converted.reserve(mixture.size());
75 for (const auto &[basisState, weight] : mixture)
76 converted.emplace_back(static_cast<size_t>(basisState), weight);
77 mpoSimulator->setToMixtureOfBasisStates(converted);
78 }
79
80 void InitializeToMixtureOfBasisStates(size_t num_qubits, const std::vector<std::pair<std::vector<bool>, double>> &mixture) override
81 {
82 if (num_qubits == 0)
83 return;
84 Clear();
85 nrQubits = num_qubits;
86 Initialize();
87 mpoSimulator->setToMixtureOfBasisStates(mixture);
88 }
89
90 void Reset() override
91 {
92 if (mpoSimulator)
93 {
94 mpoSimulator->Clear();
95 curMaxBondDim = 1;
96 }
97 upcomingGateIndex = 0;
98 ResetDummySimulator();
99 }
100
101 void SetInitialQubitsMap(const std::vector<long long int> &initialMap) override
102 {
103 if (mpoSimulator)
104 {
105 mpoSimulator->SetInitialQubitsMap(initialMap);
106 EnsureDummySimulator(initialMap.size());
107 dummySim->SetInitialQubitsMap(initialMap);
108 }
109 }
110
111 void SetUseOptimalMeetingPosition(bool enable) override
112 {
113 useOptimalMeetingPosition = enable;
114 RefreshRoutingSettings();
115 if (mpoSimulator)
116 mpoSimulator->SetUseOptimalMeetingPosition(enable);
117 RefreshRoutingCallback();
118 }
119
120 void Configure(const char *key, const char *value) override
121 {
122 if (std::string("method") == key)
123 {
124 ValidateMethod(value);
125 configuration.SetConfiguration(key, value);
126 return;
127 }
128 if ((std::string(key) == "matrix_product_state_max_bond_dimension" || std::string(key) == "matrix_product_operator_max_bond_dimension"))
129 {
130 const auto limit = std::stoll(value);
131 if (limit < 0)
132 throw std::invalid_argument("Negative MPO bond dimension");
133 if (mpoSimulator)
134 {
135 if (limit == 0)
136 mpoSimulator->dontLimitBondDimension();
137 else
138 mpoSimulator->setLimitBondDimension(limit);
139 }
140 const std::string limitValue(value);
141 configuration.SetConfiguration("matrix_product_state_max_bond_dimension", limitValue);
142 configuration.SetConfiguration("matrix_product_operator_max_bond_dimension", limitValue);
143 RefreshRoutingSettings();
144 RefreshRoutingCallback();
145 return;
146 }
147 if (!configuration.WasApplied(key, value))
148 configuration.SetConfiguration(key, value);
149 RefreshRoutingSettings();
150 if (std::string("seed") == key)
151 {
152 const uint64_t seed = std::stoull(value);
153 SeedAuxiliaryRng(seed);
154 nextSeedStream = 0;
155 rng.seed(seed);
156 if (mpoSimulator)
157 SeedBackend(mpoSimulator.get(), seed);
158 return;
159 }
160 if (mpoSimulator)
161 {
162 if (std::string(key) == "matrix_product_state_max_bond_dimension" || std::string(key) == "matrix_product_operator_max_bond_dimension")
163 {
164 mpoSimulator->setLimitBondDimension(configuration.GetConfigurationAsInt(key));
165 }
166 else if (std::string(key) == "matrix_product_state_truncation_threshold" || std::string(key) == "matrix_product_operator_truncation_threshold")
167 {
168 const double threshold = configuration.GetConfigurationAsDouble(key);
169 if (threshold >= 0.)
170 mpoSimulator->setLimitEntanglement(threshold);
171 }
172 else if (std::string(key) == "matrix_product_state_truncation_mode" || std::string(key) == "matrix_product_operator_truncation_mode")
173 {
174 if (std::string(value) == "relative_max")
175 mpoSimulator->setTruncationMode(QC::TensorNetworks::MPOSimulator::TruncationMode::RelativeToMax);
176 else if (std::string(value) == "discarded_weight")
177 mpoSimulator->setTruncationMode(QC::TensorNetworks::MPOSimulator::TruncationMode::DiscardedWeight);
178 }
179 else if (std::string(key) == "matrix_product_operator_kraus_completeness_check")
180 {
181 using Check = QC::TensorNetworks::MPOSimulator::KrausCompletenessCheck;
182 if (std::string(value) == "ignore")
183 mpoSimulator->setKrausCompletenessCheck(Check::Ignore);
184 else if (std::string(value) == "warn")
185 mpoSimulator->setKrausCompletenessCheck(Check::Warn);
186 else if (std::string(value) == "strict")
187 mpoSimulator->setKrausCompletenessCheck(Check::Strict);
188 }
189 else if (std::string(key) == "matrix_product_operator_restore_trace_after_truncation")
190 {
191 mpoSimulator->setRestoreTraceAfterTruncation(std::string(value) == "1" || std::string(value) == "true");
192 }
193 else if (std::string(key) == "matrix_product_operator_hermitize_after_truncation")
194 {
195 mpoSimulator->setHermitizeAfterTruncation(std::string(value) == "1" || std::string(value) == "true");
196 }
197 }
198 if (std::string(key) == "pauli_propagator_workers")
199 {
200 const auto workers = configuration.GetConfigurationAsUnsigned(key);
201 if (workers > 1024)
202 throw std::invalid_argument("pauli_propagator_workers exceeds 1024");
203 }
204 RefreshRoutingCallback();
205 }
206
207 std::string GetConfiguration(const char *key) const override
208 {
209 if (!key)
210 return {};
211 if (std::string(key) == "precision")
212 return "double";
213 if (std::string(key) == "use_double_precision")
214 return "1";
215 if (mpoSimulator)
216 {
217 if (std::string(key) == "matrix_product_operator_restore_trace_after_truncation")
218 return mpoSimulator->getRestoreTraceAfterTruncation() ? "true" : "false";
219 if (std::string(key) == "matrix_product_operator_hermitize_after_truncation")
220 return mpoSimulator->getHermitizeAfterTruncation() ? "true" : "false";
221 }
222 if (std::string("method") == key)
223 return MethodName();
224 return QCSimTensorChainSimulator::GetConfiguration(key);
225 }
226
227 size_t AllocateQubits(size_t num_qubits) override
228 {
229 if (mpoSimulator)
230 return 0;
231 const size_t oldNrQubits = nrQubits;
232 nrQubits += num_qubits;
233 return oldNrQubits;
234 }
235
236 void Clear() override
237 {
238 automaticRoutingDepth = false;
239 InvalidateBoundedRouting(true);
240 mpoSimulator = nullptr;
241 ClearDummySimulator();
242 nrQubits = 0;
243 upcomingGateIndex = 0;
244 upcomingGates.clear();
245 }
246
247 size_t Measure(const Types::qubits_vector &qubits) override
248 {
249 InvalidateDummyHistory();
250 if (mpoSimulator && qubits.size() > sizeof(size_t) * 8)
251 throw std::invalid_argument("Use MeasureMany for more than 64 measured qubits");
252 if (qubits.size() > sizeof(size_t) * 8)
253 std::cerr << "Warning: The number of qubits to measure is larger than the "
254 "number of bits in the size_t type, the outcome will be undefined"
255 << std::endl;
256 size_t res = 0;
257 size_t mask = 1ULL;
258 DontNotify();
259 {
260 const std::set<Eigen::Index> qubitsSet(qubits.begin(), qubits.end());
261 const auto measured = mpoSimulator->MeasureQubits(qubitsSet);
262 for (Types::qubit_t qubit : qubits)
263 {
264 if (measured.at(static_cast<Eigen::Index>(qubit)))
265 res |= mask;
266 mask <<= 1;
267 }
268 }
269 Notify();
270 NotifyObservers(qubits);
271 return res;
272 }
273
274 std::vector<bool> MeasureMany(const Types::qubits_vector &qubits) override
275 {
276 InvalidateDummyHistory();
277 std::vector<bool> res(qubits.size(), false);
278 DontNotify();
279 {
280 const std::set<Eigen::Index> qubitsSet(qubits.begin(), qubits.end());
281 const auto measured = mpoSimulator->MeasureQubits(qubitsSet);
282 for (size_t q = 0; q < qubits.size(); ++q)
283 res[q] = measured.at(static_cast<Eigen::Index>(qubits[q]));
284 }
285 Notify();
286 NotifyObservers(qubits);
287 return res;
288 }
289
290 void ApplyReset(const Types::qubits_vector &qubits) override
291 {
292 InvalidateDummyHistory();
293 DontNotify();
294 {
295 for (size_t qubit : qubits)
296 mpoSimulator->ApplyReset(static_cast<Eigen::Index>(qubit));
297 }
298 Notify();
299 NotifyObservers(qubits);
300 }
301
302 bool SupportsQuantumChannels() const override
303 {
304 return true;
305 }
306
307 void ApplyQuantumChannel(const Types::qubits_vector &targets, const QuantumChannel &channel) override
308 {
309 if (!SupportsQuantumChannels())
310 throw std::runtime_error("QCSim quantum channels require density_matrix or "
311 "matrix_product_operator simulation");
312 if (targets.size() != channel.GetNumberOfQubits())
313 throw std::invalid_argument("The number of channel targets does not match its Kraus operators");
314 if (targets.empty() || targets.size() > 2)
315 throw std::invalid_argument("QCSim supports only one- and two-qubit local channels");
316 std::unordered_set<Types::qubit_t> uniqueTargets;
317 for (const Types::qubit_t target : targets)
318 {
319 if (target >= nrQubits)
320 throw std::invalid_argument("Quantum-channel qubit is out of range");
321 if (!uniqueTargets.insert(target).second)
322 throw std::invalid_argument("Quantum-channel target qubits must be distinct");
323 }
324 const auto &krausOperators = channel.GetKrausOperators();
325 // Native channel/operator application can invoke routing before it
326 // finishes; discard old estimates both before and after that call.
327 InvalidateDummyHistory();
328 {
329 if (!mpoSimulator)
330 throw std::runtime_error("QCSim MPO state is not initialized");
331 if (targets.size() == 1)
332 mpoSimulator->ApplyKrausOperators(krausOperators, static_cast<Eigen::Index>(targets[0]));
333 else
334 {
335 mpoSimulator->ApplyKrausOperators(krausOperators, static_cast<Eigen::Index>(targets[0]), static_cast<Eigen::Index>(targets[1]));
336 }
337 }
338 InvalidateDummyHistory();
339 NotifyObservers(targets);
340 }
341
342 double ProbabilityBits(const std::vector<bool> &bits) override
343 {
344 if (!mpoSimulator)
345 return IState::ProbabilityBits(bits);
346 MPOValidation::Bits(bits, nrQubits);
347 return mpoSimulator->getBasisStateProbability(bits);
348 }
349
350 std::complex<double> DensityMatrixElementBits(const std::vector<bool> &row, const std::vector<bool> &col) const override
351 {
352 if (!mpoSimulator)
353 return IState::DensityMatrixElementBits(row, col);
354 MPOValidation::Bits(row, nrQubits);
355 MPOValidation::Bits(col, nrQubits);
356 return mpoSimulator->getBasisStateMatrixElement(row, col);
357 }
358
359 Eigen::MatrixXcd GetDensityMatrix(bool normalized = true) const override
360 {
361 if (!mpoSimulator)
362 return IState::GetDensityMatrix(normalized);
363 if (nrQubits > 13)
364 throw std::invalid_argument("MPO dense output supports at most 13 qubits");
365 return normalized ? mpoSimulator->getDensityMatrix() : mpoSimulator->getUnnormalizedDensityMatrix();
366 }
367
368 std::complex<double> ExpectationValueComplex(const std::string &pauli, bool normalized = true) const override
369 {
370 if (!mpoSimulator)
371 return IState::ExpectationValueComplex(pauli, normalized);
372 MPOValidation::Pauli(pauli, nrQubits);
373 return normalized ? mpoSimulator->ExpectationValue(pauli) : mpoSimulator->UnnormalizedExpectationValue(pauli);
374 }
375
376 std::vector<std::complex<double>> ExpectationValuesComplex(const std::vector<std::string> &paulis, bool normalized = true) const override
377 {
378 if (!mpoSimulator)
379 return IState::ExpectationValuesComplex(paulis, normalized);
380 for (const auto &pauli : paulis)
381 MPOValidation::Pauli(pauli, nrQubits);
382 return ComplexTensorExpectationBatch(*mpoSimulator, paulis, normalized);
383 }
384
385 void ApplyOperator(const Types::qubits_vector &qubits, const Eigen::MatrixXcd &matrix, bool normalize = false) override
386 {
387 if (!mpoSimulator)
388 return IState::ApplyOperator(qubits, matrix, normalize);
389 const auto selected = MPOValidation::Operator(qubits, matrix, nrQubits);
390 if (!normalize && qubits.size() == 2 && IsExactSwapMatrix(matrix))
391 return ApplySwap(qubits[0], qubits[1]);
392 const QC::Gates::AppliedGate<> op(matrix, selected[0], selected.size() == 2 ? selected[1] : 0);
393 // Native channel/operator application can invoke routing before it
394 // finishes; discard old estimates both before and after that call.
395 InvalidateDummyHistory();
396 if (normalize)
397 mpoSimulator->ApplyOperatorAndNormalize(op);
398 else
399 mpoSimulator->ApplyOperator(op);
400 InvalidateDummyHistory();
401 NotifyObservers(qubits);
402 }
403
404 void MoveAtBeginningOfChain(const Types::qubits_vector &qubits) override
405 {
406 if (!mpoSimulator)
407 return IState::MoveAtBeginningOfChain(qubits);
408 const auto selected = MPOValidation::Qubits(qubits, nrQubits);
409 const std::set<Eigen::Index> targets(selected.begin(), selected.end());
410 mpoSimulator->MoveAtBeginningOfChain(targets);
411 InvalidateDummyHistory();
412 }
413
414 std::complex<double> DensityMatrixTrace() const override
415 {
416 if (mpoSimulator)
417 return mpoSimulator->Trace();
418 throw std::runtime_error("Mixed-state diagnostics require density_matrix or "
419 "matrix_product_operator");
420 }
421
422 double DensityMatrixPurity() const override
423 {
424 if (mpoSimulator)
425 return mpoSimulator->Purity();
426 throw std::runtime_error("Mixed-state diagnostics require density_matrix or "
427 "matrix_product_operator");
428 }
429
430 std::complex<double> DensityMatrixTraceOfSquare() const override
431 {
432 if (mpoSimulator)
433 return mpoSimulator->TraceOfSquare();
434 throw std::runtime_error("Mixed-state diagnostics require density_matrix or "
435 "matrix_product_operator");
436 }
437
438 std::complex<double> DensityMatrixOverlap(const IState &other) const override
439 {
440 const auto *rhs = dynamic_cast<const QCSimMPOSimulator *>(&other);
441 if (!rhs)
442 throw std::invalid_argument("Density-matrix overlap requires matching QCSim backends");
443 if (mpoSimulator && rhs->mpoSimulator)
444 return mpoSimulator->HilbertSchmidtOverlap(*rhs->mpoSimulator);
445 throw std::invalid_argument("Density-matrix overlap requires two density matrices or two MPOs");
446 }
447
448 double DensityMatrixHermiticityResidual() const override
449 {
450 if (mpoSimulator)
451 return mpoSimulator->HermiticityResidual();
452 throw std::runtime_error("Mixed-state diagnostics require density_matrix or "
453 "matrix_product_operator");
454 }
455
456 bool IsDensityMatrixHermitian(double eps = 1e-10) const override
457 {
458 if (mpoSimulator)
459 return mpoSimulator->IsHermitian(eps);
460 throw std::runtime_error("Mixed-state diagnostics require density_matrix or "
461 "matrix_product_operator");
462 }
463
464 Eigen::MatrixXcd PartialTrace(const Types::qubits_vector &qubits) const override
465 {
466 if (mpoSimulator)
467 return mpoSimulator->PartialTrace(std::vector<Eigen::Index>(qubits.begin(), qubits.end()));
468 throw std::runtime_error("Partial trace requires density_matrix or matrix_product_operator");
469 }
470
471 double FidelityWithStatevector(const Eigen::VectorXcd &psi) const override
472 {
473 if (mpoSimulator)
474 return mpoSimulator->FidelityWithStatevector(psi);
475 throw std::runtime_error("Mixed-state fidelity requires density_matrix or "
476 "matrix_product_operator");
477 }
478
479 void RestoreDensityMatrixTrace() override
480 {
481 if (!mpoSimulator)
482 throw std::runtime_error("Trace restoration is only available for QCSim MPO");
483 mpoSimulator->RestoreTrace();
484 }
485
486 void HermitizeDensityMatrix() override
487 {
488 if (!mpoSimulator)
489 throw std::runtime_error("Hermitization is only available for QCSim MPO");
490 mpoSimulator->Hermitize();
491 }
492
493 void Trim() override
494 {
495 InvalidateDummyHistory();
496 if (mpoSimulator)
497 mpoSimulator->Trim();
498 else
499 throw std::runtime_error("Trim is only available for QCSim MPS and MPO");
500 }
501
502 void ReCanonicalize() override
503 {
504 InvalidateDummyHistory();
505 if (mpoSimulator)
506 mpoSimulator->ReCanonicalize();
507 else
508 throw std::runtime_error("Canonicalization is only available for QCSim MPS and MPO");
509 }
510
511 double Probability(Types::qubit_t outcome) override
512 {
513 if (nrQubits < sizeof(Types::qubit_t) * 8 && outcome >= (Types::qubit_t{1} << nrQubits))
514 throw std::out_of_range("MPO basis state is out of range");
515 std::vector<bool> bits(nrQubits);
516 for (size_t q = 0; q < std::min(nrQubits, sizeof(Types::qubit_t) * 8); ++q)
517 bits[q] = ((outcome >> q) & 1) != 0;
518 return ProbabilityBits(bits);
519 }
520
521 std::complex<double> Amplitude(Types::qubit_t outcome) override
522 {
523 throw std::runtime_error("QCSimState::Amplitude: Amplitudes are not defined for the matrix "
524 "product operator simulator.");
525 }
526
527 std::vector<double> AllProbabilities() override
528 {
529 const size_t nrBasisStates = CheckedBasisStateCountForQueries();
530 std::vector<double> result(nrBasisStates);
531 for (size_t i = 0; i < nrBasisStates; ++i)
532 result[i] = mpoSimulator->getBasisStateProbability(i);
533 return result;
534 }
535
536 std::vector<double> Probabilities(const Types::qubits_vector &qubits) override
537 {
538 std::vector<double> result(qubits.size());
539 {
540 for (int i = 0; i < static_cast<int>(qubits.size()); ++i)
541 result[i] = Probability(qubits[i]);
542 }
543 return result;
544 }
545
546 std::unordered_map<Types::qubit_t, Types::qubit_t> SampleCounts(const Types::qubits_vector &qubits, size_t shots = 1000) override
547 {
548 if (mpoSimulator && qubits.size() > sizeof(Types::qubit_t) * 8)
549 throw std::invalid_argument("Use SampleCountsMany for more than 64 measured qubits");
550 if (mpoSimulator)
551 for (auto q : qubits)
552 if (q >= nrQubits)
553 throw std::out_of_range("MPO sampled qubit is out of range");
554 if (qubits.empty() || shots == 0)
555 return {};
556 if (qubits.size() > sizeof(size_t) * 8)
557 std::cerr << "Warning: The number of qubits to measure is larger than the "
558 "number of bits in the size_t type, the outcome will be undefined"
559 << std::endl;
560 std::unordered_map<Types::qubit_t, Types::qubit_t> result;
561 DontNotify();
562 {
563 const std::set<Eigen::Index> qubitsSet(qubits.begin(), qubits.end());
564 for (size_t shot = 0; shot < shots; ++shot)
565 {
566 const auto measured = mpoSimulator->MeasureNoCollapse(qubitsSet);
567 Types::qubit_t packed = 0;
568 for (size_t i = 0; i < qubits.size(); ++i)
569 if (measured.at(static_cast<Eigen::Index>(qubits[i])))
570 packed |= 1ULL << i;
571 ++result[packed];
572 }
573 }
574 Notify();
575 NotifyObservers(qubits);
576 return result;
577 }
578
579 std::unordered_map<std::vector<bool>, Types::qubit_t> SampleCountsMany(const Types::qubits_vector &qubits, size_t shots = 1000) override
580 {
581 if (mpoSimulator)
582 for (auto q : qubits)
583 if (q >= nrQubits)
584 throw std::out_of_range("MPO sampled qubit is out of range");
585 if (qubits.empty() || shots == 0)
586 return {};
587 std::unordered_map<std::vector<bool>, Types::qubit_t> result;
588 DontNotify();
589 {
590 const std::set<Eigen::Index> qubitsSet(qubits.begin(), qubits.end());
591 for (size_t shot = 0; shot < shots; ++shot)
592 {
593 const auto measured = mpoSimulator->MeasureNoCollapse(qubitsSet);
594 std::vector<bool> packed(qubits.size(), false);
595 for (size_t i = 0; i < qubits.size(); ++i)
596 packed[i] = measured.at(static_cast<Eigen::Index>(qubits[i]));
597 ++result[packed];
598 }
599 }
600 Notify();
601 NotifyObservers(qubits);
602 return result;
603 }
604
605 std::vector<double> ExpectationValues(const std::vector<std::string> &paulis) override
606 {
607 std::vector<double> result(paulis.size(), 1.);
608 std::vector<std::string> selected;
609 std::vector<size_t> indices;
610 const size_t n = GetNumberOfQubits();
611 for (size_t i = 0; i < paulis.size(); ++i)
612 {
613 if (paulis[i].empty())
614 continue;
615 bool zero = false;
616 for (size_t q = n; q < paulis[i].size(); ++q)
617 {
618 const auto p = toupper(static_cast<unsigned char>(paulis[i][q]));
619 if (p != 'I' && p != 'Z')
620 {
621 zero = true;
622 break;
623 }
624 }
625 if (zero)
626 {
627 result[i] = 0.;
628 continue;
629 }
630 std::string pauli = paulis[i];
631 pauli.resize(n, 'I');
632 for (char &p : pauli)
633 {
634 p = static_cast<char>(toupper(static_cast<unsigned char>(p)));
635 }
636 indices.push_back(i);
637 selected.push_back(std::move(pauli));
638 }
639 if (selected.empty())
640 return result;
641 const auto values = TensorExpectationBatch(*mpoSimulator, selected);
642 for (size_t i = 0; i < indices.size(); ++i)
643 result[indices[i]] = values[i].real();
644 return result;
645 }
646
647 double ExpectationValue(const std::string &pauliStringOrig) override
648 {
649 if (pauliStringOrig.empty())
650 return 1.0;
651 std::string pauliString = pauliStringOrig;
652 if (pauliString.size() > GetNumberOfQubits())
653 {
654 for (size_t i = GetNumberOfQubits(); i < pauliString.size(); ++i)
655 {
656 const auto pauliOp = toupper(pauliString[i]);
657 if (pauliOp != 'I' && pauliOp != 'Z')
658 return 0.0;
659 }
660 pauliString.resize(GetNumberOfQubits());
661 }
662 {
663 pauliString.resize(GetNumberOfQubits(), 'I');
664 return mpoSimulator->ExpectationValue(pauliString).real();
665 }
666 }
667
668 SimulationType GetSimulationType() const override
669 {
670 return SimulationType::kMatrixProductOperator;
671 }
672
673 void SaveState() override
674 {
675 mpoSimulator->SaveState();
676 SaveDummyState();
677 }
678
679 void RestoreState() override
680 {
681 mpoSimulator->RestoreState();
682 RestoreDummyState();
683 }
684
685 void SetMultithreading(bool multithreading = true) override
686 {
687 enableMultithreading = multithreading;
688 if (mpoSimulator)
689 mpoSimulator->SetMultithreading(multithreading);
690 }
691
693 {
694 if (mpoSimulator && nrQubits > sizeof(Types::qubit_t) * 8)
695 throw std::invalid_argument("Use MeasureNoCollapseMany for more than 64 qubits");
696 if (GetNumberOfQubits() > sizeof(Types::qubit_t) * 8)
697 std::cerr << "Warning: The number of qubits to measure is larger than the "
698 "number of bits in the Types::qubit_t type, the outcome will be "
699 "undefined"
700 << std::endl;
701 {
702 const auto measured = mpoSimulator->MeasureNoCollapse();
703 Types::qubit_t result = 0;
704 for (size_t qubit = 0; qubit < nrQubits; ++qubit)
705 if (measured.at(static_cast<Eigen::Index>(qubit)))
706 result |= 1ULL << qubit;
707 return result;
708 }
709 }
710
711 std::vector<bool> MeasureNoCollapseMany() override
712 {
713 const auto measured = mpoSimulator->MeasureNoCollapse();
714 std::vector<bool> res(nrQubits);
715 for (size_t i = 0; i < nrQubits; ++i)
716 res[i] = measured.at(static_cast<Eigen::Index>(i));
717 return res;
718 }
719
720 void ApplyGenericOneQubitGate(Types::qubit_t qubit, const Eigen::Matrix2cd &gate) override
721 {
722 const QC::Gates::AppliedGate<> agate(gate, qubit);
723 ApplyNativeGate(agate);
724 NotifyObservers({qubit});
725 }
726
727 void ApplyGenericTwoQubitGate(Types::qubit_t qubit0, Types::qubit_t qubit1, const Eigen::Matrix4cd &gate) override
728 {
729 if (IsExactSwapMatrix(gate))
730 return ApplySwap(qubit0, qubit1);
731 const QC::Gates::AppliedGate<> agate(gate, qubit0, qubit1);
732 ApplyNativeGate(agate);
733 NotifyObservers({qubit0, qubit1});
734 }
735
736 void ApplyP(Types::qubit_t qubit, double lambda) override
737 {
738 pgate.SetPhaseShift(lambda);
739 ApplyNativeGate(pgate, static_cast<unsigned int>(qubit));
740 NotifyObservers({qubit});
741 }
742
743 void ApplyX(Types::qubit_t qubit) override
744 {
745 ApplyNativeGate(xgate, static_cast<unsigned int>(qubit));
746 NotifyObservers({qubit});
747 }
748
749 void ApplyY(Types::qubit_t qubit) override
750 {
751 ApplyNativeGate(ygate, static_cast<unsigned int>(qubit));
752 NotifyObservers({qubit});
753 }
754
755 void ApplyZ(Types::qubit_t qubit) override
756 {
757 ApplyNativeGate(zgate, static_cast<unsigned int>(qubit));
758 NotifyObservers({qubit});
759 }
760
761 void ApplyH(Types::qubit_t qubit) override
762 {
763 ApplyNativeGate(h, static_cast<unsigned int>(qubit));
764 NotifyObservers({qubit});
765 }
766
767 void ApplyS(Types::qubit_t qubit) override
768 {
769 ApplyNativeGate(sgate, static_cast<unsigned int>(qubit));
770 NotifyObservers({qubit});
771 }
772
773 void ApplySDG(Types::qubit_t qubit) override
774 {
775 ApplyNativeGate(sdggate, static_cast<unsigned int>(qubit));
776 NotifyObservers({qubit});
777 }
778
779 void ApplyT(Types::qubit_t qubit) override
780 {
781 ApplyNativeGate(tgate, static_cast<unsigned int>(qubit));
782 NotifyObservers({qubit});
783 }
784
785 void ApplyTDG(Types::qubit_t qubit) override
786 {
787 ApplyNativeGate(tdggate, static_cast<unsigned int>(qubit));
788 NotifyObservers({qubit});
789 }
790
791 void ApplySx(Types::qubit_t qubit) override
792 {
793 ApplyNativeGate(sxgate, static_cast<unsigned int>(qubit));
794 NotifyObservers({qubit});
795 }
796
797 void ApplySxDAG(Types::qubit_t qubit) override
798 {
799 ApplyNativeGate(sxdaggate, static_cast<unsigned int>(qubit));
800 NotifyObservers({qubit});
801 }
802
803 void ApplyK(Types::qubit_t qubit) override
804 {
805 ApplyNativeGate(k, static_cast<unsigned int>(qubit));
806 NotifyObservers({qubit});
807 }
808
809 void ApplyRx(Types::qubit_t qubit, double theta) override
810 {
811 rxgate.SetTheta(theta);
812 ApplyNativeGate(rxgate, static_cast<unsigned int>(qubit));
813 NotifyObservers({qubit});
814 }
815
816 void ApplyRy(Types::qubit_t qubit, double theta) override
817 {
818 rygate.SetTheta(theta);
819 ApplyNativeGate(rygate, static_cast<unsigned int>(qubit));
820 NotifyObservers({qubit});
821 }
822
823 void ApplyRz(Types::qubit_t qubit, double theta) override
824 {
825 rzgate.SetTheta(theta);
826 ApplyNativeGate(rzgate, static_cast<unsigned int>(qubit));
827 NotifyObservers({qubit});
828 }
829
830 void ApplyU(Types::qubit_t qubit, double theta, double phi, double lambda, double gamma) override
831 {
832 ugate.SetParams(theta, phi, lambda, gamma);
833 ApplyNativeGate(ugate, static_cast<unsigned int>(qubit));
834 NotifyObservers({qubit});
835 }
836
837 void ApplyCX(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit) override
838 {
839 ApplyNativeGate(cxgate, static_cast<unsigned int>(tgt_qubit), static_cast<unsigned int>(ctrl_qubit));
840 NotifyObservers({tgt_qubit, ctrl_qubit});
841 }
842
843 void ApplyCY(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit) override
844 {
845 ApplyNativeGate(cygate, static_cast<unsigned int>(tgt_qubit), static_cast<unsigned int>(ctrl_qubit));
846 NotifyObservers({tgt_qubit, ctrl_qubit});
847 }
848
849 void ApplyCZ(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit) override
850 {
851 ApplyNativeGate(czgate, static_cast<unsigned int>(tgt_qubit), static_cast<unsigned int>(ctrl_qubit));
852 NotifyObservers({tgt_qubit, ctrl_qubit});
853 }
854
855 void ApplyCP(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit, double lambda) override
856 {
857 cpgate.SetPhaseShift(lambda);
858 ApplyNativeGate(cpgate, static_cast<unsigned int>(tgt_qubit), static_cast<unsigned int>(ctrl_qubit));
859 NotifyObservers({tgt_qubit, ctrl_qubit});
860 }
861
862 void ApplyCRx(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit, double theta) override
863 {
864 crxgate.SetTheta(theta);
865 ApplyNativeGate(crxgate, static_cast<unsigned int>(tgt_qubit), static_cast<unsigned int>(ctrl_qubit));
866 NotifyObservers({tgt_qubit, ctrl_qubit});
867 }
868
869 void ApplyCRy(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit, double theta) override
870 {
871 crygate.SetTheta(theta);
872 ApplyNativeGate(crygate, static_cast<unsigned int>(tgt_qubit), static_cast<unsigned int>(ctrl_qubit));
873 NotifyObservers({tgt_qubit, ctrl_qubit});
874 }
875
876 void ApplyCRz(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit, double theta) override
877 {
878 crzgate.SetTheta(theta);
879 ApplyNativeGate(crzgate, static_cast<unsigned int>(tgt_qubit), static_cast<unsigned int>(ctrl_qubit));
880 NotifyObservers({tgt_qubit, ctrl_qubit});
881 }
882
883 void ApplyCH(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit) override
884 {
885 ApplyNativeGate(ch, static_cast<unsigned int>(tgt_qubit), static_cast<unsigned int>(ctrl_qubit));
886 NotifyObservers({tgt_qubit, ctrl_qubit});
887 }
888
889 void ApplyCSx(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit) override
890 {
891 ApplyNativeGate(csx, static_cast<unsigned int>(tgt_qubit), static_cast<unsigned int>(ctrl_qubit));
892 NotifyObservers({tgt_qubit, ctrl_qubit});
893 }
894
895 void ApplyCSxDAG(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit) override
896 {
897 ApplyNativeGate(csxdag, static_cast<unsigned int>(tgt_qubit), static_cast<unsigned int>(ctrl_qubit));
898 NotifyObservers({tgt_qubit, ctrl_qubit});
899 }
900
901 void ApplySwap(Types::qubit_t qubit0, Types::qubit_t qubit1) override
902 {
903 ApplyNativeGate(swapgate, static_cast<unsigned int>(qubit1), static_cast<unsigned int>(qubit0));
904 NotifyObservers({qubit1, qubit0});
905 }
906
907 void ApplyCCX(Types::qubit_t qubit0, Types::qubit_t qubit1, Types::qubit_t qubit2) override
908 {
909 ApplyDecomposedGate(
910 [&]() {
911 ApplyCSx(qubit1, qubit2);
912 ApplyCX(qubit0, qubit1);
913 ApplyCSxDAG(qubit1, qubit2);
914 ApplyCX(qubit0, qubit1);
915 ApplyCSx(qubit0, qubit2);
916 },
917 {qubit2, qubit1, qubit0});
918 }
919
920 void ApplyCSwap(Types::qubit_t ctrl_qubit, Types::qubit_t qubit0, Types::qubit_t qubit1) override
921 {
922 ApplyDecomposedGate(
923 [&]() {
924 ApplyCX(qubit1, qubit0);
925 ApplyCSx(qubit0, qubit1);
926 ApplyCX(ctrl_qubit, qubit0);
927 ApplyP(qubit1, M_PI);
928 ApplyP(qubit0, -M_PI_2);
929 ApplyCSx(qubit0, qubit1);
930 ApplyCX(ctrl_qubit, qubit0);
931 ApplyP(qubit1, M_PI);
932 ApplyCSx(ctrl_qubit, qubit1);
933 ApplyCX(qubit1, qubit0);
934 },
935 {qubit1, qubit0, ctrl_qubit});
936 }
937
938 void ApplyCU(Types::qubit_t ctrl_qubit, Types::qubit_t tgt_qubit, double theta, double phi, double lambda, double gamma) override
939 {
940 cugate.SetParams(theta, phi, lambda, gamma);
941 ApplyNativeGate(cugate, static_cast<unsigned int>(tgt_qubit), static_cast<unsigned int>(ctrl_qubit));
942 NotifyObservers({tgt_qubit, ctrl_qubit});
943 }
944
945 std::unique_ptr<ISimulator> Clone() override
946 {
947 auto cloned = std::make_unique<QCSimMPOSimulator>();
948 cloned->nrQubits = nrQubits;
949 cloned->enableMultithreading = enableMultithreading;
950 cloned->lookaheadDepth = lookaheadDepth;
951 cloned->automaticRoutingDepth = automaticRoutingDepth;
952 cloned->lookaheadDepthWithHeuristic = lookaheadDepthWithHeuristic;
953 cloned->useOptimalMeetingPosition = useOptimalMeetingPosition;
954 cloned->upcomingGates = upcomingGates;
955 cloned->upcomingGateIndex = upcomingGateIndex;
956 cloned->boundedRoutingOperations = boundedRoutingOperations;
957 cloned->boundedRoutingOperationsQubits = boundedRoutingOperationsQubits;
958 cloned->growthFactorGate = growthFactorGate;
959 cloned->growthFactorSwap = growthFactorSwap;
960 if (mpoSimulator)
961 {
962 cloned->mpoSimulator = mpoSimulator->Clone();
963 cloned->curMaxBondDim = curMaxBondDim;
964 cloned->gateCounterObserver = std::make_shared<GateCounterObserver>(cloned->upcomingGateIndex);
965 cloned->RegisterObserver(cloned->gateCounterObserver);
966 cloned->mpoSimulator->SetMeetingPositionCallback(cloned->meetingPositionCallback);
967 InstallBondSummary(*cloned->mpoSimulator, cloned->bondDimensionCallback);
968 }
969 for (const auto &[key, value] : configuration.GetConfigMap())
970 cloned->Configure(key.c_str(), value.c_str());
971 cloned->RefreshRoutingSettings();
972 cloned->dummySim = dummySim ? dummySim->Clone() : nullptr;
973 cloned->dummyHistoryValid = dummyHistoryValid;
974 cloned->savedDummyBondDims = savedDummyBondDims;
975 if (configuration.IsSet("seed"))
976 cloned->SetSeed(DeriveSeed(std::stoull(configuration.GetConfiguration("seed")), nextSeedStream++));
977 cloned->RefreshRoutingCallback();
978 return cloned;
979 }
980
981 protected:
982 std::vector<double> CurrentBondDimensions() const override
983 {
984 if (!mpoSimulator)
985 return {};
986 const auto dims = mpoSimulator->getBondDimensions();
987 return {dims.begin(), dims.end()};
988 }
989
990 std::vector<long long> CurrentRoutingMap() const override
991 {
992 if (mpoSimulator)
993 return ReadRoutingMap(mpoSimulator.get());
994 return {};
995 }
996
997 void RefreshRoutingCallback() override
998 {
999 RefreshBoundedRouting();
1000 const bool active = IsRoutingLookaheadEnabled() && !upcomingGates.empty();
1001 if (mpoSimulator)
1002 mpoSimulator->SetMeetingPositionCallback(active ? meetingPositionCallback : nullptr);
1003 }
1004
1005 std::unique_ptr<QC::TensorNetworks::MPOSimulator> mpoSimulator;
1006
1007 private:
1008 template <class Function> void ApplyDecomposedGate(Function &&apply, const Types::qubits_vector &affectedQubits)
1009 {
1010 {
1011 DecompositionScope scope(*this);
1012 std::forward<Function>(apply)();
1013 }
1014 NotifyObservers(affectedQubits);
1015 }
1016
1017 template <class Gate, class... Qubits> void ApplyNativeGate(const Gate &gate, Qubits... qubits)
1018 {
1019 if constexpr (sizeof...(Qubits) <= 2)
1020 {
1021 mpoSimulator->ApplyGate(gate, static_cast<Eigen::Index>(qubits)...);
1022 if constexpr (sizeof...(Qubits) == 2)
1023 {
1024 const std::array<Types::qubit_t, 2> qs{static_cast<Types::qubit_t>(qubits)...};
1025 RecordExecutedTwoQubitGate(qs[0], qs[1], gate.isSwapGate(), NativeGateSchmidtRank(gate));
1026 }
1027 else if constexpr (sizeof...(Qubits) == 0)
1028 {
1029 if (gate.getQubitsNumber() == 2)
1030 RecordExecutedTwoQubitGate(static_cast<Types::qubit_t>(gate.getQubit1()), static_cast<Types::qubit_t>(gate.getQubit2()), gate.isSwapGate(),
1031 NativeGateSchmidtRank(gate));
1032 }
1033 }
1034 else
1035 {
1036 throw std::runtime_error("ImmediateQCSimSimulator: The matrix product operator simulator "
1037 "supports "
1038 "only one- and two-qubit gate operators.");
1039 }
1040 }
1041};
1042} // namespace Simulators::Private
1043#endif
int ApplyK(void *sim, int qubit)
double Probability(void *sim, unsigned long long int outcome)
char * GetConfiguration(void *sim, const char *key)
int RestoreState(void *sim)
int ApplyRx(void *sim, int qubit, double theta)
int ApplyReset(void *sim, const unsigned long int *qubits, unsigned long int nrQubits)
int ApplyX(void *sim, int qubit)
int ApplyU(void *sim, int qubit, double theta, double phi, double lambda, double gamma)
int ApplyCRy(void *sim, int controlQubit, int targetQubit, double theta)
int ApplyTDG(void *sim, int qubit)
int ApplyS(void *sim, int qubit)
int ApplyCX(void *sim, int controlQubit, int targetQubit)
unsigned long int AllocateQubits(void *sim, unsigned long int nrQubits)
int ApplyCRz(void *sim, int controlQubit, int targetQubit, double theta)
unsigned long int GetNumberOfQubits(void *sim)
double * AllProbabilities(void *sim)
unsigned long long int MeasureNoCollapse(void *sim)
int ApplyCP(void *sim, int controlQubit, int targetQubit, double theta)
int 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 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)
std::vector< std::complex< double > > ComplexTensorExpectationBatch(const T &backend, const std::vector< std::string > &paulis, bool normalized)
std::vector< std::complex< double > > TensorExpectationBatch(T &backend, const std::vector< std::string > &paulis)
void InstallBondSummary(T &backend, Callback callback)
std::vector< long long > ReadRoutingMap(const T *simulator)
Definition RoutingMap.h:18
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