Maestro 0.2.5
Unified interface for quantum circuit simulation
Loading...
Searching...
No Matches
ExecutionCost.h
Go to the documentation of this file.
1
16#pragma once
17
18#ifndef __EXECUTION_COST_H_
19#define __EXECUTION_COST_H_
20
21#include "../Simulators/QcsimPauliPropagator.h"
22#include "../Simulators/Factory.h"
23#include "../Utils/LogFile.h"
24
25#include <cstddef>
26
27namespace Estimators {
28
29// this is not for time estimation, it can be used for something very, very,
30// VERY rough, to decide just if it would execute in a reasonable time or not it
31// cannot be used to compare runtime for two simulators of the same circuit, but
32// it sort of can be used to compare two circuits in the same simulator
33// (especially if the same number of samples is used for both circuits) for
34// something better we have the estimators which got the constants from
35// benchmarking and rely on implementation details of the simulators, so they
36// are more accurate, but also less general
37
38// Except for pauli propagation, where the operation cost depends on the
39// position in the circuit (if the circuit is non-clifford), using a neural
40// network (or even a simpler regressor) to learn the relation between the
41// estimated cost and the actual execution time (single threaded, multithreading
42// rises some other issues) would be probably possible
44 public:
56
57 struct ExecutionInfo : public CircuitInfo {
58 size_t nrSamples = 0;
59 size_t nrQubitsSampled = 0;
60 size_t maxBondDim = 0;
61 size_t nrPauliOps = 0;
62 double executionCost = 0;
63 double samplingCost = 0;
64 };
65
66 static double EstimateExecutionCost(
67 Simulators::SimulationType method, size_t nrQubits,
68 const std::shared_ptr<Circuits::Circuit<>>& circuit, size_t maxBondDim) {
72 const double opOrder = exp2(nrQubits);
73
74 double cost = 0;
75 for (const auto& op : *circuit) {
76 const auto affectedQubits = op->AffectedQubits();
77 if (op->GetType() == Circuits::OperationType::kMeasurement ||
79 cost += opOrder * affectedQubits.size();
80 if (op->GetType() == Circuits::OperationType::kReset)
81 cost += 2. * opOrder * affectedQubits.size();
82 else if (op->GetType() == Circuits::OperationType::kGate ||
84 if (affectedQubits.size() == 1)
85 cost += opOrder;
86 else if (affectedQubits.size() == 2)
87 cost += 4 * opOrder;
88 else if (affectedQubits.size() == 3)
89 cost += 16 * opOrder;
90 }
91 }
92 return cost;
94 const double twoQubitOpOrder = pow(maxBondDim, 3);
95 const double oneQubitOpOrder = pow(maxBondDim, 2);
96
97 double cost = 0;
98 for (const auto& op : *circuit) {
99 const auto affectedQubits = op->AffectedQubits();
100 if (op->GetType() == Circuits::OperationType::kMeasurement ||
102 op->GetType() == Circuits::OperationType::kReset)
103 // it's not that simple at all
104 // a qubit measurement works by applying a projector on the qubit
105 // tensor, which would cost as a one quibit gate but then we need to
106 // propagate the effect of the measurement along the chain, left and
107 // right, which is like applying a two qubit gate (SVD is the costlier
108 // operation there) on all the qubits that are entangled with the
109 // measured one, and in the worst case this can be all the other
110 // qubits if more than one qubits are measured at the same time, then
111 // we can have some optimizations, but let's just say that it's like
112 // measuring them one by one, so the cost is multiplied by the number
113 // of measured qubits
114 cost += twoQubitOpOrder * affectedQubits.size() * nrQubits / 2.;
115 else if (op->GetType() == Circuits::OperationType::kGate ||
117 if (affectedQubits.size() == 1)
118 cost += oneQubitOpOrder;
119 else if (affectedQubits.size() == 2)
120 // I wish it were simple, but applying a gate involves swapping the
121 // qubits next to each other, then applying the gate
122 cost += twoQubitOpOrder * nrQubits / 3.;
123 else if (affectedQubits.size() == 3)
124 // qiskit aer has three qubit ops, qcsim has not, they are
125 // decomposed into one and two qubit gates
126 cost += twoQubitOpOrder * nrQubits * 2; // very rough estimation
127 }
128 }
129 return cost;
130 } else if (method == Simulators::SimulationType::kStabilizer) {
131 const double measOrder = pow(nrQubits, 2);
132 const double opOrder = nrQubits;
133
134 double cost = 0;
135 for (const auto& op : *circuit) {
136 const auto affectedQubits = op->AffectedQubits();
137 if (op->GetType() == Circuits::OperationType::kMeasurement ||
139 op->GetType() == Circuits::OperationType::kReset)
140 cost += measOrder * affectedQubits.size();
141 else if (op->GetType() == Circuits::OperationType::kGate ||
143 cost += opOrder * affectedQubits.size();
144 }
145 return cost;
146 }
147
148 // for tensor network is hard to guess, it depends on contraction path
149
150 return std::numeric_limits<double>::infinity();
151 }
152
153 static double EstimateSamplingCost(
154 Simulators::SimulationType method, size_t nrQubits,
155 size_t nrQubitsSampled, size_t samples,
156 const std::shared_ptr<Circuits::Circuit<>>& circuit, size_t maxBondDim) {
157 // sampling works very differenty for such circuits
158 Circuits::OperationState dummyState;
159 const bool hasMeasurementsInTheMiddle = circuit->HasOpsAfterMeasurements();
160 const std::vector<bool> executedOps =
161 circuit->ExecuteNonMeasurements(nullptr, dummyState);
162
163 const size_t dif = circuit->size() - executedOps.size();
164
167 circuit, nrQubitsSampled, samples);
168
170 const double opOrder = exp2(nrQubits);
171
172 double cost = 0;
173 for (size_t i = 0; i < circuit->size(); ++i) {
174 if (i >= dif && !executedOps[i - dif]) continue;
175
176 const auto& op = (*circuit)[i];
177 const auto affectedQubits = op->AffectedQubits();
178 if (op->GetType() == Circuits::OperationType::kMeasurement ||
180 cost += opOrder * affectedQubits.size();
181 if (op->GetType() == Circuits::OperationType::kReset)
182 cost += 2. * opOrder * affectedQubits.size();
183 else if (op->GetType() == Circuits::OperationType::kGate ||
185 if (affectedQubits.size() == 1)
186 cost += opOrder;
187 else if (affectedQubits.size() == 2)
188 cost += 4 * opOrder;
189 else if (affectedQubits.size() == 3)
190 cost += 16 * opOrder;
191 }
192 }
193
194 // the sampling cost depends on the simulator
195 // qiskit aer constucts an index gowing over the statevector, qcsim does
196 // something similar to have the samples then O(nrQubits), but in maestro
197 // for qcsim (and quest) this is replaced by alias sampling with O(1) for
198 // each sample
199
200 if (hasMeasurementsInTheMiddle) {
201 double samplingCost = 0;
202 for (size_t i = dif; i < circuit->size(); ++i) {
203 if (executedOps[i - dif]) continue;
204
205 const auto& op = (*circuit)[i];
206 const auto affectedQubits = op->AffectedQubits();
207 if (op->GetType() == Circuits::OperationType::kMeasurement ||
209 samplingCost += opOrder * affectedQubits.size();
210 if (op->GetType() == Circuits::OperationType::kReset)
211 samplingCost += 2. * opOrder * affectedQubits.size();
212 else if (op->GetType() == Circuits::OperationType::kGate ||
214 if (affectedQubits.size() == 1)
215 samplingCost += opOrder;
216 else if (affectedQubits.size() == 2)
217 samplingCost += 4 * opOrder;
218 else if (affectedQubits.size() == 3)
219 samplingCost += 16 * opOrder;
220 }
221 }
222
223 samplingCost += opOrder * nrQubitsSampled;
224
225 return cost + samplingCost * samples;
226 }
227
228 // some dummy cost, it's not going to fit all anyways
229 return cost + 30 * opOrder + samples * nrQubits;
231 const double twoQubitOpOrder = pow(maxBondDim, 3);
232 const double oneQubitOpOrder = pow(maxBondDim, 2);
233
234 double cost = 0;
235 for (size_t i = 0; i < circuit->size(); ++i) {
236 if (i >= dif && !executedOps[i - dif]) continue;
237
238 const auto& op = (*circuit)[i];
239 const auto affectedQubits = op->AffectedQubits();
240 if (op->GetType() == Circuits::OperationType::kMeasurement ||
242 op->GetType() == Circuits::OperationType::kReset)
243 // it's not that simple at all
244 // a qubit measurement works by applying a projector on the qubit
245 // tensor, which would cost as a one quibit gate but then we need to
246 // propagate the effect of the measurement along the chain, left and
247 // right, which is like applying a two qubit gate (SVD is the costlier
248 // operation there) on all the qubits that are entangled with the
249 // measured one, and in the worst case this can be all the other
250 // qubits if more than one qubits are measured at the same time, then
251 // we can have some optimizations, but let's just say that it's like
252 // measuring them one by one, so the cost is multiplied by the number
253 // of measured qubits
254 cost += twoQubitOpOrder * affectedQubits.size() * nrQubits / 2.;
255 else if (op->GetType() == Circuits::OperationType::kGate ||
257 if (affectedQubits.size() == 1)
258 cost += oneQubitOpOrder;
259 else if (affectedQubits.size() == 2)
260 // I wish it were simple, but applying a gate involves swapping the
261 // qubits next to each other, then applying the gate
262 cost += twoQubitOpOrder * nrQubits / 3.;
263 else if (affectedQubits.size() == 3)
264 // qiskit aer has three qubit ops, qcsim has not, they are
265 // decomposed into one and two qubit gates
266 cost += twoQubitOpOrder * nrQubits * 2; // very rough estimation
267 }
268 }
269
270 if (hasMeasurementsInTheMiddle) {
271 double samplingCost = 0;
272 for (size_t i = dif; i < circuit->size(); ++i) {
273 if (executedOps[i - dif]) continue;
274
275 const auto& op = (*circuit)[i];
276 const auto affectedQubits = op->AffectedQubits();
277 if (op->GetType() == Circuits::OperationType::kMeasurement ||
278 op->GetType() ==
280 op->GetType() == Circuits::OperationType::kReset)
281 samplingCost +=
282 twoQubitOpOrder * affectedQubits.size() * nrQubits / 2.;
283 else if (op->GetType() == Circuits::OperationType::kGate ||
285 if (affectedQubits.size() == 1)
286 samplingCost += oneQubitOpOrder;
287 else if (affectedQubits.size() == 2)
288 // I wish it were simple, but applying a gate involves swapping
289 // the qubits next to each other, then applying the gate
290 samplingCost += twoQubitOpOrder * nrQubits / 3.;
291 else if (affectedQubits.size() == 3)
292 // qiskit aer has three qubit ops, qcsim has not, they are
293 // decomposed into one and two qubit gates
294 samplingCost +=
295 twoQubitOpOrder * nrQubits * 2; // very rough estimation
296 }
297 }
298
299 samplingCost += twoQubitOpOrder * nrQubits * nrQubits;
300
301 return cost + samplingCost * samples;
302 }
303
304 // sampling can be done here (and for other simulator types as well)
305 // either by saving the state, measuring then restoring the state or
306 // simply going along the chain, computing probabilities, throwing biased
307 // coins and doing matrix multiplications (qcsim does this if all qubits
308 // are sampled, otherwise the measurement method is used)
309
310 // some dummy cost, it's not going to fit all anyways
311 return cost + samples * twoQubitOpOrder * nrQubits * nrQubits;
312 } else if (method == Simulators::SimulationType::kStabilizer) {
313 const double measOrder = pow(nrQubits, 2);
314 const double opOrder = nrQubits;
315
316 double cost = 0;
317 for (size_t i = 0; i < circuit->size(); ++i) {
318 if (i >= dif && !executedOps[i - dif]) continue;
319
320 const auto& op = (*circuit)[i];
321 const auto affectedQubits = op->AffectedQubits();
322 if (op->GetType() == Circuits::OperationType::kMeasurement ||
324 op->GetType() == Circuits::OperationType::kReset)
325 cost += measOrder * affectedQubits.size();
326 else if (op->GetType() == Circuits::OperationType::kGate ||
328 cost += opOrder * affectedQubits.size();
329 }
330
331 if (hasMeasurementsInTheMiddle) {
332 double samplingCost = 0;
333 for (size_t i = dif; i < circuit->size(); ++i) {
334 if (executedOps[i - dif]) continue;
335
336 const auto& op = (*circuit)[i];
337 const auto affectedQubits = op->AffectedQubits();
338 if (op->GetType() == Circuits::OperationType::kMeasurement ||
339 op->GetType() ==
341 op->GetType() == Circuits::OperationType::kReset)
342 samplingCost += measOrder * affectedQubits.size();
343 else if (op->GetType() == Circuits::OperationType::kGate ||
345 samplingCost += opOrder * affectedQubits.size();
346 }
347
348 samplingCost += measOrder * nrQubitsSampled;
349
350 return cost + samplingCost * samples;
351 }
352
353 // sampling is done with saving state, measuring and restoring state
354 // the overhead for saving / restoring is not here (it's of the same order
355 // as measuring), but measurements are not all O(n^2) anyways
356 return cost + samples * measOrder * nrQubitsSampled;
357 }
358
359 // for tensor network is hard to guess, it depends on contraction path
360
361 return std::numeric_limits<double>::infinity();
362 }
363
365 const std::string& pauliString, Simulators::SimulationType method,
366 size_t nrQubits, const std::shared_ptr<Circuits::Circuit<>>& circuit,
367 size_t maxBondDim) {
368 // a pauli string propagated
371
372 size_t pauliCnt = 0;
373 for (char c : pauliString) {
374 if (c == 'X' || c == 'x' || c == 'Y' || c == 'y' || c == 'Z' || c == 'z')
375 ++pauliCnt;
376 }
377 double cost = EstimateExecutionCost(method, nrQubits, circuit, maxBondDim);
378
380 const double opOrder = exp2(nrQubits);
381
382 // each pauli matrix is applied to the statevector, then the product with
383 // the original is computed
384 cost += opOrder * (pauliCnt + 1);
385
386 return cost;
388 const double twoQubitOpOrder = pow(maxBondDim, 3);
389 const double oneQubitOpOrder = pow(maxBondDim, 2);
390
391 // each pauli is applied to the chain then the resulted chain is
392 // contracted with the original one
393 cost += oneQubitOpOrder * pauliCnt + nrQubits * twoQubitOpOrder;
394
395 return cost;
396 } else if (method == Simulators::SimulationType::kStabilizer) {
397 cost += nrQubits * nrQubits;
398 return cost;
399 }
400
401 return std::numeric_limits<double>::infinity();
402 }
403
404 static std::shared_ptr<Circuits::Circuit<>> GenerateRandomCircuit(
405 size_t nrQubits, size_t depth, double measureInsideProbability = 0.,
406 size_t nrMeasAtEnd = 0, bool isClifford = false,
407 size_t nrNonCliffordGatesLimit = 0) {
408 auto circuit = std::make_shared<Circuits::Circuit<>>();
409 std::random_device rdev;
410 std::mt19937 rng(rdev());
411 std::uniform_real_distribution<double> dist(0.0, 1.0);
412 std::uniform_real_distribution<double> paramDist(-2 * M_PI, 2 * M_PI);
413 std::uniform_int_distribution<Types::qubit_t> qubitDist(0, nrQubits - 1);
414 std::uniform_int_distribution<int> gateDist(
415 0, static_cast<int>(Circuits::QuantumGateType::kCCXGateType));
416
417 std::uniform_int_distribution<int> gateDistOneQubit(
418 0, static_cast<int>(Circuits::QuantumGateType::kUGateType));
419
420 std::vector<Types::qubit_t> qubits(nrQubits);
421 std::iota(qubits.begin(), qubits.end(), 0);
422
423 size_t nrNonCliffordGates = 0;
424 if (nrNonCliffordGatesLimit == 0 && !isClifford)
425 nrNonCliffordGatesLimit = depth;
426
427 for (size_t i = 0; i < depth; ++i) {
428 if (dist(rng) < measureInsideProbability) {
429 Types::qubit_t q = qubitDist(rng);
430 std::vector<std::pair<Types::qubit_t, size_t>> qs = {{q, q}};
431 circuit->AddOperation(
432 std::make_shared<Circuits::MeasurementOperation<>>(qs));
433 continue;
434 }
435
436 std::shuffle(qubits.begin(), qubits.end(), rng);
437 const auto q1 = qubits[0];
438 const auto q2 = qubits[1];
439 const auto q3 = qubits[2];
440
441 auto gateType = static_cast<Circuits::QuantumGateType>(
442 nrNonCliffordGatesLimit < depth
443 ? gateDistOneQubit(rng) // avoid three qubit gates, they are
444 // non-clifford and they cost a lot
445 : gateDist(rng));
446 auto param1 = paramDist(rng);
447 auto param2 = paramDist(rng);
448 auto param3 = paramDist(rng);
449 auto param4 = paramDist(rng);
450
452 gateType, q1, q2, q3, param1, param2, param3, param4);
453 if (isClifford) {
454 while (!theGate->IsClifford()) {
455 gateType = static_cast<Circuits::QuantumGateType>(gateDist(rng));
457 gateType, q1, q2, q3, param1, param2, param3, param4);
458 }
459 }
460
461 if (!theGate->IsClifford()) ++nrNonCliffordGates;
462
463 if (nrNonCliffordGates > nrNonCliffordGatesLimit) {
464 // replace the non clifford gate with a clifford one
465 gateType =
466 static_cast<Circuits::QuantumGateType>(gateDistOneQubit(rng));
468 gateType, q1, q2, q3, param1, param2, param3, param4);
469 while (!theGate->IsClifford()) {
470 gateType =
471 static_cast<Circuits::QuantumGateType>(gateDistOneQubit(rng));
473 gateType, q1, q2, q3, param1, param2, param3, param4);
474 }
475 --nrNonCliffordGates;
476 }
477
478 circuit->AddOperation(theGate);
479 }
480
481 if (nrMeasAtEnd > 0) {
482 if (nrMeasAtEnd > nrQubits) nrMeasAtEnd = nrQubits;
483 std::shuffle(qubits.begin(), qubits.end(), rng);
484
485 for (size_t i = 0; i < nrMeasAtEnd; ++i) {
486 std::vector<std::pair<Types::qubit_t, size_t>> qs = {{qubits[i], i}};
487 circuit->AddOperation(
488 std::make_shared<Circuits::MeasurementOperation<>>(qs));
489 }
490 }
491
492 return circuit;
493 }
494
495 static double MeasureExecutionTime(
497 size_t nrQubits, const std::shared_ptr<Circuits::Circuit<>>& circuit,
498 size_t nrReps, size_t maxBondDim) {
499 auto sim = GetSimulator(simType, method, nrQubits, maxBondDim);
500
501 Circuits::OperationState dummyState(nrQubits);
502 auto start = std::chrono::high_resolution_clock::now();
503 for (size_t i = 0; i < nrReps; ++i) circuit->Execute(sim, dummyState);
504 auto end = std::chrono::high_resolution_clock::now();
505 return std::chrono::duration<double>(end - start).count();
506 }
507
508 static double MeasureSamplingTime(
510 size_t nrQubits, const std::shared_ptr<Circuits::Circuit<>>& circuit,
511 size_t nrQubitsSampled, size_t nrSamples, size_t nrReps,
512 size_t maxBondDim) {
513 auto sim = GetSimulator(simType, method, nrQubits, maxBondDim);
514 Circuits::OperationState dummyState(nrQubits);
515
516 if (nrQubitsSampled > nrQubits) nrQubitsSampled = nrQubits;
517 Types::qubits_vector qubitsSampled(nrQubitsSampled);
518 std::iota(qubitsSampled.begin(), qubitsSampled.end(), 0);
519
520 const bool hasMeasurementsInTheMiddle = circuit->HasOpsAfterMeasurements();
521
522 auto start = std::chrono::high_resolution_clock::now();
523
524 if (hasMeasurementsInTheMiddle) {
525 for (size_t i = 0; i < nrReps; ++i) {
526 const auto executedOps = circuit->ExecuteNonMeasurements(
527 sim, dummyState); // execute the circuit up to measurements
528
529 // now sample
530 for (size_t sample = 0; sample < nrSamples; ++sample) {
531 circuit->ExecuteMeasurements(
532 sim, dummyState, executedOps); // execute the measurements
533 }
534 }
535 } else {
536 for (size_t i = 0; i < nrReps; ++i) {
537 circuit->Execute(sim, dummyState); // execute the circuit first
538 sim->SampleCountsMany(qubitsSampled, nrSamples);
539 }
540 }
541 auto end = std::chrono::high_resolution_clock::now();
542
543 return std::chrono::duration<double>(end - start).count();
544 }
545
548 size_t nrQubits, const std::shared_ptr<Circuits::Circuit<>>& circuit,
549 const std::string& pauliString, size_t nrReps, size_t maxBondDim) {
550 auto sim = GetSimulator(simType, method, nrQubits, maxBondDim);
551 Circuits::OperationState dummyState(nrQubits);
552 auto start = std::chrono::high_resolution_clock::now();
553 for (size_t i = 0; i < nrReps; ++i) {
554 circuit->Execute(sim, dummyState); // execute the circuit first
555 sim->ExpectationValue(pauliString);
556 }
557 auto end = std::chrono::high_resolution_clock::now();
558 return std::chrono::duration<double>(end - start).count();
559 }
560
561 static std::shared_ptr<Simulators::ISimulator> GetSimulator(
563 size_t nrQubits, size_t maxBondDim) {
564 auto sim = Simulators::SimulatorsFactory::CreateSimulator(simType, method);
566 const auto strVal = std::to_string(maxBondDim);
567 sim->Configure("matrix_product_state_max_bond_dimension", strVal.c_str());
568 }
569 sim->AllocateQubits(nrQubits);
570 sim->SetMultithreading(false);
571 sim->Initialize();
572 return sim;
573 }
574
576 const std::shared_ptr<Circuits::Circuit<>>& circuit) {
577 CircuitInfo info;
578
579 info.nrQubits = circuit->GetMaxQubitIndex() + 1;
580 Circuits::OperationState dummyState(info.nrQubits);
581 const bool hasMeasurementsInTheMiddle = circuit->HasOpsAfterMeasurements();
582 const std::vector<bool> executedOps =
583 circuit->ExecuteNonMeasurements(nullptr, dummyState);
584
585 const size_t dif = circuit->size() - executedOps.size();
586
587 size_t i = 0;
588 for (const auto& op : *circuit) {
589 const auto affectedQubits = op->AffectedQubits();
590 if (op->GetType() == Circuits::OperationType::kMeasurement ||
592 if (hasMeasurementsInTheMiddle)
594 else
595 ++info.nrEndMeasurementOps;
596 } else if (op->GetType() == Circuits::OperationType::kGate ||
598 if (affectedQubits.size() == 1) {
599 ++info.nrOneQubitOps;
600 if (i < dif || executedOps[i - dif]) ++info.nrOneQubitOpsExecutedOnce;
601 } else if (affectedQubits.size() == 2) {
602 ++info.nrTwoQubitOps;
603 if (i < dif || executedOps[i - dif]) ++info.nrTwoQubitOpsExecutedOnce;
604 } else if (affectedQubits.size() == 3) {
605 ++info.nrThreeQubitOps;
606 if (i < dif || executedOps[i - dif])
608 }
609 }
610 ++i;
611 }
612
613 return info;
614 }
615
616 static std::vector<ExecutionInfo> ReadLog(const std::string& logFilePath) {
617 std::vector<ExecutionInfo> executionInfos;
618
619 std::ifstream logFile(logFilePath);
620 if (!logFile.is_open()) {
621 std::cerr << "Failed to open log file: " << logFilePath << std::endl;
622 return executionInfos;
623 }
624
625 std::string line;
626
627 while (std::getline(logFile, line)) {
628 std::stringstream ss(line);
629 std::string value;
630 ExecutionInfo info;
631 std::getline(ss, value, ',');
632 info.nrQubits = std::stoul(value);
633 std::getline(ss, value, ',');
634 info.nrOneQubitOps = std::stoul(value);
635 std::getline(ss, value, ',');
636 info.nrTwoQubitOps = std::stoul(value);
637 std::getline(ss, value, ',');
638 info.nrThreeQubitOps = std::stoul(value);
639 std::getline(ss, value, ',');
640 info.nrMiddleMeasurementOps = std::stoul(value);
641 std::getline(ss, value, ',');
642 info.nrEndMeasurementOps = std::stoul(value);
643 std::getline(ss, value, ',');
644 info.nrOneQubitOpsExecutedOnce = std::stoul(value);
645 std::getline(ss, value, ',');
646 info.nrTwoQubitOpsExecutedOnce = std::stoul(value);
647 std::getline(ss, value, ',');
648 info.nrThreeQubitOpsExecutedOnce = std::stoul(value);
649
650 std::getline(ss, value, ',');
651 info.nrSamples = std::stoul(value);
652 std::getline(ss, value, ',');
653 info.nrQubitsSampled = std::stoul(value);
654 std::getline(ss, value, ',');
655 info.maxBondDim = std::stoul(value);
656 std::getline(ss, value, ',');
657 info.nrPauliOps = std::stoul(value);
658
659 std::getline(ss, value, ',');
660 info.executionCost = std::stod(value);
661 std::getline(ss, value, ',');
662 info.samplingCost = std::stod(value);
663
664 if (info.nrSamples < 1) info.nrSamples = 1;
665
666 executionInfos.push_back(std::move(info));
667 }
668
669 return executionInfos;
670 }
671
674 size_t nrReps, size_t nrMinQubits, size_t nrMaxQubits, size_t stepQubits,
675 size_t depthMin, size_t depthMax, size_t stepDepth,
676 double measureInsideProbability, size_t nrMeasAtEndMin,
677 size_t nrMeasAtEndMax, size_t stepMeasAtEnd,
678 size_t nrRandomCircuitsPerConfig, size_t maxBondDim,
679 const std::string& logFilePath) {
680 bool isClifford = (method == Simulators::SimulationType::kStabilizer);
681 int nrNonCliffordGates = depthMax; // a limit
682 // but if it's pauli...
684 nrNonCliffordGates = 1;
685 } else if (method == Simulators::SimulationType::kStabilizer) {
686 nrNonCliffordGates = 0;
687 }
688
689 std::cout << "Benchmarking execution for simType: "
690 << static_cast<int>(simType)
691 << ", method: " << static_cast<int>(method) << std::endl;
692
693 Utils::LogFile log(logFilePath);
694
695 for (size_t nrQubits = nrMinQubits; nrQubits <= nrMaxQubits;
696 nrQubits += stepQubits) {
697 std::cout << " Qubits: " << nrQubits << std::endl;
698 for (size_t depth = depthMin; depth <= depthMax; depth += stepDepth) {
699 std::cout << " Depth: " << depth << std::endl;
700 for (size_t nrMeasAtEnd = nrMeasAtEndMin;
701 nrMeasAtEnd <= std::min(nrMeasAtEndMax, nrQubits);
702 nrMeasAtEnd += stepMeasAtEnd) {
703 std::cout << " Measurements at end: " << nrMeasAtEnd
704 << std::endl;
705 for (size_t i = 0; i < nrRandomCircuitsPerConfig; ++i) {
706 std::cout << " Random circuit: " << i + 1 << "/"
707 << nrRandomCircuitsPerConfig << std::endl;
709 isClifford = !isClifford;
710
711 const auto circuit = GenerateRandomCircuit(
712 nrQubits, depth, measureInsideProbability, nrMeasAtEnd,
713 isClifford, nrNonCliffordGates);
714 BenchmarkAndLogExecution(simType, method, circuit, nrReps,
715 maxBondDim, log);
716 }
717 }
718 }
719 }
720 }
721
722 static std::string GeneratePauliString(size_t nrQubits) {
723 std::string pauli;
724 pauli.resize(nrQubits);
725 std::random_device rd;
726 std::mt19937 g(rd());
727 std::uniform_int_distribution<int> dist(0, 3);
728
729 for (size_t i = 0; i < nrQubits; ++i) {
730 const int v = dist(g);
731 switch (v) {
732 case 0:
733 pauli[i] = 'I';
734 break;
735 case 1:
736 pauli[i] = 'X';
737 break;
738 case 2:
739 pauli[i] = 'Y';
740 break;
741 case 3:
742 pauli[i] = 'Z';
743 break;
744 }
745 }
746
747 return pauli;
748 }
749
752 size_t nrReps, size_t nrMinQubits, size_t nrMaxQubits, size_t stepQubits,
753 size_t depthMin, size_t depthMax, size_t stepDepth,
754 size_t nrRandomCircuitsPerConfig, size_t maxBondDim,
755 const std::string& logFilePath) {
756 bool isClifford = (method == Simulators::SimulationType::kStabilizer);
757 int nrNonCliffordGates = depthMax; // a limit
758 // but if it's pauli...
760 nrNonCliffordGates = 1;
761 } else if (method == Simulators::SimulationType::kStabilizer) {
762 nrNonCliffordGates = 0;
763 }
764 Utils::LogFile log(logFilePath);
765
766 std::cout << "Benchmarking Pauli expectation for simType: "
767 << static_cast<int>(simType)
768 << ", method: " << static_cast<int>(method) << std::endl;
769
770 for (size_t nrQubits = nrMinQubits; nrQubits <= nrMaxQubits;
771 nrQubits += stepQubits) {
772 std::cout << " Qubits: " << nrQubits << std::endl;
773 for (size_t depth = depthMin; depth <= depthMax; depth += stepDepth) {
774 std::cout << " Depth: " << depth << std::endl;
775 for (size_t i = 0; i < nrRandomCircuitsPerConfig; ++i) {
777 isClifford = !isClifford;
778
779 const auto circuit = GenerateRandomCircuit(
780 nrQubits, depth, 0., 0, isClifford, nrNonCliffordGates);
781 const std::string pauliString = GeneratePauliString(nrQubits);
782
783 std::cout << " Random circuit: " << i + 1 << "/"
784 << nrRandomCircuitsPerConfig
785 << " Pauli string: " << pauliString << std::endl;
786 BenchmarkAndLogPauliExpectation(simType, method, circuit, pauliString,
787 nrReps, maxBondDim, log);
788 }
789 }
790 }
791 }
792
795 size_t nrReps, size_t nrMinQubits, size_t nrMaxQubits, size_t stepQubits,
796 size_t depthMin, size_t depthMax, size_t stepDepth, size_t nrMeasAtEndMin,
797 size_t nrMeasAtEndMax, size_t stepMeasAtEnd, size_t nrSamplesMin,
798 size_t nrSamplesMax, size_t multiplierSamples,
799 size_t nrRandomCircuitsPerConfig, size_t maxBondDim,
800 const std::string& logFilePath) {
801 bool isClifford = (method == Simulators::SimulationType::kStabilizer);
802 int nrNonCliffordGates = depthMax; // a limit
803 // but if it's pauli...
805 nrNonCliffordGates = 1;
806 } else if (method == Simulators::SimulationType::kStabilizer) {
807 nrNonCliffordGates = 0;
808 }
809 Utils::LogFile log(logFilePath);
810
811 std::cout << "Benchmarking sampling for simType: "
812 << static_cast<int>(simType)
813 << ", method: " << static_cast<int>(method) << std::endl;
814
815 for (size_t nrQubits = nrMinQubits; nrQubits <= nrMaxQubits;
816 nrQubits += stepQubits) {
817 std::cout << " Qubits: " << nrQubits << std::endl;
818 for (size_t depth = depthMin; depth <= depthMax; depth += stepDepth) {
819 std::cout << " Depth: " << depth << std::endl;
820 for (size_t nrSamples = nrSamplesMin; nrSamples <= nrSamplesMax;
821 nrSamples *= multiplierSamples) {
822 std::cout << " Samples: " << nrSamples << std::endl;
823 for (size_t i = 0; i < nrRandomCircuitsPerConfig; ++i) {
825 isClifford = !isClifford;
826 const auto circuit = GenerateRandomCircuit(
827 nrQubits, depth, 0., 0, isClifford, nrNonCliffordGates);
828 for (size_t nrQubitsSampled = nrQubits; nrQubitsSampled >= 1;
829 nrQubitsSampled /= 2) {
830 std::cout << " Random circuit: " << i + 1 << "/"
831 << nrRandomCircuitsPerConfig
832 << " Qubits sampled: " << nrQubitsSampled << std::endl;
833 BenchmarkAndLogSampling(simType, method, circuit, nrQubitsSampled,
834 nrSamples, nrReps, maxBondDim, log);
835 }
836 }
837 }
838 }
839 }
840 }
841
844 const std::shared_ptr<Circuits::Circuit<>>& circuit, size_t nrReps,
845 size_t maxBondDim, Utils::LogFile& log) {
846 const auto info = GetCircuitInfo(circuit);
847 const double estimatedCost =
848 EstimateExecutionCost(method, info.nrQubits, circuit, maxBondDim);
849 const double executionTime =
850 MeasureExecutionTime(simType, method, info.nrQubits, circuit, nrReps,
851 maxBondDim) /
852 nrReps;
853
854 std::stringstream ss;
855
856 ss << info.nrQubits << "," << info.nrOneQubitOps << ","
857 << info.nrTwoQubitOps << "," << info.nrThreeQubitOps << ","
858 << info.nrMiddleMeasurementOps << "," << info.nrEndMeasurementOps << ","
859 << info.nrOneQubitOpsExecutedOnce << ","
860 << info.nrTwoQubitOpsExecutedOnce << ","
861 << info.nrThreeQubitOpsExecutedOnce << ","
862 << "0,0," << maxBondDim << ","
863 << "0," << estimatedCost << "," << executionTime;
864
865 log.Log(ss.str());
866 }
867
870 const std::shared_ptr<Circuits::Circuit<>>& circuit,
871 size_t nrQubitsSampled, size_t nrSamples, size_t nrReps,
872 size_t maxBondDim, Utils::LogFile& log) {
873 const auto info = GetCircuitInfo(circuit);
874 const double estimatedCost = EstimateSamplingCost(
875 method, info.nrQubits, nrQubitsSampled, nrSamples, circuit, maxBondDim);
876 const double samplingTime =
877 MeasureSamplingTime(simType, method, info.nrQubits, circuit,
878 nrQubitsSampled, nrSamples, nrReps, maxBondDim) /
879 nrReps;
880 std::stringstream ss;
881 ss << info.nrQubits << "," << info.nrOneQubitOps << ","
882 << info.nrTwoQubitOps << "," << info.nrThreeQubitOps << ","
883 << info.nrMiddleMeasurementOps << "," << info.nrEndMeasurementOps << ","
884 << info.nrOneQubitOpsExecutedOnce << ","
885 << info.nrTwoQubitOpsExecutedOnce << ","
886 << info.nrThreeQubitOpsExecutedOnce << "," << nrSamples << ","
887 << nrQubitsSampled << "," << maxBondDim << ","
888 << "0," << estimatedCost << "," << samplingTime;
889 log.Log(ss.str());
890 }
891
894 const std::shared_ptr<Circuits::Circuit<>>& circuit,
895 const std::string& pauliString, size_t nrReps, size_t maxBondDim,
896 Utils::LogFile& log) {
897 auto info = GetCircuitInfo(circuit);
898 info.nrQubits = std::max(info.nrQubits, pauliString.size());
899 const double estimatedCost = EstimatePauliExpectationCost(
900 pauliString, method, info.nrQubits, circuit, maxBondDim);
901 const double expectationTime =
902 MeasurePauliExpectationTime(simType, method, info.nrQubits, circuit,
903 pauliString, nrReps, maxBondDim) /
904 nrReps;
905
906 size_t cntPauli = 0;
907 for (char c : pauliString) {
908 if (c == 'X' || c == 'x' || c == 'Y' || c == 'y' || c == 'Z' || c == 'z')
909 ++cntPauli;
910 }
911
912 std::stringstream ss;
913 ss << info.nrQubits << "," << info.nrOneQubitOps << ","
914 << info.nrTwoQubitOps << "," << info.nrThreeQubitOps << ","
915 << info.nrMiddleMeasurementOps << "," << info.nrEndMeasurementOps << ","
916 << info.nrOneQubitOpsExecutedOnce << ","
917 << info.nrTwoQubitOpsExecutedOnce << ","
918 << info.nrThreeQubitOpsExecutedOnce << ","
919 << "0,0," << maxBondDim << "," << cntPauli << "," << estimatedCost << ","
920 << expectationTime;
921 log.Log(ss.str());
922 }
923};
924
925} // namespace Estimators
926
927#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:46
Measurement operation class.
The state class that stores the classical state of a quantum circuit execution.
Definition Operations.h:63
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 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 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< 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 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, size_t maxBondDim, const std::string &logFilePath)
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, 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, size_t maxBondDim, const std::string &logFilePath)
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 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, size_t maxBondDim, const std::string &logFilePath)
static std::vector< ExecutionInfo > ReadLog(const std::string &logFilePath)
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)
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 std::shared_ptr< ISimulator > CreateSimulator(SimulatorType t=SimulatorType::kQCSim, SimulationType method=SimulationType::kMatrixProductState)
Create a quantum computing simulator.
Definition Factory.cpp:101
void Log(const std::string &message)
Definition LogFile.h:30
QuantumGateType
The type of quantum gates.
@ kConditionalGate
conditional gate, similar with gate, but conditioned on something from 'OperationState'
@ kConditionalMeasurement
conditional measurement, similar with measurement, but conditioned on something from 'OperationState'
@ kMeasurement
measurement, result in 'OperationState'
@ kGate
the usual quantum gate, result stays in simulator's state
@ kReset
reset, no result in 'state', just apply measurement, then apply not on all qubits that were measured ...
SimulationType
The type of simulation.
Definition State.h:85
@ kStatevector
statevector simulation type
@ kMatrixProductState
matrix product state simulation type
@ kStabilizer
Clifford gates simulation type.
@ kPauliPropagator
Pauli propagator simulation type.
SimulatorType
The type of simulator.
Definition State.h:68
uint_fast64_t qubit_t
The type of a qubit.
Definition Types.h:21
std::vector< qubit_t > qubits_vector
The type of a vector of qubits.
Definition Types.h:23