Maestro 0.3.11
Unified interface for quantum circuit simulation
Loading...
Searching...
No Matches
ImmediateQCSimSimulator.h
Go to the documentation of this file.
1#pragma once
2#if defined(INCLUDED_BY_FACTORY)
8#include <algorithm>
9#include <cctype>
10#include <charconv>
11#include <functional>
12#include <iomanip>
13#include <limits>
14#include <random>
15#include <sstream>
16#include <utility>
17
18namespace Simulators::Private
19{
20template <typename T, typename = void> struct HasSetSeed : std::false_type
21{
22};
23
24template <typename T> struct HasSetSeed<T, std::void_t<decltype(std::declval<T &>().SetSeed(std::declval<uint64_t>()))>> : std::true_type
25{
26};
27
28template <typename T> void SeedBackend(T *backend, uint64_t seed)
29{
30 if constexpr (HasSetSeed<T>::value)
31 backend->SetSeed(seed);
32}
33
34} // namespace Simulators::Private
35
36namespace Simulators::Private
37{
38// Common configuration, sampling and circuit bookkeeping. Native state belongs
39// exclusively to the concrete simulator selected by the factory.
40class ImmediateQCSimSimulator : public ISimulator
41{
42 public:
43 virtual const char *MethodName() const = 0;
44
45 protected:
46 void ValidateMethod(const char *value) const
47 {
48 if (std::string(value) != MethodName())
49 throw std::invalid_argument("A concrete simulator cannot change method; select the backend through SimulatorsFactory");
50 }
51
52 virtual void RefreshRoutingCallback()
53 {
54 }
55
56 ImmediateQCSimSimulator() : samplingSeed(Utils::RandomStream::FreshSeed()), rng(samplingSeed), uniformZeroOne(0, 1)
57 {
58 SeedAuxiliaryRng(samplingSeed);
59 }
60
61 public:
62 void InitializeState(size_t num_qubits, std::vector<std::complex<double>> &amplitudes) override
63 {
64 if (num_qubits == 0)
65 return;
66 Clear();
67 nrQubits = num_qubits;
68 Initialize();
69 throw std::runtime_error("QCSimState::InitializeState: Invalid "
70 "simulation type for initializing the state.");
71 }
72
73#ifndef NO_QISKIT_AER
74 void InitializeState(size_t num_qubits, AER::Vector<std::complex<double>> &amplitudes) override
75 {
76 if (num_qubits == 0)
77 return;
78 Clear();
79 nrQubits = num_qubits;
80 Initialize();
81 throw std::runtime_error("QCSimState::InitializeState: Invalid "
82 "simulation type for initializing the state.");
83 }
84
85#endif
86
87 void InitializeState(size_t num_qubits, Eigen::VectorXcd &amplitudes) override
88 {
89 if (num_qubits == 0)
90 return;
91 Clear();
92 nrQubits = num_qubits;
93 Initialize();
94 throw std::runtime_error("QCSimState::InitializeState: Invalid "
95 "simulation type for initializing the state.");
96 }
97
98 void InitializeToBasisState(size_t num_qubits, Types::qubit_t basisState) override
99 {
100 if (num_qubits == 0)
101 return;
102 Clear();
103 nrQubits = num_qubits;
104 Initialize();
105 for (size_t q = 0; q < num_qubits; ++q)
106 if ((basisState >> q) & 1ULL)
107 ApplyX(static_cast<Types::qubit_t>(q));
108 }
109
110 void InitializeToBasisState(size_t num_qubits, const std::vector<bool> &basisState) override
111 {
112 if (num_qubits == 0)
113 return;
114 Clear();
115 nrQubits = num_qubits;
116 Initialize();
117 for (size_t q = 0; q < num_qubits && q < basisState.size(); ++q)
118 if (basisState[q])
119 ApplyX(static_cast<Types::qubit_t>(q));
120 }
121
122 void InitializeToMixtureOfBasisStates(size_t num_qubits, const std::vector<std::pair<Types::qubit_t, double>> &mixture) override
123 {
124 if (num_qubits == 0)
125 return;
126 Clear();
127 nrQubits = num_qubits;
128 Initialize();
129 throw std::runtime_error("QCSimState::InitializeToMixtureOfBasisStates: Invalid simulation "
130 "type for initializing to a mixture of basis states.");
131 }
132
133 void InitializeToMixtureOfBasisStates(size_t num_qubits, const std::vector<std::pair<std::vector<bool>, double>> &mixture) override
134 {
135 if (num_qubits == 0)
136 return;
137 Clear();
138 nrQubits = num_qubits;
139 Initialize();
140 throw std::runtime_error("QCSimState::InitializeToMixtureOfBasisStates: Invalid simulation "
141 "type for initializing to a mixture of basis states.");
142 }
143
144 bool SupportsMPSSwapOptimization() const override
145 {
146 return true;
147 }
148
149 void SetUpcomingGates(const std::vector<std::shared_ptr<Circuits::IOperation<double>>> &gates) override
150 {
151 upcomingGates = gates;
152 upcomingGateIndex = 0;
153 if (!gateCounterObserver)
154 gateCounterObserver = std::make_shared<GateCounterObserver>(upcomingGateIndex);
155 RegisterObserver(gateCounterObserver);
156 RefreshRoutingCallback();
157 }
158
159 long long int GetGatesCounter() const override
160 {
161 return upcomingGateIndex;
162 }
163
164 void SetGatesCounter(long long int counter) override
165 {
166 upcomingGateIndex = counter;
167 }
168
169 void IncrementGatesCounter() override
170 {
171 ++upcomingGateIndex;
172 }
173
174 std::string GetConfiguration(const char *key) const override
175 {
176 if (!key)
177 return {};
178 const bool adaptiveSampling =
179 std::string(MethodName()) == "statevector" || std::string(MethodName()) == "density_matrix" || std::string(MethodName()) == "path_integral";
180 if (adaptiveSampling && std::string(key) == "sampling_seed")
181 return std::to_string(samplingSeed);
182 if (adaptiveSampling && std::string(key) == "sampling_policy" && !configuration.IsSet(key))
183 return "reproducible_v1";
184 if (std::string(key) == "precision")
185 return "double";
186 if (std::string(key) == "use_double_precision")
187 return "1";
188 if (std::string("method") == key)
189 return MethodName();
190 return configuration.GetConfiguration(key);
191 }
192
193 size_t GetNumberOfQubits() const override
194 {
195 return nrQubits;
196 }
197
198 bool SupportsQuantumChannels() const override
199 {
200 return false;
201 }
202
203 void ApplyQuantumChannel(const Types::qubits_vector &targets, const QuantumChannel &channel) override
204 {
205 throw std::runtime_error("QCSim quantum channels require density_matrix or matrix_product_operator simulation");
206 }
207
208 double ProbabilityBits(const std::vector<bool> &bits) override
209 {
210 return IState::ProbabilityBits(bits);
211 }
212
213 std::complex<double> DensityMatrixElementBits(const std::vector<bool> &row, const std::vector<bool> &col) const override
214 {
215 return IState::DensityMatrixElementBits(row, col);
216 }
217
218 Eigen::MatrixXcd GetDensityMatrix(bool normalized = true) const override
219 {
220 return IState::GetDensityMatrix(normalized);
221 }
222
223 std::complex<double> ExpectationValueComplex(const std::string &pauli, bool normalized = true) const override
224 {
225 return IState::ExpectationValueComplex(pauli, normalized);
226 }
227
228 std::vector<std::complex<double>> ExpectationValuesComplex(const std::vector<std::string> &paulis, bool normalized = true) const override
229 {
230 return IState::ExpectationValuesComplex(paulis, normalized);
231 }
232
233 void ApplyOperator(const Types::qubits_vector &qubits, const Eigen::MatrixXcd &matrix, bool normalize = false) override
234 {
235 return IState::ApplyOperator(qubits, matrix, normalize);
236 }
237
238 void MoveAtBeginningOfChain(const Types::qubits_vector &qubits) override
239 {
240 return IState::MoveAtBeginningOfChain(qubits);
241 }
242
243 std::vector<std::complex<double>> GetStateVector() override
244 {
245 return IState::GetStateVector();
246 }
247
248 std::complex<double> ExpectationValueOperators(const Types::qubits_vector &qubits, const std::vector<Eigen::MatrixXcd> &matrices) override
249 {
250 return IState::ExpectationValueOperators(qubits, matrices);
251 }
252
253 std::complex<double> DensityMatrixTrace() const override
254 {
255 throw std::runtime_error("Mixed-state diagnostics require density_matrix or "
256 "matrix_product_operator");
257 }
258
259 std::complex<double> DensityMatrixOverlap(const IState &other) const override
260 {
261 throw std::invalid_argument("Density-matrix overlap requires two density matrices or two MPOs");
262 }
263
264 double DensityMatrixPurity() const override
265 {
266 throw std::runtime_error("Mixed-state diagnostics require density_matrix or "
267 "matrix_product_operator");
268 }
269
270 std::complex<double> DensityMatrixTraceOfSquare() const override
271 {
272 throw std::runtime_error("Mixed-state diagnostics require density_matrix or "
273 "matrix_product_operator");
274 }
275
276 double DensityMatrixHermiticityResidual() const override
277 {
278 throw std::runtime_error("Mixed-state diagnostics require density_matrix or "
279 "matrix_product_operator");
280 }
281
282 bool IsDensityMatrixHermitian(double eps = 1e-10) const override
283 {
284 throw std::runtime_error("Mixed-state diagnostics require density_matrix or "
285 "matrix_product_operator");
286 }
287
288 Eigen::MatrixXcd PartialTrace(const Types::qubits_vector &qubits) const override
289 {
290 throw std::runtime_error("Partial trace requires density_matrix or matrix_product_operator");
291 }
292
293 double FidelityWithStatevector(const Eigen::VectorXcd &psi) const override
294 {
295 throw std::runtime_error("Mixed-state fidelity requires density_matrix or "
296 "matrix_product_operator");
297 }
298
299 void RestoreDensityMatrixTrace() override
300 {
301 throw std::runtime_error("Trace restoration is only available for QCSim MPO");
302 }
303
304 void HermitizeDensityMatrix() override
305 {
306 throw std::runtime_error("Hermitization is only available for QCSim MPO");
307 }
308
309 void Trim() override
310 {
311 throw std::runtime_error("Trim is only available for QCSim MPS and MPO");
312 }
313
314 void ReCanonicalize() override
315 {
316 throw std::runtime_error("Canonicalization is only available for QCSim MPS and MPO");
317 }
318
319 std::complex<double> Amplitude(Types::qubit_t outcome) override
320 {
321 throw std::runtime_error("QCSimState::Amplitude: Invalid simulation type for obtaining the "
322 "amplitude of the specified outcome.");
323 }
324
325 std::complex<double> ProjectOnZero() override
326 {
327 return Amplitude(0);
328 }
329
330 std::vector<double> ExpectationValues(const std::vector<std::string> &paulis) override
331 {
332 return ISimulator::ExpectationValues(paulis);
333 }
334
335 SimulatorType GetType() const override
336 {
337 return SimulatorType::kQCSim;
338 }
339
340 void Flush() override
341 {
342 }
343
344 void SaveStateToInternalDestructive() override
345 {
346 }
347
349 {
350 }
351
352 std::complex<double> AmplitudeRaw(Types::qubit_t outcome) override
353 {
354 return Amplitude(outcome);
355 }
356
357 bool GetMultithreading() const override
358 {
359 return enableMultithreading;
360 }
361
362 bool IsQcsim() const override
363 {
364 return true;
365 }
366
368 {
369 if (GetNumberOfQubits() > sizeof(Types::qubit_t) * 8)
370 std::cerr << "Warning: The number of qubits to measure is larger than the "
371 "number of bits in the Types::qubit_t type, the outcome will be "
372 "undefined"
373 << std::endl;
374 throw std::runtime_error("QCSimState::MeasureNoCollapse: Invalid simulation type for "
375 "measuring "
376 "all the qubits without collapsing the state.");
377 }
378
379 std::vector<bool> MeasureNoCollapseMany() override
380 {
381 throw std::runtime_error("QCSimState::MeasureNoCollapseMany: Invalid simulation type for "
382 "measuring all the qubits without collapsing the state.");
383 }
384
385 const Configuration &GetConfiguration() const
386 {
387 return configuration;
388 }
389
390 const std::unordered_map<std::string, std::string> &GetConfigMap() const override
391 {
392 return configuration.GetConfigMap();
393 }
394
395 void ApplyGenericOneQubitGate(Types::qubit_t qubit, const Eigen::Matrix2cd &gate) override
396 {
397 throw std::runtime_error("QCSimSimulator::ApplyGenericOneQubitGate: Unsupported simulation "
398 "type.");
399 }
400
401 void ApplyGenericTwoQubitGate(Types::qubit_t qubit0, Types::qubit_t qubit1, const Eigen::Matrix4cd &gate) override
402 {
403 throw std::runtime_error("QCSimSimulator::ApplyGenericTwoQubitGate: Unsupported simulation "
404 "type.");
405 }
406
407 void ApplyGenericThreeQubitGate(Types::qubit_t q0, Types::qubit_t q1, Types::qubit_t q2, const Matrix8cd &gate) override
408 {
409 throw std::runtime_error("Generic three-qubit gates require a statevector or density matrix");
410 }
411
412 void ApplyNop() override
413 {
414 }
415
416 protected:
417 bool ConfigureSampling(const char *key, const char *value)
418 {
419 if (!Utils::Sampling::ValidateSetting(key, value))
420 return false;
421 configuration.SetConfiguration(key, value);
422 return true;
423 }
424
425 bool LegacySampling() const
426 {
427 return configuration.GetConfiguration("sampling_policy") == "legacy";
428 }
429
430 Utils::Sampling::Options SamplingOptions() const
431 {
432 Utils::Sampling::Options options;
434 if (configuration.IsSet("sampling_max_memory_mb"))
435 options.memoryBytes = static_cast<size_t>(std::stoull(configuration.GetConfiguration("sampling_max_memory_mb"))) << 20;
436 return options;
437 }
438
439 void SeedSampling(uint64_t seed)
440 {
441 samplingSeed = seed;
442 nextSeedStream = 0;
443 rng.seed(seed);
444 uniformZeroOne.reset();
445 }
446
447 void InitializeSamplingSeed()
448 {
449 // Recreating an unseeded native state must not restart an old stream.
450 // Explicitly configured seeds deliberately replay on initialization.
451 if (!configuration.IsSet("seed"))
452 {
453 SeedSampling(Utils::RandomStream::FreshSeed());
454 SeedAuxiliaryRng(samplingSeed);
455 }
456 }
457
458 uint64_t NextCloneSeed()
459 {
460 if (nextSeedStream == std::numeric_limits<uint64_t>::max())
461 throw std::overflow_error("Simulator clone stream ids exhausted");
462 return DeriveSeed(samplingSeed, nextSeedStream++);
463 }
464
465 size_t CheckedBasisStateCountForQueries() const
466 {
467 if (nrQubits >= std::numeric_limits<size_t>::digits)
468 throw std::runtime_error("ImmediateQCSimState: Too many qubits for enumerating basis states.");
469 return 1ULL << nrQubits;
470 }
471
472 size_t nrQubits = 0;
473
474 bool enableMultithreading = true;
475
476 std::vector<std::shared_ptr<Circuits::IOperation<>>> upcomingGates;
477
478 long long int upcomingGateIndex = 0;
479
480 class GateCounterObserver : public ISimulatorObserver
481 {
482 public:
483 GateCounterObserver(long long int &indexRef) : index(indexRef)
484 {
485 }
486
487 void Update(const Types::qubits_vector &) override
488 {
489 ++index;
490 }
491
492 private:
493 long long int &index;
494 };
495
496 std::shared_ptr<GateCounterObserver> gateCounterObserver;
497
498 uint64_t samplingSeed;
499 std::mt19937_64 rng;
500
501 uint64_t nextSeedStream = 0;
502
503 std::uniform_real_distribution<double> uniformZeroOne;
504
505 Configuration configuration;
506};
507} // namespace Simulators::Private
508#endif
char * GetConfiguration(void *sim, const char *key)
int ApplyX(void *sim, int qubit)
unsigned long int GetNumberOfQubits(void *sim)
unsigned long long int MeasureNoCollapse(void *sim)
int GetMultithreading(void *sim)
double * Amplitude(void *sim, unsigned long long int outcome)
int ApplyGenericThreeQubitGate(void *sim, unsigned long q0, unsigned long q1, unsigned long q2, const double *buffer)
int SaveStateToInternalDestructive(void *sim)
int RestoreInternalDestructiveSavedState(void *sim)
int ApplyGenericOneQubitGate(void *sim, unsigned long q0, const double *buffer)
int ApplyGenericTwoQubitGate(void *sim, unsigned long q0, unsigned long q1, const double *buffer)
int IsQcsim(void *sim)
SimulatorType
The type of simulator.
Definition State.h:79
std::vector< qubit_t > qubits_vector
The type of a vector of qubits.
Definition Types.h:24
uint_fast64_t qubit_t
The type of a qubit.
Definition Types.h:23
bool ValidateSetting(std::string_view name, std::string_view setting)
Definition Sampling.h:45