Maestro 0.3.11
Unified interface for quantum circuit simulation
Loading...
Searching...
No Matches
SimpleDisconnectedNetwork.h
Go to the documentation of this file.
1
12
13#pragma once
14
15#ifndef _SIMPLE_NETWORK_H_
16#define _SIMPLE_NETWORK_H_
17
19#include "NetworkJob.h"
20#include "QubitRegister.h"
21#include "SimpleController.h"
22#include "SimpleHost.h"
23#include <atomic>
24#include <charconv>
25
27
28#include "Configuration.h"
29
30namespace Network
31{
32
46template <typename Time = Types::time_type, class Controller = SimpleController<Time>> class SimpleDisconnectedNetwork : public INetwork<Time>
47{
48 public:
51
61 SimpleDisconnectedNetwork(const std::vector<Types::qubit_t> &qubits = {}, const std::vector<size_t> &cbits = {})
62 {
63 configuration.SetConfiguration("trajectory_policy", "shot_v1");
67 // this needs more work until it becomes useful - it's typically way too
68 // slow to be timed
69 // simulatorsForOptimizations.insert({
70 // SimulatorType::kQCSim,
71 // SimulationType::kTensorNetwork });
73
74#ifndef NO_QISKIT_AER
79#endif
80
81#ifdef __linux__
82 { // GPU candidates are resolved lazily when simulation is requested.
86 }
87#endif
88
89 if (!qubits.empty())
90 CreateNetwork(qubits, cbits);
91 }
92
102 void CreateNetwork(const std::vector<Types::qubit_t> &qubits, const std::vector<size_t> &cbits)
103 {
104 size_t qubitsOffset = 0;
105 size_t cbitsOffset = 0;
106
107 for (size_t i = 0; i < qubits.size(); ++i)
108 {
109 const size_t numQubits = qubits[i];
110 const size_t numBits = (i < cbits.size() ? cbits[i] : 0);
111 hosts.emplace_back(std::make_shared<SimpleHost<Time>>(i, qubitsOffset, numQubits, cbitsOffset, numBits));
112 qubitsOffset += numQubits;
113 cbitsOffset += numBits;
114 }
115
116 for (size_t i = 0; i < hosts.size(); ++i)
117 {
118 std::static_pointer_cast<SimpleHost<Time>>(hosts[i])->SetEntangledQubitId(qubitsOffset);
119 std::static_pointer_cast<SimpleHost<Time>>(hosts[i])->SetEntangledQubitMeasurementBit(cbitsOffset);
120 ++qubitsOffset;
121 ++cbitsOffset;
122 }
123
124 controller = std::make_shared<Controller>();
125 }
126
137 void Execute(const std::shared_ptr<Circuits::Circuit<Time>> &circuit) override
138 {
139 const auto recreate = recreateIfNeeded;
140
143 size_t numQubits = 2;
144 if (simulator)
145 {
146 simType = simulator->GetType();
147 method = simulator->GetSimulationType();
148 numQubits = simulator->GetNumberOfQubits();
149 }
150
151 // Execute continues from the caller's prepared quantum state. Repeated
152 // shots and expectation queries still start from zero, even for one shot.
153 const auto reset = resetBeforeExecution;
154 recreateIfNeeded = false;
155 resetBeforeExecution = false;
156
157 ExecuteResults res;
158 try
159 {
160 res = RepeatedExecute(circuit, 1);
161 }
162 catch (...)
163 {
164 recreateIfNeeded = recreate;
165 resetBeforeExecution = reset;
166 throw;
167 }
168
169 recreateIfNeeded = recreate;
170 resetBeforeExecution = reset;
171
172 // put the results in the state
173 if (!res.empty())
174 {
175 const auto &first = *res.begin();
176 GetState().SetResultsInOrder(first.first);
177 }
178
179 if (recreate && (!simulator || (simulator && (simType != simulator->GetType() || method != simulator->GetSimulationType() ||
180 simulator->GetNumberOfQubits() != numQubits))))
181 RecreateSimulatorKeepingResults(simType, method);
182 }
183
196 void ExecuteOnHost(const std::shared_ptr<Circuits::Circuit<Time>> &circuit, size_t hostId) override
197 {
198 const auto recreate = recreateIfNeeded;
199
202 size_t numQubits = 2;
203 if (simulator)
204 {
205 simType = simulator->GetType();
206 method = simulator->GetSimulationType();
207 numQubits = simulator->GetNumberOfQubits();
208 }
209
210 recreateIfNeeded = false;
211
212 const auto res = RepeatedExecuteOnHost(circuit, hostId, 1);
213
214 recreateIfNeeded = recreate;
215
216 // put the results in the state
217 if (!res.empty())
218 {
219 const auto &first = *res.begin();
220 GetState().SetResultsInOrder(first.first);
221 }
222
223 if (recreate && (!simulator || (simulator && (simType != simulator->GetType() || method != simulator->GetSimulationType() ||
224 simulator->GetNumberOfQubits() != numQubits))))
225 RecreateSimulatorKeepingResults(simType, method);
226 }
227
243 std::vector<double> ExecuteExpectations(const std::shared_ptr<Circuits::Circuit<Time>> &circuit, const std::vector<std::string> &paulis) override
244 {
245 const auto recreate = recreateIfNeeded;
246
249 size_t numQubits = 2;
250 if (simulator)
251 {
252 simType = simulator->GetType();
253 method = simulator->GetSimulationType();
254 numQubits = simulator->GetNumberOfQubits();
255 }
256
257 recreateIfNeeded = false;
258
259 const auto cleanCircuit = circuit ? circuit->RemoveEndMeasurements() : circuit;
260
261 pauliStrings = &paulis;
262 const auto res = RepeatedExecute(cleanCircuit, 1);
263 pauliStrings = nullptr;
264
265 recreateIfNeeded = recreate;
266
267 // put the results in the state
268 if (!res.empty())
269 {
270 const auto &first = *res.begin();
271 GetState().SetResultsInOrder(first.first);
272 }
273
274 std::vector<double> expectations(paulis.size());
275 if (simulator)
276 {
277 // translate the pauli strings to the mapped order of qubits
278 const size_t numOps = simulator->GetNumberOfQubits();
279
280 auto optimiser = controller->GetOptimiser();
281 if (optimiser)
282 {
283 // convert the classical state results back to the expected order
284 const auto &qubitsMap = optimiser->GetQubitsMap();
285 std::vector<std::string> translatedPaulis;
286 translatedPaulis.reserve(paulis.size());
287
288 for (size_t i = 0; i < paulis.size(); ++i)
289 {
290 std::string translated(numOps, 'I');
291
292 for (size_t j = 0; j < paulis[i].size(); ++j)
293 {
294 const auto pos = qubitsMap.find(j);
295 if (pos != qubitsMap.end())
296 translated[pos->second] = paulis[i][j];
297 else
298 translated[j] = paulis[i][j];
299 }
300
301 translatedPaulis.push_back(std::move(translated));
302 }
303 expectations = simulator->ExpectationValues(translatedPaulis);
304 }
305 else
306 {
307 expectations = simulator->ExpectationValues(paulis);
308 }
309 }
310
311 if (recreate &&
312 (!simulator || simType != simulator->GetType() || method != simulator->GetSimulationType() || simulator->GetNumberOfQubits() != numQubits))
313 RecreateSimulatorKeepingResults(simType, method);
314
315 return expectations;
316 }
317
333 std::vector<double> ExecuteOnHostExpectations(const std::shared_ptr<Circuits::Circuit<Time>> &circuit, size_t hostId,
334 const std::vector<std::string> &paulis) override
335 {
336 const auto recreate = recreateIfNeeded;
337
340 size_t numQubits = 2;
341 if (simulator)
342 {
343 simType = simulator->GetType();
344 method = simulator->GetSimulationType();
345 numQubits = simulator->GetNumberOfQubits();
346 }
347
348 // RAII: restore recreateIfNeeded and clear pauliStrings on any exit path.
349 struct ScopedRestore
350 {
351 bool &flag, saved;
352 const std::vector<std::string> **ps;
353
354 ScopedRestore(bool &f, const std::vector<std::string> **p) : flag(f), saved(f), ps(p)
355 {
356 flag = false;
357 }
358
359 ~ScopedRestore()
360 {
361 flag = saved;
362 *ps = nullptr;
363 }
364 } restoreGuard(recreateIfNeeded, &pauliStrings);
365
366 const auto cleanCircuit = circuit ? circuit->RemoveEndMeasurements() : circuit;
367
368 pauliStrings = &paulis;
369 const auto res = RepeatedExecuteOnHost(cleanCircuit, hostId, 1);
370
371 // put the results in the state
372 if (!res.empty())
373 {
374 const auto &first = *res.begin();
375 GetState().SetResultsInOrder(first.first);
376 }
377
378 // for (const auto& m : qubitsMapOnHost)
379 // std::cout << "Mapping qubit " << m.first << " to " << m.second <<
380 // std::endl;
381
382 const size_t offsetBase = qubitsMapOnHost.size();
383
384 std::vector<double> expectations(paulis.size(), 1.);
385 if (simulator)
386 {
387 // translate the pauli strings to the mapped order of qubits
388 const size_t numOps = simulator->GetNumberOfQubits();
389
390 // convert the pauli strings to the actual qubits order
391 std::vector<std::string> translatedPaulis;
392 translatedPaulis.reserve(paulis.size());
393 for (size_t i = 0; i < paulis.size(); ++i)
394 {
395 std::string translated(std::max(numOps, paulis[i].size()), 'I');
396
397 size_t offset = offsetBase;
398
399 for (size_t j = 0; j < paulis[i].size(); ++j)
400 {
401 auto pos = qubitsMapOnHost.find(j);
402 if (pos != qubitsMapOnHost.end())
403 translated[pos->second] = paulis[i][j];
404 else
405 {
406 translated[offset] = paulis[i][j];
407 ++offset;
408 }
409 }
410
411 // std::cout << "Translated pauli string: " << translated << std::endl;
412
413 translatedPaulis.push_back(std::move(translated));
414 }
415 expectations = simulator->ExpectationValues(translatedPaulis);
416 }
417 else
418 {
419 throw std::runtime_error("ExecuteOnHostExpectations: no simulator available after execution.");
420 }
421
422 if (recreate &&
423 (!simulator || simType != simulator->GetType() || method != simulator->GetSimulationType() || simulator->GetNumberOfQubits() != numQubits))
424 RecreateSimulatorKeepingResults(simType, method);
425
426 return expectations;
427 }
428
441 std::vector<std::complex<double>> ExecuteOnHostAmplitudes(const std::shared_ptr<Circuits::Circuit<Time>> &circuit, size_t hostId) override
442 {
443 const auto recreate = recreateIfNeeded;
444
447 size_t numQubits = 2;
448 if (simulator)
449 {
450 simType = simulator->GetType();
451 method = simulator->GetSimulationType();
452 numQubits = simulator->GetNumberOfQubits();
453 }
454
455 // RAII: restore recreateIfNeeded on any exit path (including exceptions).
456 struct ScopedRestoreFlag
457 {
458 bool &flag, saved;
459
460 ScopedRestoreFlag(bool &f) : flag(f), saved(f)
461 {
462 flag = false;
463 }
464
465 ~ScopedRestoreFlag()
466 {
467 flag = saved;
468 }
469 } restoreGuard(recreateIfNeeded);
470
471 const auto res = RepeatedExecuteOnHost(circuit, hostId, 1);
472
473 if (!res.empty())
474 {
475 const auto &first = *res.begin();
476 GetState().SetResultsInOrder(first.first);
477 }
478
479 if (!simulator)
480 throw std::runtime_error("ExecuteOnHostAmplitudes: no simulator available after execution.");
481
482 const size_t n = simulator->GetNumberOfQubits();
483 auto amplitudes = simulator->GetStateVector();
484 const size_t dim = amplitudes.size();
485
486 // Restore ascending original-qubit order within the compact register.
487 // Original indices may be sparse, so use their ranks as output positions.
488 if (!qubitsMapOnHost.empty())
489 {
490 // Build reverse mapping: simulator qubit position -> rank of its
491 // original qubit among the mapped ones.
492 std::vector<std::pair<Types::qubit_t, Types::qubit_t>> byOriginal(qubitsMapOnHost.begin(), qubitsMapOnHost.end());
493 std::sort(byOriginal.begin(), byOriginal.end());
494
495 std::vector<size_t> simToOrig(n);
496 for (size_t pos = 0; pos < byOriginal.size(); ++pos)
497 simToOrig[byOriginal[pos].second] = pos;
498
499 std::vector<std::complex<double>> remapped(dim);
500
501 for (size_t sim_state = 0; sim_state < dim; ++sim_state)
502 {
503 size_t orig_state = 0;
504 for (size_t qbit = 0; qbit < n; ++qbit)
505 {
506 if (sim_state & (1ULL << qbit))
507 orig_state |= (1ULL << simToOrig[qbit]);
508 }
509 if (orig_state < dim)
510 remapped[orig_state] = amplitudes[sim_state];
511 }
512 amplitudes.swap(remapped);
513 }
514
515 if (recreate &&
516 (!simulator || simType != simulator->GetType() || method != simulator->GetSimulationType() || simulator->GetNumberOfQubits() != numQubits))
517 RecreateSimulatorKeepingResults(simType, method);
518
519 return amplitudes;
520 }
521
535 std::complex<double> ExecuteOnHostProjectOnZero(const std::shared_ptr<Circuits::Circuit<Time>> &circuit, size_t hostId) override
536 {
537 const auto recreate = recreateIfNeeded;
538
541 size_t numQubits = 2;
542 if (simulator)
543 {
544 simType = simulator->GetType();
545 method = simulator->GetSimulationType();
546 numQubits = simulator->GetNumberOfQubits();
547 }
548
549 // RAII: restore recreateIfNeeded on any exit path (including exceptions).
550 struct ScopedRestoreFlag
551 {
552 bool &flag, saved;
553
554 ScopedRestoreFlag(bool &f) : flag(f), saved(f)
555 {
556 flag = false;
557 }
558
559 ~ScopedRestoreFlag()
560 {
561 flag = saved;
562 }
563 } restoreGuard(recreateIfNeeded);
564
565 const auto res = RepeatedExecuteOnHost(circuit, hostId, 1);
566
567 if (!res.empty())
568 {
569 const auto &first = *res.begin();
570 GetState().SetResultsInOrder(first.first);
571 }
572
573 if (!simulator)
574 throw std::runtime_error("ExecuteOnHostProjectOnZero: no simulator available after "
575 "execution.");
576
577 const std::complex<double> result = simulator->ProjectOnZero();
578
579 if (recreate &&
580 (!simulator || simType != simulator->GetType() || method != simulator->GetSimulationType() || simulator->GetNumberOfQubits() != numQubits))
581 RecreateSimulatorKeepingResults(simType, method);
582
583 return result;
584 }
585
599 ExecuteResults RepeatedExecute(const std::shared_ptr<Circuits::Circuit<Time>> &circuit, size_t shots = 1000) override
600 {
601 if (!controller || !circuit)
602 return {};
603
604 distCirc = controller->DistributeCircuit(BaseClass::getptr(), circuit);
605 if (!distCirc)
606 return {};
607
608#ifdef _DEBUG
609 for (auto q : distCirc->AffectedQubits())
610 {
611 if (q >= GetNumQubits())
612 {
613 std::cout << "This is a distributed circuit, using entanglement or cutting" << std::endl;
614 break;
615 }
616 }
617#endif
618
619 if (!simulator)
620 return {};
621
622 auto simType = simulator->GetType();
623 if (distCirc->HasOpsAfterMeasurements() && (
624#ifndef NO_QISKIT_AER
626#endif
628 distCirc->MoveMeasurementsAndResets();
629
630 auto method = simulator->GetSimulationType();
631
632 const auto saveSimType = simType;
633 const auto saveMethod = method;
634
636 // this is for the gpu simulator, as it doesn't support stabilizer
637#ifdef __linux__
638 && !Simulators::IsGpuSimulator(simType)
639#endif
640 )
641 {
643
646#ifndef NO_QISKIT_AER
649#endif
650 }
651
652 ExecuteResults res;
653 const size_t nrQubits = GetNumQubits() + GetNumNetworkEntangledQubits();
654 const size_t nrCbitsResults = GetNumClassicalBits();
655 const size_t nrCbits = nrCbitsResults + GetNumNetworkEntangledQubits();
656
658
659 // do that only if the optimization for simulator is on and the estimator is
660 // available, ortherwise an 'optimal' simulator won't be created
662 {
663 simulator->Clear();
664 GetState().Clear();
665 }
666
667 curMaxBondDim = 0;
668
669 std::vector<bool> executed;
670 auto optSim = ChooseBestSimulator(distCirc, shots, nrQubits, nrCbits, nrCbitsResults, simType, method, executed);
671
672 lastSimulatorType = simType;
673 lastMethod = method;
674 lastGpuDevice = -1;
675 lastFusionWidth = 0;
676 lastFusionEnabled = false;
677
678 const bool reproducibleSampling = UsesReproducibleSampling(simType, method);
679 size_t nrThreads = GetMaxSimulators();
680
681#ifdef __linux__
682 if (Simulators::IsGpuSimulator(simType))
683 nrThreads = 1;
684 else
685#endif
688 !distCirc->HasOpsAfterMeasurements()) ||
690 nrThreads = 1;
691
692 nrThreads = std::min(nrThreads, std::max<size_t>(shots, 1ULL));
693
694 std::mutex resultsMutex;
695
696 auto dcirc = distCirc;
697
698 if (reproducibleSampling && dcirc->HasOpsAfterMeasurements() &&
699 (shots > 1 || (shots == 1 && configuration.GetConfiguration("trajectory_policy") == "shot_v1")))
700 {
701 ExecuteLogicalShotBlocks(dcirc, res, shots, nrThreads, nrQubits, nrCbits, nrCbitsResults, simType, method, optSim, executed, resultsMutex);
702 }
703 else if (nrThreads > 1)
704 {
705 // Clones are owned by outer shot workers; avoid constructing inner pools.
706 if (optSim && method == Simulators::SimulationType::kPauliPropagator && optSim->GetType() == Simulators::SimulatorType::kQCSim)
707 optSim->SetMultithreading(false);
708 // since it's going to execute on multiple threads, free the memory from
709 // the network's simulator and state, it's going to use other ones,
710 // created in the threads if optimization already exists, it will be
711 // cloned in the threads, otherwise a new one will be created in the
712 // threads
713 if (!optimizeSimulator || !simulatorsEstimator || !simulatorsEstimator->IsInitialized()) // otherwise it was already cleared
714 {
715 simulator->Clear();
716 GetState().Clear();
717 }
718
719 const size_t cntPerThread = std::max<size_t>(shots / nrThreads, 1ULL);
720
721 threadsPool.Resize(nrThreads);
722 threadsPool.SetFinishLimit(shots);
723
724 uint64_t jobStream = 0;
725 while (shots > 0)
726 {
727 const size_t curCnt = std::min(cntPerThread, shots);
728
729 shots -= curCnt;
730
731 auto job = std::make_shared<ExecuteJob<Time>>(dcirc, res, curCnt, nrQubits, nrCbits, nrCbitsResults, simType, method, resultsMutex);
732 job->optimiseMultipleShotsExecution = curCnt > 1 || GetOptimizeSimulator();
733
734 job->network = BaseClass::getptr();
735 job->onSimulatorReady = [this](const Simulators::ISimulator &actual) {
736 lastFusionWidth.store(actual.GetGateFusionMaxQubits());
737 lastFusionEnabled.store(actual.IsGateFusionEnabled());
738 };
739 job->curMaxBondDim = &curMaxBondDim;
740
741 job->config = ExecutionConfiguration(simType, nrQubits);
742 job->randomStream = jobStream++;
743 if (configuration.IsSet("seed"))
744 {
745 const uint64_t childSeed = Simulators::IState::DeriveSeed(std::stoull(configuration.GetConfiguration("seed")), job->randomStream);
746 job->config.SetConfiguration("seed", std::to_string(childSeed));
747 }
748
749 if (optSim)
750 {
751 job->optSim = optSim->Clone();
752 job->executedGates = executed;
753 if (job->config.IsSet("seed"))
754 job->optSim->SetSeed(std::stoull(job->config.GetConfiguration("seed")));
755 }
756
757 threadsPool.AddRunJob(std::move(job));
758 }
759
760 // The workers stay for the next execution: creating them costs
761 // more than a small batch of shots.
762 threadsPool.WaitForFinish();
763 }
764 else
765 {
766 const size_t curCnt = shots;
767
768 auto job = std::make_shared<ExecuteJob<Time>>(dcirc, res, curCnt, nrQubits, nrCbits, nrCbitsResults, simType, method, resultsMutex);
769 job->optimiseMultipleShotsExecution = curCnt > 1 || GetOptimizeSimulator();
770
771 job->network = BaseClass::getptr();
772 job->onSimulatorReady = [this](const Simulators::ISimulator &actual) {
773 lastFusionWidth.store(actual.GetGateFusionMaxQubits());
774 lastFusionEnabled.store(actual.IsGateFusionEnabled());
775 };
776 job->curMaxBondDim = &curMaxBondDim;
777
778 job->config = ExecutionConfiguration(simType, nrQubits);
779
780 if (optSim)
781 {
782 optSim->SetMultithreading(true);
783 job->optSim = optSim;
784 job->executedGates = executed;
785 }
786 else
787 {
788 if (simulator && method == saveMethod && simType == saveSimType)
789 {
790 // use the already created simulator
791 optSim = simulator;
792 job->optSim = optSim;
793 if (optSim->GetNumberOfQubits() == nrQubits)
794 {
796 {
797 optSim->Reset();
798 optSim->SetGatesCounter(0);
799 }
800 job->config.ApplyConfigurationToSimulator(optSim);
801 // An initial mapping is only valid for a freshly reset register.
803 OptimizeMPSInitialQubitsMap(optSim, dcirc, nrQubits);
804 }
805 job->executedGates.resize(dcirc->size(),
806 false); // no gates executed yet
807 simulator = nullptr;
808 }
809 }
810
811 job->DoWorkNoLock();
812 lastGpuDevice = job->optSim ? job->optSim->GetGpuDevice() : -1;
813 if (!recreateIfNeeded)
814 simulator = job->optSim;
815 }
816
818 CreateSimulator(saveSimType, saveMethod);
819
821
822 return res;
823 }
824
840 ExecuteResults RepeatedExecuteOnHost(const std::shared_ptr<Circuits::Circuit<Time>> &circuit, size_t hostId, size_t shots = 1000) override
841 {
842 if (!circuit || hostId >= GetNumHosts())
843 return {};
844
845 size_t nrQubits = 0;
846 size_t nrCbits = 0;
847
848 const bool distributed = simulator && Simulators::IsDistributedGpuSimulator(simulator->GetType());
849 auto mappingCircuit = circuit;
850 if (!distributed && GetController()->GetOptimizeCircuit())
851 {
852 mappingCircuit = std::static_pointer_cast<Circuits::Circuit<Time>>(circuit->Clone());
853 mappingCircuit->Optimize();
854 }
855 const auto reverseQubitsMap = MapCircuitOnHost(mappingCircuit, hostId, nrQubits, nrCbits, true);
856 // Resolve indexing before optimization can remove a wire that disambiguates
857 // local from global numbering (for example a cancelling pair on qubit 0).
858 if (distributed && distCirc && GetController()->GetOptimizeCircuit())
859 distCirc->Optimize();
860 if (nrCbits == 0)
861 nrCbits = nrQubits;
862
863 if (!simulator || !distCirc)
864 return {};
865
866 auto simType = simulator->GetType();
867
869
870 if (distCirc->HasOpsAfterMeasurements() && (
871#ifndef NO_QISKIT_AER
873#endif
875 distCirc->MoveMeasurementsAndResets();
876
877 auto method = simulator->GetSimulationType();
878 const auto saveSimType = simType;
879 const auto saveMethod = method;
880
882 // this is for the gpu simulator, as it doesn't support stabilizer
883#ifdef __linux__
884 && !Simulators::IsGpuSimulator(simType)
885#endif
886 )
887 {
889
892#ifndef NO_QISKIT_AER
895#endif
896 }
897
898 ExecuteResults res;
899
900 // Backend selection can allocate a replacement. Fixed single-worker jobs
901 // retain the existing simulator; release it below if workers are needed.
902 if (GetOptimizeSimulator() && !distributed)
903 simulator->Clear();
904 GetState().Clear();
905
906 curMaxBondDim = 0;
907
908 std::vector<bool> executed;
909 auto optSim = ChooseBestSimulator(distCirc, shots, nrQubits, nrCbits, nrCbits, simType, method, executed);
910
911 lastSimulatorType = simType;
912 lastMethod = method;
913 lastGpuDevice = -1;
914 lastFusionWidth = 0;
915 lastFusionEnabled = false;
916
917 const bool reproducibleSampling = UsesReproducibleSampling(simType, method);
918 size_t nrThreads = GetMaxSimulators();
919
920#ifdef __linux__
921 if (Simulators::IsGpuSimulator(simType))
922 nrThreads = 1;
923 else
924#endif
927 !distCirc->HasOpsAfterMeasurements()) ||
929 nrThreads = 1;
930
931 nrThreads = std::min(nrThreads, std::max<size_t>(shots, 1ULL));
932
933 // WARNING: be sure to not put this above ChooseBestSimulator, as that one
934 // can change the shots value!
935
936 std::mutex resultsMutex;
937
938 auto dcirc = distCirc;
939
940 if (reproducibleSampling && dcirc->HasOpsAfterMeasurements() &&
941 (shots > 1 || (shots == 1 && configuration.GetConfiguration("trajectory_policy") == "shot_v1")))
942 {
943 ExecuteLogicalShotBlocks(dcirc, res, shots, nrThreads, nrQubits, nrCbits, nrCbits, simType, method, optSim, executed, resultsMutex);
944 }
945 else if (nrThreads > 1)
946 {
947 // Clones are owned by outer shot workers; avoid constructing inner pools.
948 if (optSim && method == Simulators::SimulationType::kPauliPropagator && optSim->GetType() == Simulators::SimulatorType::kQCSim)
949 optSim->SetMultithreading(false);
950 simulator->Clear();
951 // this rounds up, rounding down is better
952 // const size_t cntPerThread = static_cast<size_t>((shots - 1) / nrThreads
953 // + 1);
954 const size_t cntPerThread = std::max<size_t>(shots / nrThreads, 1ULL);
955
956 threadsPool.Resize(nrThreads);
957 threadsPool.SetFinishLimit(shots);
958
959 uint64_t jobStream = 0;
960 while (shots > 0)
961 {
962 const size_t curCnt = std::min(cntPerThread, shots);
963 shots -= curCnt;
964
965 auto job = std::make_shared<ExecuteJob<Time>>(dcirc, res, curCnt, nrQubits, nrCbits, nrCbits, simType, method, resultsMutex);
966 job->optimiseMultipleShotsExecution = curCnt > 1 || GetOptimizeSimulator();
967
968 job->network = BaseClass::getptr();
969 job->onSimulatorReady = [this](const Simulators::ISimulator &actual) {
970 lastFusionWidth.store(actual.GetGateFusionMaxQubits());
971 lastFusionEnabled.store(actual.IsGateFusionEnabled());
972 };
973 job->curMaxBondDim = &curMaxBondDim;
974
975 job->config = ExecutionConfiguration(simType, nrQubits);
976 job->randomStream = jobStream++;
977 if (configuration.IsSet("seed"))
978 {
979 const uint64_t childSeed = Simulators::IState::DeriveSeed(std::stoull(configuration.GetConfiguration("seed")), job->randomStream);
980 job->config.SetConfiguration("seed", std::to_string(childSeed));
981 }
982
983 if (optSim)
984 {
985 job->optSim = optSim->Clone();
986 job->executedGates = executed;
987 if (job->config.IsSet("seed"))
988 job->optSim->SetSeed(std::stoull(job->config.GetConfiguration("seed")));
989 }
990
991 threadsPool.AddRunJob(std::move(job));
992 }
993
994 // The workers stay for the next execution: creating them costs
995 // more than a small batch of shots.
996 threadsPool.WaitForFinish();
997 }
998 else
999 {
1000 const size_t curCnt = shots;
1001
1002 auto job = std::make_shared<ExecuteJob<Time>>(dcirc, res, curCnt, nrQubits, nrCbits, nrCbits, simType, method, resultsMutex);
1003 job->optimiseMultipleShotsExecution = curCnt > 1 || GetOptimizeSimulator();
1004
1005 job->network = BaseClass::getptr();
1006 job->onSimulatorReady = [this](const Simulators::ISimulator &actual) {
1007 lastFusionWidth.store(actual.GetGateFusionMaxQubits());
1008 lastFusionEnabled.store(actual.IsGateFusionEnabled());
1009 };
1010 job->curMaxBondDim = &curMaxBondDim;
1011
1012 job->config = ExecutionConfiguration(simType, nrQubits);
1013
1014 if (optSim)
1015 {
1016 optSim->SetMultithreading(true);
1017 job->optSim = optSim;
1018 job->executedGates = executed;
1019 }
1020 else if (simulator && method == saveMethod && simType == saveSimType)
1021 {
1022 optSim = simulator;
1023 job->optSim = optSim;
1024 if (optSim->GetNumberOfQubits() == nrQubits)
1025 {
1026 // Host execution always starts from zero, even when the previous
1027 // invocation retained its state for an expectation/state query.
1028 optSim->Reset();
1029 optSim->SetGatesCounter(0);
1030 job->config.ApplyConfigurationToSimulator(optSim);
1031 OptimizeMPSInitialQubitsMap(optSim, dcirc, nrQubits);
1032 }
1033 // DoWorkNoLock reallocates/reinitializes when the host width changed.
1034 job->executedGates.resize(dcirc->size(), false);
1035 }
1036
1037 job->DoWorkNoLock();
1038 lastGpuDevice = job->optSim ? job->optSim->GetGpuDevice() : -1;
1039 if (!recreateIfNeeded)
1040 simulator = job->optSim;
1041 }
1042
1043 if (recreateIfNeeded)
1044 CreateSimulator(saveSimType, saveMethod);
1045
1046 if (!reverseQubitsMap.empty())
1047 ConvertBackResults(res, reverseQubitsMap);
1048
1049 return res;
1050 }
1051
1060 size_t GetNumberOfGatesDistributedOrCut(const std::shared_ptr<Circuits::Circuit<Time>> &circuit) const override
1061 {
1062 if (!circuit)
1063 return 0;
1064
1065 size_t distgates = 0;
1066
1067 for (const auto &op : circuit->GetOperations())
1068 if (!IsLocalOperation(op))
1069 ++distgates;
1070
1071 return distgates;
1072 }
1073
1090 std::vector<ExecuteResults> ExecuteScheduled(const std::vector<Schedulers::ExecuteCircuit<Time>> &circuits) override
1091 {
1092 // create a default one if not set
1093 if (!GetScheduler())
1094 {
1096
1097 if (!GetScheduler())
1098 return {};
1099 }
1100
1101 return GetScheduler()->ExecuteScheduled(circuits);
1102 }
1103
1124 Simulators::SimulationType simExecType = Simulators::SimulationType::kMatrixProductState, size_t nrQubits = 0) override
1125 {
1126 classicalState.Clear();
1128
1130
1131 if (simulator)
1132 {
1133 const size_t allocationQubits = nrQubits == 0 ? GetNumQubits() + GetNumNetworkEntangledQubits() : nrQubits;
1134 ExecutionConfiguration(simType, allocationQubits).ApplyConfigurationToSimulator(simulator);
1135
1136 simulator->AllocateQubits(allocationQubits);
1137 simulator->Initialize();
1138
1139 // Pin the resolved default as well as explicit selections. Cloning or
1140 // recreating this network must not follow later process-default changes.
1142 configuration.SetConfiguration("gpu_device", std::to_string(simulator->GetGpuDevice()));
1143
1144 if (Simulators::IsDistributedGpuSimulator(simType) && nrQubits == 0)
1145 {
1146 resolvedDistributedDevices[simType] = simulator->GetConfiguration("distributed_shard_devices");
1147 }
1148 simulator->setGrowthFactorGate(growthFactorGate);
1149 simulator->setGrowthFactorSwap(growthFactorSwap);
1150 simulator->SetLookaheadDepth(lookaheadDepth);
1151 simulator->SetLookaheadDepthWithHeuristic(lookaheadDepthWithHeuristic);
1152 }
1153 }
1154
1164 void Configure(const char *key, const char *value) override
1165 {
1166 if (!key || !value)
1167 return;
1168
1169 if (std::string("trajectory_policy") == key)
1170 {
1171 const std::string policy(value);
1172 if (policy != "block_v1" && policy != "shot_v1")
1173 throw std::invalid_argument("trajectory_policy must be block_v1 or shot_v1");
1174 }
1175 if (std::string("trajectory_max_memory_mb") == key)
1176 {
1177 size_t mb = 0;
1178 const std::string_view text(value);
1179 const auto parsed = std::from_chars(text.data(), text.data() + text.size(), mb);
1180 if (parsed.ec != std::errc{} || parsed.ptr != text.data() + text.size() || !mb || mb > (std::numeric_limits<size_t>::max() >> 20))
1181 throw std::invalid_argument("trajectory_max_memory_mb must be a positive representable MiB count");
1182 }
1183
1184 if (std::string("gate_fusion") == key)
1185 {
1186 const std::string setting(value);
1187 if (setting != "true" && setting != "false" && setting != "0" && setting != "1" && setting != "auto")
1188 throw std::invalid_argument("gate_fusion must be true, false, 1, 0 or auto");
1189 configuration.SetConfiguration(key, value);
1192 simulator->Configure(key, value);
1193 return;
1194 }
1195 if (std::string("distributed_host_qubit_indexing") == key)
1196 {
1197 const std::string mode(value);
1198 if (mode != "auto" && mode != "local" && mode != "global")
1199 throw std::invalid_argument("distributed_host_qubit_indexing must be auto, local or global");
1201 return;
1202 }
1203
1204 if (std::string("distributed_devices") == key || std::string("gpu_device") == key)
1206
1207 if (std::string("max_simulators") == key)
1208 maxSimulators = std::stoull(value);
1209
1210 if (std::string("gpu_device") == key)
1211 {
1213 if (simulator)
1214 simulator->Configure(key, value);
1215 configuration.SetConfiguration(key, value);
1216 return;
1217 }
1219 {
1220 if (simulator)
1221 simulator->Configure(key, value);
1222 configuration.SetConfiguration(key, value);
1223 return;
1224 }
1225 configuration.SetConfiguration(key, value);
1226
1227 if (simulator)
1228 simulator->Configure(key, value);
1229 }
1230
1239 std::shared_ptr<Simulators::ISimulator> GetSimulator() const override
1240 {
1241 return simulator;
1242 }
1243
1253 {
1254 return classicalState;
1255 }
1256
1269 {
1270 if (!controller)
1271 return;
1272
1273 controller->CreateScheduler(BaseClass::getptr(), schType);
1274 }
1275
1284 std::shared_ptr<Schedulers::IScheduler<Time>> GetScheduler() const override
1285 {
1286 if (!controller)
1287 return nullptr;
1288
1289 return controller->GetScheduler();
1290 }
1291
1301 const std::shared_ptr<IHost<Time>> GetHost(size_t hostId) const override
1302 {
1303 if (hostId >= hosts.size())
1304 return nullptr;
1305
1306 return hosts[hostId];
1307 }
1308
1317 const std::shared_ptr<IController<Time>> GetController() const override
1318 {
1319 return controller;
1320 }
1321
1329 size_t GetNumHosts() const override
1330 {
1331 return hosts.size();
1332 }
1333
1342 size_t GetNumQubits() const override
1343 {
1344 size_t res = 0;
1345
1346 for (const auto &host : hosts)
1347 res += host->GetNumQubits();
1348
1349 return res;
1350 }
1351
1361 size_t GetNumQubitsForHost(size_t hostId) const override
1362 {
1363 if (hostId >= hosts.size())
1364 return 0;
1365
1366 return hosts[hostId]->GetNumQubits();
1367 }
1368
1377 size_t GetNumNetworkEntangledQubits() const override
1378 {
1379 size_t res = 0;
1380
1381 for (const auto &host : hosts)
1382 res += host->GetNumNetworkEntangledQubits();
1383
1384 return res;
1385 }
1386
1399 size_t GetNumNetworkEntangledQubitsForHost(size_t hostId) const override
1400 {
1401 if (hostId >= hosts.size())
1402 return 0;
1403
1404 return hosts[hostId]->GetNumNetworkEntangledQubits();
1405 }
1406
1415 size_t GetNumClassicalBits() const override
1416 {
1417 size_t res = 0;
1418
1419 for (const auto &host : hosts)
1420 res += host->GetNumClassicalBits();
1421
1422 return res;
1423 }
1424
1436 size_t GetNumClassicalBitsForHost(size_t hostId) const override
1437 {
1438 if (hostId >= hosts.size())
1439 return 0;
1440
1441 return hosts[hostId]->GetNumClassicalBits();
1442 }
1443
1453 std::vector<std::shared_ptr<IHost<Time>>> &GetHosts()
1454 {
1455 return hosts;
1456 }
1457
1465 void SetController(const std::shared_ptr<IController<Time>> &cntrl)
1466 {
1467 controller = cntrl;
1468 }
1469
1483 bool SendPacket(size_t fromHostId, size_t toHostId, const std::vector<uint8_t> &packet) override
1484 {
1485 return false;
1486 }
1487
1495 NetworkType GetType() const override
1496 {
1498 }
1499
1510 bool IsLocalOperation(const std::shared_ptr<Circuits::IOperation<Time>> &op) const override
1511 {
1512 const auto qubits = op->AffectedQubits();
1513
1514 if (qubits.empty())
1515 return true;
1516
1517 size_t firstQubit = qubits[0];
1518
1519 for (size_t q = 1; q < qubits.size(); ++q)
1520 if (!AreQubitsOnSameHost(firstQubit, qubits[q]))
1521 return false;
1522
1523 return true;
1524 }
1525
1538 bool IsDistributedOperation(const std::shared_ptr<Circuits::IOperation<Time>> &op) const override
1539 {
1540 const auto qubits = op->AffectedQubits();
1541
1542 if (qubits.empty())
1543 return false;
1544
1545 // grab the first qubit that is on a host (skip over network entangled
1546 // qubits)
1547 size_t firstQubit = qubits[0];
1548 size_t q = 1;
1549 for (; IsNetworkEntangledQubit(firstQubit) && q < qubits.size(); ++q)
1550 firstQubit = qubits[q];
1551
1552 // check to see one of the other qubits is on a different host, but ignore
1553 // the network entangled qubits
1554 for (; q < qubits.size(); ++q)
1555 if (!IsNetworkEntangledQubit(qubits[q]) && !AreQubitsOnSameHost(firstQubit, qubits[q]))
1556 return true;
1557
1558 return false;
1559 }
1560
1572 bool OperatesWithNetworkEntangledQubit(const std::shared_ptr<Circuits::IOperation<Time>> &op) const override
1573 {
1574 const auto qubits = op->AffectedQubits();
1575
1576 if (qubits.empty())
1577 return false;
1578
1579 for (size_t q = 0; q < qubits.size(); ++q)
1580 if (IsNetworkEntangledQubit(qubits[q]))
1581 return true;
1582
1583 return false;
1584 }
1585
1595 bool IsEntanglingGate(const std::shared_ptr<Circuits::IOperation<Time>> &op) const override
1596 {
1597 if (op->GetType() != Circuits::OperationType::kGate)
1598 return false;
1599 const auto qubits = op->AffectedQubits();
1600 if (qubits.size() != 2)
1601 return false;
1602
1603 return IsNetworkEntangledQubit(qubits[0]) && IsNetworkEntangledQubit(qubits[1]);
1604 }
1605
1616 bool ExpectsClassicalBitFromOtherHost(const std::shared_ptr<Circuits::IOperation<Time>> &op) const override
1617 {
1618 if (!op->IsConditional())
1619 return false;
1620
1621 const auto qubits = op->AffectedQubits();
1622
1623 const std::shared_ptr<Circuits::IConditionalOperation<Time>> condOp = std::static_pointer_cast<Circuits::IConditionalOperation<Time>>(op);
1624 const auto &classicalBits = condOp->GetCondition()->GetBitsIndices();
1625
1626 if (qubits.empty() && classicalBits.empty())
1627 throw std::runtime_error("No classical bits specified!"); // this would be odd!
1628
1629 // consider it on the host where it has the first qubit (or bit, if there
1630 // are no qubits)
1631
1632 const size_t hostId = GetHostIdForAnyQubit(qubits[0]);
1633
1634 // now check the classical bits
1635 for (const auto bit : classicalBits)
1636 if (hostId != GetHostIdForClassicalBit(bit))
1637 return true;
1638
1639 return false;
1640 }
1641
1652 size_t GetHostIdForClassicalControl(const std::shared_ptr<Circuits::IOperation<Time>> &op) const override
1653 {
1654 if (!op->IsConditional())
1655 throw std::runtime_error("Operation is not conditional!");
1656
1657 std::shared_ptr<Circuits::IConditionalOperation<Time>> condOp = std::static_pointer_cast<Circuits::IConditionalOperation<Time>>(op);
1658 const auto classicalBits = condOp->AffectedBits();
1659
1660 if (classicalBits.empty())
1661 throw std::runtime_error("No classical bits specified!");
1662
1663 return GetHostIdForClassicalBit(classicalBits[0]);
1664 }
1665
1674 bool AreQubitsOnSameHost(size_t qubitId1, size_t qubitId2) const override
1675 {
1676 for (const auto &host : hosts)
1677 {
1678 const bool present1 = host->IsQubitOnHost(qubitId1);
1679 const bool present2 = host->IsQubitOnHost(qubitId2);
1680
1681 if (present1 && present2)
1682 return true;
1683 else if (present1 || present2)
1684 return false;
1685 }
1686
1687 return false;
1688 }
1689
1699 bool AreClassicalBitsOnSameHost(size_t bitId1, size_t bitId2) const override
1700 {
1701 for (const auto &host : hosts)
1702 {
1703 const bool present1 = host->IsClassicalBitOnHost(bitId1);
1704 const bool present2 = host->IsClassicalBitOnHost(bitId2);
1705
1706 if (present1 && present2)
1707 return true;
1708 else if (present1 || present2)
1709 return false;
1710 }
1711
1712 return false;
1713 }
1714
1725 bool AreQubitAndClassicalBitOnSameHost(size_t qubitId, size_t bitId) const override
1726 {
1727 for (const auto &host : hosts)
1728 {
1729 const bool present1 = host->IsQubitOnHost(qubitId);
1730 const bool present2 = host->IsClassicalBitOnHost(bitId);
1731
1732 if (present1 && present2)
1733 return true;
1734 else if (present1 || present2)
1735 return false;
1736 }
1737
1738 return false;
1739 }
1740
1750 size_t GetHostIdForQubit(size_t qubitId) const override
1751 {
1752 for (const auto &host : hosts)
1753 if (host->IsQubitOnHost(qubitId))
1754 return host->GetId();
1755
1756 return std::numeric_limits<size_t>::max();
1757 }
1758
1771 size_t GetHostIdForEntangledQubit(size_t qubitId) const override
1772 {
1773 for (const auto &host : hosts)
1774 if (host->IsEntangledQubitOnHost(qubitId))
1775 return host->GetId();
1776
1777 return std::numeric_limits<size_t>::max();
1778 }
1779
1789 size_t GetHostIdForAnyQubit(size_t qubitId) const override
1790 {
1791 if (IsNetworkEntangledQubit(qubitId))
1792 return GetHostIdForEntangledQubit(qubitId);
1793
1794 return GetHostIdForQubit(qubitId);
1795 }
1796
1806 size_t GetHostIdForClassicalBit(size_t classicalBitId) const override
1807 {
1808 for (const auto &host : hosts)
1809 if (host->IsClassicalBitOnHost(classicalBitId))
1810 return host->GetId();
1811
1812 return std::numeric_limits<size_t>::max();
1813 }
1814
1823 std::vector<size_t> GetQubitsIds(size_t hostId) const override
1824 {
1825 if (hostId >= hosts.size())
1826 return std::vector<size_t>();
1827
1828 return hosts[hostId]->GetQubitsIds();
1829 }
1830
1841 std::vector<size_t> GetNetworkEntangledQubitsIds(size_t hostId) const override
1842 {
1843 if (hostId >= hosts.size())
1844 return std::vector<size_t>();
1845
1846 return hosts[hostId]->GetNetworkEntangledQubitsIds();
1847 }
1848
1858 std::vector<size_t> GetClassicalBitsIds(size_t hostId) const override
1859 {
1860 if (hostId >= hosts.size())
1861 return std::vector<size_t>();
1862
1863 return hosts[hostId]->GetClassicalBitsIds();
1864 }
1865
1876 std::vector<size_t> GetEntangledQubitMeasurementBitIds(size_t hostId) const override
1877 {
1878 if (hostId >= hosts.size())
1879 return std::vector<size_t>();
1880
1881 return hosts[hostId]->GetEntangledQubitMeasurementBitIds();
1882 }
1883
1895 bool IsNetworkEntangledQubit(size_t qubitId) const override
1896 {
1897 return qubitId >= GetNumQubits();
1898 }
1899
1912 bool IsEntanglementQubitBusy(size_t qubitId) const override
1913 {
1914 return false;
1915 }
1916
1932 bool AreEntanglementQubitsBusy(size_t qubitId1, size_t qubitId2) const override
1933 {
1934 return false;
1935 }
1936
1949 void MarkEntangledQubitsBusy(size_t qubitId1, size_t qubitId2) override
1950 {
1951 throw std::runtime_error("Entanglement between hosts is not supported in the simple network");
1952 }
1953
1965 void MarkEntangledQubitFree(size_t qubitId) override
1966 {
1967 throw std::runtime_error("Entanglement between hosts is not supported in the simple network");
1968 }
1969
1979 void ClearEntanglements() override
1980 {
1981 throw std::runtime_error("Entanglement between hosts is not supported in the simple network");
1982 }
1983
1993 std::shared_ptr<Circuits::Circuit<Time>> GetDistributedCircuit() const override
1994 {
1995 return distCirc;
1996 }
1997
2005 int GetLastGpuDevice() const override
2006 {
2007 return lastGpuDevice;
2008 }
2009
2010 unsigned GetLastGateFusionMaxQubits() const override
2011 {
2012 return lastFusionWidth.load();
2013 }
2014
2015 bool WasGateFusionEnabled() const override
2016 {
2017 return lastFusionEnabled.load();
2018 }
2019
2020 bool GetGateFusion() const override
2021 {
2022 if (simulator)
2023 {
2024 const auto &options = simulator->GetConfigMap();
2025 const auto found = options.find("gate_fusion");
2026 if (found != options.end())
2027 return found->second != "false" && found->second != "0";
2028 }
2029 if (!configuration.IsSet("gate_fusion"))
2030 return true;
2031 const auto value = configuration.GetConfiguration("gate_fusion");
2032 return value != "false" && value != "0";
2033 }
2034
2036 {
2037 return lastSimulatorType;
2038 }
2039
2048 {
2049 return lastMethod;
2050 }
2051
2062 size_t GetMaxSimulators() const override
2063 {
2064 return maxSimulators;
2065 }
2066
2078 void SetMaxSimulators(size_t val) override
2079 {
2080 if (val < 1)
2081 val = 1;
2082 else if (val > (size_t)QC::QubitRegisterCalculator<>::GetNumberOfThreads())
2083 val = (size_t)QC::QubitRegisterCalculator<>::GetNumberOfThreads();
2084
2085 maxSimulators = val;
2086 }
2087
2099 {
2100 trajectoryMinWorkerMicros = std::max(0., micros);
2101 }
2102
2104 {
2106 }
2107
2119 void SetOptimizeSimulator(bool optimize = true) override
2120 {
2121 optimizeSimulator = optimize;
2122 }
2123
2131 bool GetOptimizeSimulator() const override
2132 {
2133 return optimizeSimulator;
2134 }
2135
2144 const typename BaseClass::SimulatorsSet &GetSimulatorsSet() const override
2145 {
2147 }
2148
2159 {
2160 simulatorsForOptimizations.insert({type, kind});
2161 }
2162
2175
2191
2203 {
2204 if (simulatorsForOptimizations.empty())
2205 return true;
2206
2207 return simulatorsForOptimizations.find({type, kind}) != simulatorsForOptimizations.end();
2208 }
2209
2216 std::shared_ptr<INetwork<Time>> Clone() const override
2217 {
2218 const size_t numHosts = GetNumHosts();
2219
2220 std::vector<Types::qubit_t> qubits(numHosts);
2221 std::vector<size_t> cbits(numHosts);
2222
2223 for (size_t h = 0; h < numHosts; ++h)
2224 {
2225 qubits[h] = GetNumQubitsForHost(h);
2226 cbits[h] = GetNumClassicalBitsForHost(h);
2227 }
2228
2229 const auto cloned = std::make_shared<SimpleDisconnectedNetwork<Time, Controller>>(qubits, cbits);
2230
2231 cloned->configuration = configuration;
2232 cloned->distributedHostQubitIndexing = distributedHostQubitIndexing;
2233 cloned->resolvedDistributedDevices = resolvedDistributedDevices;
2234
2235 cloned->maxSimulators = maxSimulators;
2236 cloned->trajectoryMinWorkerMicros = trajectoryMinWorkerMicros;
2237
2238 cloned->optimizeSimulator = optimizeSimulator;
2239 cloned->simulatorsForOptimizations = simulatorsForOptimizations;
2240 cloned->GetController()->SetOptimizeCircuit(GetController()->GetOptimizeCircuit());
2241 cloned->GetController()->SetOptimizeRotationGates(GetController()->GetOptimizeRotationGates());
2242
2243 cloned->SetInitialQubitsMapOptimization(GetInitialQubitsMapOptimization());
2244 cloned->SetMPSOptimizeSwaps(GetMPSOptimizeSwaps());
2245
2246 cloned->SetMPSOptimizationBondDimensionThreshold(GetMPSOptimizationBondDimensionThreshold());
2247 cloned->SetMPSOptimizationQubitsNumberThreshold(GetMPSOptimizationQubitsNumberThreshold());
2248
2249 cloned->SetLookaheadDepth(GetLookaheadDepth());
2250 cloned->SetLookaheadDepthWithHeuristic(GetLookaheadDepthWithHeuristic());
2251
2252 cloned->setGrowthFactorGate(getGrowthFactorGate());
2253 cloned->setGrowthFactorSwap(getGrowthFactorSwap());
2254
2255 if (GetSimulator())
2256 cloned->CreateSimulator(GetSimulator()->GetType(), GetSimulator()->GetSimulationType());
2257
2258 return cloned;
2259 }
2260
2261 std::shared_ptr<Simulators::ISimulator> ChooseBestSimulator(std::shared_ptr<Circuits::Circuit<Time>> &dcirc, size_t &counts, size_t nrQubits,
2262 size_t nrCbits, size_t nrResultCbits, Simulators::SimulatorType &simType,
2263 Simulators::SimulationType &method, std::vector<bool> &executed, bool multithreading = false,
2264 bool dontRunCircuitStart = false) override
2265 {
2266 // Distribution is an explicit execution choice. Timing-based backend
2267 // selection must not replace it or diverge between MPI ranks.
2269 return nullptr;
2270 if (!optimizeSimulator)
2271 return nullptr;
2272
2273 if ((!simulatorsEstimator || !simulatorsEstimator->IsInitialized()) && simulatorsForOptimizations.size() != 1)
2274 return nullptr;
2275
2276 // when multithreading is set to true it means it needs a multithreaded
2277 // simulator
2278
2279 std::vector<std::pair<Simulators::SimulatorType, Simulators::SimulationType>> simulatorTypes;
2280
2281 const bool checkTensorNetwork = method == Simulators::SimulationType::kTensorNetwork;
2282
2283 // the others are to be picked between statevector, composite, tensor
2284 // networks and mps, for now at least for tensor networks in the future it's
2285 // worth checking different contractors!!!!
2286 //
2287 // clifford was decided at higher level
2289 {
2290 // compare qcsim with qiskit aer if qiskit aer is available, let the best
2291 // one win
2294
2295#ifndef NO_QISKIT_AER
2296 // if the number of shots is too small, probably it's not worth it, it's
2297 // going to be better to just execute them multithreading
2300#endif
2301 }
2302
2305
2308
2311
2312 const long long int maxBondDim = configuration.GetConfigurationAsInt("matrix_product_state_max_bond_dimension");
2313
2315 (nrQubits <= 4 || maxBondDim > 0))
2317
2320
2323
2324#ifndef NO_QISKIT_AER
2325 // tensor networks are out of the picture for now for qiskit aer, since they
2326 // are available with cuda library, and work only on linux (obviously when
2327 // compiled properly and if there is the right hw an driver installed)
2328
2331
2334
2336 (nrQubits <= 4 || maxBondDim > 0))
2338#endif
2339
2340#ifdef __linux__
2341 const int gpuDevice = configuration.IsSet("gpu_device") ? Simulators::Configuration::ParseGpuDevice(configuration.GetConfiguration("gpu_device"))
2342 : Simulators::SimulatorsFactory::ResolveGpuDevice();
2343 Simulators::SimulatorsFactory::ScopedGpuDevice gpuDeviceScope(gpuDevice);
2344 const bool hasGpuCandidate = std::any_of(simulatorsForOptimizations.begin(), simulatorsForOptimizations.end(),
2345 [](const auto &candidate) { return candidate.first == Simulators::SimulatorType::kGpuSim; });
2346 if (configuration.IsSet("gpu_device") && hasGpuCandidate && !Simulators::SimulatorsFactory::IsGpuLibraryAvailable(gpuDevice))
2347 throw std::runtime_error("Unable to initialize requested GPU device " + std::to_string(gpuDevice));
2348 if (hasGpuCandidate && Simulators::SimulatorsFactory::IsGpuLibraryAvailable(gpuDevice))
2349 {
2358 }
2359#endif
2360
2363
2364 // Honor a singleton optimization set (e.g. density_matrix) even if it is
2365 // not in the hardcoded candidate list. Skip backends that cannot actually
2366 // be constructed, such as GPU MPS when the GPU library is missing: the
2367 // caller then keeps the network simulator instead of recording 0 shots.
2368 if (simulatorTypes.empty() && simulatorsForOptimizations.size() == 1)
2369 {
2370 const auto candidate = *simulatorsForOptimizations.begin();
2372 return nullptr;
2373 if (Simulators::SimulatorsFactory::CreateSimulator(candidate.first, candidate.second))
2374 simulatorTypes.push_back(candidate);
2375 }
2376
2377 if (simulatorTypes.empty())
2378 return nullptr;
2379 else if (simulatorTypes.size() == 1)
2380 {
2381 const auto candidateType = simulatorTypes[0].first;
2382 const auto candidateMethod = simulatorTypes[0].second;
2383
2384 std::shared_ptr<Simulators::ISimulator> sim = Simulators::SimulatorsFactory::CreateSimulator(candidateType, candidateMethod);
2385 if (sim)
2386 {
2387 simType = candidateType;
2388 method = candidateMethod;
2389 configuration.ApplyConfigurationToSimulator(sim);
2390
2392 {
2393 sim->AllocateQubits(nrQubits);
2394 sim->Initialize();
2395
2396 sim->setGrowthFactorGate(growthFactorGate);
2397 sim->setGrowthFactorSwap(growthFactorSwap);
2398 sim->SetLookaheadDepth(lookaheadDepth);
2399 sim->SetLookaheadDepthWithHeuristic(lookaheadDepthWithHeuristic);
2400
2401 OptimizeMPSInitialQubitsMap(sim, dcirc, nrQubits);
2402 }
2403 else
2404 {
2405 sim->AllocateQubits(nrQubits);
2406 sim->Initialize();
2407 }
2408
2409 if (!dontRunCircuitStart)
2410 {
2411 if (sim->GetType() == Simulators::SimulatorType::kQCSim)
2412 sim->Configure("reproducible_trajectory", dcirc->HasOpsAfterMeasurements() ? "true" : "false");
2413 sim->SetMultithreading(true);
2414 Estimators::SimulatorsEstimatorInterface<Time>::ExecuteUpToMeasurements(dcirc, nrQubits, nrCbits, nrResultCbits, sim, executed,
2415 &curMaxBondDim);
2416 }
2417 sim->SetMultithreading(multithreading || GetMaxSimulators() == 1);
2418
2419 return sim;
2420 }
2421
2422 // Singleton backend cannot be constructed (typical for GPU on CI).
2423 // Leave simType/method unchanged so RepeatedExecuteOnHost keeps the
2424 // network simulator instead of recording empty counts.
2425 return nullptr;
2426 }
2427
2428 const double singularValueThreshold = configuration.GetConfigurationAsDouble("matrix_product_state_truncation_threshold");
2429
2430 const std::string truncationMode = configuration.GetConfiguration("matrix_product_state_truncation_mode");
2431
2432 const std::string mpsSample = configuration.GetConfiguration("mps_sample_measure_algorithm");
2433
2434 std::shared_ptr<Simulators::ISimulator> sim =
2435 simulatorsEstimator->ChooseBestSimulator(simulatorTypes, dcirc, counts, nrQubits, nrCbits, nrResultCbits, simType, method, executed, maxBondDim,
2436 singularValueThreshold, truncationMode, mpsSample, GetMaxSimulators(), pauliStrings, multithreading);
2437
2438 if (sim)
2439 {
2440 sim->AllocateQubits(nrQubits);
2441 sim->Initialize();
2442
2443 sim->setGrowthFactorGate(growthFactorGate);
2444 sim->setGrowthFactorSwap(growthFactorSwap);
2445 sim->SetLookaheadDepth(lookaheadDepth);
2446 sim->SetLookaheadDepthWithHeuristic(lookaheadDepthWithHeuristic);
2447
2448 OptimizeMPSInitialQubitsMap(sim, dcirc, nrQubits);
2449
2450 if (!dontRunCircuitStart)
2451 {
2452 if (sim->GetType() == Simulators::SimulatorType::kQCSim)
2453 sim->Configure("reproducible_trajectory", dcirc->HasOpsAfterMeasurements() ? "true" : "false");
2454 sim->SetMultithreading(true);
2455 Estimators::SimulatorsEstimatorInterface<Time>::ExecuteUpToMeasurements(dcirc, nrQubits, nrCbits, nrResultCbits, sim, executed, &curMaxBondDim);
2456 }
2457 sim->SetMultithreading(multithreading || GetMaxSimulators() == 1);
2458 }
2459
2460 return sim;
2461 }
2462
2463 void SetInitialQubitsMapOptimization(bool optimize = true) override
2464 {
2465 optimizeInitialQubitsMap = optimize;
2466 }
2467
2469 {
2470 return optimizeInitialQubitsMap;
2471 }
2472
2473 void SetMPSOptimizeSwaps(bool optimize = true) override
2474 {
2475 mpsOptimizeSwaps = optimize;
2476
2477 if (simulator)
2478 {
2479 simulator->SetLookaheadDepth(0);
2480 simulator->SetLookaheadDepthWithHeuristic(0);
2481 }
2482 }
2483
2484 bool GetMPSOptimizeSwaps() const override
2485 {
2486 return mpsOptimizeSwaps;
2487 }
2488
2489 void SetMPSOptimizationBondDimensionThreshold(size_t threshold) override
2490 {
2491 mpsOptimizationBondDimensionThreshold = threshold;
2492
2493 if (simulator && std::stoull(simulator->GetConfiguration("matrix_product_state_max_bond_dimension")) < threshold)
2494 {
2495 simulator->SetLookaheadDepth(0);
2496 simulator->SetLookaheadDepthWithHeuristic(0);
2497 }
2498 }
2499
2501 {
2502 return mpsOptimizationBondDimensionThreshold;
2503 }
2504
2505 void SetMPSOptimizationQubitsNumberThreshold(size_t threshold) override
2506 {
2507 mpsOptimizationQubitsNumberThreshold = threshold;
2508
2509 if (GetNumQubits() < threshold && simulator)
2510 {
2511 simulator->SetLookaheadDepth(0);
2512 simulator->SetLookaheadDepthWithHeuristic(0);
2513 }
2514 }
2515
2517 {
2518 return mpsOptimizationQubitsNumberThreshold;
2519 }
2520
2521 void SetLookaheadDepth(int depth) override
2522 {
2523 if (depth < 0)
2524 depth = std::numeric_limits<int>::max();
2525
2526 lookaheadDepth = depth;
2527
2528 if (simulator && lookaheadDepth != std::numeric_limits<int>::max())
2529 {
2530 simulator->SetLookaheadDepth(depth);
2531 if (lookaheadDepthWithHeuristic != std::numeric_limits<int>::max())
2532 simulator->SetLookaheadDepthWithHeuristic(std::min(depth, lookaheadDepthWithHeuristic));
2533 }
2534 }
2535
2536 int GetLookaheadDepth() const override
2537 {
2538 return lookaheadDepth;
2539 }
2540
2541 void SetLookaheadDepthWithHeuristic(int depth) override
2542 {
2543 if (depth < 0)
2544 depth = std::numeric_limits<int>::max();
2545
2546 if (depth > lookaheadDepth)
2547 depth = lookaheadDepth;
2548
2549 lookaheadDepthWithHeuristic = depth;
2550
2551 if (simulator && lookaheadDepthWithHeuristic != std::numeric_limits<int>::max())
2552 simulator->SetLookaheadDepthWithHeuristic(depth);
2553 }
2554
2556 {
2557 return lookaheadDepthWithHeuristic;
2558 }
2559
2560 double getGrowthFactorSwap() const override
2561 {
2562 return growthFactorSwap;
2563 }
2564
2565 double getGrowthFactorGate() const override
2566 {
2567 return growthFactorGate;
2568 }
2569
2570 void setGrowthFactorSwap(double factor) override
2571 {
2572 growthFactorSwap = factor;
2573
2574 if (simulator)
2575 simulator->setGrowthFactorSwap(factor);
2576 }
2577
2578 void setGrowthFactorGate(double factor) override
2579 {
2580 growthFactorGate = factor;
2581
2582 if (simulator)
2583 simulator->setGrowthFactorGate(factor);
2584 }
2585
2592 size_t GetCurrentMaxBondDimension() const override
2593 {
2594 return curMaxBondDim;
2595 }
2596
2597 protected:
2598 // Resolved placement is separate from user settings. Importing a smaller
2599 // simulator must not turn its automatic shard subset into an explicit choice.
2601 {
2602 const auto requested = configuration.GetConfiguration("distributed_devices");
2603 configuration.ApplyConfigurationFromSimulator(simulator);
2605 configuration.SetConfiguration("distributed_devices", requested);
2606 }
2607
2608 // Recreating the network simulator after a single execution must not drop
2609 // the classical results that execution stored in the network state.
2611 {
2612 const auto results = GetState().GetAllBits();
2613 CreateSimulator(simType, method);
2614 GetState().SetResultsInOrder(results);
2615 }
2616
2618 {
2619 return type == Simulators::SimulatorType::kQCSim && configuration.GetConfiguration("sampling_policy") != "legacy" &&
2622 }
2623
2624 void ExecuteLogicalShotBlocks(const std::shared_ptr<Circuits::Circuit<Time>> &circuit, ExecuteResults &results, size_t shots, size_t workers, size_t qubits,
2625 size_t cbits, size_t resultBits, Simulators::SimulatorType type, Simulators::SimulationType method,
2626 const std::shared_ptr<Simulators::ISimulator> &prepared, const std::vector<bool> &executed, std::mutex &resultsMutex)
2627 {
2628 // Per-shot streams are the default. block_v1 retains the original fixed
2629 // logical ranges for replay. Terminal batches use one simulator instead.
2630 const bool independentShots = configuration.GetConfiguration("trajectory_policy") == "shot_v1";
2631 workers = std::max<size_t>(1, workers);
2632#ifdef _OPENMP
2633 // Outer jobs count against the same CPU budget as their internal teams.
2634 int threadBudget = omp_in_parallel() ? 1 : std::max(1, omp_get_max_threads());
2635 workers = std::min(workers, static_cast<size_t>(threadBudget));
2636#endif
2638 {
2639 const size_t bitsPerQubit = method == Simulators::SimulationType::kDensityMatrix ? 2 : 1;
2640 if (qubits <= (std::numeric_limits<size_t>::digits - 5) / bitsPerQubit)
2641 {
2642 const size_t elements = size_t{1} << (bitsPerQubit * qubits);
2643 const size_t bytes = elements * sizeof(std::complex<double>);
2644 const size_t mb =
2645 configuration.IsSet("trajectory_max_memory_mb") ? std::stoull(configuration.GetConfiguration("trajectory_max_memory_mb")) : 1024;
2646 const size_t slots = (mb << 20) / bytes;
2647 // Prepared executions retain a source and one shared checkpoint.
2648 // Without preparation each worker owns its own checkpoint too.
2649 const size_t memoryWorkers = prepared ? (slots > 2 ? slots - 2 : 1) : slots / 2;
2650 workers = std::min(workers, std::max<size_t>(1, memoryWorkers));
2651#ifdef _OPENMP
2652 // Large independent states contend for cache and memory bandwidth.
2653 // Reserve a small internal team instead of filling every CPU with
2654 // a separate state, and use no more threads in all than QCSim's
2655 // team for one such register (half the processors by default):
2656 // memory-bound work gains nothing from the second hardware
2657 // thread of a core, and with the default OpenMP limit filling
2658 // every logical CPU made these runs 1.3-1.5x slower. Logical RNG
2659 // streams do not depend on this choice.
2660 if (elements >= (size_t{1} << 20))
2661 {
2662 threadBudget = std::min(threadBudget, std::max(1, QC::QubitRegisterCalculator<>::GetParallelTeamSize(elements)));
2663 workers = std::min(workers, static_cast<size_t>(std::max(1, threadBudget / 4)));
2664 }
2665#endif
2666 }
2667 else
2668 workers = 1;
2669 }
2670 // shot_v1 streams do not depend on the worker count, so tiny states can
2671 // skip workers whose dispatch would cost more than their shots.
2672 if (independentShots && trajectoryMinWorkerMicros > 0)
2673 workers = std::min(workers, TrajectoryWorkersForWork(circuit, shots, qubits, method, executed));
2674 // shot_v1 can change physical job boundaries without changing any RNG
2675 // stream. Keep batches bounded while exposing short requests to workers.
2676 const size_t blockSize = independentShots ? std::min<size_t>(256, std::max<size_t>(1, shots / workers + (shots % workers != 0))) : 256;
2677 const uint64_t seed = configuration.IsSet("seed") ? std::stoull(configuration.GetConfiguration("seed"))
2678 : Simulators::GenerateRandomSeed(type, configuration.GetConfigMap());
2679 const auto cloneMutex = std::make_shared<std::mutex>();
2680 // Execute() continues the live register when no optimizer replaced it.
2681 // A one-shot trajectory must not clear that state or invalidate handles
2682 // held by callers preparing and inspecting the register around Execute().
2683 const bool continueExecution = !resetBeforeExecution && shots == 1 && !prepared && simulator && simulator->GetType() == type &&
2684 simulator->GetSimulationType() == method && simulator->GetNumberOfQubits() == qubits;
2685 if (prepared)
2686 prepared->SetMultithreading(false);
2687 if (simulator && simulator != prepared && !continueExecution)
2688 simulator->Clear();
2689 workers = std::max<size_t>(1, std::min(workers, shots / blockSize + (shots % blockSize != 0)));
2690 if (workers > 1)
2691 {
2692 threadsPool.Resize(workers);
2693 threadsPool.SetFinishLimit(shots);
2694 }
2695 // The caller's OpenMP limit is a budget shared by concurrent shot jobs.
2696 // std::thread workers do not inherit the caller's OpenMP ICV, so pass
2697 // the budget explicitly and apply it only for the lifetime of each job.
2698 int internalThreads = 1;
2699#ifdef _OPENMP
2700 if (!omp_in_parallel() && method != Simulators::SimulationType::kPathIntegral)
2701 internalThreads = static_cast<int>(std::max<size_t>(1, static_cast<size_t>(threadBudget) / workers));
2702#endif
2703#if defined(_OPENMP) && defined(_MSC_VER) && !defined(__clang__) && !defined(_OPENMP_LLVM_RUNTIME)
2704 // Legacy MSVC OpenMP keeps this setting process-wide. Set it once before
2705 // starting jobs; per-worker save/restore races with other active jobs.
2706 struct RestoreThreadLimit
2707 {
2708 const int previous = omp_get_max_threads();
2709
2710 ~RestoreThreadLimit()
2711 {
2712 omp_set_num_threads(previous);
2713 }
2714 } restoreThreadLimit;
2715
2716 omp_set_num_threads(internalThreads);
2717#endif
2718 std::shared_ptr<ExecuteJob<Time>> last;
2719 for (size_t begin = 0, block = 0; begin < shots; ++block)
2720 {
2721 const size_t count = std::min(blockSize, shots - begin);
2722 auto job = std::make_shared<ExecuteJob<Time>>(circuit, results, count, qubits, cbits, resultBits, type, method, resultsMutex);
2723 // Even a one-shot tail must remove the same deterministic prefix:
2724 // classical instruction positions are part of shot_v1 stream IDs.
2725 job->optimiseMultipleShotsExecution = independentShots || count > 1 || GetOptimizeSimulator();
2726 // Reproducible measurement retains serial scan/reduction order;
2727 // gates and disjoint collapse updates may use the remaining budget.
2728#if defined(_OPENMP) && defined(_MSC_VER) && !defined(__clang__) && !defined(_OPENMP_LLVM_RUNTIME)
2729 job->internalThreads = 0; // The dispatcher owns the process-wide limit.
2730#else
2731 job->internalThreads = internalThreads;
2732#endif
2733 job->allowInternalMultithreading = internalThreads > 1;
2734 job->network = BaseClass::getptr();
2735 job->onSimulatorReady = [this](const Simulators::ISimulator &actual) {
2736 lastFusionWidth.store(actual.GetGateFusionMaxQubits());
2737 lastFusionEnabled.store(actual.IsGateFusionEnabled());
2738 };
2739 job->curMaxBondDim = &curMaxBondDim;
2740 job->config = ExecutionConfiguration(type, qubits);
2741 job->config.SetConfiguration("seed", std::to_string(Simulators::IState::DeriveSeed(seed, block)));
2742 job->randomStream = block;
2743 job->independentShotStreams = independentShots;
2744 job->trajectorySeed = Simulators::IState::DeriveSeed(seed, 0x53484f545f5631ULL);
2745 job->firstShot = begin;
2746 job->cloneSource = prepared;
2747 job->cloneMutex = cloneMutex;
2748 job->executedGates = executed;
2749 if (continueExecution)
2750 {
2751 job->optSim = simulator;
2752 job->config.ApplyConfigurationToSimulator(job->optSim);
2753 job->executedGates.assign(circuit->size(), false);
2754 }
2755 last = job;
2756 if (workers > 1)
2757 threadsPool.AddRunJob(std::move(job));
2758 else
2759 job->DoWork();
2760 begin += count;
2761 }
2762 if (workers > 1)
2763 threadsPool.WaitForFinish();
2764 if (!recreateIfNeeded && last)
2765 simulator = last->optSim;
2766 }
2767
2768 // Rough serial cost of the shots of a statevector or density-matrix
2769 // trajectory batch (fixed per-shot cost plus the operations replayed after
2770 // the checkpoint over every amplitude), measured on QCSim; one worker per
2771 // trajectoryMinWorkerMicros of it.
2772 size_t TrajectoryWorkersForWork(const std::shared_ptr<Circuits::Circuit<Time>> &circuit, size_t shots, size_t qubits, Simulators::SimulationType method,
2773 const std::vector<bool> &executed) const
2774 {
2776 return std::numeric_limits<size_t>::max();
2777 const size_t bitsPerQubit = method == Simulators::SimulationType::kDensityMatrix ? 2 : 1;
2778 if (qubits > (std::numeric_limits<size_t>::digits - 5) / bitsPerQubit)
2779 return std::numeric_limits<size_t>::max();
2780 const double elements = static_cast<double>(size_t{1} << (bitsPerQubit * qubits));
2781 const size_t done = static_cast<size_t>(std::count(executed.begin(), executed.end(), true));
2782 const double ops = static_cast<double>(circuit->size() > done ? circuit->size() - done : 1);
2783 const double micros = static_cast<double>(shots) * (6. + 0.4e-3 * ops * elements);
2784 return std::max<size_t>(1, static_cast<size_t>(micros / trajectoryMinWorkerMicros));
2785 }
2786
2788 {
2789 auto result = configuration;
2791 result.SetConfiguration("reproducible_trajectory", distCirc && distCirc->HasOpsAfterMeasurements() ? "true" : "false");
2792 const auto found = resolvedDistributedDevices.find(type);
2793 if (!Simulators::IsDistributedGpuSimulator(type) || !configuration.GetConfiguration("distributed_devices").empty() ||
2794 found == resolvedDistributedDevices.end())
2795 return result;
2796 auto devices = found->second;
2797 // MPI shard count belongs to the communicator. Explicit global-qubit
2798 // settings also constrain the shard count and must be validated unchanged.
2799 if (type == Simulators::SimulatorType::kDistGpuSim && !configuration.IsSet("distributed_global_qubits"))
2800 {
2801 size_t count = 1;
2802 for (char c : devices)
2803 if (c == ',')
2804 ++count;
2805 size_t bits = 0;
2806 for (size_t n = count; n > 1; n >>= 1)
2807 ++bits;
2808 while (count > 1 && bits >= qubits)
2809 {
2810 count /= 2;
2811 --bits;
2812 }
2813 size_t end = 0;
2814 for (size_t i = 0; i < count; ++i)
2815 {
2816 end = devices.find(',', end);
2817 if (end == std::string::npos)
2818 break;
2819 if (i + 1 < count)
2820 ++end;
2821 }
2822 devices = devices.substr(0, end);
2823 }
2824 result.SetConfiguration("distributed_devices", devices);
2825 return result;
2826 }
2827
2828 void OptimizeMPSInitialQubitsMap(std::shared_ptr<Simulators::ISimulator> &sim, std::shared_ptr<Circuits::Circuit<Time>> &dcirc, size_t nrQubits) const
2829 {
2830 if ((sim->GetSimulationType() == Simulators::SimulationType::kMatrixProductState ||
2831 sim->GetSimulationType() == Simulators::SimulationType::kMatrixProductOperator) &&
2832 (optimizeInitialQubitsMap || mpsOptimizeSwaps) && sim->SupportsMPSSwapOptimization() && !dcirc->HasCompositeOperations())
2833 {
2834 // an MPO site carries a two-qubit (operator) physical index, so its
2835 // bonds grow like those of an MPS twice as long
2836 const size_t routedQubits = sim->GetSimulationType() == Simulators::SimulationType::kMatrixProductOperator ? 2 * nrQubits : nrQubits;
2837 if (mpsOptimizationQubitsNumberThreshold <= routedQubits)
2838 {
2839 const auto maxBondDimValue =
2840 configuration.GetConfigurationAsInt(sim->GetSimulationType() == Simulators::SimulationType::kMatrixProductOperator &&
2841 configuration.IsSet("matrix_product_operator_max_bond_dimension")
2842 ? "matrix_product_operator_max_bond_dimension"
2843 : "matrix_product_state_max_bond_dimension");
2844
2845 if (maxBondDimValue <= 0 || static_cast<int>(mpsOptimizationBondDimensionThreshold) <= maxBondDimValue)
2846 {
2847 // need to be sure the circuit is correctly converted
2848 dcirc->ConvertForCutting(); // convert the three qubit gates
2849 auto layers = dcirc->ToMultipleQubitsLayersNoClone();
2850 // Finalize source ordering before preparing fusion. Routing layers
2851 // are only a cost model and must never replace executable operations.
2852 auto ordered = Circuits::Circuit<Time>::LayersToCircuit(layers);
2853 dcirc->SetOperations(ordered->GetOperations());
2854 sim->SetUpcomingGates(dcirc->GetOperations());
2855 if (sim->IsGateFusionEnabled())
2856 {
2857 Circuits::Circuit<Time> routingCircuit;
2858 for (const auto &op : sim->GetUpcomingRoutingOperations())
2859 if (op->GetType() == Circuits::OperationType::kGate)
2860 routingCircuit.AddOperation(op);
2861 layers = routingCircuit.ToMultipleQubitsLayersNoClone();
2862 }
2863
2864 Simulators::MPSDummySimulator dummySim(nrQubits, sim->GetSimulationType() == Simulators::SimulationType::kMatrixProductOperator);
2865 dummySim.SetSwapGatesRelabelQubits(sim->SwapGatesRelabelQubits());
2866 dummySim.setGrowthFactorGate(growthFactorGate);
2867 dummySim.setGrowthFactorSwap(growthFactorSwap);
2868
2869 if (maxBondDimValue > 0)
2870 {
2872 dummySim.SetMaxBondDimension(
2874 nrQubits, sim->GetSimulationType() == Simulators::SimulationType::kMatrixProductOperator, maxBondDimValue)
2875 : maxBondDimValue);
2876 dummySim.SetRoutingCostModel(virtualBonds ? maxBondDimValue : 0, Simulators::RoutingConfig::TruncationWeight(configuration));
2877 }
2878
2879 if (optimizeInitialQubitsMap)
2880 {
2881 const auto optimalMap = dummySim.ComputeOptimalQubitsMap(layers);
2882 sim->SetInitialQubitsMap(optimalMap);
2883 }
2884
2885 if (mpsOptimizeSwaps)
2886 {
2887 int lookaheadDepthLocal = lookaheadDepth;
2888
2889 if (lookaheadDepthLocal == std::numeric_limits<int>::max())
2890 {
2891 double avgTwoQubitGatesPerLayer = 0.0;
2892 for (const auto &layer : layers)
2893 {
2894 int twoQubitGates = 0;
2895 for (const auto &op : layer->GetOperations())
2896 {
2897 if (op->AffectedQubits().size() >= 2)
2898 {
2899 ++twoQubitGates;
2900 }
2901 }
2902 avgTwoQubitGatesPerLayer += twoQubitGates;
2903 }
2904 if (!layers.empty())
2905 avgTwoQubitGatesPerLayer /= layers.size();
2906
2907 int lookaheadVal = static_cast<int>(4. * avgTwoQubitGatesPerLayer);
2908 if (lookaheadVal > 15)
2909 lookaheadVal = 15;
2910
2911 lookaheadDepthLocal = layers.size() < 8 || routedQubits <= 10 ? 0
2912 : layers.size() < 15 ? static_cast<int>(lookaheadVal)
2913 : layers.size() < 25 ? static_cast<int>(1.5 * lookaheadVal)
2914 : 2 * lookaheadVal;
2915 }
2916
2917 int lookaheadHeuristicDepthLocal = lookaheadDepthWithHeuristic;
2918
2919 if (lookaheadHeuristicDepthLocal == std::numeric_limits<int>::max())
2920 lookaheadHeuristicDepthLocal = layers.size() < 8 || routedQubits <= 10 ? 0 : lookaheadDepthLocal - 1;
2921
2922 if (lookaheadHeuristicDepthLocal < 0)
2923 lookaheadHeuristicDepthLocal = 0;
2924
2925 sim->setGrowthFactorGate(growthFactorGate);
2926 sim->setGrowthFactorSwap(growthFactorSwap);
2927 sim->SetUseOptimalMeetingPosition(true);
2928 if (lookaheadDepth == std::numeric_limits<int>::max())
2929 sim->SetAutomaticLookaheadDepth(lookaheadDepthLocal);
2930 else
2931 sim->SetLookaheadDepth(lookaheadDepthLocal);
2932 sim->SetLookaheadDepthWithHeuristic(lookaheadHeuristicDepthLocal);
2933 // The CPU window installed before layout planning has not
2934 // changed. Its routing setters already refresh the plan.
2935 if (sim->GetType() != Simulators::SimulatorType::kQCSim)
2936 sim->SetUpcomingGates(dcirc->GetOperations());
2937 }
2938 }
2939 }
2940 }
2941 }
2942
2952 {
2953 auto optimiser = controller->GetOptimiser();
2954 if (optimiser)
2955 {
2956 // convert the classical state results back to the expected order
2957 const auto &qubitsMap = optimiser->GetReverseQubitsMap();
2958
2959 ConvertBackState(qubitsMap);
2960 }
2961 }
2962
2974 void ConvertBackState(const std::unordered_map<Types::qubit_t, Types::qubit_t> &qubitsMap)
2975 {
2976 // might not be the one stored in the network, might exist in the DES
2977 Circuits::OperationState &theClassicalState = GetState();
2978
2979 theClassicalState.Remap(qubitsMap);
2980 }
2981
2993 {
2994 auto optimiser = controller->GetOptimiser();
2995 if (optimiser)
2996 {
2997 // convert the classical state results back to the expected order
2998 const auto &qubitsMap = optimiser->GetReverseQubitsMap();
2999
3000 ConvertBackResults(res, qubitsMap);
3001 }
3002 }
3003
3016 void ConvertBackResults(ExecuteResults &res, const std::unordered_map<Types::qubit_t, Types::qubit_t> &bitsMap) const
3017 {
3018 ExecuteResults translatedRes;
3019
3020 size_t numClassicalBits = 0;
3021 for (const auto &[q, b] : bitsMap)
3022 if (b >= numClassicalBits)
3023 numClassicalBits = b + 1;
3024
3025 numClassicalBits = std::max(numClassicalBits, GetNumClassicalBits());
3026
3027 for (const auto &r : res)
3028 {
3029 Circuits::OperationState translatedState(r.first);
3030
3031 translatedState.Remap(bitsMap, false, numClassicalBits);
3032 translatedRes[translatedState.GetAllBits()] = r.second;
3033 }
3034
3035 res.swap(translatedRes);
3036 }
3037
3049 std::unordered_map<Types::qubit_t, Types::qubit_t> MapCircuitOnHost(const std::shared_ptr<Circuits::Circuit<Time>> &circuit, size_t hostId,
3050 size_t &nrQubits, size_t &nrCbits, bool useSeparateSimForHosts = false)
3051 {
3052 qubitsMapOnHost.clear();
3053 nrQubits = 0;
3054 nrCbits = 0;
3055 if (!circuit)
3056 return {};
3057
3058 const auto host = std::static_pointer_cast<SimpleHost<Time>>(GetHost(hostId));
3059 const size_t hostNrQubits = host->GetNumQubits();
3060
3061 std::unordered_map<Types::qubit_t, Types::qubit_t> reverseQubitsMap;
3062
3063 if (!useSeparateSimForHosts)
3064 {
3065 // A circuit already using this host's qubit IDs still replaces the
3066 // previous circuit (and must be available on the first invocation).
3067 distCirc = circuit;
3068 size_t mxq = 0;
3069 size_t mnq = std::numeric_limits<size_t>::max();
3070 size_t mxb = 0;
3071 size_t mnb = std::numeric_limits<size_t>::max();
3072
3073 for (const auto &op : circuit->GetOperations())
3074 {
3075 const auto qbits = op->AffectedQubits();
3076 for (auto q : qbits)
3077 {
3078 if (q > mxq)
3079 mxq = q;
3080 if (q < mnq)
3081 mnq = q;
3082 }
3083 }
3084 for (auto b : circuit->GetBits())
3085 {
3086 if (b > mxb)
3087 mxb = b;
3088 if (b < mnb)
3089 mnb = b;
3090 }
3091
3092 if (mnq > mxq)
3093 mnq = 0;
3094 if (mnb > mxb)
3095 mnb = 0;
3096
3097 nrQubits = mxq - mnq + 1;
3098 nrCbits = mxb - mnb + 1;
3099 if (nrCbits < nrQubits)
3100 nrCbits = nrQubits;
3101
3102 const size_t startQubit = host->GetStartQubitId();
3103
3104 if (mnq < startQubit || mxq >= startQubit + hostNrQubits)
3105 {
3106 if (nrQubits > hostNrQubits + 1) // the host has an additional 'special' qubit for the
3107 // entanglement or other operations (like those for cutting)
3108 throw std::runtime_error("Circuit does not fit on the host!");
3109
3110 for (size_t i = 0; i < nrCbits; ++i)
3111 {
3112 const size_t mapFrom = mnq + i;
3113 const size_t mapTo = startQubit + i;
3114
3115 qubitsMapOnHost[mapFrom] = mapTo;
3116 reverseQubitsMap[mapTo] = mapFrom;
3117 }
3118
3119 distCirc = std::static_pointer_cast<Circuits::Circuit<Time>>(circuit->Remap(qubitsMapOnHost, qubitsMapOnHost));
3120 }
3121
3122 return reverseQubitsMap;
3123 }
3124
3125 distCirc = circuit->RemapToContinuous(qubitsMapOnHost, reverseQubitsMap, nrQubits, nrCbits);
3126
3128 {
3129 // Distribution settings refer to register qubits. Keep their numbering
3130 // and idle wires when the network creates a smaller per-host simulator.
3131 // Classical results retain RemapToContinuous's independent mapping.
3132 if (!hostNrQubits)
3133 throw std::runtime_error("Circuit does not fit on a host with no qubits!");
3134 const size_t start = host->GetStartQubitId();
3135 bool fitsLocal = true, fitsGlobal = true;
3136 for (const auto &entry : qubitsMapOnHost)
3137 {
3138 const auto q = entry.first;
3139 fitsLocal = fitsLocal && q < hostNrQubits;
3140 fitsGlobal = fitsGlobal && q >= start && q - start < hostNrQubits;
3141 }
3142 // Auto follows the host API's already-mapped precedence. Sparse local
3143 // circuits in the overlap must explicitly select local indexing.
3144 const bool global = distributedHostQubitIndexing == "global" || (distributedHostQubitIndexing == "auto" && fitsGlobal);
3145 if (!(global ? fitsGlobal : fitsLocal))
3146 throw std::runtime_error("Circuit does not fit on the host!");
3147 const size_t offset = global ? start : 0;
3148 if (pauliStrings)
3149 for (const auto &pauli : *pauliStrings)
3150 if (pauli.size() > hostNrQubits)
3151 throw std::invalid_argument("Host Pauli strings use local indices and must fit the host");
3152 std::unordered_map<Types::qubit_t, Types::qubit_t> restoreQubits;
3153 for (const auto &[original, compact] : qubitsMapOnHost)
3154 {
3155 if (original < offset || original - offset >= hostNrQubits)
3156 throw std::runtime_error("Circuit does not fit on the host!");
3157 restoreQubits[compact] = original - offset;
3158 }
3159 distCirc = std::static_pointer_cast<Circuits::Circuit<Time>>(distCirc->Remap(restoreQubits));
3160 qubitsMapOnHost.clear();
3161 for (size_t q = 0; q < hostNrQubits; ++q)
3162 qubitsMapOnHost[q] = q;
3163 nrQubits = hostNrQubits;
3164 }
3165 else if (pauliStrings)
3166 {
3167 // Observables can mention idle qubits absent from the circuit. Allocate
3168 // them in |0> and include them in the expectation remapping.
3169 size_t width = 0;
3170 for (const auto &pauli : *pauliStrings)
3171 width = std::max(width, pauli.size());
3172 for (size_t q = 0; q < width; ++q)
3173 if (!qubitsMapOnHost.count(q))
3174 qubitsMapOnHost[q] = nrQubits++;
3175 }
3176
3177 assert(nrQubits == qubitsMapOnHost.size());
3178
3179 if (nrQubits == 0)
3180 nrQubits = 1;
3181
3182 if (nrQubits > hostNrQubits + 1) // the host has an additional 'special' qubit for the
3183 // entanglement or other operations (like those for cutting)
3184 throw std::runtime_error("Circuit does not fit on the host!");
3185
3186 return reverseQubitsMap;
3187 }
3188
3189 bool optimizeSimulator = true;
3191
3193 std::atomic<unsigned> lastFusionWidth{0};
3194 std::atomic<bool> lastFusionEnabled{false};
3198
3200 std::string distributedHostQubitIndexing = "auto";
3201 std::unordered_map<Simulators::SimulatorType, std::string> resolvedDistributedDevices;
3202
3203 size_t maxSimulators = QC::QubitRegisterCalculator<>::GetNumberOfThreads();
3206
3208 std::shared_ptr<Simulators::ISimulator> simulator;
3209
3210 std::shared_ptr<Circuits::Circuit<Time>> distCirc;
3211
3212 std::shared_ptr<IController<Time>> controller;
3213 // TODO: depending on the network topology, we will have adiacency lists, etc.
3214 // or simply a vector of hosts for a totally connected network (or where the
3215 // communication details do not matter so much)
3216 std::vector<std::shared_ptr<IHost<Time>>> hosts;
3217
3218 std::unique_ptr<Estimators::SimulatorsEstimatorInterface<Time>> simulatorsEstimator;
3219
3220 private:
3222
3223 protected:
3224 // Only Execute() preserves a prepared input state. Derived shot loops must
3225 // honor this independently of retaining the final simulator for queries.
3227
3228 // Derived execution loops must retain the final state for state queries.
3229 bool recreateIfNeeded = true;
3230
3231 private:
3232 std::unordered_map<Types::qubit_t, Types::qubit_t> qubitsMapOnHost;
3235 const std::vector<std::string> *pauliStrings = nullptr;
3237
3238 bool optimizeInitialQubitsMap = true;
3240 bool mpsOptimizeSwaps = true;
3241 size_t mpsOptimizationBondDimensionThreshold = 32;
3242 size_t mpsOptimizationQubitsNumberThreshold = 12;
3243
3244 int lookaheadDepth = std::numeric_limits<int>::max();
3246 int lookaheadDepthWithHeuristic = std::numeric_limits<int>::max();
3248
3249 double growthFactorSwap = 1.;
3250 double growthFactorGate = 0.7;
3251 size_t curMaxBondDim = 0;
3252};
3253
3254} // namespace Network
3255
3256#endif // !_SIMPLE_NETWORK_H_
int GetSimulationType(void *sim)
Circuit class for holding the sequence of operations.
Definition Circuit.h:51
void AddOperation(const OperationPtr &op)
Adds an operation to the circuit.
Definition Circuit.h:146
std::vector< std::shared_ptr< Circuits::Circuit< Time > > > ToMultipleQubitsLayersNoClone() const
Converts the circuit to layers oriented on multiple qubit gates.
Definition Circuit.h:2938
static std::shared_ptr< Circuits::Circuit< Time > > LayersToCircuit(const std::vector< std::shared_ptr< Circuits::Circuit< Time > > > &layers)
Converts the layers back to a circuit.
Definition Circuit.h:3031
The operation interface.
Definition Operations.h:402
The state class that stores the classical state of a quantum circuit execution.
Definition Operations.h:68
const std::vector< bool > & GetAllBits() const
Get the classical bits.
Definition Operations.h:244
void Clear()
Clear the classical state.
Definition Operations.h:189
void SetResultsInOrder(const std::vector< bool > &results)
Set the classical bits.
Definition Operations.h:294
void Remap(const std::unordered_map< Types::qubit_t, Types::qubit_t > &mapping, bool ignoreNotMapped=false, size_t newSize=0)
Convert the state using the provided mapping.
Definition Operations.h:345
static void ExecuteUpToMeasurements(const std::shared_ptr< Circuits::Circuit< Time > > &dcirc, size_t nrQubits, size_t nrCbits, size_t nrResultCbits, const std::shared_ptr< Simulators::ISimulator > &sim, std::vector< bool > &executed, size_t *curMaxBondDim=nullptr)
The controller host interface.
Definition Controller.h:108
The network interface.
Definition Network.h:60
std::shared_ptr< INetwork< Time > > getptr()
Definition Network.h:745
typename Circuits::Circuit< Time >::ExecuteResults ExecuteResults
Definition Network.h:62
std::unordered_set< SimulatorPair, boost::hash< SimulatorPair > > SimulatorsSet
Definition Network.h:64
Simulators::SimulatorType GetLastSimulatorType() const override
Get the last used simulator type.
void SetController(const std::shared_ptr< IController< Time > > &cntrl)
Set the network controller host.
std::vector< double > ExecuteExpectations(const std::shared_ptr< Circuits::Circuit< Time > > &circuit, const std::vector< std::string > &paulis) override
Execute the circuit on the network and return the expectation values for the specified Pauli strings.
Simulators::SimulatorType lastSimulatorType
The last simulator type used.
size_t GetHostIdForEntangledQubit(size_t qubitId) const override
Get the host id for the specified qubit used for entanglement between hosts.
std::shared_ptr< Simulators::ISimulator > simulator
The quantum computing simulator for the network.
bool ExpectsClassicalBitFromOtherHost(const std::shared_ptr< Circuits::IOperation< Time > > &op) const override
Checks if a gate expects a classical bit from another host.
void ExecuteOnHost(const std::shared_ptr< Circuits::Circuit< Time > > &circuit, size_t hostId) override
Execute the circuit on the specified host.
size_t GetHostIdForClassicalControl(const std::shared_ptr< Circuits::IOperation< Time > > &op) const override
Get the host id where the classical control bit resides for a conditioned gate.
void MarkEntangledQubitFree(size_t qubitId) override
Mark the specified qubit used for entanglement between hosts as free.
bool OptimizationSimulatorExists(Simulators::SimulatorType type, Simulators::SimulationType kind) const override
Checks if a simulator exists in the optimization set.
size_t GetNumNetworkEntangledQubits() const override
Get the number of qubits used for entanglement between hosts.
std::unordered_map< Simulators::SimulatorType, std::string > resolvedDistributedDevices
std::unordered_map< Types::qubit_t, Types::qubit_t > MapCircuitOnHost(const std::shared_ptr< Circuits::Circuit< Time > > &circuit, size_t hostId, size_t &nrQubits, size_t &nrCbits, bool useSeparateSimForHosts=false)
Map the circuit on the host.
size_t GetNumQubitsForHost(size_t hostId) const override
Get the number of qubits in the network for the specified host.
Circuits::OperationState & GetState() override
Get the classical state of the network.
void AddOptimizationSimulator(Simulators::SimulatorType type, Simulators::SimulationType kind) override
Adds a simulator to the simulators optimization set.
ExecuteResults RepeatedExecuteOnHost(const std::shared_ptr< Circuits::Circuit< Time > > &circuit, size_t hostId, size_t shots=1000) override
Execute the circuit on the specified host, repeatedly.
void OptimizeMPSInitialQubitsMap(std::shared_ptr< Simulators::ISimulator > &sim, std::shared_ptr< Circuits::Circuit< Time > > &dcirc, size_t nrQubits) const
Simulators::SimulationType lastMethod
The last simulation method used.
ExecuteResults RepeatedExecute(const std::shared_ptr< Circuits::Circuit< Time > > &circuit, size_t shots=1000) override
Execute the circuit on the network, repeatedly.
bool optimizeSimulator
The flag to optimize the simulator.
std::vector< size_t > GetQubitsIds(size_t hostId) const override
Get the qubit ids for the specified host.
std::shared_ptr< Circuits::Circuit< Time > > GetDistributedCircuit() const override
Get the distributed circuit.
size_t GetNumQubits() const override
Get the number of qubits in the network.
Configuration< Time > ExecutionConfiguration(Simulators::SimulatorType type, size_t qubits) const
bool GetOptimizeSimulator() const override
Returns the 'optimize' flag.
size_t GetNumberOfGatesDistributedOrCut(const std::shared_ptr< Circuits::Circuit< Time > > &circuit) const override
Get the number of gates that span more than one host.
size_t GetHostIdForClassicalBit(size_t classicalBitId) const override
Get the host id for the specified classical bit.
bool IsLocalOperation(const std::shared_ptr< Circuits::IOperation< Time > > &op) const override
Check if the circuit operation is local.
void SetLookaheadDepthWithHeuristic(int depth) override
void Configure(const char *key, const char *value) override
Configures the network.
void CreateSimulator(Simulators::SimulatorType simType=Simulators::SimulatorType::kQCSim, Simulators::SimulationType simExecType=Simulators::SimulationType::kMatrixProductState, size_t nrQubits=0) override
Create the simulator for the network.
std::unique_ptr< Estimators::SimulatorsEstimatorInterface< Time > > simulatorsEstimator
The simulators estimator.
const std::shared_ptr< IHost< Time > > GetHost(size_t hostId) const override
Get the host with the specified id.
size_t GetNumHosts() const override
Get the number of hosts in the network.
void SetMPSOptimizeSwaps(bool optimize=true) override
void SetInitialQubitsMapOptimization(bool optimize=true) override
size_t GetMPSOptimizationBondDimensionThreshold() const override
unsigned GetLastGateFusionMaxQubits() const override
size_t GetHostIdForQubit(size_t qubitId) const override
Get the host id for the specified qubit.
bool IsNetworkEntangledQubit(size_t qubitId) const override
Check if the specified qubit id is for a qubit used for entanglement between hosts.
std::vector< double > ExecuteOnHostExpectations(const std::shared_ptr< Circuits::Circuit< Time > > &circuit, size_t hostId, const std::vector< std::string > &paulis) override
Execute the circuit on the specified host and return the expectation values for the specified Pauli s...
void RemoveOptimizationSimulator(Simulators::SimulatorType type, Simulators::SimulationType kind) override
Removes a simulator from the simulators optimization set.
std::shared_ptr< INetwork< Time > > Clone() const override
Clone the network.
bool UsesReproducibleSampling(Simulators::SimulatorType type, Simulators::SimulationType method) const
std::complex< double > ExecuteOnHostProjectOnZero(const std::shared_ptr< Circuits::Circuit< Time > > &circuit, size_t hostId) override
Execute circuit on host and return the projection onto the zero state.
Simulators::SimulationType GetLastSimulationType() const override
Get the last used simulation type.
void SetMaxSimulators(size_t val) override
Set the maximum number of simulators that can be used in the network.
bool AreQubitAndClassicalBitOnSameHost(size_t qubitId, size_t bitId) const override
Check if the specified qubit and classical bit are on the same host.
typename BaseClass::ExecuteResults ExecuteResults
The execute results type.
void SetOptimizeSimulator(bool optimize=true) override
Allows using an optimized simulator.
size_t GetNumClassicalBitsForHost(size_t hostId) const override
Get the number of classical bits in the network for the specified host.
bool IsEntanglingGate(const std::shared_ptr< Circuits::IOperation< Time > > &op) const override
Checks if a gate is an entangling gate.
void Execute(const std::shared_ptr< Circuits::Circuit< Time > > &circuit) override
Execute the circuit on the network.
std::vector< std::shared_ptr< IHost< Time > > > hosts
The hosts in the network.
std::shared_ptr< Simulators::ISimulator > ChooseBestSimulator(std::shared_ptr< Circuits::Circuit< Time > > &dcirc, size_t &counts, size_t nrQubits, size_t nrCbits, size_t nrResultCbits, Simulators::SimulatorType &simType, Simulators::SimulationType &method, std::vector< bool > &executed, bool multithreading=false, bool dontRunCircuitStart=false) override
bool AreQubitsOnSameHost(size_t qubitId1, size_t qubitId2) const override
Check if the specified qubits are on the same host.
std::vector< ExecuteResults > ExecuteScheduled(const std::vector< Schedulers::ExecuteCircuit< Time > > &circuits) override
Schedule and execute circuits on the network.
size_t GetHostIdForAnyQubit(size_t qubitId) const override
Get the host id for the specified qubit.
size_t GetNumNetworkEntangledQubitsForHost(size_t hostId) const override
Get the number of qubits used for entanglement between hosts for the specified host.
Circuits::OperationState classicalState
The classical state of the network.
int GetLastGpuDevice() const override
Get the last used simulator type.
void ConvertBackResults(ExecuteResults &res, const std::unordered_map< Types::qubit_t, Types::qubit_t > &bitsMap) const
Converts back the results using the passed qubits map.
void ConvertBackState()
Converts back the state from the optimized network distribution mapping.
std::shared_ptr< Circuits::Circuit< Time > > distCirc
The distributed circuit.
std::shared_ptr< Schedulers::IScheduler< Time > > GetScheduler() const override
Get the scheduler for the network.
size_t GetNumClassicalBits() const override
Get the number of classical bits in the network.
void SetTrajectoryMinWorkerMicros(double micros)
Set the estimated work per trajectory worker.
bool recreateIfNeeded
The flag to recreate the simulator if needed.
const std::shared_ptr< IController< Time > > GetController() const override
Get the controller for the network.
bool IsDistributedOperation(const std::shared_ptr< Circuits::IOperation< Time > > &op) const override
Check if the circuit operation is distributed.
std::vector< std::shared_ptr< IHost< Time > > > & GetHosts()
Get the hosts in the network.
size_t GetCurrentMaxBondDimension() const override
Returns the maximum bond dimension reached.
bool SendPacket(size_t fromHostId, size_t toHostId, const std::vector< uint8_t > &packet) override
Sends a packet between two hosts.
const BaseClass::SimulatorsSet & GetSimulatorsSet() const override
Get the optimizations simulators set.
std::shared_ptr< Simulators::ISimulator > GetSimulator() const override
Get the simulator for the network.
bool OperatesWithNetworkEntangledQubit(const std::shared_ptr< Circuits::IOperation< Time > > &op) const override
Check if the circuit operation operates on the entanglement qubits between hosts.
std::vector< std::complex< double > > ExecuteOnHostAmplitudes(const std::shared_ptr< Circuits::Circuit< Time > > &circuit, size_t hostId) override
Execute circuit on host and return full statevector amplitudes.
size_t TrajectoryWorkersForWork(const std::shared_ptr< Circuits::Circuit< Time > > &circuit, size_t shots, size_t qubits, Simulators::SimulationType method, const std::vector< bool > &executed) const
double trajectoryMinWorkerMicros
Estimated serial work per trajectory worker, see SetTrajectoryMinWorkerMicros.
size_t GetMaxSimulators() const override
Get the maximum number of simulators that can be used in the network.
void MarkEntangledQubitsBusy(size_t qubitId1, size_t qubitId2) override
Mark the pair of the specified qubits used for entanglement between hosts as busy.
void CreateScheduler(SchedulerType schType=SchedulerType::kNoEntanglementQubitsParallel) override
Create the scheduler for the network.
void setGrowthFactorSwap(double factor) override
std::vector< size_t > GetClassicalBitsIds(size_t hostId) const override
Get the classical bit ids for the specified host.
std::vector< size_t > GetEntangledQubitMeasurementBitIds(size_t hostId) const override
Get the classical bit ids used for measurement of entanglement qubits between the hosts for the speci...
void ExecuteLogicalShotBlocks(const std::shared_ptr< Circuits::Circuit< Time > > &circuit, ExecuteResults &results, size_t shots, size_t workers, size_t qubits, size_t cbits, size_t resultBits, Simulators::SimulatorType type, Simulators::SimulationType method, const std::shared_ptr< Simulators::ISimulator > &prepared, const std::vector< bool > &executed, std::mutex &resultsMutex)
bool AreEntanglementQubitsBusy(size_t qubitId1, size_t qubitId2) const override
Check if any of the two specified qubits used for entanglement between hosts are busy.
INetwork< Time > BaseClass
The base class type.
size_t maxSimulators
The maximum number of simulators that can be used in the network.
std::shared_ptr< IController< Time > > controller
The controller for the network.
void ClearEntanglements() override
Clear all entanglements between hosts in the network.
void ConvertBackResults(ExecuteResults &res)
Converts back the results from the optimized network distribution mapping.
void RecreateSimulatorKeepingResults(Simulators::SimulatorType simType, Simulators::SimulationType method)
void SetMPSOptimizationQubitsNumberThreshold(size_t threshold) override
bool IsEntanglementQubitBusy(size_t qubitId) const override
Check if the specified qubit used for entanglement between hosts is busy.
void RemoveAllOptimizationSimulatorsAndAdd(Simulators::SimulatorType type, Simulators::SimulationType kind) override
Removes all simulators from the simulators optimization set and adds the one specified.
void CreateNetwork(const std::vector< Types::qubit_t > &qubits, const std::vector< size_t > &cbits)
Creates the network hosts and controller.
SimpleDisconnectedNetwork(const std::vector< Types::qubit_t > &qubits={}, const std::vector< size_t > &cbits={})
The constructor.
std::vector< size_t > GetNetworkEntangledQubitsIds(size_t hostId) const override
Get the qubit ids used for entanglement between hosts for the specified host.
void ConvertBackState(const std::unordered_map< Types::qubit_t, Types::qubit_t > &qubitsMap)
Converts back the state using the passed qubits map.
void setGrowthFactorGate(double factor) override
NetworkType GetType() const override
Get the type of the network.
void SetMPSOptimizationBondDimensionThreshold(size_t threshold) override
bool AreClassicalBitsOnSameHost(size_t bitId1, size_t bitId2) const override
Check if the specified classical bits are on the same host.
size_t GetMPSOptimizationQubitsNumberThreshold() const override
The simple host implementation.
Definition SimpleHost.h:45
static std::string GpuSvdSettingGroup(const std::string &key)
static int ParseGpuDevice(const std::string &value)
Set a configuration value.
Interface class for a quantum computing simulator.
Definition Simulator.h:41
static uint64_t DeriveSeed(uint64_t seed, uint64_t stream)
Definition State.h:162
void SetSwapGatesRelabelQubits(bool enabled)
static IndexType EffectiveMapMaxBondDimension(size_t nrQubits, bool operatorChain, IndexType cap)
void setGrowthFactorSwap(double factor)
void SetRoutingCostModel(IndexType cap, double weight)
std::vector< long long int > ComputeOptimalQubitsMap(const std::vector< std::shared_ptr< Circuits::Circuit<> > > &layers, int nrShuffles=0, int nrSwaps=0)
void setGrowthFactorGate(double factor)
void SetMaxBondDimension(IndexType val)
static MAESTRO_FACTORY_EXPORT std::shared_ptr< ISimulator > CreateSimulator(SimulatorType t=SimulatorType::kQCSim, SimulationType method=SimulationType::kMatrixProductState)
Create a quantum computing simulator.
Definition Factory.cpp:45
ThreadsPool class for holding and controlling a pool of threads.
@ kGate
the usual quantum gate, result stays in simulator's state
Definition Operations.h:30
NetworkType
The type of the network.
Definition Network.h:36
@ kSimpleDisconnectedNetwork
Simple network, no communication among hosts, sequential simulation.
Definition Network.h:37
SchedulerType
The type of the network scheduler for scheduling execution of multiple circuits.
Definition Controller.h:89
bool VirtualBondsEnabled(const Config &config)
double TruncationWeight(const Config &config)
uint64_t GenerateRandomSeed(SimulatorType type, const std::unordered_map< std::string, std::string > &options={})
Definition RandomSeed.h:11
SimulationType
The type of simulation.
Definition State.h:109
@ kStatevector
statevector simulation type
Definition State.h:110
@ kMatrixProductState
matrix product state simulation type
Definition State.h:111
@ kDensityMatrix
Density matrix simulation type.
Definition State.h:117
@ kStabilizer
Clifford gates simulation type.
Definition State.h:112
@ kMatrixProductOperator
Matrix product operator simulation type.
Definition State.h:118
@ kPauliPropagator
Pauli propagator simulation type.
Definition State.h:114
@ kTensorNetwork
Tensor network simulation type.
Definition State.h:113
@ kPathIntegral
Path integral simulation type.
Definition State.h:116
SimulatorType
The type of simulator.
Definition State.h:79
@ kCompositeQCSim
composite qcsim simulator type
Definition State.h:87
@ kQCSim
qcsim simulator type
Definition State.h:83
@ kQiskitAer
qiskit aer simulator type
Definition State.h:81
@ kQuestSim
quest simulator type
Definition State.h:89
@ kCompositeQiskitAer
composite qiskit aer simulator type
Definition State.h:85
@ kDistGpuSim
state distributed across local GPUs
Definition State.h:90
@ kGpuSim
gpu simulator type
Definition State.h:88
bool IsDistributedGpuSimulator(SimulatorType type)
Definition State.h:94
bool IsGpuSimulator(SimulatorType type)
Definition State.h:99
A way to pack together a circuit and the number of shots for its execution.
Definition Controller.h:61