Maestro 0.3.11
Unified interface for quantum circuit simulation
Loading...
Searching...
No Matches
FusionState.h
Go to the documentation of this file.
1#pragma once
2#include "FusionGate.h"
3#include <memory>
4#include <optional>
5#include <unordered_map>
6
8{
9// State boundary for the fusion-aware adapters. The owned implementation is
10// immediate and never calls back into this cache. Composite children use that
11// implementation directly and do not instantiate this class.
12class FusionState : public ISimulator
13{
14 public:
15 explicit FusionState(std::shared_ptr<ISimulator> immediate) : immediate_(std::move(immediate)), cache_(3)
16 {
17 const auto seed = immediate_->GetConfiguration("sampling_seed");
18 if (!seed.empty())
19 SeedAuxiliaryRng(std::stoull(seed));
20 }
21
23 {
24 return stats_;
25 }
26
27 // An explicit gate_fusion setting always wins. Without one, fusion is on
28 // from the backend's minimum register size; below it the fusion cache and
29 // dense kernels cost more than the gates they replace.
30 bool IsGateFusionEnabled() const override
31 {
32 if (GetGateFusionMaxQubits() == 0)
33 return false;
34 if (requested_)
35 return *requested_;
36 const auto qubits = GetNumberOfQubits();
37 return qubits == 0 || qubits >= DefaultGateFusionMinQubits();
38 }
39
40 void Configure(const char *key, const char *value) override
41 {
42 const std::string name(key), setting(value);
43 const auto oldWidth = GetGateFusionMaxQubits();
44 const bool oldEnabled = IsGateFusionEnabled();
45 const bool oldLookahead = IsRoutingLookaheadEnabled();
46 const auto seed = name == "seed" ? std::optional<uint64_t>(std::stoull(setting)) : std::nullopt;
47 if (name == "gate_fusion")
48 {
49 if (setting != "true" && setting != "false" && setting != "1" && setting != "0" && setting != "auto")
50 throw std::invalid_argument("gate_fusion must be true, false, 1, 0 or auto");
51 Flush();
52 if (setting == "auto")
53 requested_.reset();
54 else
55 requested_ = setting == "true" || setting == "1";
56 }
57 else
58 {
59 Flush();
60 immediate_->Configure(key, value);
61 if (seed)
62 SeedAuxiliaryRng(*seed);
63 }
64 cache_.SetWidth(GetGateFusionMaxQubits());
65 if (oldWidth != GetGateFusionMaxQubits() || oldEnabled != IsGateFusionEnabled() || oldLookahead != IsRoutingLookaheadEnabled())
66 RebuildPlan();
67 }
68
69 std::string GetConfiguration(const char *key) const override
70 {
71 if (std::string(key) == "gate_fusion")
72 return RequestedSetting();
73 return immediate_->GetConfiguration(key);
74 }
75
76 const std::unordered_map<std::string, std::string> &GetConfigMap() const override
77 {
78 config_ = immediate_->GetConfigMap();
79 config_["gate_fusion"] = RequestedSetting();
80 return config_;
81 }
82
83 void SetSeed(uint64_t seed) override
84 {
85 Flush();
86 SeedAuxiliaryRng(seed);
87 immediate_->SetSeed(seed);
88 }
89
90 void FlushPendingGates() override
91 {
92 cache_.Flush([this](const auto &block) { Emit(block); });
93 }
94
95 void Flush() override
96 {
98 // Distributed backends cannot synchronize until a native state exists.
99 if (ready_)
100 immediate_->Flush();
101 }
102
103 void SetUpcomingGates(const std::vector<std::shared_ptr<Circuits::IOperation<double>>> &gates) override
104 {
105 Flush();
106 sources_ = gates;
107 sourceIndex_ = 0;
108 RebuildPlan();
109 }
110
111 const std::vector<std::shared_ptr<Circuits::IOperation<double>>> &GetUpcomingRoutingOperations() const override
112 {
113 static const std::vector<Operation> empty;
114 // An active plan retains its complete indexed window, not a suffix.
115 // Inactive metadata is only an initial-layout view. Reading it after
116 // execution starts must neither expose that stale view nor flush pending
117 // fusion blocks or install a new backend operation window.
118 if (!planValid_ && sourceIndex_ != 0)
119 return empty;
120 // Initial placement may request a fused view even without lookahead.
121 // Build it lazily; statevectors never need a routing plan.
122 if ((!routingPlan_ || routingPlan_->routing.empty()) && !sources_.empty() && IsGateFusionEnabled() &&
124 const_cast<FusionState *>(this)->PreparePlan();
125 return routingPlan_ ? routingPlan_->routing : empty;
126 }
127
128 long long GetGatesCounter() const override
129 {
130 return sourceIndex_;
131 }
132
133 void SetGatesCounter(long long counter) override
134 {
135 if (counter < 0)
136 throw std::invalid_argument("Negative circuit position");
137 if (counter == sourceIndex_)
138 return;
139 // Moving the counter only needs the pending blocks emitted at the old
140 // position; it does not need the backend to finish its queued work.
142 // Advancing over a classical operation does not change the prepared
143 // quantum blocks. Arbitrary jumps use local routing until explicitly
144 // prepared again, rather than rescanning the suffix on every skipped gate.
145 if (counter != sourceIndex_ + 1 || !AtBoundary())
146 InvalidatePlan();
147 sourceIndex_ = counter;
148 if (!planValid_)
149 immediate_->SetGatesCounter(sourceIndex_);
150 }
151
152 void IncrementGatesCounter() override
153 {
154 SetGatesCounter(sourceIndex_ + 1);
155 }
156
157 void Initialize() override
158 {
159 Discard();
160 immediate_->Initialize();
161 FinishInitialization(GetNumberOfQubits(), !InitializationPreservesSnapshots());
162 }
163
164 void Reset() override
165 {
166 Discard();
167 immediate_->Reset();
168 sourceIndex_ = 0;
169 RebuildPlan();
170 }
171
172 void Clear() override
173 {
174 Discard();
175 immediate_->Clear();
176 ready_ = false;
177 sourceIndex_ = 0;
178 sources_.clear();
179 routingPlan_.reset();
180 backendRoutingInstalled_ = false;
181 saved_.reset();
182 destructiveSaved_.reset();
183 }
184
185 size_t AllocateQubits(size_t n) override
186 {
187 Flush();
188 return immediate_->AllocateQubits(n);
189 }
190
191 void SaveState() override
192 {
193 Flush();
194 immediate_->SaveState();
195 saved_ = Context{sourceIndex_, sources_};
196 }
197
198 void RestoreState() override
199 {
200 // Several immediate implementations treat restore-without-save as a no-op.
201 // Such a call must not silently throw away newly submitted gates.
202 if (saved_)
203 Discard();
204 else
205 Flush();
206 immediate_->RestoreState();
207 if (saved_)
208 {
209 sourceIndex_ = saved_->source;
210 sources_ = saved_->operations;
211 }
212 RebuildPlan();
213 }
214
216 {
217 Flush();
218 immediate_->SaveStateToInternalDestructive();
220 destructiveSaved_ = Context{sourceIndex_, sources_};
221 }
222
224 {
225 if (destructiveSaved_)
226 Discard();
227 else
228 Flush();
229 immediate_->RestoreInternalDestructiveSavedState();
230 if (destructiveSaved_)
231 {
232 sourceIndex_ = destructiveSaved_->source;
233 sources_ = destructiveSaved_->operations;
234 destructiveSaved_.reset();
235 }
236 RebuildPlan();
237 }
238
239 size_t Measure(const Types::qubits_vector &qs) override
240 {
241 Flush();
242 auto result = immediate_->Measure(qs);
244 NotifyObservers(qs);
245 return result;
246 }
247
248 std::vector<bool> MeasureMany(const Types::qubits_vector &qs) override
249 {
250 Flush();
251 auto result = immediate_->MeasureMany(qs);
253 NotifyObservers(qs);
254 return result;
255 }
256
257 void ApplyReset(const Types::qubits_vector &qs) override
258 {
259 Flush();
260 immediate_->ApplyReset(qs);
262 NotifyObservers(qs);
263 }
264
265 void ApplyQuantumChannel(const Types::qubits_vector &qs, const QuantumChannel &channel) override
266 {
267 Flush();
268 immediate_->ApplyQuantumChannel(qs, channel);
270 NotifyObservers(qs);
271 }
272
273 std::complex<double> DensityMatrixOverlap(const IState &other) const override
274 {
275 Synchronize();
276 const auto *adapter = dynamic_cast<const FusionState *>(&other);
277 if (adapter)
278 {
279 adapter->Synchronize();
280 return immediate_->DensityMatrixOverlap(*adapter->immediate_);
281 }
282 return immediate_->DensityMatrixOverlap(other);
283 }
284
285 size_t GetCurrentMaxBondDimension() const override
286 {
287 Synchronize();
288 return immediate_->GetCurrentMaxBondDimension();
289 }
290
291 size_t GetExecutedMaxBondDimension() const override
292 {
293 return immediate_->GetCurrentMaxBondDimension();
294 }
295
296 size_t GetNumberOfQubits() const override
297 {
298 return immediate_->GetNumberOfQubits();
299 }
300
301 SimulatorType GetType() const override
302 {
303 return immediate_->GetType();
304 }
305
307 {
308 return immediate_->GetSimulationType();
309 }
310
311 int GetGpuDevice() const override
312 {
313 return immediate_->GetGpuDevice();
314 }
315
316 bool IsQcsim() const override
317 {
318 return immediate_->IsQcsim();
319 }
320
321 bool GetMultithreading() const override
322 {
323 return immediate_->GetMultithreading();
324 }
325
326 bool SupportsMPSSwapOptimization() const override
327 {
328 return immediate_->SupportsMPSSwapOptimization();
329 }
330
331 bool SwapGatesRelabelQubits() const override
332 {
333 return immediate_->SwapGatesRelabelQubits();
334 }
335
336 bool IsRoutingLookaheadEnabled() const override
337 {
338 return immediate_->IsRoutingLookaheadEnabled();
339 }
340
341 bool SupportsQuantumChannels() const override
342 {
343 return immediate_->SupportsQuantumChannels();
344 }
345
346 double getGrowthFactorSwap() const override
347 {
348 return immediate_->getGrowthFactorSwap();
349 }
350
351 double getGrowthFactorGate() const override
352 {
353 return immediate_->getGrowthFactorGate();
354 }
355
356 double Probability(Types::qubit_t outcome) override
357 {
358 Synchronize();
359 return immediate_->Probability(outcome);
360 }
361
362 std::complex<double> Amplitude(Types::qubit_t outcome) override
363 {
364 Synchronize();
365 return immediate_->Amplitude(outcome);
366 }
367
368 std::complex<double> AmplitudeRaw(Types::qubit_t outcome) override
369 {
370 Synchronize();
371 return immediate_->AmplitudeRaw(outcome);
372 }
373
374 std::complex<double> ProjectOnZero() override
375 {
376 Synchronize();
377 return immediate_->ProjectOnZero();
378 }
379
380 std::vector<double> AllProbabilities() override
381 {
382 Synchronize();
383 return immediate_->AllProbabilities();
384 }
385
386 std::vector<std::complex<double>> GetStateVector() override
387 {
388 Synchronize();
389 return immediate_->GetStateVector();
390 }
391
392 std::complex<double> ExpectationValueOperators(const Types::qubits_vector &qubits, const std::vector<Eigen::MatrixXcd> &matrices) override
393 {
394 Synchronize();
395 return immediate_->ExpectationValueOperators(qubits, matrices);
396 }
397
398 std::vector<double> Probabilities(const Types::qubits_vector &qs) override
399 {
400 Synchronize();
401 return immediate_->Probabilities(qs);
402 }
403
404 std::unordered_map<Types::qubit_t, Types::qubit_t> SampleCounts(const Types::qubits_vector &qs, size_t shots = 1000) override
405 {
406 Synchronize();
407 return immediate_->SampleCounts(qs, shots);
408 }
409
410 std::unordered_map<std::vector<bool>, Types::qubit_t> SampleCountsMany(const Types::qubits_vector &qs, size_t shots = 1000) override
411 {
412 Synchronize();
413 return immediate_->SampleCountsMany(qs, shots);
414 }
415
416 std::vector<double> ExpectationValues(const std::vector<std::string> &paulis) override
417 {
418 if (paulis.empty())
419 return {};
420 Synchronize();
421 return immediate_->ExpectationValues(paulis);
422 }
423
424 double ExpectationValue(const std::string &pauli) override
425 {
426 Synchronize();
427 return immediate_->ExpectationValue(pauli);
428 }
429
431 {
432 Synchronize();
433 return immediate_->MeasureNoCollapse();
434 }
435
436 std::vector<bool> MeasureNoCollapseMany() override
437 {
438 Synchronize();
439 return immediate_->MeasureNoCollapseMany();
440 }
441
442 double ProbabilityBits(const std::vector<bool> &bits) override
443 {
444 Synchronize();
445 return immediate_->ProbabilityBits(bits);
446 }
447
448 std::complex<double> DensityMatrixElementBits(const std::vector<bool> &row, const std::vector<bool> &col) const override
449 {
450 Synchronize();
451 return immediate_->DensityMatrixElementBits(row, col);
452 }
453
454 Eigen::MatrixXcd GetDensityMatrix(bool normalized = true) const override
455 {
456 Synchronize();
457 return immediate_->GetDensityMatrix(normalized);
458 }
459
460 std::complex<double> ExpectationValueComplex(const std::string &pauli, bool normalized = true) const override
461 {
462 Synchronize();
463 return immediate_->ExpectationValueComplex(pauli, normalized);
464 }
465
466 std::vector<std::complex<double>> ExpectationValuesComplex(const std::vector<std::string> &paulis, bool normalized = true) const override
467 {
468 if (paulis.empty())
469 return {};
470 Synchronize();
471 return immediate_->ExpectationValuesComplex(paulis, normalized);
472 }
473
474 void ApplyOperator(const Types::qubits_vector &qubits, const Eigen::MatrixXcd &matrix, bool normalize = false) override
475 {
476 Flush();
477 immediate_->ApplyOperator(qubits, matrix, normalize);
478 InvalidatePlan();
479 NotifyObservers(qubits);
480 }
481
482 void MoveAtBeginningOfChain(const Types::qubits_vector &qubits) override
483 {
484 Flush();
485 // A failed routing SWAP may have already changed the physical qubit map.
486 InvalidatePlan();
487 immediate_->MoveAtBeginningOfChain(qubits);
488 RebuildPlan();
489 }
490
491 std::complex<double> DensityMatrixTrace() const override
492 {
493 Synchronize();
494 return immediate_->DensityMatrixTrace();
495 }
496
497 double DensityMatrixPurity() const override
498 {
499 Synchronize();
500 return immediate_->DensityMatrixPurity();
501 }
502
503 std::complex<double> DensityMatrixTraceOfSquare() const override
504 {
505 Synchronize();
506 return immediate_->DensityMatrixTraceOfSquare();
507 }
508
509 double DensityMatrixHermiticityResidual() const override
510 {
511 Synchronize();
512 return immediate_->DensityMatrixHermiticityResidual();
513 }
514
515 bool IsDensityMatrixHermitian(double eps = 1e-10) const override
516 {
517 Synchronize();
518 return immediate_->IsDensityMatrixHermitian(eps);
519 }
520
521 Eigen::MatrixXcd PartialTrace(const Types::qubits_vector &qs) const override
522 {
523 Synchronize();
524 return immediate_->PartialTrace(qs);
525 }
526
527 double FidelityWithStatevector(const Eigen::VectorXcd &psi) const override
528 {
529 Synchronize();
530 return immediate_->FidelityWithStatevector(psi);
531 }
532
534 {
535 Flush();
536 immediate_->RestoreDensityMatrixTrace();
537 }
538
540 {
541 Flush();
542 immediate_->HermitizeDensityMatrix();
543 }
544
545 void Trim() override
546 {
547 Flush();
548 immediate_->Trim();
549 }
550
551 void ReCanonicalize() override
552 {
553 Flush();
554 immediate_->ReCanonicalize();
555 }
556
557 void SetMultithreading(bool value = true) override
558 {
559 Flush();
560 immediate_->SetMultithreading(value);
561 }
562
563 void SetInitialQubitsMap(const std::vector<long long> &value) override
564 {
565 Flush();
566 immediate_->SetInitialQubitsMap(value);
567 }
568
569 void SetUseOptimalMeetingPosition(bool value) override
570 {
571 UpdateRoutingSetting([&] { immediate_->SetUseOptimalMeetingPosition(value); });
572 }
573
574 void SetLookaheadDepth(int value) override
575 {
576 UpdateRoutingSetting([&] { immediate_->SetLookaheadDepth(value); });
577 }
578
579 void SetAutomaticLookaheadDepth(int value) override
580 {
581 UpdateRoutingSetting([&] { immediate_->SetAutomaticLookaheadDepth(value); });
582 }
583
584 void SetLookaheadDepthWithHeuristic(int value) override
585 {
586 UpdateRoutingSetting([&] { immediate_->SetLookaheadDepthWithHeuristic(value); });
587 }
588
589 void setGrowthFactorSwap(double value) override
590 {
591 Flush();
592 const bool oldLookahead = IsRoutingLookaheadEnabled();
593 immediate_->setGrowthFactorSwap(value);
594 if (oldLookahead != IsRoutingLookaheadEnabled())
595 RebuildPlan();
596 }
597
598 void setGrowthFactorGate(double value) override
599 {
600 Flush();
601 const bool oldLookahead = IsRoutingLookaheadEnabled();
602 immediate_->setGrowthFactorGate(value);
603 if (oldLookahead != IsRoutingLookaheadEnabled())
604 RebuildPlan();
605 }
606
607 void InitializeState(size_t n, std::vector<std::complex<double>> &value) override
608 {
609 if (n == 0)
610 {
611 immediate_->InitializeState(n, value);
612 return;
613 }
614 Discard();
615 immediate_->InitializeState(n, value);
616 FinishInitialization(n);
617 }
618#ifndef NO_QISKIT_AER
619 void InitializeState(size_t n, AER::Vector<std::complex<double>> &value) override
620 {
621 if (n == 0)
622 {
623 immediate_->InitializeState(n, value);
624 return;
625 }
626 Discard();
627 immediate_->InitializeState(n, value);
628 FinishInitialization(n);
629 }
630#endif
631 void InitializeState(size_t n, Eigen::VectorXcd &value) override
632 {
633 if (n == 0)
634 {
635 immediate_->InitializeState(n, value);
636 return;
637 }
638 Discard();
639 immediate_->InitializeState(n, value);
640 FinishInitialization(n);
641 }
642
643 void InitializeToBasisState(size_t n, Types::qubit_t value) override
644 {
645 if (n == 0)
646 {
647 immediate_->InitializeToBasisState(n, value);
648 return;
649 }
650 Discard();
651 immediate_->InitializeToBasisState(n, value);
652 FinishInitialization(n);
653 }
654
655 void InitializeToBasisState(size_t n, const std::vector<bool> &value) override
656 {
657 if (n == 0)
658 {
659 immediate_->InitializeToBasisState(n, value);
660 return;
661 }
662 Discard();
663 immediate_->InitializeToBasisState(n, value);
664 FinishInitialization(n);
665 }
666
667 void InitializeToMixtureOfBasisStates(size_t n, const std::vector<std::pair<Types::qubit_t, double>> &value) override
668 {
669 if (n == 0)
670 {
671 immediate_->InitializeToMixtureOfBasisStates(n, value);
672 return;
673 }
674 Discard();
675 immediate_->InitializeToMixtureOfBasisStates(n, value);
676 FinishInitialization(n);
677 }
678
679 void InitializeToMixtureOfBasisStates(size_t n, const std::vector<std::pair<std::vector<bool>, double>> &value) override
680 {
681 if (n == 0)
682 {
683 immediate_->InitializeToMixtureOfBasisStates(n, value);
684 return;
685 }
686 Discard();
687 immediate_->InitializeToMixtureOfBasisStates(n, value);
688 FinishInitialization(n);
689 }
690
691 protected:
692 // QCSim's destructive save/restore hooks are no-ops. Backends that move
693 // their storage opt in so the routing context follows that storage.
694 // Smallest register for which fusion is on when gate_fusion is not set.
695 virtual size_t DefaultGateFusionMinQubits() const
696 {
697 return 0;
698 }
699
700 virtual bool UsesDestructiveStateStorage() const
701 {
702 return false;
703 }
704
706 {
707 return false;
708 }
709
710 // Backends where a dense matrix costs more than native structured gates
711 // (e.g. communication on distributed global qubits) can decline merges of
712 // exclusively diagonal/permutation gates without changing the cache
713 // algorithm. Elsewhere merging them measured faster or equal.
714 virtual bool PreserveStructuredGates() const
715 {
716 return false;
717 }
718
720 using Operation = std::shared_ptr<Circuits::IOperation<double>>;
721 std::shared_ptr<ISimulator> immediate_;
722
723 void CloneInto(FusionState &copy, const uint64_t *executionSeed = nullptr)
724 {
725 Flush();
726 copy.immediate_ = executionSeed ? immediate_->CloneForExecution(*executionSeed) : immediate_->Clone();
727 copy.requested_ = requested_;
728 copy.stats_ = stats_;
729 copy.ready_ = ready_;
730 copy.sources_ = sources_;
731 copy.sourceIndex_ = sourceIndex_;
732 copy.saved_ = saved_;
733 copy.destructiveSaved_ = destructiveSaved_;
734 copy.cache_.SetWidth(copy.GetGateFusionMaxQubits());
735 const auto seed = copy.immediate_->GetConfiguration("seed");
736 if (!seed.empty())
737 copy.SeedAuxiliaryRng(std::stoull(seed));
738 // Flush above either completed the pending planned block or invalidated
739 // it. A still-valid plan uses the same complete backend operation window
740 // and cursor copied by the native wrapper; do not reparse its suffix.
741 const auto method = GetSimulationType();
742 const bool cpuChain =
744 if (cpuChain && (!IsGateFusionEnabled() || (planValid_ && routingPlan_)))
745 {
746 copy.routingPlan_ = routingPlan_;
747 copy.planIndex_ = planIndex_;
748 copy.planValid_ = planValid_;
749 copy.backendRoutingInstalled_ = backendRoutingInstalled_;
750 }
751 else
752 copy.RebuildPlan();
753 }
754
755 void SubmitGate(const FusionGate &gate)
756 {
757 if (!IsGateFusionEnabled())
758 {
759 immediate_->SetGatesCounter(sourceIndex_);
760 gate.Apply(*immediate_);
761 ++stats_.submittedGates;
762 ++stats_.backendGates;
763 ++sourceIndex_;
765 return;
766 }
767 if (!ready_)
768 throw std::logic_error("Initialize the simulator before applying gates");
769 auto qs = gate.qubits;
770 std::sort(qs.begin(), qs.end());
771 if (qs.empty() || qs.back() >= GetNumberOfQubits() || std::adjacent_find(qs.begin(), qs.end()) != qs.end())
772 throw std::invalid_argument("Invalid or duplicate gate target");
773 for (auto p : gate.params)
774 if (!std::isfinite(p))
775 throw std::invalid_argument("Non-finite gate parameter");
776 if (gate.kind == FusionGate::Kind::kNone && !gate.generic.allFinite())
777 throw std::invalid_argument("Non-finite gate matrix");
778 ++stats_.submittedGates;
779 const auto width = GetGateFusionMaxQubits();
780 if (gate.kind == FusionGate::Kind::kNone && gate.qubits.size() > width)
781 {
783 InvalidatePlan();
784 gate.Apply(*immediate_);
785 ++stats_.backendGates;
786 ++sourceIndex_;
788 return;
789 }
790 // Nonunitary generic operators retain their backend's existing semantics.
791 if (gate.kind == FusionGate::Kind::kNone &&
792 !(gate.generic.adjoint() * gate.generic).isApprox(Eigen::MatrixXcd::Identity(gate.generic.rows(), gate.generic.rows()), 1e-10))
793 {
795 InvalidatePlan();
796 gate.Apply(*immediate_);
797 ++stats_.backendGates;
798 ++sourceIndex_;
800 return;
801 }
802 if (planValid_)
803 {
804 const auto index = static_cast<size_t>(sourceIndex_);
805 const auto boundary = routingPlan_->boundaries.find(index);
806 if (boundary != routingPlan_->boundaries.end() && sources_[index]->GetType() == Circuits::OperationType::kConditionalGate)
807 {
808 // A conditional gate that fires is a boundary of the plan: apply it on
809 // its own, at its routing position, and keep the plan.
811 immediate_->SetGatesCounter(static_cast<long long>(boundary->second));
812 gate.Apply(*immediate_);
813 ++stats_.backendGates;
814 ++sourceIndex_;
816 return;
817 }
818 const auto original = index < sources_.size() ? std::dynamic_pointer_cast<Circuits::IQuantumGate<>>(sources_[index]) : nullptr;
819 if (!original || !FusionGate::FromCircuit(*original).Matches(gate))
820 InvalidatePlan();
821 }
822 const auto source = static_cast<uint64_t>(sourceIndex_++);
823 if (cache_.Empty())
824 cache_.SetWidth(width);
825 const auto emit = [this](const auto &block) { Emit(block); };
826 if (gate.qubits.size() <= width) // Expand would return {gate}
827 SubmitToCache(cache_, gate, source << 4, emit);
828 else
829 {
830 const auto primitives = gate.Expand(width);
831 for (size_t i = 0; i < primitives.size(); ++i)
832 SubmitToCache(cache_, primitives[i], (source << 4) | i, emit);
833 }
835 }
836
838 {
839 if (!AtBoundary() || sources_[sourceIndex_]->GetType() != type)
840 InvalidatePlan();
841 ++sourceIndex_;
842 if (!planValid_)
843 immediate_->SetGatesCounter(sourceIndex_);
844 }
845
846 private:
847 template <class Setter> void UpdateRoutingSetting(Setter &&setter)
848 {
849 Flush();
850 const bool oldLookahead = IsRoutingLookaheadEnabled();
851 std::forward<Setter>(setter)();
852 const bool newLookahead = IsRoutingLookaheadEnabled();
853 // Reuse is limited to the CPU tensor chains verified here. Initial
854 // routing metadata does not depend on their depth/heuristic.
855 // Mid-circuit calls still rebuild the suffix: Flush may split a planned
856 // block, and a counter rewind can leave an invalid plan even at zero.
857 const auto method = GetSimulationType();
858 const bool cpuChain =
860 if (!cpuChain || sourceIndex_ != 0 || oldLookahead != newLookahead || (newLookahead && !planValid_))
861 RebuildPlan();
862 }
863
864 template <class EmitBlock> void SubmitToCache(Cache &cache, const FusionGate &gate, uint64_t source, EmitBlock &&emit)
865 {
866 cache.Submit(gate, source, std::forward<EmitBlock>(emit), PreserveStructuredGates() && gate.IsStructured(),
867 SwapGatesRelabelQubits() && gate.kind == FusionGate::Kind::kSwapGateType);
868 }
869
870 struct Context
871 {
872 long long source;
873 std::vector<Operation> operations;
874 };
875
876 struct Planned
877 {
878 Types::qubits_vector targets;
879 Cache::Sources sources;
880 size_t routingIndex;
881 };
882
883 struct RoutingPlan
884 {
885 std::vector<Operation> routing;
886 std::vector<Planned> planned;
887 // Source position -> routing position for non-gate boundaries.
888 std::unordered_map<size_t, size_t> boundaries;
889 };
890
891 void Synchronize() const
892 {
893 const_cast<FusionState *>(this)->Flush();
894 }
895
896 bool AtBoundary() const
897 {
898 return sourceIndex_ >= 0 && static_cast<size_t>(sourceIndex_) < sources_.size() && sources_[sourceIndex_]->GetType() != Circuits::OperationType::kGate;
899 }
900
901 void FinishInitialization(size_t n, bool invalidateSnapshots = true)
902 {
903 // An unseeded native initialization may have generated a fresh root.
904 // Keep the wrapper's readout stream attached to that same root.
905 const auto seed = immediate_->GetConfiguration("sampling_seed");
906 if (!seed.empty())
907 SeedAuxiliaryRng(std::stoull(seed));
908 ready_ = n != 0;
909 sourceIndex_ = 0;
910 if (invalidateSnapshots)
911 {
912 saved_.reset();
913 destructiveSaved_.reset();
914 }
915 // Native initialization may have cleared its operation list.
916 backendRoutingInstalled_ = false;
917 RebuildPlan();
918 }
919
920 void Discard()
921 {
922 cache_.Clear();
923 routingPlan_.reset();
924 planValid_ = false;
925 }
926
927 void InvalidatePlan()
928 {
929 planValid_ = false;
930 routingPlan_.reset();
931 if (IsGateFusionEnabled() && backendRoutingInstalled_)
932 {
933 immediate_->SetUpcomingGates({});
934 backendRoutingInstalled_ = false;
935 }
936 }
937
938 void RebuildPlan()
939 {
940 routingPlan_.reset();
941 planIndex_ = 0;
942 planValid_ = false;
943 if (!IsGateFusionEnabled())
944 {
945 if (!sources_.empty() || backendRoutingInstalled_)
946 immediate_->SetUpcomingGates(sources_);
947 backendRoutingInstalled_ = !sources_.empty();
948 immediate_->SetGatesCounter(sourceIndex_);
949 return;
950 }
951 if (!sources_.empty() && IsRoutingLookaheadEnabled())
952 PreparePlan();
953 else if (backendRoutingInstalled_)
954 {
955 immediate_->SetUpcomingGates({});
956 backendRoutingInstalled_ = false;
957 }
958 }
959
960 void PreparePlan()
961 {
962 planValid_ = false;
963 routingPlan_.reset();
964 auto prepared = std::make_shared<RoutingPlan>();
965 planIndex_ = 0;
966 const bool installRouting = IsRoutingLookaheadEnabled();
967 Cache planner(GetGateFusionMaxQubits(), false);
968 auto emit = [&prepared](const auto &block) {
969 prepared->planned.push_back({block.targets, block.sources, prepared->routing.size()});
970 const bool swap = block.IsSingle() && block.original.kind == Circuits::QuantumGateType::kSwapGateType;
971 prepared->routing.push_back(std::make_shared<FusionRoutingOperation>(block.targets, block.schmidtRank, swap));
972 };
973 for (size_t i = static_cast<size_t>(sourceIndex_); i < sources_.size(); ++i)
974 {
975 const auto gate = std::dynamic_pointer_cast<Circuits::IQuantumGate<>>(sources_[i]);
976 if (gate && sources_[i]->GetType() == Circuits::OperationType::kGate)
977 {
978 const auto primitives = FusionGate::FromCircuit(*gate).Expand(GetGateFusionMaxQubits());
979 for (size_t j = 0; j < primitives.size(); ++j)
980 SubmitToCache(planner, primitives[j], (uint64_t(i) << 4) | j, emit);
981 }
982 else
983 {
984 planner.Flush(emit);
985 prepared->boundaries[i] = prepared->routing.size();
986 prepared->routing.push_back(sources_[i]);
987 }
988 }
989 planner.Flush(emit);
990 routingPlan_ = prepared;
991 planValid_ = installRouting;
992 if (planValid_)
993 {
994 immediate_->SetUpcomingGates(prepared->routing);
995 backendRoutingInstalled_ = !prepared->routing.empty();
996 }
997 }
998
999 void Emit(const Cache::Block &block)
1000 {
1001 if (planValid_)
1002 {
1003 if (planIndex_ >= routingPlan_->planned.size() || routingPlan_->planned[planIndex_].sources != block.sources ||
1004 routingPlan_->planned[planIndex_].targets != block.targets)
1005 InvalidatePlan();
1006 else
1007 immediate_->SetGatesCounter(routingPlan_->planned[planIndex_].routingIndex);
1008 }
1009 if (block.IsSingle())
1010 block.original.Apply(*immediate_);
1011 else if (block.targets.size() == 1)
1012 immediate_->ApplyGenericOneQubitGate(block.targets[0], block.matrix);
1013 else if (block.targets.size() == 2)
1014 immediate_->ApplyGenericTwoQubitGate(block.targets[0], block.targets[1], block.matrix);
1015 else
1016 immediate_->ApplyGenericThreeQubitGate(block.targets[0], block.targets[1], block.targets[2], block.matrix);
1017 ++stats_.backendGates;
1018 if (!block.IsSingle())
1019 ++stats_.fusedBlocks;
1020 if (planValid_)
1021 ++planIndex_;
1022 }
1023
1024 GateFusionStatistics stats_;
1025
1026 const char *RequestedSetting() const
1027 {
1028 return !requested_ ? "auto" : *requested_ ? "true" : "false";
1029 }
1030
1031 std::optional<bool> requested_; // unset: the backend default
1032 bool ready_ = false;
1033 Cache cache_;
1034 long long sourceIndex_ = 0;
1035 std::vector<Operation> sources_;
1036 // Gate/index metadata is immutable after preparation and can be shared by
1037 // compatible clones. Execution cursors and the pending fusion cache stay local.
1038 std::shared_ptr<const RoutingPlan> routingPlan_;
1039 size_t planIndex_ = 0;
1040 bool planValid_ = false;
1041 bool backendRoutingInstalled_ = false;
1042 std::optional<Context> saved_, destructiveSaved_;
1043 mutable std::unordered_map<std::string, std::string> config_;
1044};
1045} // namespace Simulators::Private
The operation interface.
Definition Operations.h:402
void SetWidth(unsigned width)
Definition GateFusion.h:78
Interface class for a quantum computing simulator.
Definition Simulator.h:41
virtual unsigned GetGateFusionMaxQubits() const
Definition Simulator.h:46
Interface class for a quantum computing simulator state.
Definition State.h:131
void SeedAuxiliaryRng(uint64_t seed)
Shared by SetSeed and backend Configure("seed") implementations.
Definition State.h:1164
void NotifyObservers(const Types::qubits_vector &affectedQubits)
Notifies observers.
Definition State.h:1197
void Configure(const char *key, const char *value) override
Configures the state.
Definition FusionState.h:40
bool SupportsMPSSwapOptimization() const override
Returns if the simulator supports MPS swap optimization.
void InitializeToBasisState(size_t n, Types::qubit_t value) override
Initializes the state to a computational basis state.
SimulationType GetSimulationType() const override
Returns the type of simulation.
void Trim() override
Compress a matrix-product state or operator to its configured limits.
SimulatorType GetType() const override
Returns the type of simulator.
std::vector< bool > MeasureNoCollapseMany() override
Measures all the qubits without collapsing the state.
void InitializeState(size_t n, AER::Vector< std::complex< double > > &value) override
Initializes the state.
void SetLookaheadDepth(int value) override
Sets the lookahead depth for swap optimization.
void SetGatesCounter(long long counter) override
void SaveStateToInternalDestructive() override
Saves the state to internal storage.
std::complex< double > ExpectationValueComplex(const std::string &pauli, bool normalized=true) const override
virtual bool UsesDestructiveStateStorage() const
std::complex< double > DensityMatrixOverlap(const IState &other) const override
std::shared_ptr< ISimulator > immediate_
bool IsQcsim() const override
Returns if the simulator is a qcsim simulator.
void SetSeed(uint64_t seed) override
Seed every random stream owned by this simulator.
Definition FusionState.h:83
virtual bool InitializationPreservesSnapshots() const
std::complex< double > Amplitude(Types::qubit_t outcome) override
Returns the amplitude of the specified state.
const std::unordered_map< std::string, std::string > & GetConfigMap() const override
Definition FusionState.h:76
void Clear() override
Clears the state.
void MoveAtBeginningOfChain(const Types::qubits_vector &qubits) override
double DensityMatrixPurity() const override
virtual bool PreserveStructuredGates() const
void SetInitialQubitsMap(const std::vector< long long > &value) override
bool SwapGatesRelabelQubits() const override
std::shared_ptr< Circuits::IOperation< double > > Operation
double getGrowthFactorSwap() const override
double Probability(Types::qubit_t outcome) override
Returns the probability of the specified outcome.
void setGrowthFactorSwap(double value) override
Eigen::MatrixXcd PartialTrace(const Types::qubits_vector &qs) const override
void InitializeState(size_t n, std::vector< std::complex< double > > &value) override
Initializes the state.
double ExpectationValue(const std::string &pauli) override
Returns the expected value of a Pauli string.
void InitializeToMixtureOfBasisStates(size_t n, const std::vector< std::pair< Types::qubit_t, double > > &value) override
Initializes the state to a classical mixture of computational basis states.
void ConsumeBoundary(Circuits::OperationType type)
void SetMultithreading(bool value=true) override
Enable/disable multithreading.
size_t GetNumberOfQubits() const override
Returns the number of qubits.
void ApplyReset(const Types::qubits_vector &qs) override
Performs a reset of the specified qubits.
std::vector< bool > MeasureMany(const Types::qubits_vector &qs) override
Performs a measurement on the specified qubits.
std::complex< double > DensityMatrixTrace() const override
Mixed-state diagnostics.
bool SupportsQuantumChannels() const override
Returns whether this state retains channel ensembles directly.
void SetLookaheadDepthWithHeuristic(int value) override
Sets the lookahead depth for swap optimization.
std::complex< double > DensityMatrixTraceOfSquare() const override
GateFusion< FusionGate > Cache
const std::vector< std::shared_ptr< Circuits::IOperation< double > > > & GetUpcomingRoutingOperations() const override
void RestoreInternalDestructiveSavedState() override
Restores the state from the internally saved state.
std::string GetConfiguration(const char *key) const override
Returns configuration value.
Definition FusionState.h:69
void SaveState() override
Saves the state to internal storage.
virtual size_t DefaultGateFusionMinQubits() const
bool IsGateFusionEnabled() const override
Definition FusionState.h:30
double getGrowthFactorGate() const override
std::complex< double > DensityMatrixElementBits(const std::vector< bool > &row, const std::vector< bool > &col) const override
std::vector< double > ExpectationValues(const std::vector< std::string > &paulis) override
void Initialize() override
Initializes the state.
void SetUseOptimalMeetingPosition(bool value) override
Enables or disables optimal meeting position for MPS swaps.
std::complex< double > AmplitudeRaw(Types::qubit_t outcome) override
Gets the amplitude.
double DensityMatrixHermiticityResidual() const override
std::complex< double > ExpectationValueOperators(const Types::qubits_vector &qubits, const std::vector< Eigen::MatrixXcd > &matrices) override
void ApplyQuantumChannel(const Types::qubits_vector &qs, const QuantumChannel &channel) override
Applies a local CPTP quantum channel to one or two qubits.
void HermitizeDensityMatrix() override
double FidelityWithStatevector(const Eigen::VectorXcd &psi) const override
size_t AllocateQubits(size_t n) override
Allocates qubits.
std::vector< std::complex< double > > GetStateVector() override
void SubmitGate(const FusionGate &gate)
void ReCanonicalize() override
Restore the canonical form of a matrix-product state or operator.
void RestoreDensityMatrixTrace() override
int GetGpuDevice() const override
std::vector< std::complex< double > > ExpectationValuesComplex(const std::vector< std::string > &paulis, bool normalized=true) const override
void RestoreState() override
Restores the state from the internally saved state.
bool IsRoutingLookaheadEnabled() const override
std::unordered_map< std::vector< bool >, Types::qubit_t > SampleCountsMany(const Types::qubits_vector &qs, size_t shots=1000) override
Returns the counts of the outcomes of measurement of the specified qubits, for repeated measurements.
double ProbabilityBits(const std::vector< bool > &bits) override
MPO extensions: bit vectors are q0-first; matrices use logical basis order.
GateFusionStatistics GetGateFusionStatistics() const override
Definition FusionState.h:22
void Reset() override
Just resets the state to 0.
Types::qubit_t MeasureNoCollapse() override
Measures all the qubits without collapsing the state.
std::vector< double > Probabilities(const Types::qubits_vector &qs) override
Returns the probabilities of the specified outcomes.
void ApplyOperator(const Types::qubits_vector &qubits, const Eigen::MatrixXcd &matrix, bool normalize=false) override
size_t GetExecutedMaxBondDimension() const override
void InitializeToMixtureOfBasisStates(size_t n, const std::vector< std::pair< std::vector< bool >, double > > &value) override
Initializes the state to a classical mixture of computational basis states.
void SetAutomaticLookaheadDepth(int value) override
std::vector< double > AllProbabilities() override
Returns the probabilities of all possible outcomes.
void Flush() override
Flushes the applied operations.
Definition FusionState.h:95
std::complex< double > ProjectOnZero() override
Projects the state onto the zero state.
Eigen::MatrixXcd GetDensityMatrix(bool normalized=true) const override
bool IsDensityMatrixHermitian(double eps=1e-10) const override
std::unordered_map< Types::qubit_t, Types::qubit_t > SampleCounts(const Types::qubits_vector &qs, size_t shots=1000) override
Returns the counts of the outcomes of measurement of the specified qubits, for repeated measurements.
FusionState(std::shared_ptr< ISimulator > immediate)
Definition FusionState.h:15
void IncrementGatesCounter() override
Increments the gates counter.
void InitializeToBasisState(size_t n, const std::vector< bool > &value) override
Initializes the state to a computational basis state.
void InitializeState(size_t n, Eigen::VectorXcd &value) override
Initializes the state.
long long GetGatesCounter() const override
Returns the gates counter.
void SetUpcomingGates(const std::vector< std::shared_ptr< Circuits::IOperation< double > > > &gates) override
Supplies upcoming gates for lookahead swap optimization.
void CloneInto(FusionState &copy, const uint64_t *executionSeed=nullptr)
size_t GetCurrentMaxBondDimension() const override
Returns the maximum bond dimension reached.
void setGrowthFactorGate(double value) override
bool GetMultithreading() const override
Get the multithreading flag.
size_t Measure(const Types::qubits_vector &qs) override
Performs a measurement on the specified qubits.
A local completely-positive, trace-preserving map in Kraus form.
OperationType
The type of operations.
Definition Operations.h:29
@ kConditionalGate
conditional gate, similar with gate, but conditioned on something from 'OperationState'
Definition Operations.h:33
@ kMeasurement
measurement, result in 'OperationState'
Definition Operations.h:31
@ kGate
the usual quantum gate, result stays in simulator's state
Definition Operations.h:30
@ kReset
reset, no result in 'state', just apply measurement, then apply not on all qubits that were measured ...
Definition Operations.h:41
@ kQuantumChannel
a non-unitary CPTP operation on the simulator state
Definition Operations.h:49
SimulationType
The type of simulation.
Definition State.h:109
@ kMatrixProductState
matrix product state simulation type
Definition State.h:111
@ kMatrixProductOperator
Matrix product operator simulation type.
Definition State.h:118
SimulatorType
The type of simulator.
Definition State.h:79
@ kQCSim
qcsim simulator type
Definition State.h:83
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
Eigen::MatrixXcd generic
Definition FusionGate.h:17
void Apply(ISimulator &sim) const
Definition FusionGate.h:161
std::vector< FusionGate > Expand(unsigned width) const
Definition FusionGate.h:267
bool IsStructured() const
Definition FusionGate.h:132
Types::qubits_vector qubits
Definition FusionGate.h:15
static FusionGate FromCircuit(const Circuits::IQuantumGate<> &gate)
Definition FusionGate.h:112
std::array< double, 4 > params
Definition FusionGate.h:16