Maestro 0.2.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
18#include "QubitRegister.h"
19#include "SimpleController.h"
20#include "SimpleHost.h"
22#include "NetworkJob.h"
23
25
26namespace Network {
27
41template <typename Time = Types::time_type,
42 class Controller = SimpleController<Time>>
43class SimpleDisconnectedNetwork : public INetwork<Time> {
44 public:
48
58 SimpleDisconnectedNetwork(const std::vector<Types::qubit_t> &qubits = {},
59 const std::vector<size_t> &cbits = {}) {
60 if (!qubits.empty()) CreateNetwork(qubits, cbits);
61 }
62
72 void CreateNetwork(const std::vector<Types::qubit_t> &qubits,
73 const std::vector<size_t> &cbits) {
74 size_t qubitsOffset = 0;
75 size_t cbitsOffset = 0;
76
77 for (size_t i = 0; i < qubits.size(); ++i) {
78 const size_t numQubits = qubits[i];
79 const size_t numBits = (i < cbits.size() ? cbits[i] : 0);
80 hosts.emplace_back(std::make_shared<SimpleHost<Time>>(
81 i, qubitsOffset, numQubits, cbitsOffset, numBits));
82 qubitsOffset += numQubits;
83 cbitsOffset += numBits;
84 }
85
86 for (size_t i = 0; i < hosts.size(); ++i) {
87 std::static_pointer_cast<SimpleHost<Time>>(hosts[i])->SetEntangledQubitId(
88 qubitsOffset);
89 std::static_pointer_cast<SimpleHost<Time>>(hosts[i])
90 ->SetEntangledQubitMeasurementBit(cbitsOffset);
91 ++qubitsOffset;
92 ++cbitsOffset;
93 }
94
95 controller = std::make_shared<Controller>();
96 }
97
109 const std::shared_ptr<Circuits::Circuit<Time>> &circuit) override {
110 const auto recreate = recreateIfNeeded;
111
114 size_t numQubits = 2;
115 if (simulator) {
116 simType = simulator->GetType();
117 method = simulator->GetSimulationType();
118 numQubits = simulator->GetNumberOfQubits();
119 }
120
121 recreateIfNeeded = false;
122
123 const auto res = RepeatedExecute(circuit, 1);
124
125 recreateIfNeeded = recreate;
126
127 // put the results in the state
128 if (!res.empty()) {
129 const auto &first = *res.begin();
130 GetState().SetResultsInOrder(first.first);
131 }
132
133 if (recreate &&
134 (!simulator ||
135 (simulator && (simType != simulator->GetType() ||
136 method != simulator->GetSimulationType() ||
137 simulator->GetNumberOfQubits() != numQubits))))
138 CreateSimulator(simType, method);
139 }
140
153 void ExecuteOnHost(const std::shared_ptr<Circuits::Circuit<Time>> &circuit,
154 size_t hostId) override {
155 const auto recreate = recreateIfNeeded;
156
159 size_t numQubits = 2;
160 if (simulator) {
161 simType = simulator->GetType();
162 method = simulator->GetSimulationType();
163 numQubits = simulator->GetNumberOfQubits();
164 }
165
166 recreateIfNeeded = false;
167
168 const auto res = RepeatedExecuteOnHost(circuit, hostId, 1);
169
170 recreateIfNeeded = recreate;
171
172 // put the results in the state
173 if (!res.empty()) {
174 const auto &first = *res.begin();
175 GetState().SetResultsInOrder(first.first);
176 }
177
178 if (recreate &&
179 (!simulator ||
180 (simulator && (simType != simulator->GetType() ||
181 method != simulator->GetSimulationType() ||
182 simulator->GetNumberOfQubits() != numQubits))))
183 CreateSimulator(simType, method);
184 }
185
201 std::vector<double> ExecuteExpectations(
202 const std::shared_ptr<Circuits::Circuit<Time>> &circuit,
203 const std::vector<std::string> &paulis) override {
204 const auto recreate = recreateIfNeeded;
205
208 size_t numQubits = 2;
209 if (simulator) {
210 simType = simulator->GetType();
211 method = simulator->GetSimulationType();
212 numQubits = simulator->GetNumberOfQubits();
213 }
214
215 recreateIfNeeded = false;
216
217 pauliStrings = &paulis;
218 const auto res = RepeatedExecute(circuit, 1);
219 pauliStrings = nullptr;
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 std::vector<double> expectations(paulis.size());
230 if (simulator) {
231 // translate the pauli strings to the mapped order of qubits
232 const size_t numOps = simulator->GetNumberOfQubits();
233
234 auto optimiser = controller->GetOptimiser();
235 if (optimiser) {
236 // convert the classical state results back to the expected order
237 const auto &qubitsMap = optimiser->GetQubitsMap();
238
239 for (size_t i = 0; i < paulis.size(); ++i) {
240 std::string translated(numOps, 'I');
241
242 for (size_t j = 0; j < paulis[i].size(); ++j) {
243 const auto pos = qubitsMap.find(j);
244 if (pos != qubitsMap.end())
245 translated[pos->second] = paulis[i][j];
246 else
247 translated[j] = paulis[i][j];
248 }
249
250 expectations[i] = simulator->ExpectationValue(translated);
251 }
252 } else {
253 for (size_t i = 0; i < paulis.size(); ++i)
254 expectations[i] = simulator->ExpectationValue(paulis[i]);
255 }
256 }
257
258 if (recreate && (!simulator || simType != simulator->GetType() ||
259 method != simulator->GetSimulationType() ||
260 simulator->GetNumberOfQubits() != numQubits))
261 CreateSimulator(simType, method);
262
263 return expectations;
264 }
265
281 std::vector<double> ExecuteOnHostExpectations(
282 const std::shared_ptr<Circuits::Circuit<Time>> &circuit, size_t hostId,
283 const std::vector<std::string> &paulis) override {
284 const auto recreate = recreateIfNeeded;
285
288 size_t numQubits = 2;
289 if (simulator) {
290 simType = simulator->GetType();
291 method = simulator->GetSimulationType();
292 numQubits = simulator->GetNumberOfQubits();
293 }
294
295 // RAII: restore recreateIfNeeded and clear pauliStrings on any exit path.
296 struct ScopedRestore {
297 bool &flag, saved;
298 const std::vector<std::string> **ps;
299 ScopedRestore(bool &f, const std::vector<std::string> **p)
300 : flag(f), saved(f), ps(p) {
301 flag = false;
302 }
303 ~ScopedRestore() {
304 flag = saved;
305 *ps = nullptr;
306 }
307 } restoreGuard(recreateIfNeeded, &pauliStrings);
308
309 pauliStrings = &paulis;
310 const auto res = RepeatedExecuteOnHost(circuit, hostId, 1);
311
312 // put the results in the state
313 if (!res.empty()) {
314 const auto &first = *res.begin();
315 GetState().SetResultsInOrder(first.first);
316 }
317
318 // for (const auto& m : qubitsMapOnHost)
319 // std::cout << "Mapping qubit " << m.first << " to " << m.second <<
320 // std::endl;
321
322 const size_t offsetBase = qubitsMapOnHost.size();
323
324 std::vector<double> expectations(paulis.size(), 1.);
325 if (simulator) {
326 // translate the pauli strings to the mapped order of qubits
327 const size_t numOps = simulator->GetNumberOfQubits();
328
329 // convert the pauli strings to the actual qubits order
330 for (size_t i = 0; i < paulis.size(); ++i) {
331 std::string translated(std::max(numOps, paulis[i].size()), 'I');
332
333 size_t offset = offsetBase;
334
335 for (size_t j = 0; j < paulis[i].size(); ++j) {
336 auto pos = qubitsMapOnHost.find(j);
337 if (pos != qubitsMapOnHost.end())
338 translated[pos->second] = paulis[i][j];
339 else {
340 translated[offset] = paulis[i][j];
341 ++offset;
342 }
343 }
344
345 // std::cout << "Translated pauli string: " << translated << std::endl;
346
347 expectations[i] = simulator->ExpectationValue(translated);
348 }
349 } else {
350 throw std::runtime_error(
351 "ExecuteOnHostExpectations: no simulator available after execution.");
352 }
353
354 if (recreate && (!simulator || simType != simulator->GetType() ||
355 method != simulator->GetSimulationType() ||
356 simulator->GetNumberOfQubits() != numQubits))
357 CreateSimulator(simType, method);
358
359 return expectations;
360 }
361
373 std::vector<std::complex<double>> ExecuteOnHostAmplitudes(
374 const std::shared_ptr<Circuits::Circuit<Time>> &circuit,
375 size_t hostId) override {
376 const auto recreate = recreateIfNeeded;
377
380 size_t numQubits = 2;
381 if (simulator) {
382 simType = simulator->GetType();
383 method = simulator->GetSimulationType();
384 numQubits = simulator->GetNumberOfQubits();
385 }
386
387 // RAII: restore recreateIfNeeded on any exit path (including exceptions).
388 struct ScopedRestoreFlag {
389 bool &flag, saved;
390 ScopedRestoreFlag(bool &f) : flag(f), saved(f) { flag = false; }
391 ~ScopedRestoreFlag() { flag = saved; }
392 } restoreGuard(recreateIfNeeded);
393
394 const auto res = RepeatedExecuteOnHost(circuit, hostId, 1);
395
396 if (!res.empty()) {
397 const auto &first = *res.begin();
398 GetState().SetResultsInOrder(first.first);
399 }
400
401 if (!simulator)
402 throw std::runtime_error(
403 "ExecuteOnHostAmplitudes: no simulator available after execution.");
404
405 std::vector<std::complex<double>> amplitudes;
406 const size_t n = simulator->GetNumberOfQubits();
407 const size_t dim = 1ULL << n;
408 amplitudes.resize(dim);
409 for (size_t state = 0; state < dim; ++state)
410 amplitudes[state] = simulator->Amplitude(state);
411
412 // Remap amplitudes back to the original qubit ordering if qubits were
413 // remapped during execution on the host.
414 if (!qubitsMapOnHost.empty()) {
415 const size_t offsetBase = qubitsMapOnHost.size();
416
417 // Build reverse mapping: simulator qubit position -> original qubit
418 // position.
419 std::vector<size_t> simToOrig(n);
420 size_t offset = offsetBase;
421
422 for (size_t qbit = 0; qbit < n; ++qbit) {
423 auto pos = qubitsMapOnHost.find(qbit);
424 if (pos != qubitsMapOnHost.end())
425 simToOrig[pos->second] = pos->first;
426 else
427 simToOrig[qbit] = offset++;
428 }
429
430 std::vector<std::complex<double>> remapped(dim);
431
432 for (size_t sim_state = 0; sim_state < dim; ++sim_state) {
433 size_t orig_state = 0;
434 for (size_t qbit = 0; qbit < n; ++qbit) {
435 if (sim_state & (1ULL << qbit))
436 orig_state |= (1ULL << simToOrig[qbit]);
437 }
438 if (orig_state < dim) remapped[orig_state] = amplitudes[sim_state];
439 }
440 amplitudes.swap(remapped);
441 }
442
443 if (recreate && (!simulator || simType != simulator->GetType() ||
444 method != simulator->GetSimulationType() ||
445 simulator->GetNumberOfQubits() != numQubits))
446 CreateSimulator(simType, method);
447
448 return amplitudes;
449 }
450
464 std::complex<double> ExecuteOnHostProjectOnZero(
465 const std::shared_ptr<Circuits::Circuit<Time>> &circuit,
466 size_t hostId) override {
467 const auto recreate = recreateIfNeeded;
468
471 size_t numQubits = 2;
472 if (simulator) {
473 simType = simulator->GetType();
474 method = simulator->GetSimulationType();
475 numQubits = simulator->GetNumberOfQubits();
476 }
477
478 // RAII: restore recreateIfNeeded on any exit path (including exceptions).
479 struct ScopedRestoreFlag {
480 bool &flag, saved;
481 ScopedRestoreFlag(bool &f) : flag(f), saved(f) { flag = false; }
482 ~ScopedRestoreFlag() { flag = saved; }
483 } restoreGuard(recreateIfNeeded);
484
485 const auto res = RepeatedExecuteOnHost(circuit, hostId, 1);
486
487 if (!res.empty()) {
488 const auto &first = *res.begin();
489 GetState().SetResultsInOrder(first.first);
490 }
491
492 if (!simulator)
493 throw std::runtime_error(
494 "ExecuteOnHostProjectOnZero: no simulator available after "
495 "execution.");
496
497 const std::complex<double> result = simulator->ProjectOnZero();
498
499 if (recreate && (!simulator || simType != simulator->GetType() ||
500 method != simulator->GetSimulationType() ||
501 simulator->GetNumberOfQubits() != numQubits))
502 CreateSimulator(simType, method);
503
504 return result;
505 }
506
521 const std::shared_ptr<Circuits::Circuit<Time>> &circuit,
522 size_t shots = 1000) override {
523 if (!controller || !circuit) return {};
524
525 distCirc = controller->DistributeCircuit(BaseClass::getptr(), circuit);
526 if (!distCirc) return {};
527
528#ifdef _DEBUG
529 for (auto q : distCirc->AffectedQubits()) {
530 if (q >= GetNumQubits()) {
531 std::cout
532 << "This is a distributed circuit, using entanglement or cutting"
533 << std::endl;
534 break;
535 }
536 }
537#endif
538
539 if (!simulator) return {};
540
541 auto simType = simulator->GetType();
542 if (distCirc->HasOpsAfterMeasurements() &&
543 (
544#ifndef NO_QISKIT_AER
546#endif
548 distCirc->MoveMeasurementsAndResets();
549
550 auto method = simulator->GetSimulationType();
551
552 const auto saveSimType = simType;
553 const auto saveMethod = method;
554
555 if (GetOptimizeSimulator() && distCirc->IsClifford() &&
557 // this is for the gpu simulator, as it doesn't support stabilizer
558#ifdef __linux__
560#endif
561 ) {
563
566#ifndef NO_QISKIT_AER
569#endif
570 }
571
572 ExecuteResults res;
573 const size_t nrQubits = GetNumQubits() + GetNumNetworkEntangledQubits();
574 const size_t nrCbitsResults = GetNumClassicalBits();
575
576 maxBondDim =
577 simulator->GetConfiguration("matrix_product_state_max_bond_dimension");
578 singularValueThreshold = simulator->GetConfiguration(
579 "matrix_product_state_truncation_threshold");
580 mpsSample = simulator->GetConfiguration("mps_sample_measure_algorithm");
581
582 // do that only if the optimization for simulator is on and the estimator is
583 // available, ortherwise an 'optimal' simulator won't be created
585 simulatorsEstimator->IsInitialized()) {
586 simulator->Clear();
587 GetState().Clear();
588 }
589
590 std::vector<bool> executed;
591 auto optSim =
592 ChooseBestSimulator(distCirc, shots, nrQubits, nrQubits, nrCbitsResults,
593 simType, method, executed);
594
595 lastSimulatorType = simType;
596 lastMethod = method;
597
598 size_t nrThreads = GetMaxSimulators();
599
600#ifdef __linux__
602 nrThreads = 1;
603 else
604#endif
606 !distCirc->HasOpsAfterMeasurements()) ||
608 nrThreads = 1;
609
610 nrThreads = std::min(nrThreads, std::max<size_t>(shots, 1ULL));
611
612 std::mutex resultsMutex;
613
614 const auto dcirc = distCirc;
615
616 if (nrThreads > 1) {
617 // since it's going to execute on multiple threads, free the memory from
618 // the network's simulator and state, it's going to use other ones,
619 // created in the threads if optimization already exists, it will be
620 // cloned in the threads, otherwise a new one will be created in the
621 // threads
624 ->IsInitialized()) // otherwise it was already cleared
625 {
626 simulator->Clear();
627 GetState().Clear();
628 }
629
630 const size_t cntPerThread = std::max<size_t>(shots / nrThreads, 1ULL);
631
632 threadsPool.Resize(nrThreads);
633 threadsPool.SetFinishLimit(shots);
634
635 while (shots > 0) {
636 const size_t curCnt = std::min(cntPerThread, shots);
637
638 shots -= curCnt;
639
640 auto job = std::make_shared<ExecuteJob<Time>>(
641 dcirc, res, curCnt, nrQubits, nrQubits, nrCbitsResults, simType,
642 method, resultsMutex);
643 job->optimiseMultipleShotsExecution = GetOptimizeSimulator();
644
645 job->maxBondDim = maxBondDim;
646 job->mpsSample = mpsSample;
647 job->singularValueThreshold = singularValueThreshold;
648
649 job->network = BaseClass::getptr();
650
651 if (optSim) {
652 job->optSim = optSim->Clone();
653 job->executedGates = executed;
654 }
655
656 threadsPool.AddRunJob(std::move(job));
657 }
658
659 threadsPool.WaitForFinish();
660 threadsPool.Stop();
661 } else {
662 const size_t curCnt = shots;
663
664 auto job = std::make_shared<ExecuteJob<Time>>(
665 dcirc, res, curCnt, nrQubits, nrQubits, nrCbitsResults, simType,
666 method, resultsMutex);
667 job->optimiseMultipleShotsExecution = GetOptimizeSimulator();
668
669 job->maxBondDim = maxBondDim;
670 job->mpsSample = mpsSample;
671 job->singularValueThreshold = singularValueThreshold;
672
673 job->network = BaseClass::getptr();
674
675 if (optSim) {
676 optSim->SetMultithreading(true);
677 job->optSim = optSim;
678 job->executedGates = executed;
679 } else {
680 if (simulator && method == saveMethod && simType == saveSimType) {
681 // use the already created simulator
682
683 optSim = simulator;
684 job->optSim = optSim;
686 optSim->GetNumberOfQubits());
687 job->executedGates.resize(distCirc->size(),
688 false); // no gates executed yet
689 simulator = nullptr;
690 }
691 }
692
693 job->DoWorkNoLock();
694 if (!recreateIfNeeded) simulator = job->optSim;
695 }
696
697 if (recreateIfNeeded) CreateSimulator(saveSimType, saveMethod);
698
700
701 return res;
702 }
703
720 const std::shared_ptr<Circuits::Circuit<Time>> &circuit, size_t hostId,
721 size_t shots = 1000) override {
722 if (!circuit || hostId >= GetNumHosts()) return {};
723
724 size_t nrQubits = 0;
725 size_t nrCbits = 0;
726
727 std::shared_ptr<Circuits::Circuit<Time>> optCircuit;
728 if (GetController()->GetOptimizeCircuit()) {
729 optCircuit =
730 std::static_pointer_cast<Circuits::Circuit<Time>>(circuit->Clone());
731 optCircuit->Optimize();
732 }
733 const auto reverseQubitsMap = MapCircuitOnHost(
734 GetController()->GetOptimizeCircuit() ? optCircuit : circuit, hostId,
735 nrQubits, nrCbits, true);
736 if (nrCbits == 0) nrCbits = nrQubits;
737
738 if (!simulator || !distCirc) return {};
739
740 auto simType = simulator->GetType();
741
742 maxBondDim =
743 simulator->GetConfiguration("matrix_product_state_max_bond_dimension");
744 singularValueThreshold = simulator->GetConfiguration(
745 "matrix_product_state_truncation_threshold");
746 mpsSample = simulator->GetConfiguration("mps_sample_measure_algorithm");
747
748 if (distCirc->HasOpsAfterMeasurements() &&
749 (
750#ifndef NO_QISKIT_AER
752#endif
754 distCirc->MoveMeasurementsAndResets();
755
756 auto method = simulator->GetSimulationType();
757 const auto saveSimType = simType;
758 const auto saveMethod = method;
759
760 if (GetOptimizeSimulator() && distCirc->IsClifford() &&
762 // this is for the gpu simulator, as it doesn't support stabilizer
763#ifdef __linux__
765#endif
766 ) {
768
771#ifndef NO_QISKIT_AER
774#endif
775 }
776
777 ExecuteResults res;
778
779 // since it's going to execute on multiple threads, free the memory from the
780 // network's simulator and state, it's going to use other ones, created in
781 // the threads
782 simulator->Clear();
783 GetState().Clear();
784
785 std::vector<bool> executed;
786 auto optSim = ChooseBestSimulator(distCirc, shots, nrQubits, nrCbits,
787 nrCbits, simType, method, executed);
788
789 lastSimulatorType = simType;
790 lastMethod = method;
791
792 size_t nrThreads = GetMaxSimulators();
793
794#ifdef __linux__
796 nrThreads = 1;
797 else
798#endif
801 !distCirc->HasOpsAfterMeasurements()) ||
803 nrThreads = 1;
804
805 nrThreads = std::min(nrThreads, std::max<size_t>(shots, 1ULL));
806
807 // WARNING: be sure to not put this above ChooseBestSimulator, as that one
808 // can change the shots value!
809
810 std::mutex resultsMutex;
811
812 const auto dcirc = distCirc;
813
814 if (nrThreads > 1) {
815 // this rounds up, rounding down is better
816 // const size_t cntPerThread = static_cast<size_t>((shots - 1) / nrThreads
817 // + 1);
818 const size_t cntPerThread = std::max<size_t>(shots / nrThreads, 1ULL);
819
820 threadsPool.Resize(nrThreads);
821 threadsPool.SetFinishLimit(shots);
822
823 while (shots > 0) {
824 const size_t curCnt = std::min(cntPerThread, shots);
825 shots -= curCnt;
826
827 auto job = std::make_shared<ExecuteJob<Time>>(
828 dcirc, res, curCnt, nrQubits, nrCbits, nrCbits, simType, method,
829 resultsMutex);
830 job->optimiseMultipleShotsExecution = GetOptimizeSimulator();
831
832 job->maxBondDim = maxBondDim;
833 job->mpsSample = mpsSample;
834 job->singularValueThreshold = singularValueThreshold;
835
836 job->network = BaseClass::getptr();
837
838 if (optSim) {
839 job->optSim = optSim->Clone();
840 job->executedGates = executed;
841 }
842
843 threadsPool.AddRunJob(std::move(job));
844 }
845
846 threadsPool.WaitForFinish();
847 threadsPool.Stop();
848 } else {
849 const size_t curCnt = shots;
850
851 auto job = std::make_shared<ExecuteJob<Time>>(
852 dcirc, res, curCnt, nrQubits, nrCbits, nrCbits, simType, method,
853 resultsMutex);
854 job->optimiseMultipleShotsExecution = GetOptimizeSimulator();
855
856 job->maxBondDim = maxBondDim;
857 job->mpsSample = mpsSample;
858 job->singularValueThreshold = singularValueThreshold;
859
860 job->network = BaseClass::getptr();
861
862 if (optSim) {
863 optSim->SetMultithreading(true);
864 job->optSim = optSim;
865 job->executedGates = executed;
866 }
867
868 job->DoWorkNoLock();
869 if (!recreateIfNeeded) simulator = job->optSim;
870 }
871
872 if (recreateIfNeeded) CreateSimulator(saveSimType, saveMethod);
873
874 if (!reverseQubitsMap.empty()) ConvertBackResults(res, reverseQubitsMap);
875
876 return res;
877 }
878
888 const std::shared_ptr<Circuits::Circuit<Time>> &circuit) const override {
889 if (!circuit) return 0;
890
891 size_t distgates = 0;
892
893 for (const auto &op : circuit->GetOperations())
894 if (!IsLocalOperation(op)) ++distgates;
895
896 return distgates;
897 }
898
915 std::vector<ExecuteResults> ExecuteScheduled(
916 const std::vector<Schedulers::ExecuteCircuit<Time>> &circuits) override {
917 // create a default one if not set
918 if (!GetScheduler()) {
920
921 if (!GetScheduler()) return {};
922 }
923
924 return GetScheduler()->ExecuteScheduled(circuits);
925 }
926
948 Simulators::SimulationType simExecType =
950 size_t nrQubits = 0) override {
951 classicalState.Clear();
952 classicalState.AllocateBits(GetNumClassicalBits() +
954
955 simulator =
957
958 if (simulator) {
959 if (!maxBondDim.empty())
960 simulator->Configure("matrix_product_state_max_bond_dimension",
961 maxBondDim.c_str());
962 if (!singularValueThreshold.empty())
963 simulator->Configure("matrix_product_state_truncation_threshold",
964 singularValueThreshold.c_str());
965 if (!mpsSample.empty())
966 simulator->Configure("mps_sample_measure_algorithm", mpsSample.c_str());
967 if (useDoublePrecision) simulator->Configure("use_double_precision", "1");
968
969 simulator->AllocateQubits(
970 nrQubits == 0 ? GetNumQubits() + GetNumNetworkEntangledQubits()
971 : nrQubits);
972 simulator->Initialize();
973 }
974 }
975
985 void Configure(const char *key, const char *value) override {
986 if (!key || !value) return;
987
988 if (std::string("matrix_product_state_max_bond_dimension") == key)
989 maxBondDim = value;
990 else if (std::string("matrix_product_state_truncation_threshold") == key)
992 else if (std::string("mps_sample_measure_algorithm") == key)
993 mpsSample = value;
994 else if (std::string("use_double_precision") == key)
996 (std::string("1") == value || std::string("true") == value);
997 else if (std::string("max_simulators") == key)
998 maxSimulators = std::stoull(value);
999
1000 if (simulator) simulator->Configure(key, value);
1001 }
1002
1011 std::shared_ptr<Simulators::ISimulator> GetSimulator() const override {
1012 return simulator;
1013 }
1014
1024
1037 SchedulerType schType =
1039 if (!controller) return;
1040
1041 controller->CreateScheduler(BaseClass::getptr(), schType);
1042 }
1043
1052 std::shared_ptr<Schedulers::IScheduler<Time>> GetScheduler() const override {
1053 if (!controller) return nullptr;
1054
1055 return controller->GetScheduler();
1056 }
1057
1067 const std::shared_ptr<IHost<Time>> GetHost(size_t hostId) const override {
1068 if (hostId >= hosts.size()) return nullptr;
1069
1070 return hosts[hostId];
1071 }
1072
1081 const std::shared_ptr<IController<Time>> GetController() const override {
1082 return controller;
1083 }
1084
1092 size_t GetNumHosts() const override { return hosts.size(); }
1093
1102 size_t GetNumQubits() const override {
1103 size_t res = 0;
1104
1105 for (const auto &host : hosts) res += host->GetNumQubits();
1106
1107 return res;
1108 }
1109
1119 size_t GetNumQubitsForHost(size_t hostId) const override {
1120 if (hostId >= hosts.size()) return 0;
1121
1122 return hosts[hostId]->GetNumQubits();
1123 }
1124
1133 size_t GetNumNetworkEntangledQubits() const override {
1134 size_t res = 0;
1135
1136 for (const auto &host : hosts) res += host->GetNumNetworkEntangledQubits();
1137
1138 return res;
1139 }
1140
1153 size_t GetNumNetworkEntangledQubitsForHost(size_t hostId) const override {
1154 if (hostId >= hosts.size()) return 0;
1155
1156 return hosts[hostId]->GetNumNetworkEntangledQubits();
1157 }
1158
1167 size_t GetNumClassicalBits() const override {
1168 size_t res = 0;
1169
1170 for (const auto &host : hosts) res += host->GetNumClassicalBits();
1171
1172 return res;
1173 }
1174
1186 size_t GetNumClassicalBitsForHost(size_t hostId) const override {
1187 if (hostId >= hosts.size()) return 0;
1188
1189 return hosts[hostId]->GetNumClassicalBits();
1190 }
1191
1201 std::vector<std::shared_ptr<IHost<Time>>> &GetHosts() { return hosts; }
1202
1210 void SetController(const std::shared_ptr<IController<Time>> &cntrl) {
1211 controller = cntrl;
1212 }
1213
1227 bool SendPacket(size_t fromHostId, size_t toHostId,
1228 const std::vector<uint8_t> &packet) override {
1229 return false;
1230 }
1231
1239 NetworkType GetType() const override {
1241 }
1242
1254 const std::shared_ptr<Circuits::IOperation<Time>> &op) const override {
1255 const auto qubits = op->AffectedQubits();
1256
1257 if (qubits.empty()) return true;
1258
1259 size_t firstQubit = qubits[0];
1260
1261 for (size_t q = 1; q < qubits.size(); ++q)
1262 if (!AreQubitsOnSameHost(firstQubit, qubits[q])) return false;
1263
1264 return true;
1265 }
1266
1280 const std::shared_ptr<Circuits::IOperation<Time>> &op) const override {
1281 const auto qubits = op->AffectedQubits();
1282
1283 if (qubits.empty()) return false;
1284
1285 // grab the first qubit that is on a host (skip over network entangled
1286 // qubits)
1287 size_t firstQubit = qubits[0];
1288 size_t q = 1;
1289 for (; IsNetworkEntangledQubit(firstQubit) && q < qubits.size(); ++q)
1290 firstQubit = qubits[q];
1291
1292 // check to see one of the other qubits is on a different host, but ignore
1293 // the network entangled qubits
1294 for (; q < qubits.size(); ++q)
1295 if (!IsNetworkEntangledQubit(qubits[q]) &&
1296 !AreQubitsOnSameHost(firstQubit, qubits[q]))
1297 return true;
1298
1299 return false;
1300 }
1301
1314 const std::shared_ptr<Circuits::IOperation<Time>> &op) const override {
1315 const auto qubits = op->AffectedQubits();
1316
1317 if (qubits.empty()) return false;
1318
1319 for (size_t q = 0; q < qubits.size(); ++q)
1320 if (IsNetworkEntangledQubit(qubits[q])) return true;
1321
1322 return false;
1323 }
1324
1335 const std::shared_ptr<Circuits::IOperation<Time>> &op) const override {
1336 if (op->GetType() != Circuits::OperationType::kGate) return false;
1337 const auto qubits = op->AffectedQubits();
1338 if (qubits.size() != 2) return false;
1339
1340 return IsNetworkEntangledQubit(qubits[0]) &&
1341 IsNetworkEntangledQubit(qubits[1]);
1342 }
1343
1355 const std::shared_ptr<Circuits::IOperation<Time>> &op) const override {
1356 if (!op->IsConditional()) return false;
1357
1358 const auto qubits = op->AffectedQubits();
1359
1360 const std::shared_ptr<Circuits::IConditionalOperation<Time>> condOp =
1361 std::static_pointer_cast<Circuits::IConditionalOperation<Time>>(op);
1362 const auto &classicalBits = condOp->GetCondition()->GetBitsIndices();
1363
1364 if (qubits.empty() && classicalBits.empty())
1365 throw std::runtime_error(
1366 "No classical bits specified!"); // this would be odd!
1367
1368 // consider it on the host where it has the first qubit (or bit, if there
1369 // are no qubits)
1370
1371 const size_t hostId = GetHostIdForAnyQubit(qubits[0]);
1372
1373 // now check the classical bits
1374 for (const auto bit : classicalBits)
1375 if (hostId != GetHostIdForClassicalBit(bit)) return true;
1376
1377 return false;
1378 }
1379
1391 const std::shared_ptr<Circuits::IOperation<Time>> &op) const override {
1392 if (!op->IsConditional())
1393 throw std::runtime_error("Operation is not conditional!");
1394
1395 std::shared_ptr<Circuits::IConditionalOperation<Time>> condOp =
1396 std::static_pointer_cast<Circuits::IConditionalOperation<Time>>(op);
1397 const auto classicalBits = condOp->AffectedBits();
1398
1399 if (classicalBits.empty())
1400 throw std::runtime_error("No classical bits specified!");
1401
1402 return GetHostIdForClassicalBit(classicalBits[0]);
1403 }
1404
1413 bool AreQubitsOnSameHost(size_t qubitId1, size_t qubitId2) const override {
1414 for (const auto &host : hosts) {
1415 const bool present1 = host->IsQubitOnHost(qubitId1);
1416 const bool present2 = host->IsQubitOnHost(qubitId2);
1417
1418 if (present1 && present2)
1419 return true;
1420 else if (present1 || present2)
1421 return false;
1422 }
1423
1424 return false;
1425 }
1426
1436 bool AreClassicalBitsOnSameHost(size_t bitId1, size_t bitId2) const override {
1437 for (const auto &host : hosts) {
1438 const bool present1 = host->IsClassicalBitOnHost(bitId1);
1439 const bool present2 = host->IsClassicalBitOnHost(bitId2);
1440
1441 if (present1 && present2)
1442 return true;
1443 else if (present1 || present2)
1444 return false;
1445 }
1446
1447 return false;
1448 }
1449
1461 size_t bitId) const override {
1462 for (const auto &host : hosts) {
1463 const bool present1 = host->IsQubitOnHost(qubitId);
1464 const bool present2 = host->IsClassicalBitOnHost(bitId);
1465
1466 if (present1 && present2)
1467 return true;
1468 else if (present1 || present2)
1469 return false;
1470 }
1471
1472 return false;
1473 }
1474
1484 size_t GetHostIdForQubit(size_t qubitId) const override {
1485 for (const auto &host : hosts)
1486 if (host->IsQubitOnHost(qubitId)) return host->GetId();
1487
1488 return std::numeric_limits<size_t>::max();
1489 }
1490
1503 size_t GetHostIdForEntangledQubit(size_t qubitId) const override {
1504 for (const auto &host : hosts)
1505 if (host->IsEntangledQubitOnHost(qubitId)) return host->GetId();
1506
1507 return std::numeric_limits<size_t>::max();
1508 }
1509
1519 size_t GetHostIdForAnyQubit(size_t qubitId) const override {
1520 if (IsNetworkEntangledQubit(qubitId))
1521 return GetHostIdForEntangledQubit(qubitId);
1522
1523 return GetHostIdForQubit(qubitId);
1524 }
1525
1535 size_t GetHostIdForClassicalBit(size_t classicalBitId) const override {
1536 for (const auto &host : hosts)
1537 if (host->IsClassicalBitOnHost(classicalBitId)) return host->GetId();
1538
1539 return std::numeric_limits<size_t>::max();
1540 }
1541
1550 std::vector<size_t> GetQubitsIds(size_t hostId) const override {
1551 if (hostId >= hosts.size()) return std::vector<size_t>();
1552
1553 return hosts[hostId]->GetQubitsIds();
1554 }
1555
1566 std::vector<size_t> GetNetworkEntangledQubitsIds(
1567 size_t hostId) const override {
1568 if (hostId >= hosts.size()) return std::vector<size_t>();
1569
1570 return hosts[hostId]->GetNetworkEntangledQubitsIds();
1571 }
1572
1582 std::vector<size_t> GetClassicalBitsIds(size_t hostId) const override {
1583 if (hostId >= hosts.size()) return std::vector<size_t>();
1584
1585 return hosts[hostId]->GetClassicalBitsIds();
1586 }
1587
1599 size_t hostId) const override {
1600 if (hostId >= hosts.size()) return std::vector<size_t>();
1601
1602 return hosts[hostId]->GetEntangledQubitMeasurementBitIds();
1603 }
1604
1616 bool IsNetworkEntangledQubit(size_t qubitId) const override {
1617 return qubitId >= GetNumQubits();
1618 }
1619
1632 bool IsEntanglementQubitBusy(size_t qubitId) const override { return false; }
1633
1649 bool AreEntanglementQubitsBusy(size_t qubitId1,
1650 size_t qubitId2) const override {
1651 return false;
1652 }
1653
1666 void MarkEntangledQubitsBusy(size_t qubitId1, size_t qubitId2) override {
1667 throw std::runtime_error(
1668 "Entanglement between hosts is not supported in the simple network");
1669 }
1670
1682 void MarkEntangledQubitFree(size_t qubitId) override {
1683 throw std::runtime_error(
1684 "Entanglement between hosts is not supported in the simple network");
1685 }
1686
1696 void ClearEntanglements() override {
1697 throw std::runtime_error(
1698 "Entanglement between hosts is not supported in the simple network");
1699 }
1700
1710 std::shared_ptr<Circuits::Circuit<Time>> GetDistributedCircuit()
1711 const override {
1712 return distCirc;
1713 }
1714
1725
1734 return lastMethod;
1735 }
1736
1747 size_t GetMaxSimulators() const override { return maxSimulators; }
1748
1760 void SetMaxSimulators(size_t val) override {
1761 if (val < 1)
1762 val = 1;
1763 else if (val > (size_t)QC::QubitRegisterCalculator<>::GetNumberOfThreads())
1764 val = (size_t)QC::QubitRegisterCalculator<>::GetNumberOfThreads();
1765
1766 maxSimulators = val;
1767 }
1768
1778 void SetOptimizeSimulator(bool optimize = true) override {
1779 optimizeSimulator = optimize;
1780 }
1781
1789 bool GetOptimizeSimulator() const override { return optimizeSimulator; }
1790
1799 const typename BaseClass::SimulatorsSet &GetSimulatorsSet() const override {
1801 }
1802
1813 Simulators::SimulationType kind) override {
1814 simulatorsForOptimizations.insert({type, kind});
1815 }
1816
1829
1846
1859 Simulators::SimulationType kind) const override {
1860 if (simulatorsForOptimizations.empty()) return true;
1861
1862 return simulatorsForOptimizations.find({type, kind}) !=
1864 }
1865
1872 std::shared_ptr<INetwork<Time>> Clone() const override {
1873 const size_t numHosts = GetNumHosts();
1874
1875 std::vector<Types::qubit_t> qubits(numHosts);
1876 std::vector<size_t> cbits(numHosts);
1877
1878 for (size_t h = 0; h < numHosts; ++h) {
1879 qubits[h] = GetNumQubitsForHost(h);
1880 cbits[h] = GetNumClassicalBitsForHost(h);
1881 }
1882
1883 const auto cloned =
1884 std::make_shared<SimpleDisconnectedNetwork<Time, Controller>>(qubits,
1885 cbits);
1886
1887 cloned->maxBondDim = maxBondDim;
1888 cloned->singularValueThreshold = singularValueThreshold;
1889 cloned->mpsSample = mpsSample;
1890
1891 // cloned->optimizeSimulator = optimizeSimulator;
1892 // cloned->simulatorsForOptimizations = simulatorsForOptimizations;
1893
1894 if (GetSimulator())
1895 cloned->CreateSimulator(GetSimulator()->GetType(),
1897
1898 return cloned;
1899 }
1900
1901 std::shared_ptr<Simulators::ISimulator> ChooseBestSimulator(
1902 std::shared_ptr<Circuits::Circuit<Time>> &dcirc, size_t &counts,
1903 size_t nrQubits, size_t nrCbits, size_t nrResultCbits,
1905 std::vector<bool> &executed, bool multithreading = false,
1906 bool dontRunCircuitStart = false) const override {
1907 if (!optimizeSimulator) return nullptr;
1908
1909 if ((!simulatorsEstimator || !simulatorsEstimator->IsInitialized()) &&
1910 simulatorsForOptimizations.size() != 1)
1911 return nullptr;
1912
1913 // when multithreading is set to true it means it needs a multithreaded
1914 // simulator
1915
1916 std::vector<
1917 std::pair<Simulators::SimulatorType, Simulators::SimulationType>>
1918 simulatorTypes;
1919
1920 const bool checkTensorNetwork =
1922
1923 // the others are to be picked between statevector, composite, tensor
1924 // networks and mps, for now at least for tensor networks in the future it's
1925 // worth checking different contractors!!!!
1926 //
1927 // clifford was decided at higher level
1929 // compare qcsim with qiskit aer if qiskit aer is available, let the best
1930 // one win
1933 simulatorTypes.emplace_back(Simulators::SimulatorType::kQCSim,
1935
1936#ifndef NO_QISKIT_AER
1937 // if the number of shots is too small, probably it's not worth it, it's
1938 // going to be better to just execute them multithreading
1941 simulatorTypes.emplace_back(Simulators::SimulatorType::kQiskitAer,
1943#endif
1944 }
1945
1948 simulatorTypes.emplace_back(Simulators::SimulatorType::kQCSim,
1950
1953 simulatorTypes.emplace_back(Simulators::SimulatorType::kCompositeQCSim,
1955
1956 if (checkTensorNetwork &&
1959 simulatorTypes.emplace_back(Simulators::SimulatorType::kQCSim,
1961
1965 (nrQubits <= 4 || !maxBondDim.empty()))
1966 simulatorTypes.emplace_back(
1969
1973 simulatorTypes.emplace_back(Simulators::SimulatorType::kQCSim,
1975
1979 simulatorTypes.emplace_back(Simulators::SimulatorType::kQCSim,
1981
1982#ifndef NO_QISKIT_AER
1983 // tensor networks are out of the picture for now for qiskit aer, since they
1984 // are available with cuda library, and work only on linux (obviously when
1985 // compiled properly and if there is the right hw an driver installed)
1986
1989 simulatorTypes.emplace_back(Simulators::SimulatorType::kQiskitAer,
1991
1995 simulatorTypes.emplace_back(
1998
2002 (nrQubits <= 4 || !maxBondDim.empty()))
2003 simulatorTypes.emplace_back(
2006#endif
2007
2008#ifdef __linux__
2012 simulatorTypes.emplace_back(Simulators::SimulatorType::kGpuSim,
2017 simulatorTypes.emplace_back(
2023 simulatorTypes.emplace_back(Simulators::SimulatorType::kGpuSim,
2028 simulatorTypes.emplace_back(
2031 }
2032#endif
2033
2036 simulatorTypes.emplace_back(Simulators::SimulatorType::kQuestSim,
2038
2039 if (simulatorTypes.empty())
2040 return nullptr;
2041 else if (simulatorTypes.size() == 1) {
2042 simType = simulatorTypes[0].first;
2043 method = simulatorTypes[0].second;
2044
2045 std::shared_ptr<Simulators::ISimulator> sim =
2047 if (sim) {
2049 if (!maxBondDim.empty())
2050 sim->Configure("matrix_product_state_max_bond_dimension",
2051 maxBondDim.c_str());
2052 if (!singularValueThreshold.empty())
2053 sim->Configure("matrix_product_state_truncation_threshold",
2054 singularValueThreshold.c_str());
2055 if (!mpsSample.empty())
2056 sim->Configure("mps_sample_measure_algorithm", mpsSample.c_str());
2057
2058 sim->AllocateQubits(nrQubits);
2059 sim->Initialize();
2060
2061 OptimizeMPSInitialQubitsMap(sim, dcirc, nrQubits);
2062 } else {
2063 sim->AllocateQubits(nrQubits);
2064 sim->Initialize();
2065 }
2066
2067 if (!dontRunCircuitStart) {
2068 sim->SetMultithreading(true);
2070 Time>::ExecuteUpToMeasurements(dcirc, nrQubits, nrCbits,
2071 nrResultCbits, sim, executed);
2072 }
2073 sim->SetMultithreading(multithreading || GetMaxSimulators() == 1);
2074
2075 return sim;
2076 }
2077 }
2078
2079 std::shared_ptr<Simulators::ISimulator> sim =
2080 simulatorsEstimator->ChooseBestSimulator(
2081 simulatorTypes, dcirc, counts, nrQubits, nrCbits, nrResultCbits,
2082 simType, method, executed, maxBondDim, singularValueThreshold,
2083 mpsSample, GetMaxSimulators(), pauliStrings, multithreading);
2084
2085 if (sim) {
2086 sim->AllocateQubits(nrQubits);
2087 sim->Initialize();
2088
2089 OptimizeMPSInitialQubitsMap(sim, dcirc, nrQubits);
2090
2091 if (!dontRunCircuitStart) {
2092 sim->SetMultithreading(true);
2094 dcirc, nrQubits, nrCbits, nrResultCbits, sim, executed);
2095 }
2096 sim->SetMultithreading(multithreading || GetMaxSimulators() == 1);
2097 }
2098
2099 return sim;
2100 }
2101
2102 void SetInitialQubitsMapOptimization(bool optimize = true) override {
2103 optimizeInitialQubitsMap = optimize;
2104 }
2105
2106 bool GetInitialQubitsMapOptimization() const override {
2107 return optimizeInitialQubitsMap;
2108 }
2109
2110 void SetMPSOptimizeSwaps(bool optimize = true) override {
2111 mpsOptimizeSwaps = optimize;
2112 }
2113
2114 bool GetMPSOptimizeSwaps() const override { return mpsOptimizeSwaps; }
2115
2116 void SetMPSOptimizationBondDimensionThreshold(size_t threshold) override {
2117 mpsOptimizationBondDimensionThreshold = threshold;
2118 }
2119
2121 return mpsOptimizationBondDimensionThreshold;
2122 }
2123
2124 void SetMPSOptimizationQubitsNumberThreshold(size_t threshold) override {
2125 mpsOptimizationQubitsNumberThreshold = threshold;
2126 }
2127
2129 return mpsOptimizationQubitsNumberThreshold;
2130 }
2131
2132 void SetLookaheadDepth(int depth) override {
2133 if (depth < 0) depth = std::numeric_limits<int>::max();
2134
2135 lookaheadDepth = depth;
2136 }
2137
2138 int GetLookaheadDepth() const override { return lookaheadDepth; }
2139
2140 void SetLookaheadDepthWithHeuristic(int depth) override {
2141 if (depth < 0) depth = std::numeric_limits<int>::max();
2142
2143 if (depth > lookaheadDepth) depth = lookaheadDepth;
2144
2145 lookaheadDepthWithHeuristic = depth;
2146 }
2147
2148 int GetLookaheadDepthWithHeuristic() const override {
2149 return lookaheadDepthWithHeuristic;
2150 }
2151
2152 double getGrowthFactorSwap() const override { return growthFactorSwap; }
2153 double getGrowthFactorGate() const override { return growthFactorGate; }
2154
2155 void setGrowthFactorSwap(double factor) override {
2156 growthFactorSwap = factor;
2157 }
2158
2159 void setGrowthFactorGate(double factor) override {
2160 growthFactorGate = factor;
2161 }
2162
2163 protected:
2165 std::shared_ptr<Simulators::ISimulator> &sim,
2166 std::shared_ptr<Circuits::Circuit<Time>> &dcirc, size_t nrQubits) const {
2167 if (sim->GetSimulationType() ==
2169 (optimizeInitialQubitsMap || mpsOptimizeSwaps) &&
2170 sim->SupportsMPSSwapOptimization()) {
2171 if (mpsOptimizationQubitsNumberThreshold <= nrQubits) {
2172 const auto maxBondDimValue =
2173 maxBondDim.empty() ? 0 : std::stoi(maxBondDim);
2174
2175 if (maxBondDim.empty() ||
2176 static_cast<int>(mpsOptimizationBondDimensionThreshold) <= maxBondDimValue) {
2177 // need to be sure the circuit is correctly converted
2178 dcirc->ConvertForCutting(); // convert the three qubit gates
2179 auto layers = dcirc->ToMultipleQubitsLayersNoClone();
2180
2181 Simulators::MPSDummySimulator dummySim(nrQubits);
2182 dummySim.setGrowthFactorGate(growthFactorGate);
2183 dummySim.setGrowthFactorSwap(growthFactorSwap);
2184
2185 if (!maxBondDim.empty())
2186 dummySim.SetMaxBondDimension(maxBondDimValue);
2187
2188 if (optimizeInitialQubitsMap) {
2189 const auto optimalMap = dummySim.ComputeOptimalQubitsMap(layers);
2190 sim->SetInitialQubitsMap(optimalMap);
2191 }
2192
2193 auto optCirc = Circuits::Circuit<Time>::LayersToCircuit(layers);
2194 dcirc->SetOperations(optCirc->GetOperations());
2195
2196 if (mpsOptimizeSwaps) {
2197 // TODO: come up with something better!
2198 int lookaheadDepthLocal = lookaheadDepth;
2199
2200 if (lookaheadDepthLocal == std::numeric_limits<int>::max()) {
2201 double avgTwoQubitGatesPerLayer = 0.0;
2202 for (const auto &layer : layers) {
2203 int twoQubitGates = 0;
2204 for (const auto &op : layer->GetOperations()) {
2205 if (op->AffectedQubits().size() >= 2) {
2206 ++twoQubitGates;
2207 }
2208 }
2209 avgTwoQubitGatesPerLayer += twoQubitGates;
2210 }
2211 avgTwoQubitGatesPerLayer /= layers.size();
2212
2213 int lookaheadVal = static_cast<int>(4. * avgTwoQubitGatesPerLayer);
2214 if (lookaheadVal > 15) lookaheadVal = 15;
2215
2216 lookaheadDepthLocal = layers.size() < 10 || nrQubits <= 10 ? 0
2217 : layers.size() < 20
2218 ? static_cast<int>(lookaheadVal)
2219 : layers.size() < 35
2220 ? static_cast<int>(1.5 * lookaheadVal)
2221 : 2 * lookaheadVal;
2222 }
2223
2224 int lookaheadHeuristicDepthLocal = lookaheadDepthWithHeuristic;
2225
2226 if (lookaheadHeuristicDepthLocal == std::numeric_limits<int>::max())
2227 lookaheadHeuristicDepthLocal =
2228 layers.size() < 10 || nrQubits <= 10 ? 0
2229 : layers.size() < 20
2230 ? lookaheadDepthLocal - 1
2231 : lookaheadDepthLocal - 2;
2232
2233 sim->setGrowthFactorGate(growthFactorGate);
2234 sim->setGrowthFactorSwap(growthFactorSwap);
2235 sim->SetUseOptimalMeetingPosition(true);
2236 sim->SetLookaheadDepth(lookaheadDepthLocal);
2237 sim->SetLookaheadDepthWithHeuristic(lookaheadHeuristicDepthLocal);
2238 sim->SetUpcomingGates(dcirc->GetOperations());
2239 }
2240 }
2241 }
2242 }
2243 }
2244
2254 auto optimiser = controller->GetOptimiser();
2255 if (optimiser) {
2256 // convert the classical state results back to the expected order
2257 const auto &qubitsMap = optimiser->GetReverseQubitsMap();
2258
2259 ConvertBackState(qubitsMap);
2260 }
2261 }
2262
2275 const std::unordered_map<Types::qubit_t, Types::qubit_t> &qubitsMap) {
2276 // might not be the one stored in the network, might exist in the DES
2277 Circuits::OperationState &theClassicalState = GetState();
2278
2279 theClassicalState.Remap(qubitsMap);
2280 }
2281
2293 auto optimiser = controller->GetOptimiser();
2294 if (optimiser) {
2295 // convert the classical state results back to the expected order
2296 const auto &qubitsMap = optimiser->GetReverseQubitsMap();
2297
2298 ConvertBackResults(res, qubitsMap);
2299 }
2300 }
2301
2315 ExecuteResults &res,
2316 const std::unordered_map<Types::qubit_t, Types::qubit_t> &bitsMap) const {
2317 ExecuteResults translatedRes;
2318
2319 size_t numClassicalBits = 0;
2320 for (const auto &[q, b] : bitsMap)
2321 if (b >= numClassicalBits) numClassicalBits = b + 1;
2322
2323 numClassicalBits = std::max(numClassicalBits, GetNumClassicalBits());
2324
2325 for (const auto &r : res) {
2326 Circuits::OperationState translatedState(r.first);
2327
2328 translatedState.Remap(bitsMap, false, numClassicalBits);
2329 translatedRes[translatedState.GetAllBits()] = r.second;
2330 }
2331
2332 res.swap(translatedRes);
2333 }
2334
2346 std::unordered_map<Types::qubit_t, Types::qubit_t> MapCircuitOnHost(
2347 const std::shared_ptr<Circuits::Circuit<Time>> &circuit, size_t hostId,
2348 size_t &nrQubits, size_t &nrCbits, bool useSeparateSimForHosts = false) {
2349 qubitsMapOnHost.clear();
2350 nrQubits = 0;
2351 nrCbits = 0;
2352 if (!circuit) return {};
2353
2354 const auto host =
2355 std::static_pointer_cast<SimpleHost<Time>>(GetHost(hostId));
2356 const size_t hostNrQubits = host->GetNumQubits();
2357
2358 std::unordered_map<Types::qubit_t, Types::qubit_t> reverseQubitsMap;
2359
2360 if (!useSeparateSimForHosts) {
2361 size_t mxq = 0;
2362 size_t mnq = std::numeric_limits<size_t>::max();
2363 size_t mxb = 0;
2364 size_t mnb = std::numeric_limits<size_t>::max();
2365
2366 for (const auto &op : circuit->GetOperations()) {
2367 const auto qbits = op->AffectedQubits();
2368 for (auto q : qbits) {
2369 if (q > mxq) mxq = q;
2370 if (q < mnq) mnq = q;
2371 }
2372 const auto cbits = op->AffectedBits();
2373 for (auto b : cbits) {
2374 if (b > mxb) mxb = b;
2375 if (b < mnb) mnb = b;
2376 }
2377 }
2378
2379 if (mnq > mxq) mnq = 0;
2380 if (mnb > mxb) mnb = 0;
2381
2382 nrQubits = mxq - mnq + 1;
2383 nrCbits = mxb - mnb + 1;
2384 if (nrCbits < nrQubits) nrCbits = nrQubits;
2385
2386 const size_t startQubit = host->GetStartQubitId();
2387
2388 if (mnq < startQubit || mxq >= startQubit + hostNrQubits) {
2389 if (nrQubits >
2390 hostNrQubits +
2391 1) // the host has an additional 'special' qubit for the
2392 // entanglement or other operations (like those for cutting)
2393 throw std::runtime_error("Circuit does not fit on the host!");
2394
2395 for (size_t i = 0; i < nrCbits; ++i) {
2396 const size_t mapFrom = mnq + i;
2397 const size_t mapTo = startQubit + i;
2398
2399 qubitsMapOnHost[mapFrom] = mapTo;
2400 reverseQubitsMap[mapTo] = mapFrom;
2401 }
2402
2403 distCirc = std::static_pointer_cast<Circuits::Circuit<Time>>(
2404 circuit->Remap(qubitsMapOnHost, qubitsMapOnHost));
2405 }
2406
2407 return reverseQubitsMap;
2408 }
2409
2410 distCirc = circuit->RemapToContinuous(qubitsMapOnHost, reverseQubitsMap,
2411 nrQubits, nrCbits);
2412
2413 assert(nrQubits == qubitsMapOnHost.size());
2414
2415 if (nrQubits == 0) nrQubits = 1;
2416
2417 if (nrQubits >
2418 hostNrQubits +
2419 1) // the host has an additional 'special' qubit for the
2420 // entanglement or other operations (like those for cutting)
2421 throw std::runtime_error("Circuit does not fit on the host!");
2422
2423 return reverseQubitsMap;
2424 }
2425
2426 bool optimizeSimulator = true;
2428
2434
2435 std::string maxBondDim;
2437 std::string mpsSample;
2439
2440 size_t maxSimulators = QC::QubitRegisterCalculator<>::
2441 GetNumberOfThreads();
2443
2446 std::shared_ptr<Simulators::ISimulator>
2448
2449 std::shared_ptr<Circuits::Circuit<Time>>
2451
2452 std::shared_ptr<IController<Time>>
2454 // TODO: depending on the network topology, we will have adiacency lists, etc.
2455 // or simply a vector of hosts for a totally connected network (or where the
2456 // communication details do not matter so much)
2457 std::vector<std::shared_ptr<IHost<Time>>>
2459
2460 std::unique_ptr<Estimators::SimulatorsEstimatorInterface<Time>>
2462
2463 private:
2465 threadsPool;
2466 bool recreateIfNeeded =
2467 true;
2468 std::unordered_map<Types::qubit_t, Types::qubit_t>
2469 qubitsMapOnHost;
2472 const std::vector<std::string> *pauliStrings =
2473 nullptr;
2475
2476 bool optimizeInitialQubitsMap = true;
2478 bool mpsOptimizeSwaps = true;
2479 size_t mpsOptimizationBondDimensionThreshold =
2480 32;
2481 size_t mpsOptimizationQubitsNumberThreshold =
2482 12;
2483
2484 int lookaheadDepth =
2485 std::numeric_limits<int>::max();
2487 int lookaheadDepthWithHeuristic = std::numeric_limits<int>::max();
2489
2490 double growthFactorSwap = 1.;
2491 double growthFactorGate = 0.65;
2492};
2493
2494} // namespace Network
2495
2496#endif // !_SIMPLE_NETWORK_H_
int GetSimulationType(void *sim)
Circuit class for holding the sequence of operations.
Definition Circuit.h:46
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:2417
The operation interface.
Definition Operations.h:358
The state class that stores the classical state of a quantum circuit execution.
Definition Operations.h:63
const std::vector< bool > & GetAllBits() const
Get the classical bits.
Definition Operations.h:214
void Clear()
Clear the classical state.
Definition Operations.h:169
void SetResultsInOrder(const std::vector< bool > &results)
Set the classical bits.
Definition Operations.h:253
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:297
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)
The controller host interface.
Definition Controller.h:106
The network interface.
Definition Network.h:58
std::shared_ptr< INetwork< Time > > getptr()
Definition Network.h:772
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< 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.
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.
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.
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.
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 > 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) const override
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
void setGrowthFactorSwap(double factor)
std::vector< long long int > ComputeOptimalQubitsMap(const std::vector< std::shared_ptr< Circuits::Circuit<> > > &layers, int nrShuffles=25, int nrSwaps=10)
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:101
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:85
@ kStatevector
statevector simulation type
Definition State.h:86
@ kMatrixProductState
matrix product state simulation type
Definition State.h:87
@ kStabilizer
Clifford gates simulation type.
Definition State.h:88
@ kPauliPropagator
Pauli propagator simulation type.
Definition State.h:90
@ kTensorNetwork
Tensor network simulation type.
Definition State.h:89
@ kPathIntegral
Path integral simulation type.
Definition State.h:92
SimulatorType
The type of simulator.
Definition State.h:68
@ kCompositeQCSim
composite qcsim simulator type
Definition State.h:76
@ kQCSim
qcsim simulator type
Definition State.h:72
@ kQiskitAer
qiskit aer simulator type
Definition State.h:70
@ kQuestSim
quest simulator type
Definition State.h:78
@ kCompositeQiskitAer
composite qiskit aer simulator type
Definition State.h:74
@ kGpuSim
gpu simulator type
Definition State.h:77
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