Maestro 0.3.1
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
18#include "QubitRegister.h"
19#include "SimpleController.h"
20#include "SimpleHost.h"
22#include "NetworkJob.h"
23
25
26#include "Configuration.h"
27
28namespace Network {
29
43template <typename Time = Types::time_type,
44 class Controller = SimpleController<Time>>
45class SimpleDisconnectedNetwork : public INetwork<Time> {
46 public:
50
60 SimpleDisconnectedNetwork(const std::vector<Types::qubit_t> &qubits = {},
61 const std::vector<size_t> &cbits = {}) {
62 configuration.SetConfiguration("use_double_precision", "0");
63
73 // this needs more work until it becomes useful - it's typically way too
74 // slow to be timed
75 // simulatorsForOptimizations.insert({
76 // SimulatorType::kQCSim,
77 // SimulationType::kTensorNetwork });
81
82#ifndef NO_QISKIT_AER
95#endif
96
97#ifdef __linux__
98 { // GPU candidates are resolved lazily when simulation is requested.
108 }
109#endif
110
111 if (!qubits.empty()) CreateNetwork(qubits, cbits);
112 }
113
123 void CreateNetwork(const std::vector<Types::qubit_t> &qubits,
124 const std::vector<size_t> &cbits) {
125 size_t qubitsOffset = 0;
126 size_t cbitsOffset = 0;
127
128 for (size_t i = 0; i < qubits.size(); ++i) {
129 const size_t numQubits = qubits[i];
130 const size_t numBits = (i < cbits.size() ? cbits[i] : 0);
131 hosts.emplace_back(std::make_shared<SimpleHost<Time>>(
132 i, qubitsOffset, numQubits, cbitsOffset, numBits));
133 qubitsOffset += numQubits;
134 cbitsOffset += numBits;
135 }
136
137 for (size_t i = 0; i < hosts.size(); ++i) {
138 std::static_pointer_cast<SimpleHost<Time>>(hosts[i])->SetEntangledQubitId(
139 qubitsOffset);
140 std::static_pointer_cast<SimpleHost<Time>>(hosts[i])
141 ->SetEntangledQubitMeasurementBit(cbitsOffset);
142 ++qubitsOffset;
143 ++cbitsOffset;
144 }
145
146 controller = std::make_shared<Controller>();
147 }
148
160 const std::shared_ptr<Circuits::Circuit<Time>> &circuit) override {
161 const auto recreate = recreateIfNeeded;
162
165 size_t numQubits = 2;
166 if (simulator) {
167 simType = simulator->GetType();
168 method = simulator->GetSimulationType();
169 numQubits = simulator->GetNumberOfQubits();
170 }
171
172 recreateIfNeeded = false;
173
174 const auto res = RepeatedExecute(circuit, 1);
175
176 recreateIfNeeded = recreate;
177
178 // put the results in the state
179 if (!res.empty()) {
180 const auto &first = *res.begin();
181 GetState().SetResultsInOrder(first.first);
182 }
183
184 if (recreate &&
185 (!simulator ||
186 (simulator && (simType != simulator->GetType() ||
187 method != simulator->GetSimulationType() ||
188 simulator->GetNumberOfQubits() != numQubits))))
189 CreateSimulator(simType, method);
190 }
191
204 void ExecuteOnHost(const std::shared_ptr<Circuits::Circuit<Time>> &circuit,
205 size_t hostId) override {
206 const auto recreate = recreateIfNeeded;
207
210 size_t numQubits = 2;
211 if (simulator) {
212 simType = simulator->GetType();
213 method = simulator->GetSimulationType();
214 numQubits = simulator->GetNumberOfQubits();
215 }
216
217 recreateIfNeeded = false;
218
219 const auto res = RepeatedExecuteOnHost(circuit, hostId, 1);
220
221 recreateIfNeeded = recreate;
222
223 // put the results in the state
224 if (!res.empty()) {
225 const auto &first = *res.begin();
226 GetState().SetResultsInOrder(first.first);
227 }
228
229 if (recreate &&
230 (!simulator ||
231 (simulator && (simType != simulator->GetType() ||
232 method != simulator->GetSimulationType() ||
233 simulator->GetNumberOfQubits() != numQubits))))
234 CreateSimulator(simType, method);
235 }
236
252 std::vector<double> ExecuteExpectations(
253 const std::shared_ptr<Circuits::Circuit<Time>> &circuit,
254 const std::vector<std::string> &paulis) override {
255 const auto recreate = recreateIfNeeded;
256
259 size_t numQubits = 2;
260 if (simulator) {
261 simType = simulator->GetType();
262 method = simulator->GetSimulationType();
263 numQubits = simulator->GetNumberOfQubits();
264 }
265
266 recreateIfNeeded = false;
267
268 pauliStrings = &paulis;
269 const auto res = RepeatedExecute(circuit, 1);
270 pauliStrings = nullptr;
271
272 recreateIfNeeded = recreate;
273
274 // put the results in the state
275 if (!res.empty()) {
276 const auto &first = *res.begin();
277 GetState().SetResultsInOrder(first.first);
278 }
279
280 std::vector<double> expectations(paulis.size());
281 if (simulator) {
282 // translate the pauli strings to the mapped order of qubits
283 const size_t numOps = simulator->GetNumberOfQubits();
284
285 auto optimiser = controller->GetOptimiser();
286 if (optimiser) {
287 // convert the classical state results back to the expected order
288 const auto &qubitsMap = optimiser->GetQubitsMap();
289
290 for (size_t i = 0; i < paulis.size(); ++i) {
291 std::string translated(numOps, 'I');
292
293 for (size_t j = 0; j < paulis[i].size(); ++j) {
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 expectations[i] = simulator->ExpectationValue(translated);
302 }
303 } else {
304 for (size_t i = 0; i < paulis.size(); ++i)
305 expectations[i] = simulator->ExpectationValue(paulis[i]);
306 }
307 }
308
309 if (recreate && (!simulator || simType != simulator->GetType() ||
310 method != simulator->GetSimulationType() ||
311 simulator->GetNumberOfQubits() != numQubits))
312 CreateSimulator(simType, method);
313
314 return expectations;
315 }
316
332 std::vector<double> ExecuteOnHostExpectations(
333 const std::shared_ptr<Circuits::Circuit<Time>> &circuit, size_t hostId,
334 const std::vector<std::string> &paulis) override {
335 const auto recreate = recreateIfNeeded;
336
339 size_t numQubits = 2;
340 if (simulator) {
341 simType = simulator->GetType();
342 method = simulator->GetSimulationType();
343 numQubits = simulator->GetNumberOfQubits();
344 }
345
346 // RAII: restore recreateIfNeeded and clear pauliStrings on any exit path.
347 struct ScopedRestore {
348 bool &flag, saved;
349 const std::vector<std::string> **ps;
350 ScopedRestore(bool &f, const std::vector<std::string> **p)
351 : flag(f), saved(f), ps(p) {
352 flag = false;
353 }
354 ~ScopedRestore() {
355 flag = saved;
356 *ps = nullptr;
357 }
358 } restoreGuard(recreateIfNeeded, &pauliStrings);
359
360 pauliStrings = &paulis;
361 const auto res = RepeatedExecuteOnHost(circuit, hostId, 1);
362
363 // put the results in the state
364 if (!res.empty()) {
365 const auto &first = *res.begin();
366 GetState().SetResultsInOrder(first.first);
367 }
368
369 // for (const auto& m : qubitsMapOnHost)
370 // std::cout << "Mapping qubit " << m.first << " to " << m.second <<
371 // std::endl;
372
373 const size_t offsetBase = qubitsMapOnHost.size();
374
375 std::vector<double> expectations(paulis.size(), 1.);
376 if (simulator) {
377 // translate the pauli strings to the mapped order of qubits
378 const size_t numOps = simulator->GetNumberOfQubits();
379
380 // convert the pauli strings to the actual qubits order
381 for (size_t i = 0; i < paulis.size(); ++i) {
382 std::string translated(std::max(numOps, paulis[i].size()), 'I');
383
384 size_t offset = offsetBase;
385
386 for (size_t j = 0; j < paulis[i].size(); ++j) {
387 auto pos = qubitsMapOnHost.find(j);
388 if (pos != qubitsMapOnHost.end())
389 translated[pos->second] = paulis[i][j];
390 else {
391 translated[offset] = paulis[i][j];
392 ++offset;
393 }
394 }
395
396 // std::cout << "Translated pauli string: " << translated << std::endl;
397
398 expectations[i] = simulator->ExpectationValue(translated);
399 }
400 } else {
401 throw std::runtime_error(
402 "ExecuteOnHostExpectations: no simulator available after execution.");
403 }
404
405 if (recreate && (!simulator || simType != simulator->GetType() ||
406 method != simulator->GetSimulationType() ||
407 simulator->GetNumberOfQubits() != numQubits))
408 CreateSimulator(simType, method);
409
410 return expectations;
411 }
412
424 std::vector<std::complex<double>> ExecuteOnHostAmplitudes(
425 const std::shared_ptr<Circuits::Circuit<Time>> &circuit,
426 size_t hostId) override {
427 const auto recreate = recreateIfNeeded;
428
431 size_t numQubits = 2;
432 if (simulator) {
433 simType = simulator->GetType();
434 method = simulator->GetSimulationType();
435 numQubits = simulator->GetNumberOfQubits();
436 }
437
438 // RAII: restore recreateIfNeeded on any exit path (including exceptions).
439 struct ScopedRestoreFlag {
440 bool &flag, saved;
441 ScopedRestoreFlag(bool &f) : flag(f), saved(f) { flag = false; }
442 ~ScopedRestoreFlag() { flag = saved; }
443 } restoreGuard(recreateIfNeeded);
444
445 const auto res = RepeatedExecuteOnHost(circuit, hostId, 1);
446
447 if (!res.empty()) {
448 const auto &first = *res.begin();
449 GetState().SetResultsInOrder(first.first);
450 }
451
452 if (!simulator)
453 throw std::runtime_error(
454 "ExecuteOnHostAmplitudes: no simulator available after execution.");
455
456 std::vector<std::complex<double>> amplitudes;
457 const size_t n = simulator->GetNumberOfQubits();
458 const size_t dim = 1ULL << n;
459 amplitudes.resize(dim);
460 for (size_t state = 0; state < dim; ++state)
461 amplitudes[state] = simulator->Amplitude(state);
462
463 // Remap amplitudes back to the original qubit ordering if qubits were
464 // remapped during execution on the host.
465 if (!qubitsMapOnHost.empty()) {
466 const size_t offsetBase = qubitsMapOnHost.size();
467
468 // Build reverse mapping: simulator qubit position -> original qubit
469 // position.
470 std::vector<size_t> simToOrig(n);
471 size_t offset = offsetBase;
472
473 for (size_t qbit = 0; qbit < n; ++qbit) {
474 auto pos = qubitsMapOnHost.find(qbit);
475 if (pos != qubitsMapOnHost.end())
476 simToOrig[pos->second] = pos->first;
477 else
478 simToOrig[qbit] = offset++;
479 }
480
481 std::vector<std::complex<double>> remapped(dim);
482
483 for (size_t sim_state = 0; sim_state < dim; ++sim_state) {
484 size_t orig_state = 0;
485 for (size_t qbit = 0; qbit < n; ++qbit) {
486 if (sim_state & (1ULL << qbit))
487 orig_state |= (1ULL << simToOrig[qbit]);
488 }
489 if (orig_state < dim) remapped[orig_state] = amplitudes[sim_state];
490 }
491 amplitudes.swap(remapped);
492 }
493
494 if (recreate && (!simulator || simType != simulator->GetType() ||
495 method != simulator->GetSimulationType() ||
496 simulator->GetNumberOfQubits() != numQubits))
497 CreateSimulator(simType, method);
498
499 return amplitudes;
500 }
501
515 std::complex<double> ExecuteOnHostProjectOnZero(
516 const std::shared_ptr<Circuits::Circuit<Time>> &circuit,
517 size_t hostId) override {
518 const auto recreate = recreateIfNeeded;
519
522 size_t numQubits = 2;
523 if (simulator) {
524 simType = simulator->GetType();
525 method = simulator->GetSimulationType();
526 numQubits = simulator->GetNumberOfQubits();
527 }
528
529 // RAII: restore recreateIfNeeded on any exit path (including exceptions).
530 struct ScopedRestoreFlag {
531 bool &flag, saved;
532 ScopedRestoreFlag(bool &f) : flag(f), saved(f) { flag = false; }
533 ~ScopedRestoreFlag() { flag = saved; }
534 } restoreGuard(recreateIfNeeded);
535
536 const auto res = RepeatedExecuteOnHost(circuit, hostId, 1);
537
538 if (!res.empty()) {
539 const auto &first = *res.begin();
540 GetState().SetResultsInOrder(first.first);
541 }
542
543 if (!simulator)
544 throw std::runtime_error(
545 "ExecuteOnHostProjectOnZero: no simulator available after "
546 "execution.");
547
548 const std::complex<double> result = simulator->ProjectOnZero();
549
550 if (recreate && (!simulator || simType != simulator->GetType() ||
551 method != simulator->GetSimulationType() ||
552 simulator->GetNumberOfQubits() != numQubits))
553 CreateSimulator(simType, method);
554
555 return result;
556 }
557
572 const std::shared_ptr<Circuits::Circuit<Time>> &circuit,
573 size_t shots = 1000) override {
574 if (!controller || !circuit) return {};
575
576 distCirc = controller->DistributeCircuit(BaseClass::getptr(), circuit);
577 if (!distCirc) return {};
578
579#ifdef _DEBUG
580 for (auto q : distCirc->AffectedQubits()) {
581 if (q >= GetNumQubits()) {
582 std::cout
583 << "This is a distributed circuit, using entanglement or cutting"
584 << std::endl;
585 break;
586 }
587 }
588#endif
589
590 if (!simulator) return {};
591
592 auto simType = simulator->GetType();
593 if (distCirc->HasOpsAfterMeasurements() &&
594 (
595#ifndef NO_QISKIT_AER
597#endif
599 distCirc->MoveMeasurementsAndResets();
600
601 auto method = simulator->GetSimulationType();
602
603 const auto saveSimType = simType;
604 const auto saveMethod = method;
605
606 if (GetOptimizeSimulator() && distCirc->IsClifford() &&
608 // this is for the gpu simulator, as it doesn't support stabilizer
609#ifdef __linux__
610 && !Simulators::IsGpuSimulator(simType)
611#endif
612 ) {
614
617#ifndef NO_QISKIT_AER
620#endif
621 }
622
623 ExecuteResults res;
624 const size_t nrQubits = GetNumQubits() + GetNumNetworkEntangledQubits();
625 const size_t nrCbitsResults = GetNumClassicalBits();
626
628
629 // do that only if the optimization for simulator is on and the estimator is
630 // available, ortherwise an 'optimal' simulator won't be created
632 simulatorsEstimator->IsInitialized()) {
633 simulator->Clear();
634 GetState().Clear();
635 }
636
637 curMaxBondDim = 0;
638
639 std::vector<bool> executed;
640 auto optSim =
641 ChooseBestSimulator(distCirc, shots, nrQubits, nrQubits, nrCbitsResults,
642 simType, method, executed);
643
644 lastSimulatorType = simType;
645 lastMethod = method;
646 lastGpuDevice = -1;
647
648 size_t nrThreads = GetMaxSimulators();
649
650#ifdef __linux__
651 if (Simulators::IsGpuSimulator(simType))
652 nrThreads = 1;
653 else
654#endif
656 !distCirc->HasOpsAfterMeasurements()) ||
658 nrThreads = 1;
659
660 nrThreads = std::min(nrThreads, std::max<size_t>(shots, 1ULL));
661
662 std::mutex resultsMutex;
663
664 auto dcirc = distCirc;
665
666 if (nrThreads > 1) {
667 // since it's going to execute on multiple threads, free the memory from
668 // the network's simulator and state, it's going to use other ones,
669 // created in the threads if optimization already exists, it will be
670 // cloned in the threads, otherwise a new one will be created in the
671 // threads
674 ->IsInitialized()) // otherwise it was already cleared
675 {
676 simulator->Clear();
677 GetState().Clear();
678 }
679
680 const size_t cntPerThread = std::max<size_t>(shots / nrThreads, 1ULL);
681
682 threadsPool.Resize(nrThreads);
683 threadsPool.SetFinishLimit(shots);
684
685 uint64_t jobStream = 0;
686 while (shots > 0) {
687 const size_t curCnt = std::min(cntPerThread, shots);
688
689 shots -= curCnt;
690
691 auto job = std::make_shared<ExecuteJob<Time>>(
692 dcirc, res, curCnt, nrQubits, nrQubits, nrCbitsResults, simType,
693 method, resultsMutex);
694 job->optimiseMultipleShotsExecution = GetOptimizeSimulator();
695
696 job->network = BaseClass::getptr();
697 job->curMaxBondDim = &curMaxBondDim;
698
699 job->config = ExecutionConfiguration(simType, nrQubits);
700 if (configuration.IsSet("seed")) {
701 const uint64_t childSeed = Simulators::IState::DeriveSeed(
702 std::stoull(configuration.GetConfiguration("seed")), jobStream++);
703 job->config.SetConfiguration("seed", std::to_string(childSeed));
704 }
705
706 if (optSim) {
707 job->optSim = optSim->Clone();
708 job->executedGates = executed;
709 if (job->config.IsSet("seed"))
710 job->optSim->SetSeed(
711 std::stoull(job->config.GetConfiguration("seed")));
712 }
713
714 threadsPool.AddRunJob(std::move(job));
715 }
716
717 threadsPool.WaitForFinish();
718 threadsPool.Stop();
719 } else {
720 const size_t curCnt = shots;
721
722 auto job = std::make_shared<ExecuteJob<Time>>(
723 dcirc, res, curCnt, nrQubits, nrQubits, nrCbitsResults, simType,
724 method, resultsMutex);
725 job->optimiseMultipleShotsExecution = GetOptimizeSimulator();
726
727 job->network = BaseClass::getptr();
728 job->curMaxBondDim = &curMaxBondDim;
729
730 job->config = ExecutionConfiguration(simType, nrQubits);
731
732 if (optSim) {
733 optSim->SetMultithreading(true);
734 job->optSim = optSim;
735 job->executedGates = executed;
736 } else {
737 if (simulator && method == saveMethod && simType == saveSimType) {
738 // use the already created simulator
739 optSim = simulator;
740 job->optSim = optSim;
741 OptimizeMPSInitialQubitsMap(optSim, dcirc,
742 optSim->GetNumberOfQubits());
743 job->executedGates.resize(dcirc->size(),
744 false); // no gates executed yet
745 simulator = nullptr;
746 }
747 }
748
749 job->DoWorkNoLock();
750 lastGpuDevice = job->optSim ? job->optSim->GetGpuDevice() : -1;
751 if (!recreateIfNeeded) simulator = job->optSim;
752 }
753
754 if (recreateIfNeeded) CreateSimulator(saveSimType, saveMethod);
755
757
758 return res;
759 }
760
777 const std::shared_ptr<Circuits::Circuit<Time>> &circuit, size_t hostId,
778 size_t shots = 1000) override {
779 if (!circuit || hostId >= GetNumHosts()) return {};
780
781 size_t nrQubits = 0;
782 size_t nrCbits = 0;
783
784 const bool distributed = simulator &&
786 auto mappingCircuit = circuit;
787 if (!distributed && GetController()->GetOptimizeCircuit()) {
788 mappingCircuit = std::static_pointer_cast<Circuits::Circuit<Time>>(circuit->Clone());
789 mappingCircuit->Optimize();
790 }
791 const auto reverseQubitsMap = MapCircuitOnHost(
792 mappingCircuit, hostId, nrQubits, nrCbits, true);
793 // Resolve indexing before optimization can remove a wire that disambiguates
794 // local from global numbering (for example a cancelling pair on qubit 0).
795 if (distributed && distCirc && GetController()->GetOptimizeCircuit())
796 distCirc->Optimize();
797 if (nrCbits == 0) nrCbits = nrQubits;
798
799 if (!simulator || !distCirc) return {};
800
801 auto simType = simulator->GetType();
802
804
805 if (distCirc->HasOpsAfterMeasurements() &&
806 (
807#ifndef NO_QISKIT_AER
809#endif
811 distCirc->MoveMeasurementsAndResets();
812
813 auto method = simulator->GetSimulationType();
814 const auto saveSimType = simType;
815 const auto saveMethod = method;
816
817 if (GetOptimizeSimulator() && distCirc->IsClifford() &&
819 // this is for the gpu simulator, as it doesn't support stabilizer
820#ifdef __linux__
821 && !Simulators::IsGpuSimulator(simType)
822#endif
823 ) {
825
828#ifndef NO_QISKIT_AER
831#endif
832 }
833
834 ExecuteResults res;
835
836 // since it's going to execute on multiple threads, free the memory from the
837 // network's simulator and state, it's going to use other ones, created in
838 // the threads
839 simulator->Clear();
840 GetState().Clear();
841
842 curMaxBondDim = 0;
843
844 std::vector<bool> executed;
845 auto optSim = ChooseBestSimulator(distCirc, shots, nrQubits, nrCbits,
846 nrCbits, simType, method, executed);
847
848 lastSimulatorType = simType;
849 lastMethod = method;
850 lastGpuDevice = -1;
851
852 size_t nrThreads = GetMaxSimulators();
853
854#ifdef __linux__
855 if (Simulators::IsGpuSimulator(simType))
856 nrThreads = 1;
857 else
858#endif
861 !distCirc->HasOpsAfterMeasurements()) ||
863 nrThreads = 1;
864
865 nrThreads = std::min(nrThreads, std::max<size_t>(shots, 1ULL));
866
867 // WARNING: be sure to not put this above ChooseBestSimulator, as that one
868 // can change the shots value!
869
870 std::mutex resultsMutex;
871
872 const auto dcirc = distCirc;
873
874 if (nrThreads > 1) {
875 // this rounds up, rounding down is better
876 // const size_t cntPerThread = static_cast<size_t>((shots - 1) / nrThreads
877 // + 1);
878 const size_t cntPerThread = std::max<size_t>(shots / nrThreads, 1ULL);
879
880 threadsPool.Resize(nrThreads);
881 threadsPool.SetFinishLimit(shots);
882
883 uint64_t jobStream = 0;
884 while (shots > 0) {
885 const size_t curCnt = std::min(cntPerThread, shots);
886 shots -= curCnt;
887
888 auto job = std::make_shared<ExecuteJob<Time>>(
889 dcirc, res, curCnt, nrQubits, nrCbits, nrCbits, simType, method,
890 resultsMutex);
891 job->optimiseMultipleShotsExecution = GetOptimizeSimulator();
892
893 job->network = BaseClass::getptr();
894 job->curMaxBondDim = &curMaxBondDim;
895
896 job->config = ExecutionConfiguration(simType, nrQubits);
897 if (configuration.IsSet("seed")) {
898 const uint64_t childSeed = Simulators::IState::DeriveSeed(
899 std::stoull(configuration.GetConfiguration("seed")), jobStream++);
900 job->config.SetConfiguration("seed", std::to_string(childSeed));
901 }
902
903 if (optSim) {
904 job->optSim = optSim->Clone();
905 job->executedGates = executed;
906 if (job->config.IsSet("seed"))
907 job->optSim->SetSeed(
908 std::stoull(job->config.GetConfiguration("seed")));
909 }
910
911 threadsPool.AddRunJob(std::move(job));
912 }
913
914 threadsPool.WaitForFinish();
915 threadsPool.Stop();
916 } else {
917 const size_t curCnt = shots;
918
919 auto job = std::make_shared<ExecuteJob<Time>>(
920 dcirc, res, curCnt, nrQubits, nrCbits, nrCbits, simType, method,
921 resultsMutex);
922 job->optimiseMultipleShotsExecution = GetOptimizeSimulator();
923
924 job->network = BaseClass::getptr();
925 job->curMaxBondDim = &curMaxBondDim;
926
927 job->config = ExecutionConfiguration(simType, nrQubits);
928
929 if (optSim) {
930 optSim->SetMultithreading(true);
931 job->optSim = optSim;
932 job->executedGates = executed;
933 }
934
935 job->DoWorkNoLock();
936 lastGpuDevice = job->optSim ? job->optSim->GetGpuDevice() : -1;
937 if (!recreateIfNeeded) simulator = job->optSim;
938 }
939
940 if (recreateIfNeeded) CreateSimulator(saveSimType, saveMethod);
941
942 if (!reverseQubitsMap.empty()) ConvertBackResults(res, reverseQubitsMap);
943
944 return res;
945 }
946
956 const std::shared_ptr<Circuits::Circuit<Time>> &circuit) const override {
957 if (!circuit) return 0;
958
959 size_t distgates = 0;
960
961 for (const auto &op : circuit->GetOperations())
962 if (!IsLocalOperation(op)) ++distgates;
963
964 return distgates;
965 }
966
983 std::vector<ExecuteResults> ExecuteScheduled(
984 const std::vector<Schedulers::ExecuteCircuit<Time>> &circuits) override {
985 // create a default one if not set
986 if (!GetScheduler()) {
988
989 if (!GetScheduler()) return {};
990 }
991
992 return GetScheduler()->ExecuteScheduled(circuits);
993 }
994
1016 Simulators::SimulationType simExecType =
1018 size_t nrQubits = 0) override {
1019 classicalState.Clear();
1020 classicalState.AllocateBits(GetNumClassicalBits() +
1022
1023 simulator =
1025
1026 if (simulator) {
1027 const size_t allocationQubits = nrQubits == 0
1028 ? GetNumQubits() + GetNumNetworkEntangledQubits() : nrQubits;
1029 ExecutionConfiguration(simType, allocationQubits)
1030 .ApplyConfigurationToSimulator(simulator);
1031
1032 simulator->AllocateQubits(allocationQubits);
1033 simulator->Initialize();
1034
1035 // Pin the resolved default as well as explicit selections. Cloning or
1036 // recreating this network must not follow later process-default changes.
1038 configuration.SetConfiguration(
1039 "gpu_device", std::to_string(simulator->GetGpuDevice()));
1040
1041 if (Simulators::IsDistributedGpuSimulator(simType) && nrQubits == 0) {
1043 simulator->GetConfiguration("distributed_shard_devices");
1044 }
1045 simulator->setGrowthFactorGate(growthFactorGate);
1046 simulator->setGrowthFactorSwap(growthFactorSwap);
1047 simulator->SetLookaheadDepth(lookaheadDepth);
1048 simulator->SetLookaheadDepthWithHeuristic(lookaheadDepthWithHeuristic);
1049 }
1050 }
1051
1061 void Configure(const char *key, const char *value) override {
1062 if (!key || !value) return;
1063
1064 if (std::string("distributed_host_qubit_indexing") == key) {
1065 const std::string mode(value);
1066 if (mode != "auto" && mode != "local" && mode != "global")
1067 throw std::invalid_argument(
1068 "distributed_host_qubit_indexing must be auto, local or global");
1070 return;
1071 }
1072
1073 if (std::string("distributed_devices") == key ||
1074 std::string("gpu_device") == key)
1076
1077 if (std::string("max_simulators") == key)
1078 maxSimulators = std::stoull(value);
1079
1080 if (std::string("gpu_device") == key) {
1082 if (simulator) simulator->Configure(key, value);
1083 configuration.SetConfiguration(key, value);
1084 return;
1085 }
1087 if (simulator) simulator->Configure(key, value);
1088 configuration.SetConfiguration(key, value);
1089 return;
1090 }
1091 configuration.SetConfiguration(key, value);
1092
1093 if (simulator) simulator->Configure(key, value);
1094 }
1095
1104 std::shared_ptr<Simulators::ISimulator> GetSimulator() const override {
1105 return simulator;
1106 }
1107
1117
1130 SchedulerType schType =
1132 if (!controller) return;
1133
1134 controller->CreateScheduler(BaseClass::getptr(), schType);
1135 }
1136
1145 std::shared_ptr<Schedulers::IScheduler<Time>> GetScheduler() const override {
1146 if (!controller) return nullptr;
1147
1148 return controller->GetScheduler();
1149 }
1150
1160 const std::shared_ptr<IHost<Time>> GetHost(size_t hostId) const override {
1161 if (hostId >= hosts.size()) return nullptr;
1162
1163 return hosts[hostId];
1164 }
1165
1174 const std::shared_ptr<IController<Time>> GetController() const override {
1175 return controller;
1176 }
1177
1185 size_t GetNumHosts() const override { return hosts.size(); }
1186
1195 size_t GetNumQubits() const override {
1196 size_t res = 0;
1197
1198 for (const auto &host : hosts) res += host->GetNumQubits();
1199
1200 return res;
1201 }
1202
1212 size_t GetNumQubitsForHost(size_t hostId) const override {
1213 if (hostId >= hosts.size()) return 0;
1214
1215 return hosts[hostId]->GetNumQubits();
1216 }
1217
1226 size_t GetNumNetworkEntangledQubits() const override {
1227 size_t res = 0;
1228
1229 for (const auto &host : hosts) res += host->GetNumNetworkEntangledQubits();
1230
1231 return res;
1232 }
1233
1246 size_t GetNumNetworkEntangledQubitsForHost(size_t hostId) const override {
1247 if (hostId >= hosts.size()) return 0;
1248
1249 return hosts[hostId]->GetNumNetworkEntangledQubits();
1250 }
1251
1260 size_t GetNumClassicalBits() const override {
1261 size_t res = 0;
1262
1263 for (const auto &host : hosts) res += host->GetNumClassicalBits();
1264
1265 return res;
1266 }
1267
1279 size_t GetNumClassicalBitsForHost(size_t hostId) const override {
1280 if (hostId >= hosts.size()) return 0;
1281
1282 return hosts[hostId]->GetNumClassicalBits();
1283 }
1284
1294 std::vector<std::shared_ptr<IHost<Time>>> &GetHosts() { return hosts; }
1295
1303 void SetController(const std::shared_ptr<IController<Time>> &cntrl) {
1304 controller = cntrl;
1305 }
1306
1320 bool SendPacket(size_t fromHostId, size_t toHostId,
1321 const std::vector<uint8_t> &packet) override {
1322 return false;
1323 }
1324
1332 NetworkType GetType() const override {
1334 }
1335
1347 const std::shared_ptr<Circuits::IOperation<Time>> &op) const override {
1348 const auto qubits = op->AffectedQubits();
1349
1350 if (qubits.empty()) return true;
1351
1352 size_t firstQubit = qubits[0];
1353
1354 for (size_t q = 1; q < qubits.size(); ++q)
1355 if (!AreQubitsOnSameHost(firstQubit, qubits[q])) return false;
1356
1357 return true;
1358 }
1359
1373 const std::shared_ptr<Circuits::IOperation<Time>> &op) const override {
1374 const auto qubits = op->AffectedQubits();
1375
1376 if (qubits.empty()) return false;
1377
1378 // grab the first qubit that is on a host (skip over network entangled
1379 // qubits)
1380 size_t firstQubit = qubits[0];
1381 size_t q = 1;
1382 for (; IsNetworkEntangledQubit(firstQubit) && q < qubits.size(); ++q)
1383 firstQubit = qubits[q];
1384
1385 // check to see one of the other qubits is on a different host, but ignore
1386 // the network entangled qubits
1387 for (; q < qubits.size(); ++q)
1388 if (!IsNetworkEntangledQubit(qubits[q]) &&
1389 !AreQubitsOnSameHost(firstQubit, qubits[q]))
1390 return true;
1391
1392 return false;
1393 }
1394
1407 const std::shared_ptr<Circuits::IOperation<Time>> &op) const override {
1408 const auto qubits = op->AffectedQubits();
1409
1410 if (qubits.empty()) return false;
1411
1412 for (size_t q = 0; q < qubits.size(); ++q)
1413 if (IsNetworkEntangledQubit(qubits[q])) return true;
1414
1415 return false;
1416 }
1417
1428 const std::shared_ptr<Circuits::IOperation<Time>> &op) const override {
1429 if (op->GetType() != Circuits::OperationType::kGate) return false;
1430 const auto qubits = op->AffectedQubits();
1431 if (qubits.size() != 2) return false;
1432
1433 return IsNetworkEntangledQubit(qubits[0]) &&
1434 IsNetworkEntangledQubit(qubits[1]);
1435 }
1436
1448 const std::shared_ptr<Circuits::IOperation<Time>> &op) const override {
1449 if (!op->IsConditional()) return false;
1450
1451 const auto qubits = op->AffectedQubits();
1452
1453 const std::shared_ptr<Circuits::IConditionalOperation<Time>> condOp =
1454 std::static_pointer_cast<Circuits::IConditionalOperation<Time>>(op);
1455 const auto &classicalBits = condOp->GetCondition()->GetBitsIndices();
1456
1457 if (qubits.empty() && classicalBits.empty())
1458 throw std::runtime_error(
1459 "No classical bits specified!"); // this would be odd!
1460
1461 // consider it on the host where it has the first qubit (or bit, if there
1462 // are no qubits)
1463
1464 const size_t hostId = GetHostIdForAnyQubit(qubits[0]);
1465
1466 // now check the classical bits
1467 for (const auto bit : classicalBits)
1468 if (hostId != GetHostIdForClassicalBit(bit)) return true;
1469
1470 return false;
1471 }
1472
1484 const std::shared_ptr<Circuits::IOperation<Time>> &op) const override {
1485 if (!op->IsConditional())
1486 throw std::runtime_error("Operation is not conditional!");
1487
1488 std::shared_ptr<Circuits::IConditionalOperation<Time>> condOp =
1489 std::static_pointer_cast<Circuits::IConditionalOperation<Time>>(op);
1490 const auto classicalBits = condOp->AffectedBits();
1491
1492 if (classicalBits.empty())
1493 throw std::runtime_error("No classical bits specified!");
1494
1495 return GetHostIdForClassicalBit(classicalBits[0]);
1496 }
1497
1506 bool AreQubitsOnSameHost(size_t qubitId1, size_t qubitId2) const override {
1507 for (const auto &host : hosts) {
1508 const bool present1 = host->IsQubitOnHost(qubitId1);
1509 const bool present2 = host->IsQubitOnHost(qubitId2);
1510
1511 if (present1 && present2)
1512 return true;
1513 else if (present1 || present2)
1514 return false;
1515 }
1516
1517 return false;
1518 }
1519
1529 bool AreClassicalBitsOnSameHost(size_t bitId1, size_t bitId2) const override {
1530 for (const auto &host : hosts) {
1531 const bool present1 = host->IsClassicalBitOnHost(bitId1);
1532 const bool present2 = host->IsClassicalBitOnHost(bitId2);
1533
1534 if (present1 && present2)
1535 return true;
1536 else if (present1 || present2)
1537 return false;
1538 }
1539
1540 return false;
1541 }
1542
1554 size_t bitId) const override {
1555 for (const auto &host : hosts) {
1556 const bool present1 = host->IsQubitOnHost(qubitId);
1557 const bool present2 = host->IsClassicalBitOnHost(bitId);
1558
1559 if (present1 && present2)
1560 return true;
1561 else if (present1 || present2)
1562 return false;
1563 }
1564
1565 return false;
1566 }
1567
1577 size_t GetHostIdForQubit(size_t qubitId) const override {
1578 for (const auto &host : hosts)
1579 if (host->IsQubitOnHost(qubitId)) return host->GetId();
1580
1581 return std::numeric_limits<size_t>::max();
1582 }
1583
1596 size_t GetHostIdForEntangledQubit(size_t qubitId) const override {
1597 for (const auto &host : hosts)
1598 if (host->IsEntangledQubitOnHost(qubitId)) return host->GetId();
1599
1600 return std::numeric_limits<size_t>::max();
1601 }
1602
1612 size_t GetHostIdForAnyQubit(size_t qubitId) const override {
1613 if (IsNetworkEntangledQubit(qubitId))
1614 return GetHostIdForEntangledQubit(qubitId);
1615
1616 return GetHostIdForQubit(qubitId);
1617 }
1618
1628 size_t GetHostIdForClassicalBit(size_t classicalBitId) const override {
1629 for (const auto &host : hosts)
1630 if (host->IsClassicalBitOnHost(classicalBitId)) return host->GetId();
1631
1632 return std::numeric_limits<size_t>::max();
1633 }
1634
1643 std::vector<size_t> GetQubitsIds(size_t hostId) const override {
1644 if (hostId >= hosts.size()) return std::vector<size_t>();
1645
1646 return hosts[hostId]->GetQubitsIds();
1647 }
1648
1659 std::vector<size_t> GetNetworkEntangledQubitsIds(
1660 size_t hostId) const override {
1661 if (hostId >= hosts.size()) return std::vector<size_t>();
1662
1663 return hosts[hostId]->GetNetworkEntangledQubitsIds();
1664 }
1665
1675 std::vector<size_t> GetClassicalBitsIds(size_t hostId) const override {
1676 if (hostId >= hosts.size()) return std::vector<size_t>();
1677
1678 return hosts[hostId]->GetClassicalBitsIds();
1679 }
1680
1692 size_t hostId) const override {
1693 if (hostId >= hosts.size()) return std::vector<size_t>();
1694
1695 return hosts[hostId]->GetEntangledQubitMeasurementBitIds();
1696 }
1697
1709 bool IsNetworkEntangledQubit(size_t qubitId) const override {
1710 return qubitId >= GetNumQubits();
1711 }
1712
1725 bool IsEntanglementQubitBusy(size_t qubitId) const override { return false; }
1726
1742 bool AreEntanglementQubitsBusy(size_t qubitId1,
1743 size_t qubitId2) const override {
1744 return false;
1745 }
1746
1759 void MarkEntangledQubitsBusy(size_t qubitId1, size_t qubitId2) override {
1760 throw std::runtime_error(
1761 "Entanglement between hosts is not supported in the simple network");
1762 }
1763
1775 void MarkEntangledQubitFree(size_t qubitId) override {
1776 throw std::runtime_error(
1777 "Entanglement between hosts is not supported in the simple network");
1778 }
1779
1789 void ClearEntanglements() override {
1790 throw std::runtime_error(
1791 "Entanglement between hosts is not supported in the simple network");
1792 }
1793
1803 std::shared_ptr<Circuits::Circuit<Time>> GetDistributedCircuit()
1804 const override {
1805 return distCirc;
1806 }
1807
1815 int GetLastGpuDevice() const override { return lastGpuDevice; }
1816
1820
1829 return lastMethod;
1830 }
1831
1842 size_t GetMaxSimulators() const override { return maxSimulators; }
1843
1855 void SetMaxSimulators(size_t val) override {
1856 if (val < 1)
1857 val = 1;
1858 else if (val > (size_t)QC::QubitRegisterCalculator<>::GetNumberOfThreads())
1859 val = (size_t)QC::QubitRegisterCalculator<>::GetNumberOfThreads();
1860
1861 maxSimulators = val;
1862 }
1863
1873 void SetOptimizeSimulator(bool optimize = true) override {
1874 optimizeSimulator = optimize;
1875 }
1876
1884 bool GetOptimizeSimulator() const override { return optimizeSimulator; }
1885
1894 const typename BaseClass::SimulatorsSet &GetSimulatorsSet() const override {
1896 }
1897
1908 Simulators::SimulationType kind) override {
1909 simulatorsForOptimizations.insert({type, kind});
1910 }
1911
1924
1941
1954 Simulators::SimulationType kind) const override {
1955 if (simulatorsForOptimizations.empty()) return true;
1956
1957 return simulatorsForOptimizations.find({type, kind}) !=
1959 }
1960
1967 std::shared_ptr<INetwork<Time>> Clone() const override {
1968 const size_t numHosts = GetNumHosts();
1969
1970 std::vector<Types::qubit_t> qubits(numHosts);
1971 std::vector<size_t> cbits(numHosts);
1972
1973 for (size_t h = 0; h < numHosts; ++h) {
1974 qubits[h] = GetNumQubitsForHost(h);
1975 cbits[h] = GetNumClassicalBitsForHost(h);
1976 }
1977
1978 const auto cloned =
1979 std::make_shared<SimpleDisconnectedNetwork<Time, Controller>>(qubits,
1980 cbits);
1981
1982 cloned->configuration = configuration;
1983 cloned->distributedHostQubitIndexing = distributedHostQubitIndexing;
1984 cloned->resolvedDistributedDevices = resolvedDistributedDevices;
1985
1986 cloned->maxSimulators = maxSimulators;
1987
1988 cloned->optimizeSimulator = optimizeSimulator;
1989 cloned->simulatorsForOptimizations = simulatorsForOptimizations;
1990
1991 cloned->SetMPSOptimizeSwaps(GetMPSOptimizeSwaps());
1992
1993 cloned->SetMPSOptimizationBondDimensionThreshold(GetMPSOptimizationBondDimensionThreshold());
1994 cloned->SetMPSOptimizationQubitsNumberThreshold(GetMPSOptimizationQubitsNumberThreshold());
1995
1996 cloned->SetLookaheadDepth(GetLookaheadDepth());
1997 cloned->SetLookaheadDepthWithHeuristic(GetLookaheadDepthWithHeuristic());
1998
1999 cloned->setGrowthFactorGate(getGrowthFactorGate());
2000 cloned->setGrowthFactorSwap(getGrowthFactorSwap());
2001
2002 if (GetSimulator())
2003 cloned->CreateSimulator(GetSimulator()->GetType(),
2005
2006 return cloned;
2007 }
2008
2009 std::shared_ptr<Simulators::ISimulator> ChooseBestSimulator(
2010 std::shared_ptr<Circuits::Circuit<Time>> &dcirc, size_t &counts,
2011 size_t nrQubits, size_t nrCbits, size_t nrResultCbits,
2013 std::vector<bool> &executed, bool multithreading = false,
2014 bool dontRunCircuitStart = false) override {
2015 // Distribution is an explicit execution choice. Timing-based backend
2016 // selection must not replace it or diverge between MPI ranks.
2017 if (Simulators::IsDistributedGpuSimulator(simType)) return nullptr;
2018 if (!optimizeSimulator) return nullptr;
2019
2020 if ((!simulatorsEstimator || !simulatorsEstimator->IsInitialized()) &&
2021 simulatorsForOptimizations.size() != 1)
2022 return nullptr;
2023
2024 // when multithreading is set to true it means it needs a multithreaded
2025 // simulator
2026
2027 std::vector<
2028 std::pair<Simulators::SimulatorType, Simulators::SimulationType>>
2029 simulatorTypes;
2030
2031 const bool checkTensorNetwork =
2033
2034 // the others are to be picked between statevector, composite, tensor
2035 // networks and mps, for now at least for tensor networks in the future it's
2036 // worth checking different contractors!!!!
2037 //
2038 // clifford was decided at higher level
2040 // compare qcsim with qiskit aer if qiskit aer is available, let the best
2041 // one win
2044 simulatorTypes.emplace_back(Simulators::SimulatorType::kQCSim,
2046
2047#ifndef NO_QISKIT_AER
2048 // if the number of shots is too small, probably it's not worth it, it's
2049 // going to be better to just execute them multithreading
2052 simulatorTypes.emplace_back(Simulators::SimulatorType::kQiskitAer,
2054#endif
2055 }
2056
2059 simulatorTypes.emplace_back(Simulators::SimulatorType::kQCSim,
2061
2064 simulatorTypes.emplace_back(Simulators::SimulatorType::kCompositeQCSim,
2066
2067 if (checkTensorNetwork &&
2070 simulatorTypes.emplace_back(Simulators::SimulatorType::kQCSim,
2072
2073 const long long int maxBondDim = configuration.GetConfigurationAsInt(
2074 "matrix_product_state_max_bond_dimension");
2075
2079 (nrQubits <= 4 || maxBondDim > 0))
2080 simulatorTypes.emplace_back(
2083
2087 simulatorTypes.emplace_back(Simulators::SimulatorType::kQCSim,
2089
2093 simulatorTypes.emplace_back(Simulators::SimulatorType::kQCSim,
2095
2096#ifndef NO_QISKIT_AER
2097 // tensor networks are out of the picture for now for qiskit aer, since they
2098 // are available with cuda library, and work only on linux (obviously when
2099 // compiled properly and if there is the right hw an driver installed)
2100
2103 simulatorTypes.emplace_back(Simulators::SimulatorType::kQiskitAer,
2105
2109 simulatorTypes.emplace_back(
2112
2116 (nrQubits <= 4 || maxBondDim > 0))
2117 simulatorTypes.emplace_back(
2120#endif
2121
2122#ifdef __linux__
2123 const int gpuDevice = configuration.IsSet("gpu_device")
2124 ? Simulators::Configuration::ParseGpuDevice(configuration.GetConfiguration("gpu_device"))
2125 : Simulators::SimulatorsFactory::ResolveGpuDevice();
2126 Simulators::SimulatorsFactory::ScopedGpuDevice gpuDeviceScope(gpuDevice);
2127 const bool hasGpuCandidate = std::any_of(
2129 [](const auto& candidate) { return candidate.first == Simulators::SimulatorType::kGpuSim; });
2130 if (configuration.IsSet("gpu_device") && hasGpuCandidate &&
2132 throw std::runtime_error("Unable to initialize requested GPU device " + std::to_string(gpuDevice));
2133 if (hasGpuCandidate && Simulators::SimulatorsFactory::IsGpuLibraryAvailable(gpuDevice)) {
2136 simulatorTypes.emplace_back(Simulators::SimulatorType::kGpuSim,
2141 simulatorTypes.emplace_back(
2147 simulatorTypes.emplace_back(Simulators::SimulatorType::kGpuSim,
2152 simulatorTypes.emplace_back(
2155 }
2156#endif
2157
2160 simulatorTypes.emplace_back(Simulators::SimulatorType::kQuestSim,
2162
2163
2164 // Honor a singleton optimization set (e.g. density_matrix) even if it is
2165 // not in the hardcoded candidate list. Skip backends that cannot actually
2166 // be constructed, such as GPU MPS when the GPU library is missing: the
2167 // caller then keeps the network simulator instead of recording 0 shots.
2168 if (simulatorTypes.empty() && simulatorsForOptimizations.size() == 1) {
2169 const auto candidate = *simulatorsForOptimizations.begin();
2170 if (candidate.first == Simulators::SimulatorType::kGpuSim &&
2173 candidate.second))
2174 simulatorTypes.push_back(candidate);
2175 }
2176
2177 if (simulatorTypes.empty())
2178 return nullptr;
2179 else if (simulatorTypes.size() == 1) {
2180 const auto candidateType = simulatorTypes[0].first;
2181 const auto candidateMethod = simulatorTypes[0].second;
2182
2183 std::shared_ptr<Simulators::ISimulator> sim =
2185 candidateMethod);
2186 if (sim) {
2187 simType = candidateType;
2188 method = candidateMethod;
2189 configuration.ApplyConfigurationToSimulator(sim);
2190
2192 sim->AllocateQubits(nrQubits);
2193 sim->Initialize();
2194
2195 sim->setGrowthFactorGate(growthFactorGate);
2196 sim->setGrowthFactorSwap(growthFactorSwap);
2197 sim->SetLookaheadDepth(lookaheadDepth);
2198 sim->SetLookaheadDepthWithHeuristic(lookaheadDepthWithHeuristic);
2199
2200 OptimizeMPSInitialQubitsMap(sim, dcirc, nrQubits);
2201 } else {
2202 sim->AllocateQubits(nrQubits);
2203 sim->Initialize();
2204 }
2205
2206 if (!dontRunCircuitStart) {
2207 sim->SetMultithreading(true);
2209 Time>::ExecuteUpToMeasurements(dcirc, nrQubits, nrCbits,
2210 nrResultCbits, sim, executed, &curMaxBondDim);
2211 }
2212 sim->SetMultithreading(multithreading || GetMaxSimulators() == 1);
2213
2214 return sim;
2215 }
2216
2217 // Singleton backend cannot be constructed (typical for GPU on CI).
2218 // Leave simType/method unchanged so RepeatedExecuteOnHost keeps the
2219 // network simulator instead of recording empty counts.
2220 return nullptr;
2221 }
2222
2223 const double singularValueThreshold =
2224 configuration.GetConfigurationAsDouble(
2225 "matrix_product_state_truncation_threshold");
2226
2227 const std::string truncationMode = configuration.GetConfiguration(
2228 "matrix_product_state_truncation_mode");
2229
2230 const std::string mpsSample = configuration.GetConfiguration(
2231 "mps_sample_measure_algorithm");
2232
2233 std::shared_ptr<Simulators::ISimulator> sim =
2234 simulatorsEstimator->ChooseBestSimulator(
2235 simulatorTypes, dcirc, counts, nrQubits, nrCbits, nrResultCbits,
2236 simType, method, executed, maxBondDim, singularValueThreshold,
2237 truncationMode, mpsSample, GetMaxSimulators(), pauliStrings, multithreading);
2238
2239 if (sim) {
2240 sim->AllocateQubits(nrQubits);
2241 sim->Initialize();
2242
2243 sim->setGrowthFactorGate(growthFactorGate);
2244 sim->setGrowthFactorSwap(growthFactorSwap);
2245 sim->SetLookaheadDepth(lookaheadDepth);
2246 sim->SetLookaheadDepthWithHeuristic(lookaheadDepthWithHeuristic);
2247
2248 OptimizeMPSInitialQubitsMap(sim, dcirc, nrQubits);
2249
2250 if (!dontRunCircuitStart) {
2251 sim->SetMultithreading(true);
2253 dcirc, nrQubits, nrCbits, nrResultCbits, sim, executed, &curMaxBondDim);
2254 }
2255 sim->SetMultithreading(multithreading || GetMaxSimulators() == 1);
2256 }
2257
2258 return sim;
2259 }
2260
2261 void SetInitialQubitsMapOptimization(bool optimize = true) override {
2262 optimizeInitialQubitsMap = optimize;
2263 }
2264
2265 bool GetInitialQubitsMapOptimization() const override {
2266 return optimizeInitialQubitsMap;
2267 }
2268
2269 void SetMPSOptimizeSwaps(bool optimize = true) override {
2270 mpsOptimizeSwaps = optimize;
2271
2272 if (simulator) {
2273 simulator->SetLookaheadDepth(0);
2274 simulator->SetLookaheadDepthWithHeuristic(0);
2275 }
2276 }
2277
2278 bool GetMPSOptimizeSwaps() const override { return mpsOptimizeSwaps; }
2279
2280 void SetMPSOptimizationBondDimensionThreshold(size_t threshold) override {
2281 mpsOptimizationBondDimensionThreshold = threshold;
2282
2283 if (simulator &&
2284 std::stoull(simulator->GetConfiguration(
2285 "matrix_product_state_max_bond_dimension")) < threshold) {
2286 simulator->SetLookaheadDepth(0);
2287 simulator->SetLookaheadDepthWithHeuristic(0);
2288 }
2289 }
2290
2292 return mpsOptimizationBondDimensionThreshold;
2293 }
2294
2295 void SetMPSOptimizationQubitsNumberThreshold(size_t threshold) override {
2296 mpsOptimizationQubitsNumberThreshold = threshold;
2297
2298 if (GetNumQubits() < threshold && simulator) {
2299 simulator->SetLookaheadDepth(0);
2300 simulator->SetLookaheadDepthWithHeuristic(0);
2301 }
2302 }
2303
2305 return mpsOptimizationQubitsNumberThreshold;
2306 }
2307
2308 void SetLookaheadDepth(int depth) override {
2309 if (depth < 0) depth = std::numeric_limits<int>::max();
2310
2311 lookaheadDepth = depth;
2312
2313 if (simulator && lookaheadDepth != std::numeric_limits<int>::max()) {
2314 simulator->SetLookaheadDepth(0);
2315 simulator->SetLookaheadDepthWithHeuristic(0);
2316 }
2317 }
2318
2319 int GetLookaheadDepth() const override { return lookaheadDepth; }
2320
2321 void SetLookaheadDepthWithHeuristic(int depth) override {
2322 if (depth < 0) depth = std::numeric_limits<int>::max();
2323
2324 if (depth > lookaheadDepth) depth = lookaheadDepth;
2325
2326 lookaheadDepthWithHeuristic = depth;
2327
2328 if (simulator && lookaheadDepthWithHeuristic != std::numeric_limits<int>::max())
2329 simulator->SetLookaheadDepthWithHeuristic(depth);
2330 }
2331
2332 int GetLookaheadDepthWithHeuristic() const override {
2333 return lookaheadDepthWithHeuristic;
2334 }
2335
2336 double getGrowthFactorSwap() const override { return growthFactorSwap; }
2337 double getGrowthFactorGate() const override { return growthFactorGate; }
2338
2339 void setGrowthFactorSwap(double factor) override {
2340 growthFactorSwap = factor;
2341
2342 if (simulator) simulator->setGrowthFactorSwap(factor);
2343 }
2344
2345 void setGrowthFactorGate(double factor) override {
2346 growthFactorGate = factor;
2347
2348 if (simulator) simulator->setGrowthFactorGate(factor);
2349 }
2350
2357 size_t GetCurrentMaxBondDimension() const override { return curMaxBondDim; }
2358
2359 protected:
2360 // Resolved placement is separate from user settings. Importing a smaller
2361 // simulator must not turn its automatic shard subset into an explicit choice.
2363 const auto requested = configuration.GetConfiguration("distributed_devices");
2364 configuration.ApplyConfigurationFromSimulator(simulator);
2366 configuration.SetConfiguration("distributed_devices", requested);
2367 }
2368
2370 size_t qubits) const {
2371 auto result = configuration;
2372 const auto found = resolvedDistributedDevices.find(type);
2374 !configuration.GetConfiguration("distributed_devices").empty() ||
2375 found == resolvedDistributedDevices.end())
2376 return result;
2377 auto devices = found->second;
2378 // MPI shard count belongs to the communicator. Explicit global-qubit
2379 // settings also constrain the shard count and must be validated unchanged.
2381 !configuration.IsSet("distributed_global_qubits")) {
2382 size_t count = 1;
2383 for (char c : devices) if (c == ',') ++count;
2384 size_t bits = 0;
2385 for (size_t n = count; n > 1; n >>= 1) ++bits;
2386 while (count > 1 && bits >= qubits) {
2387 count /= 2;
2388 --bits;
2389 }
2390 size_t end = 0;
2391 for (size_t i = 0; i < count; ++i) {
2392 end = devices.find(',', end);
2393 if (end == std::string::npos) break;
2394 if (i + 1 < count) ++end;
2395 }
2396 devices = devices.substr(0, end);
2397 }
2398 result.SetConfiguration("distributed_devices", devices);
2399 return result;
2400 }
2401
2403 std::shared_ptr<Simulators::ISimulator> &sim,
2404 std::shared_ptr<Circuits::Circuit<Time>> &dcirc, size_t nrQubits) const {
2405 if (sim->GetSimulationType() ==
2407 (optimizeInitialQubitsMap || mpsOptimizeSwaps) &&
2408 sim->SupportsMPSSwapOptimization()) {
2409 if (mpsOptimizationQubitsNumberThreshold <= nrQubits) {
2410 const auto maxBondDimValue =
2411 configuration.GetConfigurationAsInt("matrix_product_state_max_bond_dimension");
2412
2413 if (maxBondDimValue <= 0 ||
2414 static_cast<int>(mpsOptimizationBondDimensionThreshold) <= maxBondDimValue) {
2415 // need to be sure the circuit is correctly converted
2416 dcirc->ConvertForCutting(); // convert the three qubit gates
2417 auto layers = dcirc->ToMultipleQubitsLayersNoClone();
2418
2419 Simulators::MPSDummySimulator dummySim(nrQubits);
2420 dummySim.setGrowthFactorGate(growthFactorGate);
2421 dummySim.setGrowthFactorSwap(growthFactorSwap);
2422
2423 if (maxBondDimValue > 0)
2424 dummySim.SetMaxBondDimension(maxBondDimValue);
2425
2426 if (optimizeInitialQubitsMap) {
2427 const auto optimalMap = dummySim.ComputeOptimalQubitsMap(layers);
2428 sim->SetInitialQubitsMap(optimalMap);
2429 }
2430
2431 auto optCirc = Circuits::Circuit<Time>::LayersToCircuit(layers);
2432 dcirc->SetOperations(optCirc->GetOperations());
2433
2434 if (mpsOptimizeSwaps) {
2435 // TODO: come up with something better!
2436 int lookaheadDepthLocal = lookaheadDepth;
2437
2438 if (lookaheadDepthLocal == std::numeric_limits<int>::max()) {
2439 double avgTwoQubitGatesPerLayer = 0.0;
2440 for (const auto &layer : layers) {
2441 int twoQubitGates = 0;
2442 for (const auto &op : layer->GetOperations()) {
2443 if (op->AffectedQubits().size() >= 2) {
2444 ++twoQubitGates;
2445 }
2446 }
2447 avgTwoQubitGatesPerLayer += twoQubitGates;
2448 }
2449 avgTwoQubitGatesPerLayer /= layers.size();
2450
2451 int lookaheadVal = static_cast<int>(4. * avgTwoQubitGatesPerLayer);
2452 if (lookaheadVal > 15) lookaheadVal = 15;
2453
2454 lookaheadDepthLocal =
2455 layers.size() < 8 || nrQubits <= 10 ? 0
2456 : layers.size() < 15 ? static_cast<int>(lookaheadVal)
2457 : layers.size() < 25 ? static_cast<int>(1.5 * lookaheadVal)
2458 : 2 * lookaheadVal;
2459 }
2460
2461 int lookaheadHeuristicDepthLocal = lookaheadDepthWithHeuristic;
2462
2463 if (lookaheadHeuristicDepthLocal == std::numeric_limits<int>::max())
2464 lookaheadHeuristicDepthLocal =
2465 layers.size() < 10 || nrQubits <= 10 ? 0
2466 : layers.size() < 20 ? lookaheadDepthLocal - 1
2467 : lookaheadDepthLocal - 2;
2468
2469 if (lookaheadHeuristicDepthLocal < 0)
2470 lookaheadHeuristicDepthLocal = 0;
2471
2472 sim->setGrowthFactorGate(growthFactorGate);
2473 sim->setGrowthFactorSwap(growthFactorSwap);
2474 sim->SetUseOptimalMeetingPosition(true);
2475 sim->SetLookaheadDepth(lookaheadDepthLocal);
2476 sim->SetLookaheadDepthWithHeuristic(lookaheadHeuristicDepthLocal);
2477 sim->SetUpcomingGates(dcirc->GetOperations());
2478 }
2479 }
2480 }
2481 }
2482 }
2483
2493 auto optimiser = controller->GetOptimiser();
2494 if (optimiser) {
2495 // convert the classical state results back to the expected order
2496 const auto &qubitsMap = optimiser->GetReverseQubitsMap();
2497
2498 ConvertBackState(qubitsMap);
2499 }
2500 }
2501
2514 const std::unordered_map<Types::qubit_t, Types::qubit_t> &qubitsMap) {
2515 // might not be the one stored in the network, might exist in the DES
2516 Circuits::OperationState &theClassicalState = GetState();
2517
2518 theClassicalState.Remap(qubitsMap);
2519 }
2520
2532 auto optimiser = controller->GetOptimiser();
2533 if (optimiser) {
2534 // convert the classical state results back to the expected order
2535 const auto &qubitsMap = optimiser->GetReverseQubitsMap();
2536
2537 ConvertBackResults(res, qubitsMap);
2538 }
2539 }
2540
2554 ExecuteResults &res,
2555 const std::unordered_map<Types::qubit_t, Types::qubit_t> &bitsMap) const {
2556 ExecuteResults translatedRes;
2557
2558 size_t numClassicalBits = 0;
2559 for (const auto &[q, b] : bitsMap)
2560 if (b >= numClassicalBits) numClassicalBits = b + 1;
2561
2562 numClassicalBits = std::max(numClassicalBits, GetNumClassicalBits());
2563
2564 for (const auto &r : res) {
2565 Circuits::OperationState translatedState(r.first);
2566
2567 translatedState.Remap(bitsMap, false, numClassicalBits);
2568 translatedRes[translatedState.GetAllBits()] = r.second;
2569 }
2570
2571 res.swap(translatedRes);
2572 }
2573
2585 std::unordered_map<Types::qubit_t, Types::qubit_t> MapCircuitOnHost(
2586 const std::shared_ptr<Circuits::Circuit<Time>> &circuit, size_t hostId,
2587 size_t &nrQubits, size_t &nrCbits, bool useSeparateSimForHosts = false) {
2588 qubitsMapOnHost.clear();
2589 nrQubits = 0;
2590 nrCbits = 0;
2591 if (!circuit) return {};
2592
2593 const auto host =
2594 std::static_pointer_cast<SimpleHost<Time>>(GetHost(hostId));
2595 const size_t hostNrQubits = host->GetNumQubits();
2596
2597 std::unordered_map<Types::qubit_t, Types::qubit_t> reverseQubitsMap;
2598
2599 if (!useSeparateSimForHosts) {
2600 size_t mxq = 0;
2601 size_t mnq = std::numeric_limits<size_t>::max();
2602 size_t mxb = 0;
2603 size_t mnb = std::numeric_limits<size_t>::max();
2604
2605 for (const auto &op : circuit->GetOperations()) {
2606 const auto qbits = op->AffectedQubits();
2607 for (auto q : qbits) {
2608 if (q > mxq) mxq = q;
2609 if (q < mnq) mnq = q;
2610 }
2611 const auto cbits = op->AffectedBits();
2612 for (auto b : cbits) {
2613 if (b > mxb) mxb = b;
2614 if (b < mnb) mnb = b;
2615 }
2616 }
2617
2618 if (mnq > mxq) mnq = 0;
2619 if (mnb > mxb) mnb = 0;
2620
2621 nrQubits = mxq - mnq + 1;
2622 nrCbits = mxb - mnb + 1;
2623 if (nrCbits < nrQubits) nrCbits = nrQubits;
2624
2625 const size_t startQubit = host->GetStartQubitId();
2626
2627 if (mnq < startQubit || mxq >= startQubit + hostNrQubits) {
2628 if (nrQubits >
2629 hostNrQubits +
2630 1) // the host has an additional 'special' qubit for the
2631 // entanglement or other operations (like those for cutting)
2632 throw std::runtime_error("Circuit does not fit on the host!");
2633
2634 for (size_t i = 0; i < nrCbits; ++i) {
2635 const size_t mapFrom = mnq + i;
2636 const size_t mapTo = startQubit + i;
2637
2638 qubitsMapOnHost[mapFrom] = mapTo;
2639 reverseQubitsMap[mapTo] = mapFrom;
2640 }
2641
2642 distCirc = std::static_pointer_cast<Circuits::Circuit<Time>>(
2643 circuit->Remap(qubitsMapOnHost, qubitsMapOnHost));
2644 }
2645
2646 return reverseQubitsMap;
2647 }
2648
2649 distCirc = circuit->RemapToContinuous(qubitsMapOnHost, reverseQubitsMap,
2650 nrQubits, nrCbits);
2651
2653 // Distribution settings refer to register qubits. Keep their numbering
2654 // and idle wires when the network creates a smaller per-host simulator.
2655 // Classical results retain RemapToContinuous's independent mapping.
2656 if (!hostNrQubits)
2657 throw std::runtime_error("Circuit does not fit on a host with no qubits!");
2658 const size_t start = host->GetStartQubitId();
2659 bool fitsLocal = true, fitsGlobal = true;
2660 for (const auto& entry : qubitsMapOnHost) {
2661 const auto q = entry.first;
2662 fitsLocal = fitsLocal && q < hostNrQubits;
2663 fitsGlobal = fitsGlobal && q >= start && q - start < hostNrQubits;
2664 }
2665 // Auto follows the host API's already-mapped precedence. Sparse local
2666 // circuits in the overlap must explicitly select local indexing.
2667 const bool global = distributedHostQubitIndexing == "global" ||
2668 (distributedHostQubitIndexing == "auto" && fitsGlobal);
2669 if (!(global ? fitsGlobal : fitsLocal))
2670 throw std::runtime_error("Circuit does not fit on the host!");
2671 const size_t offset = global ? start : 0;
2672 if (pauliStrings)
2673 for (const auto& pauli : *pauliStrings)
2674 if (pauli.size() > hostNrQubits)
2675 throw std::invalid_argument(
2676 "Host Pauli strings use local indices and must fit the host");
2677 std::unordered_map<Types::qubit_t, Types::qubit_t> restoreQubits;
2678 for (const auto& [original, compact] : qubitsMapOnHost) {
2679 if (original < offset || original - offset >= hostNrQubits)
2680 throw std::runtime_error("Circuit does not fit on the host!");
2681 restoreQubits[compact] = original - offset;
2682 }
2683 distCirc = std::static_pointer_cast<Circuits::Circuit<Time>>(
2684 distCirc->Remap(restoreQubits));
2685 qubitsMapOnHost.clear();
2686 for (size_t q = 0; q < hostNrQubits; ++q) qubitsMapOnHost[q] = q;
2687 nrQubits = hostNrQubits;
2688 } else if (pauliStrings) {
2689 // Observables can mention idle qubits absent from the circuit. Allocate
2690 // them in |0> and include them in the expectation remapping.
2691 size_t width = 0;
2692 for (const auto& pauli : *pauliStrings) width = std::max(width, pauli.size());
2693 for (size_t q = 0; q < width; ++q)
2694 if (!qubitsMapOnHost.count(q)) qubitsMapOnHost[q] = nrQubits++;
2695 }
2696
2697 assert(nrQubits == qubitsMapOnHost.size());
2698
2699 if (nrQubits == 0) nrQubits = 1;
2700
2701 if (nrQubits >
2702 hostNrQubits +
2703 1) // the host has an additional 'special' qubit for the
2704 // entanglement or other operations (like those for cutting)
2705 throw std::runtime_error("Circuit does not fit on the host!");
2706
2707 return reverseQubitsMap;
2708 }
2709
2710 bool optimizeSimulator = true;
2712
2719
2721 std::string distributedHostQubitIndexing = "auto";
2722 std::unordered_map<Simulators::SimulatorType, std::string>
2724
2725 size_t maxSimulators = QC::QubitRegisterCalculator<>::
2726 GetNumberOfThreads();
2728
2731 std::shared_ptr<Simulators::ISimulator>
2733
2734 std::shared_ptr<Circuits::Circuit<Time>>
2736
2737 std::shared_ptr<IController<Time>>
2739 // TODO: depending on the network topology, we will have adiacency lists, etc.
2740 // or simply a vector of hosts for a totally connected network (or where the
2741 // communication details do not matter so much)
2742 std::vector<std::shared_ptr<IHost<Time>>>
2744
2745 std::unique_ptr<Estimators::SimulatorsEstimatorInterface<Time>>
2747
2748 private:
2750 threadsPool;
2751 bool recreateIfNeeded =
2752 true;
2753 std::unordered_map<Types::qubit_t, Types::qubit_t>
2754 qubitsMapOnHost;
2757 const std::vector<std::string> *pauliStrings =
2758 nullptr;
2760
2761 bool optimizeInitialQubitsMap = true;
2763 bool mpsOptimizeSwaps = true;
2764 size_t mpsOptimizationBondDimensionThreshold =
2765 32;
2766 size_t mpsOptimizationQubitsNumberThreshold =
2767 12;
2768
2769 int lookaheadDepth =
2770 std::numeric_limits<int>::max();
2772 int lookaheadDepthWithHeuristic = std::numeric_limits<int>::max();
2774
2775 double growthFactorSwap = 1.;
2776 double growthFactorGate = 0.7;
2777 size_t curMaxBondDim = 0;
2778};
2779
2780} // namespace Network
2781
2782#endif // !_SIMPLE_NETWORK_H_
int GetSimulationType(void *sim)
Circuit class for holding the sequence of operations.
Definition Circuit.h:48
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:2474
The operation interface.
Definition Operations.h:360
The state class that stores the classical state of a quantum circuit execution.
Definition Operations.h:65
const std::vector< bool > & GetAllBits() const
Get the classical bits.
Definition Operations.h:216
void Clear()
Clear the classical state.
Definition Operations.h:171
void SetResultsInOrder(const std::vector< bool > &results)
Set the classical bits.
Definition Operations.h:255
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:299
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:106
The network interface.
Definition Network.h:58
std::shared_ptr< INetwork< Time > > getptr()
Definition Network.h:778
typename Circuits::Circuit< Time >::ExecuteResults ExecuteResults
Definition Network.h:60
std::unordered_set< SimulatorPair, boost::hash< SimulatorPair > > SimulatorsSet
Definition Network.h:63
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.
typename BaseClass::ExecuteResults ExecuteResults
The execute results type.
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
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.
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.
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.
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 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...
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 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:44
static std::string GpuSvdSettingGroup(const std::string &key)
static int ParseGpuDevice(const std::string &value)
Set a configuration value.
static uint64_t DeriveSeed(uint64_t seed, uint64_t stream)
Definition State.h:134
void setGrowthFactorSwap(double factor)
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 std::shared_ptr< ISimulator > CreateSimulator(SimulatorType t=SimulatorType::kQCSim, SimulationType method=SimulationType::kMatrixProductState)
Create a quantum computing simulator.
Definition Factory.cpp:173
static bool IsGpuLibraryAvailable(int=-1)
ThreadsPool class for holding and controlling a pool of threads.
Definition ThreadsPool.h:39
@ kGate
the usual quantum gate, result stays in simulator's state
Definition Operations.h:28
NetworkType
The type of the network.
Definition Network.h:34
@ kSimpleDisconnectedNetwork
Simple network, no communication among hosts, sequential simulation.
Definition Network.h:35
SchedulerType
The type of the network scheduler for scheduling execution of multiple circuits.
Definition Controller.h:86
SimulationType
The type of simulation.
Definition State.h:98
@ kStatevector
statevector simulation type
Definition State.h:99
@ kMatrixProductState
matrix product state simulation type
Definition State.h:100
@ kStabilizer
Clifford gates simulation type.
Definition State.h:101
@ kPauliPropagator
Pauli propagator simulation type.
Definition State.h:103
@ kTensorNetwork
Tensor network simulation type.
Definition State.h:102
@ kPathIntegral
Path integral simulation type.
Definition State.h:105
SimulatorType
The type of simulator.
Definition State.h:72
@ kCompositeQCSim
composite qcsim simulator type
Definition State.h:80
@ kQCSim
qcsim simulator type
Definition State.h:76
@ kQiskitAer
qiskit aer simulator type
Definition State.h:74
@ kQuestSim
quest simulator type
Definition State.h:82
@ kCompositeQiskitAer
composite qiskit aer simulator type
Definition State.h:78
@ kDistGpuSim
state distributed across local GPUs
Definition State.h:83
@ kGpuSim
gpu simulator type
Definition State.h:81
bool IsDistributedGpuSimulator(SimulatorType type)
Definition State.h:87
bool IsGpuSimulator(SimulatorType type)
Definition State.h:90
double time_type
The type of time.
Definition Types.h:24
A way to pack together a circuit and the number of shots for its execution.
Definition Controller.h:61