Maestro 0.2.5
Unified interface for quantum circuit simulation
Loading...
Searching...
No Matches
QcsimPauliPropagator.h
Go to the documentation of this file.
1
12#pragma once
13
14#ifndef _QCSIM_PAULI_PROPAGATOR_H
15#define _QCSIM_PAULI_PROPAGATOR_H 1
16
17#include "PauliPropagator.h"
18#include "../Circuit/Circuit.h"
19
20namespace Simulators {
21
22class QcsimPauliPropagator : public QC::PauliPropagator {
23 public:
24 void ApplyP(int qubit, double lambda) { ApplyRZ(qubit, lambda); }
25
26 void ApplyT(int qubit) { ApplyRZ(qubit, M_PI_4); }
27
28 void ApplyTDG(int qubit) { ApplyRZ(qubit, -M_PI_4); }
29
30 void ApplyU(int qubit, double theta, double phi, double lambda,
31 double gamma = 0.0) {
32 ApplyRZ(qubit, lambda);
33 ApplyRY(qubit, theta);
34 ApplyRZ(qubit, phi);
35 }
36
37 void ApplyCH(int controlQubit, int targetQubit) {
38 ApplyH(targetQubit);
39 ApplySDG(targetQubit);
40 ApplyCX(controlQubit, targetQubit);
41 ApplyH(targetQubit);
42 ApplyT(targetQubit);
43 ApplyCX(controlQubit, targetQubit);
44 ApplyT(targetQubit);
45 ApplyH(targetQubit);
46 ApplyS(targetQubit);
47 ApplyX(targetQubit);
48 ApplyS(controlQubit);
49 }
50
51 void ApplyCU(int controlQubit, int targetQubit, double theta, double phi,
52 double lambda, double gamma = 0.0) {
53 if (gamma != 0.0) ApplyP(controlQubit, gamma);
54
55 const double lambdaPlusPhiHalf = 0.5 * (lambda + phi);
56 const double halfTheta = 0.5 * theta;
57 ApplyP(targetQubit, 0.5 * (lambda - phi));
58 ApplyP(controlQubit, lambdaPlusPhiHalf);
59 ApplyCX(controlQubit, targetQubit);
60 ApplyU(targetQubit, -halfTheta, 0, -lambdaPlusPhiHalf);
61 ApplyCX(controlQubit, targetQubit);
62 ApplyU(targetQubit, halfTheta, phi, 0);
63 }
64
65 void ApplyCRX(int controlQubit, int targetQubit, double angle) {
66 const double halfAngle = angle * 0.5;
67
68 ApplyH(targetQubit);
69 ApplyCX(controlQubit, targetQubit);
70 ApplyRZ(targetQubit, -halfAngle);
71 ApplyCX(controlQubit, targetQubit);
72 ApplyRZ(targetQubit, halfAngle);
73 ApplyH(targetQubit);
74 }
75
76 void ApplyCRY(int controlQubit, int targetQubit, double angle) {
77 const double halfAngle = angle * 0.5;
78 ApplyRY(targetQubit, halfAngle);
79 ApplyCX(controlQubit, targetQubit);
80 ApplyRY(targetQubit, -halfAngle);
81 ApplyCX(controlQubit, targetQubit);
82 }
83
84 void ApplyCRZ(int controlQubit, int targetQubit, double angle) {
85 const double halfAngle = angle * 0.5;
86
87 ApplyRZ(targetQubit, halfAngle);
88 ApplyCX(controlQubit, targetQubit);
89 ApplyRZ(targetQubit, -halfAngle);
90 ApplyCX(controlQubit, targetQubit);
91 }
92
93 void ApplyCP(int controlQubit, int targetQubit, double lambda) {
94 const double halfAngle = lambda * 0.5;
95 ApplyP(controlQubit, halfAngle);
96 ApplyCX(controlQubit, targetQubit);
97 ApplyP(targetQubit, -halfAngle);
98 ApplyCX(controlQubit, targetQubit);
99 ApplyP(targetQubit, halfAngle);
100 }
101
102 void ApplyCS(int controlQubit, int targetQubit) {
103 ApplyT(controlQubit);
104 ApplyT(targetQubit);
105 ApplyCX(controlQubit, targetQubit);
106 ApplyTDG(targetQubit);
107 ApplyCX(controlQubit, targetQubit);
108 }
109
110 void ApplyCSDAG(int controlQubit, int targetQubit) {
111 ApplyCX(controlQubit, targetQubit);
112 ApplyT(targetQubit);
113 ApplyCX(controlQubit, targetQubit);
114 ApplyTDG(controlQubit);
115 ApplyTDG(targetQubit);
116 }
117
118 void ApplyCSX(int controlQubit, int targetQubit) {
119 ApplyH(targetQubit);
120 ApplyCS(controlQubit, targetQubit);
121 ApplyH(targetQubit);
122 }
123
124 void ApplyCSXDAG(int controlQubit, int targetQubit) {
125 ApplyH(targetQubit);
126 ApplyCSDAG(controlQubit, targetQubit);
127 ApplyH(targetQubit);
128 }
129
130 void ApplyCSwap(int controlQubit, int targetQubit1, int targetQubit2) {
131 const int q1 = controlQubit; // control
132 const int q2 = targetQubit1;
133 const int q3 = targetQubit2;
134
135 ApplyCX(q3, q2);
136 ApplyCSX(q2, q3); // 3 rotaations
137 ApplyCX(q1, q2);
138
139 ApplyP(q3, M_PI); // 1 rotation
140 ApplyP(q2, -M_PI_2); // 1 rotation
141
142 ApplyCSX(q2, q3); // 3 rotations
143 ApplyCX(q1, q2);
144
145 ApplyP(q3, M_PI); // 1 rotation
146 ApplyCSX(q1, q3); // 3 rotations
147 ApplyCX(q3, q2);
148 }
149
150 void ApplyCCX(int controlQubit1, int controlQubit2, int targetQubit) {
151 const int q1 = controlQubit1; // control 1
152 const int q2 = controlQubit2; // control 2
153 const int q3 = targetQubit; // target
154
155 ApplyCSX(q2, q3); // 3 rotations
156 ApplyCX(q1, q2);
157 ApplyCSXDAG(q2, q3); // 3 rotations
158 ApplyCX(q1, q2);
159 ApplyCSX(q1, q3); // 3 rotations
160 }
161
162 std::unique_ptr<QcsimPauliPropagator> Clone() const {
163 auto clone = std::make_unique<QcsimPauliPropagator>();
164 clone->SetNrQubits(GetNrQubits());
165 clone->SetPauliWeightThreshold(GetPauliWeightThreshold());
166 clone->SetBatchSize(GetBatchSize());
167 clone->SetBatchSizeForSum(GetBatchSizeForSum());
168 clone->SetCoefficientThreshold(GetCoefficientThreshold());
169 clone->SetParallelThreshold(GetParallelThreshold());
170 clone->SetParallelThresholdForSum(GetParallelThresholdForSum());
171 clone->SetStepsBetweenDeduplication(StepsBetweenDeduplication());
172 clone->SetStepsBetweenTrims(StepsBetweenTrims());
173 clone->SetOperations(GetOperations());
174 clone->SetSavePosition(GetSavePosition());
175 if (IsParallelEnabled()) clone->EnableParallel();
176
177 return clone;
178 }
179
180 static double GetSamplingCost(
181 const std::shared_ptr<Circuits::Circuit<>>& circuit,
182 size_t nrQubitsSampled, size_t samples) {
183 return samples * GetCost(circuit) * exp2(nrQubitsSampled - 1);
184 }
185
186 static double GetCost(const std::shared_ptr<Circuits::Circuit<>>& circuit) {
187 if (!circuit) return 0.0;
188
189 double cost = 0.0;
190 double doublingCost = 1;
191
192 // go backwards, the same way as the propagator does, to get the cost of the
193 // operations in the circuit
194
195 for (int pos = static_cast<int>(circuit->size()) - 1; pos >= 0; --pos) {
196 const std::shared_ptr<Circuits::IOperation<>>& op = (*circuit)[pos];
197 cost += GetOpCost(circuit, op, pos) * doublingCost;
198 doublingCost *= GetOpMultiplication(op);
199 }
200
201 // summing up all pauli strings expectations
202 cost += doublingCost;
203
204 return cost;
205 }
206
207 private:
208 static double GetOpCost(const std::shared_ptr<Circuits::Circuit<>>& circuit,
209 const std::shared_ptr<Circuits::IOperation<>>& op,
210 int pos) {
211 if (!circuit || !op || pos < 0 || pos >= static_cast<int>(circuit->size()))
212 return 0.;
213
214 if (op->GetType() != Circuits::OperationType::kGate) {
215 if (op->GetType() == Circuits::OperationType::kMeasurement) {
216 // measurement costs a pauli string propagated down the circuit from the
217 // point of measurement... but then it adds a projector in the circuit
218 // in its place, which means a doubling at that point for subsequent
219 // propagations
220 if (pos == 0) return 1.;
221
222 const auto& prevOp = (*circuit)[pos - 1];
223 return 1. + GetOpCost(circuit, prevOp, pos - 1);
224 } else if (op->GetType() == Circuits::OperationType::kReset) {
225 // a measurement followed by a conditional X gate, assume X half of the
226 // times
227 if (pos == 0) return 1.5;
228 const auto& prevOp = (*circuit)[pos - 1];
229 return 1.5 + GetOpCost(circuit, prevOp, pos - 1);
230 } else if (op->GetType() == Circuits::OperationType::kConditionalGate) {
231 const auto conditionalGate =
232 std::static_pointer_cast<Circuits::ConditionalGate<>>(op);
233 return GetOpCost(circuit, conditionalGate->GetOperation(), pos);
234 } else if (op->GetType() ==
236 const auto conditionalMeasurement =
237 std::static_pointer_cast<Circuits::ConditionalMeasurement<>>(op);
238 return GetOpCost(circuit, conditionalMeasurement->GetOperation(), pos);
239 }
240 return 0.;
241 }
242
243 const auto gate = std::static_pointer_cast<Circuits::IQuantumGate<>>(op);
244
245 switch (gate->GetGateType()) {
246 // cliffords
248 [[fallthrough]];
250 [[fallthrough]];
252 return 0.5; // just a sign flip
254 return 1.;
256 return 3.5;
258 return 1.;
260 return 1.5;
262 return 4.;
264 return 3.;
266 return 1.;
268 return 3.5;
270 return 3.;
272 return 3.;
273
274 // non-cliffords
275 // the ones below just rotations, they split in two operators
277 [[fallthrough]];
279 [[fallthrough]];
281 [[fallthrough]];
283 [[fallthrough]];
285 [[fallthrough]];
287 return 2.;
288
290 return 8.; // implemented with three rotations
291
293 // p, cx, p, cx, p
294 return 20;
296 // h, cx, rz, cx, rz, h
297 return 14;
299 [[fallthrough]];
301 return 12;
303 return 26.5;
305 return 35;
307 return 26;
308
310 return 546;
311
313 return 2555;
315 return 23996;
316
317 default:
318 return 1.;
319 }
320
321 return 1.;
322 }
323
324 static double GetOpMultiplication(
325 const std::shared_ptr<Circuits::IOperation<>>& op) {
326 if (!op) return 1.;
327
328 if (op->GetType() != Circuits::OperationType::kGate) {
329 if (op->GetType() == Circuits::OperationType::kMeasurement) {
330 return 2.;
331 } else if (op->GetType() == Circuits::OperationType::kReset) {
332 return 2.;
333 } else if (op->GetType() == Circuits::OperationType::kConditionalGate) {
334 const auto conditionalGate =
335 std::static_pointer_cast<Circuits::ConditionalGate<>>(op);
336 return GetOpMultiplication(conditionalGate->GetOperation());
337 } else if (op->GetType() ==
339 return 2.; // actually it depends on the measurement result
340 }
341 return 1.;
342 }
343
344 const auto gate = std::static_pointer_cast<Circuits::IQuantumGate<>>(op);
345 switch (gate->GetGateType()) {
346 // all cliffords retun 1, as they do not increase the number of pauli
347 // strings to propagate
349 [[fallthrough]];
351 [[fallthrough]];
353 [[fallthrough]];
355 [[fallthrough]];
357 [[fallthrough]];
359 [[fallthrough]];
361 [[fallthrough]];
363 [[fallthrough]];
365 [[fallthrough]];
367 [[fallthrough]];
369 [[fallthrough]];
371 [[fallthrough]];
373 return 1.;
374
376 [[fallthrough]];
378 [[fallthrough]];
380 [[fallthrough]];
382 [[fallthrough]];
384 [[fallthrough]];
386 return 2.; // just duplication of the pauli string... I guess this
387 // would be proportional with the number of qubits, but
388 // let's just put a constant for now
389
391 return 8.; // implemented with three rotations
392
394 [[fallthrough]];
396 [[fallthrough]];
398 [[fallthrough]];
400 [[fallthrough]];
402 return 4; // implemented with 2 rotations, the rest are cliffords
403
405 [[fallthrough]];
407 return 8; // implemented with 3 rotations, the rest are cliffords
408
410 return 256; // implemented with 8 rotations
411
412 // three qubit gates, particularly costly!
414 return 4096; // 12 rotations!!!!!!
416 return 512; // 9 rotations!!!!!!
417
418 default:
419 return 1.;
420 }
421 return 1.;
422 }
423};
424
425} // namespace Simulators
426
427#endif // _QCSIM_PAULI_PROPAGATOR_H
int ApplyX(void *sim, int qubit)
int ApplyS(void *sim, int qubit)
int ApplyCX(void *sim, int controlQubit, int targetQubit)
int ApplySDG(void *sim, int qubit)
int ApplyH(void *sim, int qubit)
Circuit class for holding the sequence of operations.
Definition Circuit.h:46
The operation interface.
Definition Operations.h:358
void ApplyCH(int controlQubit, int targetQubit)
void ApplyCU(int controlQubit, int targetQubit, double theta, double phi, double lambda, double gamma=0.0)
std::unique_ptr< QcsimPauliPropagator > Clone() const
static double GetSamplingCost(const std::shared_ptr< Circuits::Circuit<> > &circuit, size_t nrQubitsSampled, size_t samples)
void ApplyCRZ(int controlQubit, int targetQubit, double angle)
void ApplyCRY(int controlQubit, int targetQubit, double angle)
static double GetCost(const std::shared_ptr< Circuits::Circuit<> > &circuit)
void ApplyP(int qubit, double lambda)
void ApplyCP(int controlQubit, int targetQubit, double lambda)
void ApplyU(int qubit, double theta, double phi, double lambda, double gamma=0.0)
void ApplyCSXDAG(int controlQubit, int targetQubit)
void ApplyCSDAG(int controlQubit, int targetQubit)
void ApplyCSX(int controlQubit, int targetQubit)
void ApplyCSwap(int controlQubit, int targetQubit1, int targetQubit2)
void ApplyCS(int controlQubit, int targetQubit)
void ApplyCCX(int controlQubit1, int controlQubit2, int targetQubit)
void ApplyCRX(int controlQubit, int targetQubit, double angle)
@ 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 ...