Maestro 0.1.0
Unified interface for quantum circuit simulation
Loading...
Searching...
No Matches
QCSimState.h
Go to the documentation of this file.
1
12
13#pragma once
14
15#ifndef _QCSIMSTATE_H_
16#define _QCSIMSTATE_H_
17
18#ifdef INCLUDED_BY_FACTORY
19
20#include <algorithm>
21
22#include "Simulator.h"
23
24#include "Clifford.h"
25#include "MPSSimulator.h"
26#include "QubitRegister.h"
27
30
31#include "../Utils/Alias.h"
32
33namespace Simulators {
34// TODO: Maybe use the pimpl idiom
35// https://en.cppreference.com/w/cpp/language/pimpl to hide the implementation
36// for good but during development this should be good enough
37namespace Private {
38
49class QCSimState : public ISimulator {
50public:
51 QCSimState() : rng(std::random_device{}()), uniformZeroOne(0, 1) {}
52
60 void Initialize() override {
61 if (nrQubits != 0) {
62 if (simulationType == SimulationType::kMatrixProductState) {
63 mpsSimulator =
64 std::make_unique<QC::TensorNetworks::MPSSimulator>(nrQubits);
65 if (limitEntanglement && singularValueThreshold > 0.)
66 mpsSimulator->setLimitEntanglement(singularValueThreshold);
67 if (limitSize && chi > 0)
68 mpsSimulator->setLimitBondDimension(chi);
69 } else if (simulationType == SimulationType::kStabilizer)
70 cliffordSimulator =
71 std::make_unique<QC::Clifford::StabilizerSimulator>(nrQubits);
72 else if (simulationType == SimulationType::kTensorNetwork) {
73 tensorNetwork =
74 std::make_unique<TensorNetworks::TensorNetwork>(nrQubits);
75 // for now the only used contractor is the forest one, but we'll use
76 // more in the future
77 const auto tensorContractor =
78 std::make_shared<TensorNetworks::ForestContractor>();
79 tensorNetwork->SetContractor(tensorContractor);
80 } else
81 state = std::make_unique<QC::QubitRegister<>>(nrQubits);
82
83 SetMultithreading(enableMultithreading);
84 }
85 }
86
98 void InitializeState(size_t num_qubits,
99 std::vector<std::complex<double>> &amplitudes) override {
100 if (num_qubits == 0)
101 return;
102 Clear();
103 nrQubits = num_qubits;
104 Initialize();
105 if (simulationType != SimulationType::kStatevector)
106 throw std::runtime_error("QCSimState::InitializeState: Invalid "
107 "simulation type for initializing the state.");
108
109 Eigen::VectorXcd amplitudesEigen(
110 Eigen::Map<Eigen::VectorXcd, Eigen::Unaligned>(amplitudes.data(),
111 amplitudes.size()));
112 state->setRegisterStorageFastNoNormalize(amplitudesEigen);
113 }
114
126 /*
127 void InitializeState(size_t num_qubits, std::vector<std::complex<double>,
128 avoid_init_allocator<std::complex<double>>>& amplitudes) override
129 {
130 Clear();
131 nrQubits = num_qubits;
132 Initialize();
133 Eigen::VectorXcd amplitudesEigen(Eigen::Map<Eigen::VectorXcd,
134 Eigen::Unaligned>(amplitudes.data(), amplitudes.size()));
135 state->setRegisterStorageFastNoNormalize(amplitudesEigen);
136 }
137 */
138
150#ifndef NO_QISKIT_AER
151 void InitializeState(size_t num_qubits,
152 AER::Vector<std::complex<double>> &amplitudes) override {
153 if (num_qubits == 0)
154 return;
155 Clear();
156 nrQubits = num_qubits;
157 Initialize();
158 if (simulationType != SimulationType::kStatevector)
159 throw std::runtime_error("QCSimState::InitializeState: Invalid "
160 "simulation type for initializing the state.");
161
162 Eigen::VectorXcd amplitudesEigen(
163 Eigen::Map<Eigen::VectorXcd, Eigen::Unaligned>(amplitudes.data(),
164 amplitudes.size()));
165 state->setRegisterStorageFastNoNormalize(amplitudesEigen);
166 }
167#endif
168
180 void InitializeState(size_t num_qubits,
181 Eigen::VectorXcd &amplitudes) override {
182 if (num_qubits == 0)
183 return;
184 Clear();
185 nrQubits = num_qubits;
186 Initialize();
187
188 if (simulationType != SimulationType::kStatevector)
189 throw std::runtime_error("QCSimState::InitializeState: Invalid "
190 "simulation type for initializing the state.");
191
192 state = std::make_unique<QC::QubitRegister<>>(nrQubits, amplitudes);
193 state->SetMultithreading(enableMultithreading);
194 }
195
202 void Reset() override {
203 if (mpsSimulator)
204 mpsSimulator->Clear();
205 else if (cliffordSimulator)
206 cliffordSimulator->Reset();
207 else if (tensorNetwork)
208 tensorNetwork->Clear();
209 else if (state)
210 state->Reset();
211 }
212
221 void Configure(const char *key, const char *value) override {
222 if (std::string("method") == key) {
223 if (std::string("statevector") == value)
224 simulationType = SimulationType::kStatevector;
225 else if (std::string("matrix_product_state") == value)
226 simulationType = SimulationType::kMatrixProductState;
227 else if (std::string("stabilizer") == value)
228 simulationType = SimulationType::kStabilizer;
229 else if (std::string("tensor_network") == value)
230 simulationType = SimulationType::kTensorNetwork;
231 } else if (std::string("matrix_product_state_truncation_threshold") ==
232 key) {
233 singularValueThreshold = std::stod(value);
234 if (singularValueThreshold > 0.) {
235 limitEntanglement = true;
236 if (mpsSimulator)
237 mpsSimulator->setLimitEntanglement(singularValueThreshold);
238 } else
239 limitEntanglement = false;
240 } else if (std::string("matrix_product_state_max_bond_dimension") == key) {
241 chi = std::stoi(value);
242 if (chi > 0) {
243 limitSize = true;
244 if (mpsSimulator)
245 mpsSimulator->setLimitBondDimension(chi);
246 } else
247 limitSize = false;
248 } else if (std::string("mps_sample_measure_algorithm") == key)
249 useMPSMeasureNoCollapse = std::string("mps_probabilities") == value;
250 }
251
259 std::string GetConfiguration(const char *key) const override {
260 if (std::string("method") == key) {
261 switch (simulationType) {
262 case SimulationType::kStatevector:
263 return "statevector";
264 case SimulationType::kMatrixProductState:
265 return "matrix_product_state";
266 case SimulationType::kStabilizer:
267 return "stabilizer";
268 case SimulationType::kTensorNetwork:
269 return "tensor_network";
270 default:
271 return "other";
272 }
273 } else if (std::string("matrix_product_state_truncation_threshold") ==
274 key) {
275 if (limitEntanglement && singularValueThreshold > 0.)
276 return std::to_string(singularValueThreshold);
277 } else if (std::string("matrix_product_state_max_bond_dimension") == key) {
278 if (limitSize && limitSize > 0)
279 return std::to_string(chi);
280 } else if (std::string("mps_sample_measure_algorithm") == key) {
281 return useMPSMeasureNoCollapse ? "mps_probabilities"
282 : "mps_apply_measure";
283 }
284
285 return "";
286 }
287
295 size_t AllocateQubits(size_t num_qubits) override {
296 if ((simulationType == SimulationType::kStatevector && state) ||
297 (simulationType == SimulationType::kMatrixProductState &&
298 mpsSimulator) ||
299 (simulationType == SimulationType::kStabilizer && cliffordSimulator) ||
300 (simulationType == SimulationType::kTensorNetwork && tensorNetwork))
301 return 0;
302
303 const size_t oldNrQubits = nrQubits;
304 nrQubits += num_qubits;
305 return oldNrQubits;
306 }
307
314 size_t GetNumberOfQubits() const override { return nrQubits; }
315
323 void Clear() override {
324 state = nullptr;
325 mpsSimulator = nullptr;
326 cliffordSimulator = nullptr;
327 tensorNetwork = nullptr;
328 nrQubits = 0;
329 }
330
338 size_t Measure(const Types::qubits_vector &qubits) override {
339 // TODO: this is inefficient, maybe implement it better in qcsim
340 // for now it has the possibility of measuring a qubits interval, but not a
341 // list of qubits
342
343 size_t res = 0;
344 size_t mask = 1ULL;
345
346 DontNotify();
347 if (simulationType == SimulationType::kStatevector) {
348 for (size_t qubit : qubits) {
349 if (state->MeasureQubit(static_cast<unsigned int>(qubit)))
350 res |= mask;
351 mask <<= 1;
352 }
353 } else if (simulationType == SimulationType::kStabilizer) {
354 for (size_t qubit : qubits) {
355 if (cliffordSimulator->MeasureQubit(static_cast<unsigned int>(qubit)))
356 res |= mask;
357 mask <<= 1;
358 }
359 } else if (simulationType == SimulationType::kTensorNetwork) {
360 for (size_t qubit : qubits) {
361 if (tensorNetwork->Measure(static_cast<unsigned int>(qubit)))
362 res |= mask;
363 mask <<= 1;
364 }
365 } else {
366 /*
367 for (size_t qubit : qubits)
368 {
369 if (mpsSimulator->MeasureQubit(static_cast<unsigned int>(qubit)))
370 res |= mask;
371 mask <<= 1;
372 }
373 */
374 const std::set<Eigen::Index> qubitsSet(qubits.begin(), qubits.end());
375 auto measured = mpsSimulator->MeasureQubits(qubitsSet);
376 for (Types::qubit_t qubit : qubits) {
377 if (measured[qubit])
378 res |= mask;
379 mask <<= 1;
380 }
381 }
382 Notify();
383
384 NotifyObservers(qubits);
385
386 return res;
387 }
388
395 void ApplyReset(const Types::qubits_vector &qubits) override {
396 QC::Gates::PauliXGate xGate;
397
398 DontNotify();
399 if (simulationType == SimulationType::kStatevector) {
400 for (size_t qubit : qubits)
401 if (state->MeasureQubit(static_cast<unsigned int>(qubit)))
402 state->ApplyGate(xGate, static_cast<unsigned int>(qubit));
403 } else if (simulationType == SimulationType::kStabilizer) {
404 for (size_t qubit : qubits)
405 if (cliffordSimulator->MeasureQubit(static_cast<unsigned int>(qubit)))
406 cliffordSimulator->ApplyX(static_cast<unsigned int>(qubit));
407 } else if (simulationType == SimulationType::kTensorNetwork) {
408 for (size_t qubit : qubits)
409 if (tensorNetwork->Measure(static_cast<unsigned int>(qubit)))
410 tensorNetwork->AddGate(xGate, static_cast<unsigned int>(qubit));
411 } else {
412 for (size_t qubit : qubits)
413 if (mpsSimulator->MeasureQubit(static_cast<unsigned int>(qubit)))
414 mpsSimulator->ApplyGate(xGate, static_cast<unsigned int>(qubit));
415 }
416 Notify();
417
418 NotifyObservers(qubits);
419 }
420
432 double Probability(Types::qubit_t outcome) override {
433 if (simulationType == SimulationType::kMatrixProductState)
434 return mpsSimulator->getBasisStateProbability(
435 static_cast<unsigned int>(outcome));
436 else if (simulationType == SimulationType::kStabilizer)
437 return cliffordSimulator->getBasisStateProbability(
438 static_cast<unsigned int>(outcome));
439 else if (simulationType == SimulationType::kTensorNetwork)
440 return tensorNetwork->getBasisStateProbability(outcome);
441
442 return state->getBasisStateProbability(static_cast<unsigned int>(outcome));
443 }
444
455 std::complex<double> Amplitude(Types::qubit_t outcome) override {
456 if (simulationType == SimulationType::kMatrixProductState)
457 return mpsSimulator->getBasisStateAmplitude(
458 static_cast<unsigned int>(outcome));
459 else if (simulationType == SimulationType::kStabilizer)
460 throw std::runtime_error(
461 "QCSimState::Amplitude: Invalid simulation type for obtaining the "
462 "amplitude of the specified outcome.");
463 else if (simulationType == SimulationType::kTensorNetwork)
464 throw std::runtime_error("QCSimState::Amplitude: Not supported for the "
465 "tensor network simulator.");
466
467 return state->getBasisStateAmplitude(static_cast<unsigned int>(outcome));
468 }
469
480 std::vector<double> AllProbabilities() override {
481 // TODO: In principle this could be done, but why? It should be costly.
482 if (simulationType == SimulationType::kTensorNetwork)
483 throw std::runtime_error("QCSimState::AllProbabilities: Invalid "
484 "simulation type for obtaining probabilities.");
485 else if (simulationType == SimulationType::kStabilizer)
486 return cliffordSimulator->AllProbabilities();
487
488 const Eigen::VectorXcd probs =
489 simulationType == SimulationType::kMatrixProductState
490 ? mpsSimulator->getRegisterStorage().cwiseAbs2()
491 : state->getRegisterStorage().cwiseAbs2();
492
493 std::vector<double> result(probs.size());
494
495 for (int i = 0; i < probs.size(); ++i)
496 result[i] = probs[i].real();
497
498 return result;
499 }
500
512 std::vector<double>
513 Probabilities(const Types::qubits_vector &qubits) override {
514 if (simulationType == SimulationType::kStabilizer)
515 throw std::runtime_error("QCSimState::Probabilities: Invalid simulation "
516 "type for obtaining probabilities.");
517 else if (simulationType == SimulationType::kTensorNetwork) {
518 // TODO: Implement this!!!
519 throw std::runtime_error("QCSimState::Probabilities: Not implemented yet "
520 "for the tensor network simulator.");
521 }
522
523 std::vector<double> result(qubits.size());
524
525 if (simulationType == SimulationType::kMatrixProductState) {
526 for (int i = 0; i < static_cast<int>(qubits.size()); ++i)
527 result[i] = mpsSimulator->getBasisStateProbability(qubits[i]);
528 } else {
529 const Eigen::VectorXcd &reg = state->getRegisterStorage();
530
531 for (int i = 0; i < static_cast<int>(qubits.size()); ++i)
532 result[i] = std::norm(reg[qubits[i]]);
533 }
534
535 return result;
536 }
537
551 std::unordered_map<Types::qubit_t, Types::qubit_t>
553 size_t shots = 1000) override {
554 if (qubits.empty() || shots == 0)
555 return {};
556
557 // TODO: this is inefficient, maybe implement it better in qcsim
558 // for now it has the possibility of measuring a qubits interval, but not a
559 // list of qubits
560 std::unordered_map<Types::qubit_t, Types::qubit_t> result;
561
562 DontNotify();
563
564 if (simulationType == SimulationType::kMatrixProductState) {
565 bool normal = true;
566 if (useMPSMeasureNoCollapse) {
567 // check to see if it can be used
568 std::unordered_set qset(qubits.begin(), qubits.end());
569 if (qset.size() == GetNumberOfQubits()) {
570 // it can!
571 normal = false;
572 for (size_t shot = 0; shot < shots; ++shot) {
573 const size_t measRaw = MeasureNoCollapse();
574 size_t meas = 0;
575 size_t mask = 1ULL;
576
577 // translate the measurement
578 for (auto q : qubits) {
579 const size_t qubitMask = 1ULL << q;
580 if (measRaw & qubitMask)
581 meas |= mask;
582 mask <<= 1ULL;
583 }
584
585 ++result[meas];
586 }
587 }
588 }
589
590 if (normal) {
591 auto savedState = mpsSimulator->getState();
592 for (size_t shot = 0; shot < shots; ++shot) {
593 const size_t meas = Measure(qubits);
594 ++result[meas];
595 mpsSimulator->setState(savedState);
596 }
597 }
598 } else if (simulationType == SimulationType::kStabilizer) {
599 cliffordSimulator->SaveState();
600 for (size_t shot = 0; shot < shots; ++shot) {
601 const size_t meas = Measure(qubits);
602 ++result[meas];
603 cliffordSimulator->RestoreState();
604 }
605 cliffordSimulator->ClearSavedState();
606 } else if (simulationType == SimulationType::kTensorNetwork) {
607 tensorNetwork->SaveState();
608 for (size_t shot = 0; shot < shots; ++shot) {
609 const size_t meas = Measure(qubits);
610 ++result[meas];
611 tensorNetwork->RestoreState();
612 }
613 tensorNetwork->ClearSavedState();
614 } else {
615 if (shots > 1) {
616 const auto &statev = state->getRegisterStorage();
617
618 const Utils::Alias alias(statev);
619
620 for (size_t shot = 0; shot < shots; ++shot) {
621 const double prob = 1. - uniformZeroOne(rng);
622 const size_t measRaw = alias.Sample(prob);
623
624 size_t meas = 0;
625 size_t mask = 1ULL;
626 for (auto q : qubits) {
627 const size_t qubitMask = 1ULL << q;
628 if ((measRaw & qubitMask) != 0)
629 meas |= mask;
630 mask <<= 1ULL;
631 }
632
633 ++result[meas];
634 }
635 } else {
636 for (size_t shot = 0; shot < shots; ++shot) {
637 const size_t measRaw = MeasureNoCollapse();
638 size_t meas = 0;
639 size_t mask = 1ULL;
640
641 for (auto q : qubits) {
642 const size_t qubitMask = 1ULL << q;
643 if ((measRaw & qubitMask) != 0)
644 meas |= mask;
645 mask <<= 1ULL;
646 }
647
648 ++result[meas];
649 }
650 }
651 }
652
653 Notify();
654 NotifyObservers(qubits);
655
656 return result;
657 }
658
670 double ExpectationValue(const std::string &pauliStringOrig) override {
671 if (pauliStringOrig.empty())
672 return 1.0;
673
674 std::string pauliString = pauliStringOrig;
675 if (pauliString.size() > GetNumberOfQubits()) {
676 for (size_t i = GetNumberOfQubits(); i < pauliString.size(); ++i) {
677 const auto pauliOp = toupper(pauliString[i]);
678 if (pauliOp != 'I' && pauliOp != 'Z')
679 return 0.0;
680 }
681
682 pauliString.resize(GetNumberOfQubits());
683 }
684
685 if (simulationType == SimulationType::kStabilizer)
686 return cliffordSimulator->ExpectationValue(pauliString);
687 else if (simulationType == SimulationType::kTensorNetwork)
688 return tensorNetwork->ExpectationValue(pauliString);
689
690 // statevector or mps
691 static const QC::Gates::PauliXGate<> xgate;
692 static const QC::Gates::PauliYGate<> ygate;
693 static const QC::Gates::PauliZGate<> zgate;
694
695 std::vector<QC::Gates::AppliedGate<Eigen::MatrixXcd>> pauliStringVec;
696 pauliStringVec.reserve(pauliString.size());
697
698 for (size_t q = 0; q < pauliString.size(); ++q) {
699 switch (toupper(pauliString[q])) {
700 case 'X': {
701 QC::Gates::AppliedGate<Eigen::MatrixXcd> ag(
702 xgate.getRawOperatorMatrix(), static_cast<Types::qubit_t>(q));
703 pauliStringVec.emplace_back(std::move(ag));
704 } break;
705 case 'Y': {
706 QC::Gates::AppliedGate<Eigen::MatrixXcd> ag(
707 ygate.getRawOperatorMatrix(), static_cast<Types::qubit_t>(q));
708 pauliStringVec.emplace_back(std::move(ag));
709 } break;
710 case 'Z': {
711 QC::Gates::AppliedGate<Eigen::MatrixXcd> ag(
712 zgate.getRawOperatorMatrix(), static_cast<Types::qubit_t>(q));
713 pauliStringVec.emplace_back(std::move(ag));
714 } break;
715 case 'I':
716 [[fallthrough]];
717 default:
718 break;
719 }
720 }
721
722 if (pauliStringVec.empty())
723 return 1.0;
724
725 if (simulationType == SimulationType::kMatrixProductState)
726 return mpsSimulator->ExpectationValue(pauliStringVec).real();
727
728 return state->ExpectationValue(pauliStringVec).real();
729 }
730
738 SimulatorType GetType() const override { return SimulatorType::kQCSim; }
739
748 SimulationType GetSimulationType() const override { return simulationType; }
749
758 void Flush() override {}
759
769 void SaveStateToInternalDestructive() override {}
770
777 void RestoreInternalDestructiveSavedState() override {}
778
788 void SaveState() override {
789 if (simulationType == SimulationType::kMatrixProductState)
790 mpsSimulator->SaveState();
791 else if (simulationType == SimulationType::kStabilizer)
792 cliffordSimulator->SaveState();
793 else if (simulationType == SimulationType::kTensorNetwork)
794 tensorNetwork->SaveState();
795 else
796 state->SaveState();
797 }
798
807 void RestoreState() override {
808 if (simulationType == SimulationType::kMatrixProductState)
809 mpsSimulator->RestoreState();
810 else if (simulationType == SimulationType::kStabilizer)
811 cliffordSimulator->RestoreState();
812 else if (simulationType == SimulationType::kTensorNetwork)
813 tensorNetwork->RestoreState();
814 else
815 state->RestoreState();
816 }
817
825 std::complex<double> AmplitudeRaw(Types::qubit_t outcome) override {
826 return Amplitude(outcome);
827 }
828
837 void SetMultithreading(bool multithreading = true) override {
838 enableMultithreading = multithreading;
839 if (state)
840 state->SetMultithreading(multithreading);
841 if (cliffordSimulator)
842 cliffordSimulator->SetMultithreading(multithreading);
843 if (tensorNetwork)
844 tensorNetwork->SetMultithreading(multithreading);
845 }
846
854 bool GetMultithreading() const override { return enableMultithreading; }
855
866 bool IsQcsim() const override { return true; }
867
882 if (simulationType == SimulationType::kStatevector)
883 return state->MeasureNoCollapse();
884 else if (simulationType == SimulationType::kMatrixProductState) {
885 const auto measured = mpsSimulator->MeasureNoCollapse();
886 Types::qubit_t result = 0;
887 Types::qubit_t mask = 1;
888 for (const auto &meas : measured) {
889 if (meas)
890 result |= mask;
891 mask <<= 1;
892 }
893 return result;
894 }
895
896 throw std::runtime_error(
897 "QCSimState::MeasureNoCollapse: Invalid simulation type for measuring "
898 "all the qubits without collapsing the state.");
899
900 return 0;
901 }
902
903protected:
904 SimulationType simulationType =
905 SimulationType::kStatevector;
906
907 std::unique_ptr<QC::QubitRegister<>> state;
908 std::unique_ptr<QC::TensorNetworks::MPSSimulator>
909 mpsSimulator;
910 std::unique_ptr<QC::Clifford::StabilizerSimulator>
911 cliffordSimulator;
912 std::unique_ptr<TensorNetworks::TensorNetwork>
913 tensorNetwork;
914
915 size_t nrQubits = 0;
916 bool limitSize = false;
917 bool limitEntanglement = false;
918 Eigen::Index chi = 10; // if limitSize is true
919 double singularValueThreshold = 0.; // if limitEntanglement is true
920 bool enableMultithreading = true;
921 bool useMPSMeasureNoCollapse =
922 true;
923
924 std::mt19937_64 rng;
925 std::uniform_real_distribution<double> uniformZeroOne;
926};
927
928} // namespace Private
929} // namespace Simulators
930
931#endif
932
933#endif // !_QCSIMSTATE_H_
double Probability(void *sim, unsigned long long int outcome)
char * GetConfiguration(void *sim, const char *key)
int RestoreState(void *sim)
int ApplyReset(void *sim, const unsigned long int *qubits, unsigned long int nrQubits)
unsigned long int AllocateQubits(void *sim, unsigned long int nrQubits)
unsigned long int GetNumberOfQubits(void *sim)
double * AllProbabilities(void *sim)
unsigned long long int MeasureNoCollapse(void *sim)
int GetMultithreading(void *sim)
unsigned long long int Measure(void *sim, const unsigned long int *qubits, unsigned long int nrQubits)
double * Amplitude(void *sim, unsigned long long int outcome)
double * Probabilities(void *sim, const unsigned long long int *qubits, unsigned long int nrQubits)
int SetMultithreading(void *sim, int multithreading)
int SaveStateToInternalDestructive(void *sim)
int GetSimulationType(void *sim)
unsigned long long int * SampleCounts(void *sim, const unsigned long long int *qubits, unsigned long int nrQubits, unsigned long int shots)
int RestoreInternalDestructiveSavedState(void *sim)
int IsQcsim(void *sim)
int SaveState(void *sim)
size_t Sample(double v) const
Definition Alias.h:85
std::vector< qubit_t > qubits_vector
The type of a vector of qubits.
Definition Types.h:21
uint_fast64_t qubit_t
The type of a qubit.
Definition Types.h:20