Maestro 0.3.11
Unified interface for quantum circuit simulation
Loading...
Searching...
No Matches
ExecutionCost.h
Go to the documentation of this file.
1
15
16#pragma once
17
18#ifndef __EXECUTION_COST_H_
19#define __EXECUTION_COST_H_
20
21#include "../Circuit/Factory.h"
26#include "../Utils/LogFile.h"
27
28#include <algorithm>
29#include <cstddef>
30#include <iomanip>
31
32namespace Estimators
33{
34
35// this is not for time estimation, it can be used for something very, very,
36// VERY rough, to decide just if it would execute in a reasonable time or not it
37// cannot be used to compare runtime for two simulators of the same circuit, but
38// it sort of can be used to compare two circuits in the same simulator
39// (especially if the same number of samples is used for both circuits) for
40// something better we have the estimators which got the constants from
41// benchmarking and rely on implementation details of the simulators, so they
42// are more accurate, but also less general
43
44// Except for pauli propagation, where the operation cost depends on the
45// position in the circuit (if the circuit is non-clifford), using a neural
46// network (or even a simpler regressor) to learn the relation between the
47// estimated cost and the actual execution time (single threaded, multithreading
48// rises some other issues) would be probably possible
50{
51 public:
53 {
54 size_t nrQubits = 0;
55 size_t nrOneQubitOps = 0;
56 size_t nrTwoQubitOps = 0;
57 size_t nrThreeQubitOps = 0;
63
64 CircuitInfo() = default;
65 CircuitInfo(const CircuitInfo &other) = default;
66
67 CircuitInfo &operator=(const CircuitInfo &other) = default;
68
69 double getFieldValue(size_t index) const
70 {
71 switch (index)
72 {
73 case 0:
74 return static_cast<double>(nrQubits);
75 case 1:
76 return static_cast<double>(nrOneQubitOps);
77 case 2:
78 return static_cast<double>(nrTwoQubitOps);
79 case 3:
80 return static_cast<double>(nrThreeQubitOps);
81 case 4:
82 return static_cast<double>(nrMiddleMeasurementOps);
83 case 5:
84 return static_cast<double>(nrEndMeasurementOps);
85 case 6:
86 return static_cast<double>(nrOneQubitOpsExecutedOnce);
87 case 7:
88 return static_cast<double>(nrTwoQubitOpsExecutedOnce);
89 case 8:
90 return static_cast<double>(nrThreeQubitOpsExecutedOnce);
91 default:
92 throw std::out_of_range("Invalid index for CircuitInfo field");
93 }
94 }
95 };
96
97 struct ExecutionInfo : public CircuitInfo
98 {
99 size_t nrSamples = 0;
100 size_t nrQubitsSampled = 0;
101 size_t maxBondDim = 0;
102 size_t nrPauliOps = 0;
103 double executionCost = 0;
104 double runtime = 0;
105
106 ExecutionInfo() = default;
107 ExecutionInfo(const ExecutionInfo &other) = default;
108
109 ExecutionInfo(const CircuitInfo &circuitInfo) : CircuitInfo(circuitInfo)
110 {
111 }
112
113 ExecutionInfo &operator=(const ExecutionInfo &other) = default;
114
116 {
117 CircuitInfo::operator=(circuitInfo);
118 return *this;
119 }
120
121 double getFieldValue(size_t index) const
122 {
123 if (index < 9)
124 {
125 return CircuitInfo::getFieldValue(index);
126 }
127 else
128 {
129 switch (index)
130 {
131 case 9:
132 return static_cast<double>(nrSamples);
133 case 10:
134 return static_cast<double>(nrQubitsSampled);
135 case 11:
136 return static_cast<double>(maxBondDim);
137 case 12:
138 return static_cast<double>(nrPauliOps);
139 case 13:
140 return static_cast<double>(executionCost);
141 case 14:
142 return static_cast<double>(runtime);
143 default:
144 throw std::out_of_range("Invalid index for ExecutionInfo field");
145 }
146 }
147 }
148 };
149
150 static double EstimateExecutionCost(Simulators::SimulationType method, size_t nrQubits, const std::shared_ptr<Circuits::Circuit<>> &circuit,
151 size_t maxBondDim)
152 {
156 {
157 const double opOrder = exp2(nrQubits);
158
159 double cost = 0;
160 for (const auto &op : *circuit)
161 {
162 const auto affectedQubits = op->AffectedQubits();
164 cost += opOrder * affectedQubits.size();
165 if (op->GetType() == Circuits::OperationType::kReset)
166 cost += 2. * opOrder * affectedQubits.size();
167 else if (op->GetType() == Circuits::OperationType::kGate || op->GetType() == Circuits::OperationType::kConditionalGate)
168 {
169 if (affectedQubits.size() == 1)
170 cost += opOrder;
171 else if (affectedQubits.size() == 2)
172 cost += 4 * opOrder;
173 else if (affectedQubits.size() == 3)
174 cost += 16 * opOrder;
175 }
176 }
177 return cost;
178 }
180 {
181 const double twoQubitOpOrder = pow(maxBondDim, 3);
182 const double oneQubitOpOrder = pow(maxBondDim, 2);
183
184 double cost = 0;
185 for (const auto &op : *circuit)
186 {
187 const auto affectedQubits = op->AffectedQubits();
189 op->GetType() == Circuits::OperationType::kReset)
190 // it's not that simple at all
191 // a qubit measurement works by applying a projector on the qubit
192 // tensor, which would cost as a one quibit gate but then we need to
193 // propagate the effect of the measurement along the chain, left and
194 // right, which is like applying a two qubit gate (SVD is the costlier
195 // operation there) on all the qubits that are entangled with the
196 // measured one, and in the worst case this can be all the other
197 // qubits if more than one qubits are measured at the same time, then
198 // we can have some optimizations, but let's just say that it's like
199 // measuring them one by one, so the cost is multiplied by the number
200 // of measured qubits
201 cost += twoQubitOpOrder * affectedQubits.size() * nrQubits / 2.;
202 else if (op->GetType() == Circuits::OperationType::kGate || op->GetType() == Circuits::OperationType::kConditionalGate)
203 {
204 if (affectedQubits.size() == 1)
205 cost += oneQubitOpOrder;
206 else if (affectedQubits.size() == 2)
207 // I wish it were simple, but applying a gate involves swapping the
208 // qubits next to each other, then applying the gate
209 cost += twoQubitOpOrder * nrQubits / 3.;
210 else if (affectedQubits.size() == 3)
211 // qiskit aer has three qubit ops, qcsim has not, they are
212 // decomposed into one and two qubit gates
213 cost += twoQubitOpOrder * nrQubits * 2; // very rough estimation
214 }
215 }
216 return cost;
217 }
219 {
220 const double measOrder = pow(nrQubits, 2);
221 const double opOrder = static_cast<double>(nrQubits);
222
223 double cost = 0;
224 for (const auto &op : *circuit)
225 {
226 const auto affectedQubits = op->AffectedQubits();
228 op->GetType() == Circuits::OperationType::kReset)
229 cost += measOrder * affectedQubits.size();
230 else if (op->GetType() == Circuits::OperationType::kGate || op->GetType() == Circuits::OperationType::kConditionalGate)
231 cost += opOrder * affectedQubits.size();
232 }
233 return cost;
234 }
236 {
237 double cost = 0;
238 double doublingCost = 1;
239
240 for (const auto &op : *circuit)
241 {
242 if (op->IsBranching())
243 doublingCost *= 2;
244
245 const auto affectedQubits = op->AffectedQubits();
247 op->GetType() == Circuits::OperationType::kReset)
248 cost += doublingCost * affectedQubits.size();
249 else if (op->GetType() == Circuits::OperationType::kGate || op->GetType() == Circuits::OperationType::kConditionalGate)
250 {
251 if (affectedQubits.size() == 1)
252 cost += doublingCost;
253 else if (affectedQubits.size() == 2)
254 cost += doublingCost * 2;
255 else if (affectedQubits.size() == 3)
256 cost += doublingCost * 4;
257 }
258 }
259
260 return cost;
261 }
262
263 // for tensor network is hard to guess, it depends on contraction path
264
265 return std::numeric_limits<double>::infinity();
266 }
267
268 static double EstimateSamplingCost(Simulators::SimulationType method, size_t nrQubits, size_t nrQubitsSampled, size_t samples,
269 const std::shared_ptr<Circuits::Circuit<>> &circuit, size_t maxBondDim)
270 {
271 // sampling works very differenty for such circuits
272 Circuits::OperationState dummyState;
273 const bool hasMeasurementsInTheMiddle = circuit->HasOpsAfterMeasurements();
274 const std::vector<bool> executedOps = circuit->ExecuteNonMeasurements(nullptr, dummyState);
275
276 const size_t dif = circuit->size() - executedOps.size();
277
279 return Simulators::QcsimPauliPropagator::GetSamplingCost(circuit, nrQubitsSampled, samples);
280
282 {
283 const double opOrder = exp2(nrQubits);
284
285 double cost = 0;
286 for (size_t i = 0; i < circuit->size(); ++i)
287 {
288 if (i >= dif && !executedOps[i - dif])
289 continue;
290
291 const auto &op = (*circuit)[i];
292 const auto affectedQubits = op->AffectedQubits();
294 cost += opOrder * affectedQubits.size();
295 if (op->GetType() == Circuits::OperationType::kReset)
296 cost += 2. * opOrder * affectedQubits.size();
297 else if (op->GetType() == Circuits::OperationType::kGate || op->GetType() == Circuits::OperationType::kConditionalGate)
298 {
299 if (affectedQubits.size() == 1)
300 cost += opOrder;
301 else if (affectedQubits.size() == 2)
302 cost += 4 * opOrder;
303 else if (affectedQubits.size() == 3)
304 cost += 16 * opOrder;
305 }
306 }
307
308 // the sampling cost depends on the simulator
309 // qiskit aer constucts an index gowing over the statevector, qcsim does
310 // something similar to have the samples then O(nrQubits), but in maestro
311 // for qcsim (and quest) this is replaced by alias sampling with O(1) for
312 // each sample
313
314 if (hasMeasurementsInTheMiddle)
315 {
316 double samplingCost = 0;
317 for (size_t i = dif; i < circuit->size(); ++i)
318 {
319 if (executedOps[i - dif])
320 continue;
321
322 const auto &op = (*circuit)[i];
323 const auto affectedQubits = op->AffectedQubits();
325 samplingCost += opOrder * affectedQubits.size();
326 if (op->GetType() == Circuits::OperationType::kReset)
327 samplingCost += 2. * opOrder * affectedQubits.size();
328 else if (op->GetType() == Circuits::OperationType::kGate || op->GetType() == Circuits::OperationType::kConditionalGate)
329 {
330 if (affectedQubits.size() == 1)
331 samplingCost += opOrder;
332 else if (affectedQubits.size() == 2)
333 samplingCost += 4 * opOrder;
334 else if (affectedQubits.size() == 3)
335 samplingCost += 16 * opOrder;
336 }
337 }
338
339 samplingCost += opOrder * nrQubitsSampled;
340
341 return cost + samplingCost * samples;
342 }
343
344 // some dummy cost, it's not going to fit all anyways
345 return cost + 30 * opOrder + samples * nrQubits;
346 }
348 {
349 const double oneQubitOpOrder = static_cast<double>(maxBondDim * maxBondDim);
350 const double twoQubitOpOrder = static_cast<double>(oneQubitOpOrder * maxBondDim);
351
352 double cost = 0;
353 for (size_t i = 0; i < circuit->size(); ++i)
354 {
355 if (i >= dif && !executedOps[i - dif])
356 continue;
357
358 const auto &op = (*circuit)[i];
359 const auto affectedQubits = op->AffectedQubits();
361 op->GetType() == Circuits::OperationType::kReset)
362 // it's not that simple at all
363 // a qubit measurement works by applying a projector on the qubit
364 // tensor, which would cost as a one quibit gate but then we need to
365 // propagate the effect of the measurement along the chain, left and
366 // right, which is like applying a two qubit gate (SVD is the costlier
367 // operation there) on all the qubits that are entangled with the
368 // measured one, and in the worst case this can be all the other
369 // qubits if more than one qubits are measured at the same time, then
370 // we can have some optimizations, but let's just say that it's like
371 // measuring them one by one, so the cost is multiplied by the number
372 // of measured qubits
373 cost += twoQubitOpOrder * affectedQubits.size() * nrQubits / 2.;
374 else if (op->GetType() == Circuits::OperationType::kGate || op->GetType() == Circuits::OperationType::kConditionalGate)
375 {
376 if (affectedQubits.size() == 1)
377 cost += oneQubitOpOrder;
378 else if (affectedQubits.size() == 2)
379 // I wish it were simple, but applying a gate involves swapping the
380 // qubits next to each other, then applying the gate
381 cost += twoQubitOpOrder * nrQubits / 3.;
382 else if (affectedQubits.size() == 3)
383 // qiskit aer has three qubit ops, qcsim has not, they are
384 // decomposed into one and two qubit gates
385 cost += twoQubitOpOrder * nrQubits * 2; // very rough estimation
386 }
387 }
388
389 if (hasMeasurementsInTheMiddle)
390 {
391 double samplingCost = 0;
392 for (size_t i = dif; i < circuit->size(); ++i)
393 {
394 if (executedOps[i - dif])
395 continue;
396
397 const auto &op = (*circuit)[i];
398 const auto affectedQubits = op->AffectedQubits();
400 op->GetType() == Circuits::OperationType::kReset)
401 samplingCost += twoQubitOpOrder * affectedQubits.size() * nrQubits / 2.;
402 else if (op->GetType() == Circuits::OperationType::kGate || op->GetType() == Circuits::OperationType::kConditionalGate)
403 {
404 if (affectedQubits.size() == 1)
405 samplingCost += oneQubitOpOrder;
406 else if (affectedQubits.size() == 2)
407 // I wish it were simple, but applying a gate involves swapping
408 // the qubits next to each other, then applying the gate
409 samplingCost += twoQubitOpOrder * nrQubits / 3.;
410 else if (affectedQubits.size() == 3)
411 // qiskit aer has three qubit ops, qcsim has not, they are
412 // decomposed into one and two qubit gates
413 samplingCost += twoQubitOpOrder * nrQubits * 2; // very rough estimation
414 }
415 }
416
417 samplingCost += twoQubitOpOrder * nrQubits * nrQubits;
418
419 return cost + samplingCost * samples;
420 }
421
422 // sampling can be done here (and for other simulator types as well)
423 // either by saving the state, measuring then restoring the state or
424 // simply going along the chain, computing probabilities, throwing biased
425 // coins and doing matrix multiplications (qcsim does this if all qubits
426 // are sampled, otherwise the measurement method is used)
427
428 // some dummy cost, it's not going to fit all anyways
429 return cost + samples * twoQubitOpOrder * nrQubits * nrQubits;
430 }
432 {
433 const double measOrder = pow(nrQubits, 2);
434 const double opOrder = static_cast<double>(nrQubits);
435
436 double cost = 0;
437 for (size_t i = 0; i < circuit->size(); ++i)
438 {
439 if (i >= dif && !executedOps[i - dif])
440 continue;
441
442 const auto &op = (*circuit)[i];
443 const auto affectedQubits = op->AffectedQubits();
445 op->GetType() == Circuits::OperationType::kReset)
446 cost += measOrder * affectedQubits.size();
447 else if (op->GetType() == Circuits::OperationType::kGate || op->GetType() == Circuits::OperationType::kConditionalGate)
448 cost += opOrder * affectedQubits.size();
449 }
450
451 if (hasMeasurementsInTheMiddle)
452 {
453 double samplingCost = 0;
454 for (size_t i = dif; i < circuit->size(); ++i)
455 {
456 if (executedOps[i - dif])
457 continue;
458
459 const auto &op = (*circuit)[i];
460 const auto affectedQubits = op->AffectedQubits();
462 op->GetType() == Circuits::OperationType::kReset)
463 samplingCost += measOrder * affectedQubits.size();
464 else if (op->GetType() == Circuits::OperationType::kGate || op->GetType() == Circuits::OperationType::kConditionalGate)
465 samplingCost += opOrder * affectedQubits.size();
466 }
467
468 samplingCost += measOrder * nrQubitsSampled;
469
470 return cost + samplingCost * samples;
471 }
472
473 // TODO: to be changed to handle the all qubits sampling case, also the
474 // sampling for qcsim which is efficient for sampling less than all
475 // qubits!
476
477 // sampling is done with saving state, measuring and restoring state
478 // the overhead for saving / restoring is not here (it's of the same order
479 // as measuring), but measurements are not all O(n^2) anyways
480 return cost + samples * measOrder * nrQubitsSampled;
481 }
483 {
484 double cost = 0;
485 double doublingCost = 1;
486 for (size_t i = 0; i < circuit->size(); ++i)
487 {
488 if (i >= dif && !executedOps[i - dif])
489 continue;
490 const auto &op = (*circuit)[i];
491 if (op->IsBranching())
492 doublingCost *= 2;
493
494 const auto affectedQubits = op->AffectedQubits();
496 op->GetType() == Circuits::OperationType::kReset)
497 cost += doublingCost * affectedQubits.size();
498 else if (op->GetType() == Circuits::OperationType::kGate || op->GetType() == Circuits::OperationType::kConditionalGate)
499 {
500 if (affectedQubits.size() == 1)
501 cost += doublingCost;
502 else if (affectedQubits.size() == 2)
503 cost += doublingCost * 2;
504 else if (affectedQubits.size() == 3)
505 cost += doublingCost * 4;
506 }
507 }
508
509 if (hasMeasurementsInTheMiddle)
510 {
511 double samplingCost = 0;
512 for (size_t i = dif; i < circuit->size(); ++i)
513 {
514 if (executedOps[i - dif])
515 continue;
516
517 const auto &op = (*circuit)[i];
518 if (op->IsBranching())
519 doublingCost *= 2;
520
521 const auto affectedQubits = op->AffectedQubits();
522
524 op->GetType() == Circuits::OperationType::kReset)
525 samplingCost += doublingCost * affectedQubits.size();
526 else if (op->GetType() == Circuits::OperationType::kGate || op->GetType() == Circuits::OperationType::kConditionalGate)
527 {
528 if (affectedQubits.size() == 1)
529 samplingCost += doublingCost;
530 else if (affectedQubits.size() == 2)
531 samplingCost += doublingCost * 2;
532 else if (affectedQubits.size() == 3)
533 samplingCost += doublingCost * 4;
534 }
535 }
536
537 samplingCost += doublingCost * nrQubitsSampled;
538
539 return cost + samplingCost * samples;
540 }
541
542 // some dummy cost, it's not going to fit all anyways
543 return cost + 30 * doublingCost + samples * nrQubits;
544 }
545
546 // for tensor network is hard to guess, it depends on contraction path
547
548 return std::numeric_limits<double>::infinity();
549 }
550
551 static double EstimatePauliExpectationCost(const std::string &pauliString, Simulators::SimulationType method, size_t nrQubits,
552 const std::shared_ptr<Circuits::Circuit<>> &circuit, size_t maxBondDim)
553 {
554 // a pauli string propagated
557
558 double cost = EstimateExecutionCost(method, nrQubits, circuit, maxBondDim);
559
560 size_t pauliCnt = 0;
561 for (char c : pauliString)
562 {
563 if (c == 'X' || c == 'x' || c == 'Y' || c == 'y' || c == 'Z' || c == 'z')
564 ++pauliCnt;
565 }
566
568 {
569 const double opOrder = exp2(nrQubits);
570
571 // each pauli matrix is applied to the statevector, then the product with
572 // the original is computed
573 cost += opOrder * (pauliCnt + 1);
574
575 return cost;
576 }
578 {
579 const double twoQubitOpOrder = pow(maxBondDim, 3);
580 const double oneQubitOpOrder = pow(maxBondDim, 2);
581
582 // each pauli is applied to the chain then the resulted chain is
583 // contracted with the original one
584 cost += oneQubitOpOrder * pauliCnt + nrQubits * twoQubitOpOrder;
585
586 return cost;
587 }
589 {
590 cost += nrQubits * nrQubits;
591 return cost;
592 }
594 {
595 double doublingCost = 1;
596 for (const auto &op : *circuit)
597 {
598 if (op->IsBranching())
599 doublingCost *= 2;
600 }
601 cost += 4 * doublingCost * pauliCnt;
602
603 return cost;
604 }
605
606 return std::numeric_limits<double>::infinity();
607 }
608
609 static std::shared_ptr<Circuits::Circuit<>> GenerateRandomCircuit(size_t nrQubits, size_t depth, double measureInsideProbability = 0.,
610 size_t nrMeasAtEnd = 0, bool isClifford = false, size_t nrNonCliffordGatesLimit = 0,
611 size_t nrBranchingGatesLimit = 0)
612 {
613 auto circuit = std::make_shared<Circuits::Circuit<>>();
614 std::random_device rdev;
615 std::mt19937 rng(rdev());
616 std::uniform_real_distribution<double> dist(0.0, 1.0);
617 std::uniform_real_distribution<double> paramDist(-2 * M_PI, 2 * M_PI);
618 std::uniform_int_distribution<Types::qubit_t> qubitDist(0, nrQubits - 1);
619 std::uniform_int_distribution<int> gateDist(0, static_cast<int>(Circuits::QuantumGateType::kCCXGateType));
620
621 std::uniform_int_distribution<int> gateDistOneQubit(0, static_cast<int>(Circuits::QuantumGateType::kUGateType));
622
623 std::vector<Types::qubit_t> qubits(nrQubits);
624 std::iota(qubits.begin(), qubits.end(), 0);
625
626 size_t nrNonCliffordGates = 0;
627 if (nrNonCliffordGatesLimit == 0 && !isClifford)
628 nrNonCliffordGatesLimit = depth;
629
630 size_t nrBranchingGates = 0;
631 if (nrBranchingGatesLimit == 0)
632 nrBranchingGatesLimit = depth;
633
634 for (size_t i = 0; i < depth; ++i)
635 {
636 if (dist(rng) < measureInsideProbability)
637 {
638 Types::qubit_t q = qubitDist(rng);
639 std::vector<std::pair<Types::qubit_t, size_t>> qs = {{q, q}};
640 circuit->AddOperation(std::make_shared<Circuits::MeasurementOperation<>>(qs));
641 continue;
642 }
643
644 std::shuffle(qubits.begin(), qubits.end(), rng);
645 const auto q1 = qubits[0];
646 const auto q2 = qubits[1];
647 const auto q3 = qubits[2];
648
649 auto gateType = static_cast<Circuits::QuantumGateType>(nrNonCliffordGatesLimit < depth ? gateDistOneQubit(rng) // avoid three qubit gates, they are
650 // non-clifford and they cost a lot
651 : gateDist(rng));
652 auto param1 = paramDist(rng);
653 auto param2 = paramDist(rng);
654 auto param3 = paramDist(rng);
655 auto param4 = paramDist(rng);
656
657 auto theGate = Circuits::CircuitFactory<>::CreateGate(gateType, q1, q2, q3, param1, param2, param3, param4);
658 if (isClifford)
659 {
660 while (!theGate->IsClifford())
661 {
662 gateType = static_cast<Circuits::QuantumGateType>(gateDist(rng));
663 theGate = Circuits::CircuitFactory<>::CreateGate(gateType, q1, q2, q3, param1, param2, param3, param4);
664 }
665 }
666
667 if (!theGate->IsClifford())
668 ++nrNonCliffordGates;
669 if (theGate->IsBranching())
670 ++nrBranchingGates;
671
672 if (nrNonCliffordGates > nrNonCliffordGatesLimit)
673 {
674 // replace the non clifford gate with a clifford one
675 gateType = static_cast<Circuits::QuantumGateType>(gateDistOneQubit(rng));
676 theGate = Circuits::CircuitFactory<>::CreateGate(gateType, q1, q2, q3, param1, param2, param3, param4);
677 while (!theGate->IsClifford())
678 {
679 gateType = static_cast<Circuits::QuantumGateType>(gateDistOneQubit(rng));
680 theGate = Circuits::CircuitFactory<>::CreateGate(gateType, q1, q2, q3, param1, param2, param3, param4);
681 }
682 --nrNonCliffordGates;
683 }
684
685 if (nrBranchingGates > nrBranchingGatesLimit)
686 {
687 // replace the branching gate with a non branching one
688 gateType = static_cast<Circuits::QuantumGateType>(gateDist(rng));
689
690 theGate = Circuits::CircuitFactory<>::CreateGate(gateType, q1, q2, q3, param1, param2, param3, param4);
691 while (theGate->IsBranching())
692 {
693 gateType = static_cast<Circuits::QuantumGateType>(gateDist(rng));
694 theGate = Circuits::CircuitFactory<>::CreateGate(gateType, q1, q2, q3, param1, param2, param3, param4);
695 }
696 --nrBranchingGates;
697 }
698
699 circuit->AddOperation(theGate);
700 }
701
702 std::shuffle(circuit->begin(), circuit->end(), rng);
703
704 if (nrMeasAtEnd > 0)
705 {
706 if (nrMeasAtEnd > nrQubits)
707 nrMeasAtEnd = nrQubits;
708 std::shuffle(qubits.begin(), qubits.end(), rng);
709
710 for (size_t i = 0; i < nrMeasAtEnd; ++i)
711 {
712 std::vector<std::pair<Types::qubit_t, size_t>> qs = {{qubits[i], i}};
713 circuit->AddOperation(std::make_shared<Circuits::MeasurementOperation<>>(qs));
714 }
715 }
716
717 return circuit;
718 }
719
720 static double MeasureExecutionTime(Simulators::SimulatorType simType, Simulators::SimulationType method, size_t nrQubits,
721 const std::shared_ptr<Circuits::Circuit<>> &circuit, size_t nrReps, size_t maxBondDim)
722 {
723 auto sim = GetSimulator(simType, method, nrQubits, maxBondDim);
724
725 Circuits::OperationState dummyState(nrQubits);
726 auto start = std::chrono::high_resolution_clock::now();
727 for (size_t i = 0; i < nrReps; ++i)
728 circuit->Execute(sim, dummyState);
729 auto end = std::chrono::high_resolution_clock::now();
730 return std::chrono::duration<double>(end - start).count();
731 }
732
733 static double MeasureSamplingTime(Simulators::SimulatorType simType, Simulators::SimulationType method, size_t nrQubits,
734 const std::shared_ptr<Circuits::Circuit<>> &circuit, size_t nrQubitsSampled, size_t nrSamples, size_t nrReps,
735 size_t maxBondDim)
736 {
737 auto sim = GetSimulator(simType, method, nrQubits, maxBondDim);
738 Circuits::OperationState dummyState(nrQubits);
739
740 if (nrQubitsSampled > nrQubits)
741 nrQubitsSampled = nrQubits;
742 Types::qubits_vector qubitsSampled(nrQubitsSampled);
743 std::iota(qubitsSampled.begin(), qubitsSampled.end(), 0);
744
745 const bool hasMeasurementsInTheMiddle = circuit->HasOpsAfterMeasurements();
746
747 auto start = std::chrono::high_resolution_clock::now();
748
749 if (hasMeasurementsInTheMiddle)
750 {
751 for (size_t i = 0; i < nrReps; ++i)
752 {
753 const auto executedOps = circuit->ExecuteNonMeasurements(sim, dummyState); // execute the circuit up to measurements
754
755 // now sample
756 for (size_t sample = 0; sample < nrSamples; ++sample)
757 {
758 circuit->ExecuteMeasurements(sim, dummyState, executedOps); // execute the measurements
759 }
760 }
761 }
762 else
763 {
764 for (size_t i = 0; i < nrReps; ++i)
765 {
766 circuit->Execute(sim, dummyState); // execute the circuit first
767 sim->SampleCountsMany(qubitsSampled, nrSamples);
768 }
769 }
770 auto end = std::chrono::high_resolution_clock::now();
771
772 return std::chrono::duration<double>(end - start).count();
773 }
774
776 const std::shared_ptr<Circuits::Circuit<>> &circuit, const std::string &pauliString, size_t nrReps,
777 size_t maxBondDim)
778 {
779 auto sim = GetSimulator(simType, method, nrQubits, maxBondDim);
780 Circuits::OperationState dummyState(nrQubits);
781 auto start = std::chrono::high_resolution_clock::now();
782 for (size_t i = 0; i < nrReps; ++i)
783 {
784 circuit->Execute(sim, dummyState); // execute the circuit first
785 sim->ExpectationValue(pauliString);
786 }
787 auto end = std::chrono::high_resolution_clock::now();
788 return std::chrono::duration<double>(end - start).count();
789 }
790
791 static std::shared_ptr<Simulators::ISimulator> GetSimulator(Simulators::SimulatorType simType, Simulators::SimulationType method, size_t nrQubits,
792 size_t maxBondDim)
793 {
794 auto sim = Simulators::SimulatorsFactory::CreateSimulator(simType, method);
796 {
797 const auto strVal = std::to_string(maxBondDim);
798 sim->Configure("matrix_product_state_max_bond_dimension", strVal.c_str());
799 }
800 sim->AllocateQubits(nrQubits);
801 sim->SetMultithreading(false);
802 sim->Initialize();
803 return sim;
804 }
805
806 static CircuitInfo GetCircuitInfo(const std::shared_ptr<Circuits::Circuit<>> &circuit)
807 {
808 CircuitInfo info;
809
810 info.nrQubits = circuit->GetMaxQubitIndex() + 1;
811 Circuits::OperationState dummyState(info.nrQubits);
812 const bool hasMeasurementsInTheMiddle = circuit->HasOpsAfterMeasurements();
813 const std::vector<bool> executedOps = circuit->ExecuteNonMeasurements(nullptr, dummyState);
814
815 const size_t dif = circuit->size() - executedOps.size();
816
817 size_t i = 0;
818 for (const auto &op : *circuit)
819 {
820 const auto affectedQubits = op->AffectedQubits();
822 {
823 if (hasMeasurementsInTheMiddle)
825 else
826 ++info.nrEndMeasurementOps;
827 }
828 else if (op->GetType() == Circuits::OperationType::kGate || op->GetType() == Circuits::OperationType::kConditionalGate ||
830 {
831 if (affectedQubits.size() == 1)
832 {
833 ++info.nrOneQubitOps;
834 if (i < dif || executedOps[i - dif])
836 }
837 else if (affectedQubits.size() == 2)
838 {
839 ++info.nrTwoQubitOps;
840 if (i < dif || executedOps[i - dif])
842 }
843 else if (affectedQubits.size() == 3)
844 {
845 ++info.nrThreeQubitOps;
846 if (i < dif || executedOps[i - dif])
848 }
849 }
850 ++i;
851 }
852
853 return info;
854 }
855
856 static std::vector<ExecutionInfo> ReadLog(const std::string &logFilePath)
857 {
858 std::vector<ExecutionInfo> executionInfos;
859
860 std::ifstream logFile(logFilePath);
861 if (!logFile.is_open())
862 {
863 std::cerr << "Failed to open log file: " << logFilePath << std::endl;
864 return executionInfos;
865 }
866
867 std::string line;
868
869 while (std::getline(logFile, line))
870 {
871 std::stringstream ss(line);
872 std::string value;
873 ExecutionInfo info;
874 std::getline(ss, value, ',');
875 info.nrQubits = std::stoul(value);
876 std::getline(ss, value, ',');
877 info.nrOneQubitOps = std::stoul(value);
878 std::getline(ss, value, ',');
879 info.nrTwoQubitOps = std::stoul(value);
880 std::getline(ss, value, ',');
881 info.nrThreeQubitOps = std::stoul(value);
882 std::getline(ss, value, ',');
883 info.nrMiddleMeasurementOps = std::stoul(value);
884 std::getline(ss, value, ',');
885 info.nrEndMeasurementOps = std::stoul(value);
886 std::getline(ss, value, ',');
887 info.nrOneQubitOpsExecutedOnce = std::stoul(value);
888 std::getline(ss, value, ',');
889 info.nrTwoQubitOpsExecutedOnce = std::stoul(value);
890 std::getline(ss, value, ',');
891 info.nrThreeQubitOpsExecutedOnce = std::stoul(value);
892
893 std::getline(ss, value, ',');
894 info.nrSamples = std::stoul(value);
895 std::getline(ss, value, ',');
896 info.nrQubitsSampled = std::stoul(value);
897 std::getline(ss, value, ',');
898 info.maxBondDim = std::stoul(value);
899 std::getline(ss, value, ',');
900 info.nrPauliOps = std::stoul(value);
901
902 std::getline(ss, value, ',');
903 info.executionCost = std::stod(value);
904 std::getline(ss, value, ',');
905 info.runtime = std::stod(value);
906
907 if (info.nrSamples < 1)
908 info.nrSamples = 1;
909
910 executionInfos.push_back(std::move(info));
911 }
912
913 std::random_device rdev;
914 std::mt19937 rng(rdev());
915 std::shuffle(executionInfos.begin(), executionInfos.end(), rng);
916
917 return executionInfos;
918 }
919
920 static void BenchmarkAndLogExecution(Simulators::SimulatorType simType, Simulators::SimulationType method, size_t nrReps, size_t nrMinQubits,
921 size_t nrMaxQubits, size_t stepQubits, size_t depthMin, size_t depthMax, size_t stepDepth,
922 double measureInsideProbability, size_t nrMeasAtEndMin, size_t nrMeasAtEndMax, size_t stepMeasAtEnd,
923 size_t nrRandomCircuitsPerConfig, const std::string &logFilePath, size_t startBondDim = 16, size_t endBondDim = 16)
924 {
925 bool isClifford = (method == Simulators::SimulationType::kStabilizer);
926 int nrNonCliffordGates = static_cast<int>(depthMax); // a limit
927 int nrBranchingGates = static_cast<int>(depthMax);
928
929 // but if it's pauli...
931 {
932 nrNonCliffordGates = 1;
933 }
935 {
936 nrNonCliffordGates = 0;
937 }
939 {
940 nrBranchingGates = 8;
941 }
942
943 std::cout << "Benchmarking execution for simType: " << static_cast<int>(simType) << ", method: " << static_cast<int>(method) << std::endl;
944
945 Utils::LogFile log(logFilePath);
946
947 for (size_t nrQubits = nrMinQubits; nrQubits <= nrMaxQubits; nrQubits += stepQubits)
948 {
949 std::cout << " Qubits: " << nrQubits << std::endl;
950 for (size_t depth = depthMin; depth <= depthMax; depth += stepDepth)
951 {
952 std::cout << " Depth: " << depth << std::endl;
953 for (size_t nrMeasAtEnd = nrMeasAtEndMin; nrMeasAtEnd <= std::min(nrMeasAtEndMax, nrQubits); nrMeasAtEnd += stepMeasAtEnd)
954 {
955 std::cout << " Measurements at end: " << nrMeasAtEnd << std::endl;
956 for (size_t i = 0; i < nrRandomCircuitsPerConfig; ++i)
957 {
958 std::cout << " Random circuit: " << i + 1 << "/" << nrRandomCircuitsPerConfig << std::endl;
960 isClifford = !isClifford;
961
962 const auto circuit =
963 GenerateRandomCircuit(nrQubits, depth, measureInsideProbability, nrMeasAtEnd, isClifford, nrNonCliffordGates, nrBranchingGates);
964
965 for (size_t maxBondDim = startBondDim; maxBondDim <= endBondDim; maxBondDim *= 2)
966 {
967 BenchmarkAndLogExecution(simType, method, circuit, nrReps, maxBondDim, log);
968 }
969 }
970 }
971 }
972 }
973 }
974
975 static std::string GeneratePauliString(size_t nrQubits)
976 {
977 std::string pauli;
978 pauli.resize(nrQubits);
979 std::random_device rd;
980 std::mt19937 g(rd());
981 std::uniform_int_distribution<int> dist(0, 3);
982
983 for (size_t i = 0; i < nrQubits; ++i)
984 {
985 const int v = dist(g);
986 switch (v)
987 {
988 case 0:
989 pauli[i] = 'I';
990 break;
991 case 1:
992 pauli[i] = 'X';
993 break;
994 case 2:
995 pauli[i] = 'Y';
996 break;
997 case 3:
998 pauli[i] = 'Z';
999 break;
1000 }
1001 }
1002
1003 return pauli;
1004 }
1005
1006 static void BenchmarkAndLogPauliExpectation(Simulators::SimulatorType simType, Simulators::SimulationType method, size_t nrReps, size_t nrMinQubits,
1007 size_t nrMaxQubits, size_t stepQubits, size_t depthMin, size_t depthMax, size_t stepDepth,
1008 size_t nrRandomCircuitsPerConfig, const std::string &logFilePath, size_t startBondDim = 16,
1009 size_t endBondDim = 16)
1010 {
1011 bool isClifford = (method == Simulators::SimulationType::kStabilizer);
1012 int nrNonCliffordGates = static_cast<int>(depthMax); // a limit
1013 int nrBranchingGates = static_cast<int>(depthMax);
1014
1015 // but if it's pauli...
1017 {
1018 nrNonCliffordGates = 1;
1019 }
1020 else if (method == Simulators::SimulationType::kStabilizer)
1021 {
1022 nrNonCliffordGates = 0;
1023 }
1025 {
1026 nrBranchingGates = 8;
1027 }
1028 Utils::LogFile log(logFilePath);
1029
1030 std::cout << "Benchmarking Pauli expectation for simType: " << static_cast<int>(simType) << ", method: " << static_cast<int>(method) << std::endl;
1031
1032 for (size_t nrQubits = nrMinQubits; nrQubits <= nrMaxQubits; nrQubits += stepQubits)
1033 {
1034 std::cout << " Qubits: " << nrQubits << std::endl;
1035 for (size_t depth = depthMin; depth <= depthMax; depth += stepDepth)
1036 {
1037 std::cout << " Depth: " << depth << std::endl;
1038 for (size_t i = 0; i < nrRandomCircuitsPerConfig; ++i)
1039 {
1041 isClifford = !isClifford;
1042
1043 const auto circuit = GenerateRandomCircuit(nrQubits, depth, 0., 0, isClifford, nrNonCliffordGates, nrBranchingGates);
1044 const std::string pauliString = GeneratePauliString(nrQubits);
1045
1046 std::cout << " Random circuit: " << i + 1 << "/" << nrRandomCircuitsPerConfig << " Pauli string: " << pauliString << std::endl;
1047 for (size_t maxBondDim = startBondDim; maxBondDim <= endBondDim; maxBondDim *= 2)
1048 {
1049 std::cout << " Max bond dimension: " << maxBondDim << std::endl;
1050 BenchmarkAndLogPauliExpectation(simType, method, circuit, pauliString, nrReps, maxBondDim, log);
1051 }
1052 }
1053 }
1054 }
1055 }
1056
1057 static void BenchmarkAndLogSampling(Simulators::SimulatorType simType, Simulators::SimulationType method, size_t nrReps, size_t nrMinQubits,
1058 size_t nrMaxQubits, size_t stepQubits, size_t depthMin, size_t depthMax, size_t stepDepth, size_t nrMeasAtEndMin,
1059 size_t nrMeasAtEndMax, size_t stepMeasAtEnd, size_t nrSamplesMin, size_t nrSamplesMax, size_t multiplierSamples,
1060 size_t nrRandomCircuitsPerConfig, const std::string &logFilePath, size_t startBondDim = 16, size_t endBondDim = 16)
1061 {
1062 bool isClifford = (method == Simulators::SimulationType::kStabilizer);
1063 int nrNonCliffordGates = static_cast<int>(depthMax); // a limit
1064 int nrBranchingGates = static_cast<int>(depthMax);
1065 // but if it's pauli...
1067 {
1068 nrNonCliffordGates = 1;
1069 }
1070 else if (method == Simulators::SimulationType::kStabilizer)
1071 {
1072 nrNonCliffordGates = 0;
1073 }
1075 {
1076 nrBranchingGates = 8;
1077 }
1078 Utils::LogFile log(logFilePath);
1079
1080 std::cout << "Benchmarking sampling for simType: " << static_cast<int>(simType) << ", method: " << static_cast<int>(method) << std::endl;
1081
1082 for (size_t nrQubits = nrMinQubits; nrQubits <= nrMaxQubits; nrQubits += stepQubits)
1083 {
1084 std::cout << " Qubits: " << nrQubits << std::endl;
1085 for (size_t depth = depthMin; depth <= depthMax; depth += stepDepth)
1086 {
1087 std::cout << " Depth: " << depth << std::endl;
1088 for (size_t nrSamples = nrSamplesMin; nrSamples <= nrSamplesMax; nrSamples *= multiplierSamples)
1089 {
1090 std::cout << " Samples: " << nrSamples << std::endl;
1091 for (size_t i = 0; i < nrRandomCircuitsPerConfig; ++i)
1092 {
1094 isClifford = !isClifford;
1095 const auto circuit = GenerateRandomCircuit(nrQubits, depth, 0., 0, isClifford, nrNonCliffordGates, nrBranchingGates);
1096 for (size_t nrQubitsSampled = nrQubits; nrQubitsSampled >= 1; nrQubitsSampled /= 2)
1097 {
1098 std::cout << " Random circuit: " << i + 1 << "/" << nrRandomCircuitsPerConfig << " Qubits sampled: " << nrQubitsSampled
1099 << std::endl;
1100 for (size_t maxBondDim = startBondDim; maxBondDim <= endBondDim; maxBondDim *= 2)
1101 {
1102 BenchmarkAndLogSampling(simType, method, circuit, nrQubitsSampled, nrSamples, nrReps, maxBondDim, log);
1103 }
1104 }
1105 }
1106 }
1107 }
1108 }
1109 }
1110
1112 const std::shared_ptr<Circuits::Circuit<>> &circuit, size_t nrReps, size_t maxBondDim, Utils::LogFile &log)
1113 {
1114 const auto info = GetCircuitInfo(circuit);
1115 const double estimatedCost = EstimateExecutionCost(method, info.nrQubits, circuit, maxBondDim);
1116 const double executionTime = MeasureExecutionTime(simType, method, info.nrQubits, circuit, nrReps, maxBondDim) / nrReps;
1117
1118 std::stringstream ss;
1119
1120 ss << info.nrQubits << "," << info.nrOneQubitOps << "," << info.nrTwoQubitOps << "," << info.nrThreeQubitOps << "," << info.nrMiddleMeasurementOps
1121 << "," << info.nrEndMeasurementOps << "," << info.nrOneQubitOpsExecutedOnce << "," << info.nrTwoQubitOpsExecutedOnce << ","
1122 << info.nrThreeQubitOpsExecutedOnce << ","
1123 << "0,0," << maxBondDim << ","
1124 << "0," << estimatedCost << "," << executionTime;
1125
1126 log.Log(ss.str());
1127 }
1128
1130 const std::shared_ptr<Circuits::Circuit<>> &circuit, size_t nrQubitsSampled, size_t nrSamples, size_t nrReps,
1131 size_t maxBondDim, Utils::LogFile &log)
1132 {
1133 const auto info = GetCircuitInfo(circuit);
1134 const double estimatedCost = EstimateSamplingCost(method, info.nrQubits, nrQubitsSampled, nrSamples, circuit, maxBondDim);
1135 const double samplingTime = MeasureSamplingTime(simType, method, info.nrQubits, circuit, nrQubitsSampled, nrSamples, nrReps, maxBondDim) / nrReps;
1136 std::stringstream ss;
1137 ss << info.nrQubits << "," << info.nrOneQubitOps << "," << info.nrTwoQubitOps << "," << info.nrThreeQubitOps << "," << info.nrMiddleMeasurementOps
1138 << "," << info.nrEndMeasurementOps << "," << info.nrOneQubitOpsExecutedOnce << "," << info.nrTwoQubitOpsExecutedOnce << ","
1139 << info.nrThreeQubitOpsExecutedOnce << "," << nrSamples << "," << nrQubitsSampled << "," << maxBondDim << ","
1140 << "0," << estimatedCost << "," << samplingTime;
1141 log.Log(ss.str());
1142 }
1143
1145 const std::shared_ptr<Circuits::Circuit<>> &circuit, const std::string &pauliString, size_t nrReps,
1146 size_t maxBondDim, Utils::LogFile &log)
1147 {
1148 auto info = GetCircuitInfo(circuit);
1149 info.nrQubits = std::max(info.nrQubits, pauliString.size());
1150 const double estimatedCost = EstimatePauliExpectationCost(pauliString, method, info.nrQubits, circuit, maxBondDim);
1151 const double expectationTime = MeasurePauliExpectationTime(simType, method, info.nrQubits, circuit, pauliString, nrReps, maxBondDim) / nrReps;
1152
1153 size_t cntPauli = 0;
1154 for (char c : pauliString)
1155 {
1156 if (c == 'X' || c == 'x' || c == 'Y' || c == 'y' || c == 'Z' || c == 'z')
1157 ++cntPauli;
1158 }
1159
1160 std::stringstream ss;
1161 ss << info.nrQubits << "," << info.nrOneQubitOps << "," << info.nrTwoQubitOps << "," << info.nrThreeQubitOps << "," << info.nrMiddleMeasurementOps
1162 << "," << info.nrEndMeasurementOps << "," << info.nrOneQubitOpsExecutedOnce << "," << info.nrTwoQubitOpsExecutedOnce << ","
1163 << info.nrThreeQubitOpsExecutedOnce << ","
1164 << "0,0," << maxBondDim << "," << cntPauli << "," << estimatedCost << "," << expectationTime;
1165 log.Log(ss.str());
1166 }
1167
1168 static std::shared_ptr<Utils::MultipleLinearRegression> GetRegressor(const std::string &logFilePath, const std::vector<size_t> &featureIndices)
1169 {
1170 const auto executionInfos = ReadLog(logFilePath);
1171
1172 std::vector<std::vector<double>> features;
1173 features.reserve(executionInfos.size());
1174
1175 std::vector<double> targetValues;
1176 targetValues.reserve(executionInfos.size());
1177
1178 for (const auto &info : executionInfos)
1179 {
1180 std::vector<double> featureVector(featureIndices.size());
1181 for (size_t i = 0; i < featureVector.size(); ++i)
1182 featureVector[i] = info.getFieldValue(featureIndices[i]);
1183 features.push_back(std::move(featureVector));
1184
1185 targetValues.push_back(info.runtime);
1186 }
1187
1188 auto regressor = std::make_shared<Utils::MultipleLinearRegression>();
1189 regressor->SetSamples(features, targetValues);
1190
1191 return regressor;
1192 }
1193};
1194
1195} // namespace Estimators
1196
1197#endif
static const std::shared_ptr< IQuantumGate< Time > > CreateGate(QuantumGateType type, size_t q1, size_t q2=0, size_t q3=0, double param1=0, double param2=0, double param3=0, double param4=0)
Construct a quantum gate.
Circuit class for holding the sequence of operations.
Definition Circuit.h:51
Measurement operation class.
The state class that stores the classical state of a quantum circuit execution.
Definition Operations.h:68
static std::shared_ptr< Circuits::Circuit<> > GenerateRandomCircuit(size_t nrQubits, size_t depth, double measureInsideProbability=0., size_t nrMeasAtEnd=0, bool isClifford=false, size_t nrNonCliffordGatesLimit=0, size_t nrBranchingGatesLimit=0)
static double MeasureSamplingTime(Simulators::SimulatorType simType, Simulators::SimulationType method, size_t nrQubits, const std::shared_ptr< Circuits::Circuit<> > &circuit, size_t nrQubitsSampled, size_t nrSamples, size_t nrReps, size_t maxBondDim)
static void BenchmarkAndLogPauliExpectation(Simulators::SimulatorType simType, Simulators::SimulationType method, size_t nrReps, size_t nrMinQubits, size_t nrMaxQubits, size_t stepQubits, size_t depthMin, size_t depthMax, size_t stepDepth, size_t nrRandomCircuitsPerConfig, const std::string &logFilePath, size_t startBondDim=16, size_t endBondDim=16)
static double MeasureExecutionTime(Simulators::SimulatorType simType, Simulators::SimulationType method, size_t nrQubits, const std::shared_ptr< Circuits::Circuit<> > &circuit, size_t nrReps, size_t maxBondDim)
static void BenchmarkAndLogPauliExpectation(Simulators::SimulatorType simType, Simulators::SimulationType method, const std::shared_ptr< Circuits::Circuit<> > &circuit, const std::string &pauliString, size_t nrReps, size_t maxBondDim, Utils::LogFile &log)
static void BenchmarkAndLogSampling(Simulators::SimulatorType simType, Simulators::SimulationType method, size_t nrReps, size_t nrMinQubits, size_t nrMaxQubits, size_t stepQubits, size_t depthMin, size_t depthMax, size_t stepDepth, size_t nrMeasAtEndMin, size_t nrMeasAtEndMax, size_t stepMeasAtEnd, size_t nrSamplesMin, size_t nrSamplesMax, size_t multiplierSamples, size_t nrRandomCircuitsPerConfig, const std::string &logFilePath, size_t startBondDim=16, size_t endBondDim=16)
static double MeasurePauliExpectationTime(Simulators::SimulatorType simType, Simulators::SimulationType method, size_t nrQubits, const std::shared_ptr< Circuits::Circuit<> > &circuit, const std::string &pauliString, size_t nrReps, size_t maxBondDim)
static std::shared_ptr< Utils::MultipleLinearRegression > GetRegressor(const std::string &logFilePath, const std::vector< size_t > &featureIndices)
static std::shared_ptr< Simulators::ISimulator > GetSimulator(Simulators::SimulatorType simType, Simulators::SimulationType method, size_t nrQubits, size_t maxBondDim)
static double EstimatePauliExpectationCost(const std::string &pauliString, Simulators::SimulationType method, size_t nrQubits, const std::shared_ptr< Circuits::Circuit<> > &circuit, size_t maxBondDim)
static void BenchmarkAndLogSampling(Simulators::SimulatorType simType, Simulators::SimulationType method, const std::shared_ptr< Circuits::Circuit<> > &circuit, size_t nrQubitsSampled, size_t nrSamples, size_t nrReps, size_t maxBondDim, Utils::LogFile &log)
static double EstimateSamplingCost(Simulators::SimulationType method, size_t nrQubits, size_t nrQubitsSampled, size_t samples, const std::shared_ptr< Circuits::Circuit<> > &circuit, size_t maxBondDim)
static std::string GeneratePauliString(size_t nrQubits)
static double EstimateExecutionCost(Simulators::SimulationType method, size_t nrQubits, const std::shared_ptr< Circuits::Circuit<> > &circuit, size_t maxBondDim)
static CircuitInfo GetCircuitInfo(const std::shared_ptr< Circuits::Circuit<> > &circuit)
static void BenchmarkAndLogExecution(Simulators::SimulatorType simType, Simulators::SimulationType method, const std::shared_ptr< Circuits::Circuit<> > &circuit, size_t nrReps, size_t maxBondDim, Utils::LogFile &log)
static std::vector< ExecutionInfo > ReadLog(const std::string &logFilePath)
static void BenchmarkAndLogExecution(Simulators::SimulatorType simType, Simulators::SimulationType method, size_t nrReps, size_t nrMinQubits, size_t nrMaxQubits, size_t stepQubits, size_t depthMin, size_t depthMax, size_t stepDepth, double measureInsideProbability, size_t nrMeasAtEndMin, size_t nrMeasAtEndMax, size_t stepMeasAtEnd, size_t nrRandomCircuitsPerConfig, const std::string &logFilePath, size_t startBondDim=16, size_t endBondDim=16)
static double GetSamplingCost(const std::shared_ptr< Circuits::Circuit<> > &circuit, size_t nrQubitsSampled, size_t samples)
static double GetCost(const std::shared_ptr< Circuits::Circuit<> > &circuit)
static MAESTRO_FACTORY_EXPORT std::shared_ptr< ISimulator > CreateSimulator(SimulatorType t=SimulatorType::kQCSim, SimulationType method=SimulationType::kMatrixProductState)
Create a quantum computing simulator.
Definition Factory.cpp:45
void Log(const std::string &message)
Definition LogFile.h:34
QuantumGateType
The type of quantum gates.
@ kConditionalGate
conditional gate, similar with gate, but conditioned on something from 'OperationState'
Definition Operations.h:33
@ kConditionalMeasurement
conditional measurement, similar with measurement, but conditioned on something from 'OperationState'
Definition Operations.h:35
@ kMeasurement
measurement, result in 'OperationState'
Definition Operations.h:31
@ kGate
the usual quantum gate, result stays in simulator's state
Definition Operations.h:30
@ kReset
reset, no result in 'state', just apply measurement, then apply not on all qubits that were measured ...
Definition Operations.h:41
@ kQuantumChannel
a non-unitary CPTP operation on the simulator state
Definition Operations.h:49
SimulationType
The type of simulation.
Definition State.h:109
@ kStatevector
statevector simulation type
Definition State.h:110
@ kMatrixProductState
matrix product state simulation type
Definition State.h:111
@ kStabilizer
Clifford gates simulation type.
Definition State.h:112
@ kPauliPropagator
Pauli propagator simulation type.
Definition State.h:114
@ kPathIntegral
Path integral simulation type.
Definition State.h:116
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
CircuitInfo(const CircuitInfo &other)=default
CircuitInfo & operator=(const CircuitInfo &other)=default
double getFieldValue(size_t index) const
ExecutionInfo(const CircuitInfo &circuitInfo)
ExecutionInfo & operator=(const ExecutionInfo &other)=default
double getFieldValue(size_t index) const
ExecutionInfo(const ExecutionInfo &other)=default
ExecutionInfo & operator=(const CircuitInfo &circuitInfo)