pennylane-qrack 0.10.2__py3-none-macosx_15_0_arm64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pennylane-qrack might be problematic. Click here for more details.

@@ -0,0 +1,858 @@
1
+ #include <regex>
2
+ #include <stdexcept>
3
+ #include <string>
4
+ #include <QuantumDevice.hpp>
5
+
6
+ #define CL_HPP_TARGET_OPENCL_VERSION 300
7
+ #include "qrack/qfactory.hpp"
8
+
9
+ #define QSIM_CONFIG(numQubits) Qrack::CreateArrangedLayersFull(nw, md, sd, sh, bdt, true, tn, true, oc, numQubits, Qrack::ZERO_BCI, nullptr, Qrack::CMPLX_DEFAULT_ARG, false, true, hp)
10
+
11
+ std::string trim(std::string s)
12
+ {
13
+ // Cut leading, trailing, and extra spaces
14
+ // (See https://stackoverflow.com/questions/1798112/removing-leading-and-trailing-spaces-from-a-string#answer-1798170)
15
+ return std::regex_replace(s, std::regex("^ +| +$|( ) +"), "$1");
16
+ }
17
+
18
+ struct QrackObservable {
19
+ std::vector<Qrack::Pauli> obs;
20
+ std::vector<bitLenInt> wires;
21
+ QrackObservable()
22
+ {
23
+ // Intentionally left blank
24
+ }
25
+ QrackObservable(std::vector<Qrack::Pauli> o, std::vector<bitLenInt> w)
26
+ : obs(o)
27
+ , wires(w)
28
+ {
29
+ // Intentionally left blank
30
+ }
31
+ };
32
+
33
+ struct QrackDevice final : public Catalyst::Runtime::QuantumDevice {
34
+ bool tapeRecording;
35
+ bool sh;
36
+ bool tn;
37
+ bool sd;
38
+ bool md;
39
+ bool bdt;
40
+ bool oc;
41
+ bool hp;
42
+ bool nw;
43
+ size_t shots;
44
+ Qrack::QInterfacePtr qsim;
45
+ std::map<QubitIdType, bitLenInt> qubit_map;
46
+ std::vector<QrackObservable> obs_cache;
47
+ std::vector<Qrack::QInterfaceEngine> simulatorType;
48
+
49
+ // static constants for RESULT values
50
+ static constexpr bool QRACK_RESULT_TRUE_CONST = true;
51
+ static constexpr bool QRACK_RESULT_FALSE_CONST = false;
52
+
53
+ inline void reverseWires()
54
+ {
55
+ const bitLenInt end = qsim->GetQubitCount() - 1U;
56
+ const bitLenInt mid = qsim->GetQubitCount() >> 1U;
57
+ for (bitLenInt i = 0U; i < mid; ++i) {
58
+ qsim->Swap(i, end - i);
59
+ }
60
+ }
61
+
62
+ inline auto getDeviceWires(const std::vector<QubitIdType> &wires) -> std::vector<bitLenInt>
63
+ {
64
+ std::vector<bitLenInt> res;
65
+ res.reserve(wires.size());
66
+ std::transform(wires.begin(), wires.end(), std::back_inserter(res), [this](auto w) {
67
+ const auto& it = qubit_map.find(w);
68
+ if (it == qubit_map.end()) {
69
+ throw std::invalid_argument("Qubit ID not in wire map: " + std::to_string(w));
70
+ }
71
+ return it->second;
72
+ });
73
+ return res;
74
+ }
75
+
76
+ inline auto wiresToMask(const std::vector<bitLenInt> &wires) -> bitCapInt
77
+ {
78
+ bitCapInt mask = Qrack::ZERO_BCI;
79
+ for (const bitLenInt& target : wires) {
80
+ mask = mask | Qrack::pow2(target);
81
+ }
82
+
83
+ return mask;
84
+ }
85
+
86
+ void applyNamedOperation(const std::string &name, const std::vector<bitLenInt> &wires,
87
+ const bool& inverse, const std::vector<double> &params)
88
+ {
89
+ if (name == "PauliX") {
90
+ // Self-adjoint, so ignore "inverse"
91
+ if (wires.size() > 1U) {
92
+ qsim->XMask(wiresToMask(wires));
93
+ } else {
94
+ qsim->X(wires[0U]);
95
+ }
96
+ } else if (name == "PauliY") {
97
+ // Self-adjoint, so ignore "inverse"
98
+ if (wires.size() > 1U) {
99
+ qsim->YMask(wiresToMask(wires));
100
+ } else {
101
+ qsim->Y(wires[0U]);
102
+ }
103
+ } else if (name == "PauliZ") {
104
+ // Self-adjoint, so ignore "inverse"
105
+ if (wires.size() > 1U) {
106
+ qsim->ZMask(wiresToMask(wires));
107
+ } else {
108
+ qsim->Z(wires[0U]);
109
+ }
110
+ } else if (name == "SX") {
111
+ for (const bitLenInt& target : wires) {
112
+ if (inverse) {
113
+ qsim->ISqrtX(target);
114
+ } else {
115
+ qsim->SqrtX(target);
116
+ }
117
+ }
118
+ } else if (name == "MultiRZ") {
119
+ for (const bitLenInt& target : wires) {
120
+ qsim->RZ(inverse ? -params[0U] : params[0U], target);
121
+ }
122
+ } else if (name == "Hadamard") {
123
+ for (const bitLenInt& target : wires) {
124
+ qsim->H(target);
125
+ }
126
+ } else if (name == "S") {
127
+ for (const bitLenInt& target : wires) {
128
+ if (inverse) {
129
+ qsim->IS(target);
130
+ } else {
131
+ qsim->S(target);
132
+ }
133
+ }
134
+ } else if (name == "T") {
135
+ for (const bitLenInt& target : wires) {
136
+ if (inverse) {
137
+ qsim->IT(target);
138
+ } else {
139
+ qsim->T(target);
140
+ }
141
+ }
142
+ } else if (name == "SWAP") {
143
+ if (wires.size() != 2U) {
144
+ throw std::invalid_argument("SWAP must have exactly two target qubits!");
145
+ }
146
+ qsim->Swap(wires[0U], wires[1U]);
147
+ } else if (name == "ISWAP") {
148
+ if (wires.size() != 2U) {
149
+ throw std::invalid_argument("ISWAP must have exactly two target qubits!");
150
+ }
151
+ if (inverse) {
152
+ qsim->ISwap(wires[0U], wires[1U]);
153
+ } else {
154
+ qsim->IISwap(wires[0U], wires[1U]);
155
+ }
156
+ } else if (name == "PSWAP") {
157
+ if (wires.size() != 2U) {
158
+ throw std::invalid_argument("PSWAP must have exactly two target qubits!");
159
+ }
160
+ const std::vector<bitLenInt> c { wires[0U] };
161
+ qsim->CU(c, wires[1U], ZERO_R1, ZERO_R1, inverse ? -params[0U] : params[0U]);
162
+ qsim->Swap(wires[0U], wires[1U]);
163
+ qsim->CU(c, wires[1U], ZERO_R1, ZERO_R1, inverse ? -params[0U] : params[0U]);
164
+ } else if (name == "PhaseShift") {
165
+ const Qrack::complex bottomRight = exp(Qrack::I_CMPLX * (Qrack::real1)(inverse ? -params[0U] : params[0U]));
166
+ for (const bitLenInt& target : wires) {
167
+ qsim->Phase(Qrack::ONE_CMPLX, bottomRight, target);
168
+ }
169
+ } else if (name == "RX") {
170
+ for (const bitLenInt& target : wires) {
171
+ qsim->RX(inverse ? -params[0U] : params[0U], target);
172
+ }
173
+ } else if (name == "RY") {
174
+ for (const bitLenInt& target : wires) {
175
+ qsim->RY(inverse ? -params[0U] : params[0U], target);
176
+ }
177
+ } else if (name == "RZ") {
178
+ for (const bitLenInt& target : wires) {
179
+ qsim->RZ(inverse ? -params[0U] : params[0U], target);
180
+ }
181
+ } else if (name == "Rot") {
182
+ const Qrack::real1 phi = inverse ? -params[2U] : params[0U];
183
+ const Qrack::real1 theta = inverse ? -params[1U] : params[1U];
184
+ const Qrack::real1 omega = inverse ? -params[0U] : params[2U];
185
+ const Qrack::real1 cos0 = (Qrack::real1)cos(theta / 2);
186
+ const Qrack::real1 sin0 = (Qrack::real1)sin(theta / 2);
187
+ const Qrack::complex expP = exp(Qrack::I_CMPLX * (phi + omega) / (2 * ONE_R1));
188
+ const Qrack::complex expM = exp(Qrack::I_CMPLX * (phi - omega) / (2 * ONE_R1));
189
+ const Qrack::complex mtrx[4U]{
190
+ cos0 / expP, -sin0 * expM,
191
+ sin0 / expM, cos0 * expP
192
+ };
193
+ for (const bitLenInt& target : wires) {
194
+ qsim->Mtrx(mtrx, target);
195
+ }
196
+ } else if (name == "U3") {
197
+ for (const bitLenInt& target : wires) {
198
+ if (inverse) {
199
+ qsim->U(target, -params[0U], -params[2U], -params[1U]);
200
+ } else {
201
+ qsim->U(target, params[0U], params[1U], params[2U]);
202
+ }
203
+ }
204
+ } else if (name != "Identity") {
205
+ throw std::domain_error("Unrecognized gate name: " + name);
206
+ }
207
+ }
208
+
209
+ void applyNamedOperation(const std::string &name, const std::vector<bitLenInt> &control_wires,
210
+ const std::vector<bool> &control_values,
211
+ const std::vector<bitLenInt> &wires, const bool& inverse,
212
+ const std::vector<double> &params)
213
+ {
214
+ bitCapInt controlPerm = Qrack::ZERO_BCI;
215
+ for (bitLenInt i = 0U; i < control_values.size(); ++i) {
216
+ if (control_values[i]) {
217
+ controlPerm = controlPerm | Qrack::pow2(i);
218
+ }
219
+ }
220
+
221
+ QRACK_CONST Qrack::complex NEG_1_CMPLX(-ONE_R1, ZERO_R1);
222
+ QRACK_CONST Qrack::complex NEG_I_CMPLX(ZERO_R1, ONE_R1);
223
+ QRACK_CONST Qrack::complex SQRT1_2_CMPLX(Qrack::SQRT1_2_R1, ZERO_R1);
224
+ QRACK_CONST Qrack::complex NEG_SQRT1_2_CMPLX(-Qrack::SQRT1_2_R1, ZERO_R1);
225
+ QRACK_CONST Qrack::complex SQRTI_2_CMPLX(ZERO_R1, Qrack::SQRT1_2_R1);
226
+ QRACK_CONST Qrack::complex NEG_SQRTI_2_CMPLX(ZERO_R1, -Qrack::SQRT1_2_R1);
227
+ const Qrack::complex QBRTI_2_CMPLX(ZERO_R1, sqrt(Qrack::SQRT1_2_R1));
228
+ const Qrack::complex NEG_QBRTI_2_CMPLX(ZERO_R1, sqrt(-Qrack::SQRT1_2_R1));
229
+ QRACK_CONST Qrack::complex ONE_PLUS_I_DIV_2 = Qrack::complex((Qrack::real1)(ONE_R1 / 2), (Qrack::real1)(ONE_R1 / 2));
230
+ QRACK_CONST Qrack::complex ONE_MINUS_I_DIV_2 = Qrack::complex((Qrack::real1)(ONE_R1 / 2), (Qrack::real1)(-ONE_R1 / 2));
231
+
232
+ QRACK_CONST Qrack::complex pauliX[4U] = { Qrack::ZERO_CMPLX, Qrack::ONE_CMPLX, Qrack::ONE_CMPLX, Qrack::ZERO_CMPLX };
233
+ QRACK_CONST Qrack::complex pauliY[4U] = { Qrack::ZERO_CMPLX, NEG_I_CMPLX, Qrack::I_CMPLX, Qrack::ZERO_CMPLX };
234
+ QRACK_CONST Qrack::complex pauliZ[4U] = { Qrack::ONE_CMPLX, Qrack::ZERO_CMPLX, Qrack::ZERO_CMPLX, NEG_1_CMPLX };
235
+ QRACK_CONST Qrack::complex sqrtX[4U]{ ONE_PLUS_I_DIV_2, ONE_MINUS_I_DIV_2, ONE_MINUS_I_DIV_2, ONE_PLUS_I_DIV_2 };
236
+ QRACK_CONST Qrack::complex iSqrtX[4U]{ ONE_MINUS_I_DIV_2, ONE_PLUS_I_DIV_2, ONE_PLUS_I_DIV_2, ONE_MINUS_I_DIV_2 };
237
+ QRACK_CONST Qrack::complex hadamard[4U]{ SQRT1_2_CMPLX, SQRT1_2_CMPLX, SQRT1_2_CMPLX, NEG_SQRT1_2_CMPLX };
238
+
239
+ if ((name == "PauliX") || (name == "CNOT") || (name == "Toffoli") || (name == "MultiControlledX")) {
240
+ // Self-adjoint, so ignore "inverse"
241
+ for (const bitLenInt& target : wires) {
242
+ qsim->UCMtrx(control_wires, pauliX, target, controlPerm);
243
+ }
244
+ } else if ((name == "PauliY") || (name == "CY")) {
245
+ // Self-adjoint, so ignore "inverse"
246
+ for (const bitLenInt& target : wires) {
247
+ qsim->UCMtrx(control_wires, pauliY, target, controlPerm);
248
+ }
249
+ } else if ((name == "PauliZ") || (name == "CZ")) {
250
+ // Self-adjoint, so ignore "inverse"
251
+ for (const bitLenInt& target : wires) {
252
+ qsim->UCMtrx(control_wires, pauliZ, target, controlPerm);
253
+ }
254
+ } else if (name == "SX") {
255
+ for (const bitLenInt& target : wires) {
256
+ qsim->UCMtrx(control_wires, inverse ? iSqrtX : sqrtX, target, controlPerm);
257
+ }
258
+ } else if (name == "MultiRZ") {
259
+ const Qrack::complex bottomRight = exp(Qrack::I_CMPLX * (Qrack::real1)((inverse ? -params[0U] : params[0U]) / 2));
260
+ for (const bitLenInt& target : wires) {
261
+ qsim->UCPhase(control_wires, conj(bottomRight), bottomRight, target, controlPerm);
262
+ }
263
+ } else if (name == "Hadamard") {
264
+ for (const bitLenInt& target : wires) {
265
+ qsim->UCMtrx(control_wires, hadamard, target, controlPerm);
266
+ }
267
+ } else if (name == "S") {
268
+ for (const bitLenInt& target : wires) {
269
+ qsim->UCPhase(control_wires, Qrack::ONE_CMPLX, inverse ? NEG_SQRTI_2_CMPLX : SQRTI_2_CMPLX, target, controlPerm);
270
+ }
271
+ } else if (name == "T") {
272
+ for (const bitLenInt& target : wires) {
273
+ qsim->UCPhase(control_wires, Qrack::ONE_CMPLX, inverse ? NEG_QBRTI_2_CMPLX : QBRTI_2_CMPLX, target, controlPerm);
274
+ }
275
+ } else if ((name == "SWAP") || (name == "CSWAP")) {
276
+ if (wires.size() != 2U) {
277
+ throw std::invalid_argument("SWAP and CSWAP must have exactly two target qubits!");
278
+ }
279
+ for (bitLenInt i = 0U; i < control_wires.size(); ++i) {
280
+ if (!control_values[i]) {
281
+ qsim->X(control_wires[i]);
282
+ }
283
+ }
284
+ qsim->CSwap(control_wires, wires[0U], wires[1U]);
285
+ for (bitLenInt i = 0U; i < control_wires.size(); ++i) {
286
+ if (!control_values[i]) {
287
+ qsim->X(control_wires[i]);
288
+ }
289
+ }
290
+ } else if (name == "ISWAP") {
291
+ if (wires.size() != 2U) {
292
+ throw std::invalid_argument("ISWAP must have exactly two target qubits!");
293
+ }
294
+ for (bitLenInt i = 0U; i < control_wires.size(); ++i) {
295
+ if (!control_values[i]) {
296
+ qsim->X(control_wires[i]);
297
+ }
298
+ }
299
+ std::vector<bitLenInt> mcp_wires(control_wires);
300
+ mcp_wires.push_back(wires[0U]);
301
+ qsim->MCPhase(mcp_wires, inverse ? -Qrack::I_CMPLX : Qrack::I_CMPLX, Qrack::ONE_CMPLX, wires[1U]);
302
+ qsim->CSwap(control_wires, wires[0U], wires[1U]);
303
+ qsim->MCPhase(mcp_wires, inverse ? -Qrack::I_CMPLX : Qrack::I_CMPLX, Qrack::ONE_CMPLX, wires[1U]);
304
+ for (bitLenInt i = 0U; i < control_wires.size(); ++i) {
305
+ if (!control_values[i]) {
306
+ qsim->X(control_wires[i]);
307
+ }
308
+ }
309
+ } else if ((name == "PhaseShift") || (name == "ControlledPhaseShift") || (name == "CPhase")) {
310
+ const Qrack::complex bottomRight = exp(Qrack::I_CMPLX * (Qrack::real1)(inverse ? -params[0U] : params[0U]));
311
+ for (const bitLenInt& target : wires) {
312
+ qsim->UCPhase(control_wires, Qrack::ONE_CMPLX, bottomRight, target, controlPerm);
313
+ }
314
+ } else if (name == "PSWAP") {
315
+ std::vector<bitLenInt> c(control_wires);
316
+ c.push_back(wires[0U]);
317
+ qsim->CU(c, wires[1U], ZERO_R1, ZERO_R1, inverse ? -params[0U] : params[0U]);
318
+ qsim->CSwap(control_wires, wires[0U], wires[1U]);
319
+ qsim->CU(c, wires[1U], ZERO_R1, ZERO_R1, inverse ? -params[0U] : params[0U]);
320
+ } else if ((name == "RX") || (name == "CRX")) {
321
+ const Qrack::real1 cosine = (Qrack::real1)cos((inverse ? -params[0U] : params[0U]) / 2);
322
+ const Qrack::real1 sine = (Qrack::real1)sin((inverse ? -params[0U] : params[0U]) / 2);
323
+ const Qrack::complex mtrx[4U] = {
324
+ Qrack::complex(cosine, ZERO_R1), Qrack::complex(ZERO_R1, -sine),
325
+ Qrack::complex(ZERO_R1, -sine), Qrack::complex(cosine, ZERO_R1)
326
+ };
327
+ for (const bitLenInt& target : wires) {
328
+ qsim->UCMtrx(control_wires, mtrx, target, controlPerm);
329
+ }
330
+ } else if ((name == "RY") || (name == "CRY")) {
331
+ const Qrack::real1 cosine = (Qrack::real1)cos((inverse ? -params[0U] : params[0U]) / 2);
332
+ const Qrack::real1 sine = (Qrack::real1)sin((inverse ? -params[0U] : params[0U]) / 2);
333
+ const Qrack::complex mtrx[4U] = {
334
+ Qrack::complex(cosine, ZERO_R1), Qrack::complex(-sine, ZERO_R1),
335
+ Qrack::complex(sine, ZERO_R1), Qrack::complex(cosine, ZERO_R1)
336
+ };
337
+ for (const bitLenInt& target : wires) {
338
+ qsim->UCMtrx(control_wires, mtrx, target, controlPerm);
339
+ }
340
+ } else if ((name == "RZ") || (name == "CRZ")) {
341
+ const Qrack::complex bottomRight = exp(Qrack::I_CMPLX * (Qrack::real1)((inverse ? -params[0U] : params[0U]) / 2));
342
+ for (const bitLenInt& target : wires) {
343
+ qsim->UCPhase(control_wires, conj(bottomRight), bottomRight, target, controlPerm);
344
+ }
345
+ } else if ((name == "Rot") || (name == "CRot")) {
346
+ const Qrack::real1 phi = inverse ? -params[2U] : params[0U];
347
+ const Qrack::real1 theta = inverse ? -params[1U] : params[1U];
348
+ const Qrack::real1 omega = inverse ? -params[0U] : params[2U];
349
+ const Qrack::real1 cos0 = (Qrack::real1)cos(theta / 2);
350
+ const Qrack::real1 sin0 = (Qrack::real1)sin(theta / 2);
351
+ const Qrack::complex expP = exp(Qrack::I_CMPLX * (phi + omega) / (2 * ONE_R1));
352
+ const Qrack::complex expM = exp(Qrack::I_CMPLX * (phi - omega) / (2 * ONE_R1));
353
+ const Qrack::complex mtrx[4U]{
354
+ cos0 / expP, -sin0 * expM,
355
+ sin0 / expM, cos0 * expP
356
+ };
357
+ for (const bitLenInt& target : wires) {
358
+ qsim->UCMtrx(control_wires, mtrx, target, controlPerm);
359
+ }
360
+ } else if (name == "U3") {
361
+ const Qrack::real1 th = params[0U];
362
+ const Qrack::real1 ph = params[1U];
363
+ const Qrack::real1 lm = params[2U];
364
+ const Qrack::real1 cos0 = (Qrack::real1)cos(th / 2);
365
+ const Qrack::real1 sin0 = (Qrack::real1)sin(th / 2);
366
+ const Qrack::complex mtrx[4U]{
367
+ Qrack::complex(cos0, ZERO_R1), sin0 * Qrack::complex((Qrack::real1)(-cos(lm)),
368
+ (Qrack::real1)(-sin(lm))),
369
+ sin0 * Qrack::complex((Qrack::real1)cos(ph), (Qrack::real1)sin(ph)),
370
+ cos0 * Qrack::complex((Qrack::real1)cos(ph + lm), (Qrack::real1)sin(ph + lm))
371
+ };
372
+ Qrack::complex iMtrx[4U];
373
+ Qrack::inv2x2(mtrx, iMtrx);
374
+ for (const bitLenInt& target : wires) {
375
+ qsim->UCMtrx(control_wires, inverse ? iMtrx : mtrx, target, controlPerm);
376
+ }
377
+ } else if (name != "Identity") {
378
+ throw std::domain_error("Unrecognized gate name: " + name);
379
+ }
380
+ }
381
+
382
+ QrackDevice([[maybe_unused]] std::string kwargs = "{}")
383
+ : tapeRecording(false)
384
+ , sh(true)
385
+ , tn(true)
386
+ , sd(true)
387
+ , md(true)
388
+ , bdt(false)
389
+ , oc(true)
390
+ , hp(false)
391
+ , nw(false)
392
+ , shots(1U)
393
+ , qsim(nullptr)
394
+ {
395
+ // Cut leading '{' and trailing '}'
396
+ kwargs.erase(0U, 1U);
397
+ kwargs.erase(kwargs.size() - 1U);
398
+ // Cut leading, trailing, and extra spaces
399
+ kwargs = trim(kwargs);
400
+
401
+ std::map<std::string, int> keyMap;
402
+ keyMap["'is_hybrid_stabilizer'"] = 1;
403
+ keyMap["'is_tensor_network'"] = 2;
404
+ keyMap["'is_schmidt_decomposed'"] = 3;
405
+ keyMap["'is_schmidt_decomposition_parallel'"] = 4;
406
+ keyMap["'is_qbdd'"] = 5;
407
+ keyMap["'is_gpu'"] = 6;
408
+ keyMap["'is_host_pointer'"] =7;
409
+ keyMap["'noise'"] = 8;
410
+
411
+ size_t pos;
412
+ Qrack::real1_f noiseParam = 0;
413
+ while ((pos = kwargs.find(":")) != std::string::npos) {
414
+ std::string key = trim(kwargs.substr(0, pos));
415
+ kwargs.erase(0, pos + 1U);
416
+ pos = kwargs.find(",");
417
+ std::string value = trim(kwargs.substr(0, pos));
418
+ kwargs.erase(0, pos + 1U);
419
+ const bool val = (value == "True");
420
+ switch (keyMap[key]) {
421
+ case 1:
422
+ sh = val;
423
+ break;
424
+ case 2:
425
+ tn = val;
426
+ break;
427
+ case 3:
428
+ sd = val;
429
+ break;
430
+ case 4:
431
+ md = val;
432
+ break;
433
+ case 5:
434
+ bdt = val;
435
+ break;
436
+ case 6:
437
+ oc = val;
438
+ break;
439
+ case 7:
440
+ hp = val;
441
+ break;
442
+ case 8:
443
+ noiseParam = std::stof(value);
444
+ nw = noiseParam > ZERO_R1;
445
+ break;
446
+ default:
447
+ break;
448
+ }
449
+ }
450
+
451
+ qsim = QSIM_CONFIG(0U);
452
+ if (noiseParam > ZERO_R1) {
453
+ qsim->SetNoiseParameter(noiseParam);
454
+ }
455
+ }
456
+
457
+ QrackDevice &operator=(const QuantumDevice &) = delete;
458
+ QrackDevice(const QrackDevice &) = delete;
459
+ QrackDevice(QrackDevice &&) = delete;
460
+ QrackDevice &operator=(QuantumDevice &&) = delete;
461
+
462
+ auto AllocateQubit() -> QubitIdType override {
463
+ const QubitIdType label = qsim->GetQubitCount();
464
+ qsim->Allocate(1U);
465
+ qubit_map[label] = (bitLenInt)label;
466
+ return label;
467
+ }
468
+ auto AllocateQubits(size_t num_qubits) -> std::vector<QubitIdType> override {
469
+ std::vector<QubitIdType> ids(num_qubits);
470
+ for (size_t i = 0U; i < num_qubits; ++i) {
471
+ ids[i] = AllocateQubit();
472
+ }
473
+ return ids;
474
+ }
475
+ [[nodiscard]] auto Zero() const -> Result override { return const_cast<Result>(&QRACK_RESULT_FALSE_CONST); }
476
+ [[nodiscard]] auto One() const -> Result override { return const_cast<Result>(&QRACK_RESULT_TRUE_CONST); }
477
+ auto Observable(ObsId id, const std::vector<std::complex<double>> &matrix,
478
+ const std::vector<QubitIdType> &wires) -> ObsIdType override
479
+ {
480
+ RT_FAIL_IF(wires.size() != 1U, "Cannot have observables besides tensor products of Pauli observables");
481
+ auto &&dev_wires = getDeviceWires(wires);
482
+
483
+ Qrack::Pauli basis = Qrack::PauliI;
484
+ switch (id) {
485
+ case ObsId::PauliX:
486
+ basis = Qrack::PauliX;
487
+ break;
488
+ case ObsId::PauliY:
489
+ basis = Qrack::PauliY;
490
+ break;
491
+ case ObsId::PauliZ:
492
+ basis = Qrack::PauliZ;
493
+ break;
494
+ default:
495
+ break;
496
+ }
497
+ std::vector<Qrack::Pauli> bases;
498
+ bases.reserve(dev_wires.size());
499
+ for (size_t i = 0U; i < dev_wires.size(); ++i) {
500
+ bases.emplace_back(basis);
501
+ }
502
+ obs_cache.push_back(QrackObservable(bases, dev_wires));
503
+
504
+ return obs_cache.size() - 1U;
505
+ }
506
+ auto TensorObservable(const std::vector<ObsIdType> &obs) -> ObsIdType override
507
+ {
508
+ if (!obs.size()) {
509
+ return -1;
510
+ }
511
+ QrackObservable o;
512
+ for (const ObsIdType& id : obs) {
513
+ if (id >= obs_cache.size()) {
514
+ throw std::invalid_argument("Observable ID not in device cache: " + std::to_string(id));
515
+ }
516
+ const QrackObservable& i = obs_cache[id];
517
+ o.obs.insert(o.obs.end(), i.obs.begin(), i.obs.end());
518
+ o.wires.insert(o.wires.end(), i.wires.begin(), i.wires.end());
519
+ }
520
+ obs_cache.push_back(o);
521
+
522
+ return obs_cache.size() - 1U;
523
+ }
524
+ auto HamiltonianObservable(const std::vector<double> &coeffs, const std::vector<ObsIdType> &obs)
525
+ -> ObsIdType override
526
+ {
527
+ return -1;
528
+ }
529
+ auto Measure(QubitIdType id, std::optional<int> postselect) -> Result override {
530
+ bool *ret = (bool *)malloc(sizeof(bool));
531
+ if (postselect.has_value()) {
532
+ *ret = qsim->ForceM(id, postselect.value());
533
+ } else {
534
+ *ret = qsim->M(id);
535
+ }
536
+ return ret;
537
+ }
538
+
539
+ void ReleaseQubit(QubitIdType label) override
540
+ {
541
+ // Measure to prevent denormalization
542
+ const bitLenInt id = qubit_map[label];
543
+ qsim->M(id);
544
+ // Deallocate
545
+ qsim->Dispose(id, 1U);
546
+ }
547
+ void ReleaseAllQubits() override
548
+ {
549
+ // State vector is left empty
550
+ qsim = QSIM_CONFIG(0U);
551
+ }
552
+ [[nodiscard]] auto GetNumQubits() const -> size_t override
553
+ {
554
+ return qsim->GetQubitCount();
555
+ }
556
+ void SetDeviceShots(size_t s) override { shots = s; }
557
+ [[nodiscard]] auto GetDeviceShots() const -> size_t override { return shots; }
558
+ void StartTapeRecording() override { tapeRecording = true; }
559
+ void StopTapeRecording() override { tapeRecording = false; }
560
+ void PrintState() override
561
+ {
562
+ const size_t numQubits = qsim->GetQubitCount();
563
+ const size_t maxQPower = (size_t)((uint64_t)qsim->GetMaxQPower());
564
+ const size_t maxLcv = maxQPower - 1U;
565
+ size_t idx = 0U;
566
+ std::cout << "*** State-Vector of Size " << maxQPower << " ***" << std::endl;
567
+ std::cout << "[";
568
+ std::unique_ptr<Qrack::complex> sv(new Qrack::complex[maxQPower]);
569
+ qsim->GetQuantumState(sv.get());
570
+ for (; idx < maxLcv; ++idx) {
571
+ std::cout << sv.get()[idx] << ", ";
572
+ }
573
+ std::cout << sv.get()[idx] << "]" << std::endl;
574
+ }
575
+ void NamedOperation(const std::string &name, const std::vector<double> &params,
576
+ const std::vector<QubitIdType> &wires, bool inverse,
577
+ const std::vector<QubitIdType> &controlled_wires,
578
+ const std::vector<bool> &controlled_values) override
579
+ {
580
+ // Check the validity of number of qubits and parameters
581
+ RT_FAIL_IF(controlled_wires.size() != controlled_values.size(), "Controlled wires/values size mismatch");
582
+
583
+ // Convert wires to device wires
584
+ auto &&dev_wires = getDeviceWires(wires);
585
+ auto &&dev_controlled_wires = getDeviceWires(controlled_wires);
586
+ std::vector<bool> dev_controlled_values(controlled_values);
587
+ if ((name == "MultiControlledX")
588
+ || (name == "CNOT")
589
+ || (name == "CY")
590
+ || (name == "CZ")
591
+ || (name == "ControlledPhaseShift")
592
+ || (name == "CPhase")
593
+ || (name == "CRX")
594
+ || (name == "CRY")
595
+ || (name == "CRZ")
596
+ || (name == "CRot")
597
+ || (name == "Toffoli")) {
598
+ const size_t end = dev_wires.size() - 1U;
599
+ dev_controlled_wires.insert(dev_controlled_wires.end(), dev_wires.begin(), dev_wires.begin() + end);
600
+ dev_wires.erase(dev_wires.begin(), dev_wires.begin() + end);
601
+ const std::vector<bool> t(end, true);
602
+ dev_controlled_values.insert(dev_controlled_values.end(), t.begin(), t.end());
603
+ } else if (name == "CSWAP") {
604
+ const size_t end = dev_wires.size() - 2U;
605
+ dev_controlled_wires.insert(dev_controlled_wires.end(), dev_wires.begin(), dev_wires.begin() + end);
606
+ dev_wires.erase(dev_wires.begin(), dev_wires.begin() + end);
607
+ const std::vector<bool> t(end, true);
608
+ dev_controlled_values.insert(dev_controlled_values.end(), t.begin(), t.end());
609
+ }
610
+
611
+ // Update the state-vector
612
+ if (dev_controlled_wires.empty()) {
613
+ applyNamedOperation(name, dev_wires, inverse, params);
614
+ } else {
615
+ applyNamedOperation(name, dev_controlled_wires, dev_controlled_values, dev_wires, inverse, params);
616
+ }
617
+ }
618
+ void MatrixOperation(const std::vector<std::complex<double>> &matrix,
619
+ const std::vector<QubitIdType> &wires, bool inverse,
620
+ const std::vector<QubitIdType> &controlled_wires,
621
+ const std::vector<bool> &controlled_values) override
622
+ {
623
+ RT_FAIL_IF(controlled_wires.size() != controlled_values.size(), "Controlled wires/values size mismatch");
624
+ RT_FAIL_IF(wires.size() != 1U, "Matrix operation can only have one target qubit!");
625
+
626
+ // Convert wires to device wires
627
+ // with checking validity of wires
628
+ auto &&dev_wires = getDeviceWires(wires);
629
+ auto &&dev_controlled_wires = getDeviceWires(controlled_wires);
630
+ const Qrack::complex mtrx[4U] = {
631
+ (Qrack::complex)matrix[0U], (Qrack::complex)matrix[1U],
632
+ (Qrack::complex)matrix[2U], (Qrack::complex)matrix[3U]
633
+ };
634
+ Qrack::complex inv[4U];
635
+ Qrack::inv2x2(mtrx, inv);
636
+
637
+ // Update the state-vector
638
+ if (dev_controlled_wires.empty()) {
639
+ qsim->Mtrx(inverse ? inv : mtrx, dev_wires[0U]);
640
+ } else {
641
+ bitCapInt controlPerm = Qrack::ZERO_BCI;
642
+ for (bitLenInt i = 0U; i < controlled_values.size(); ++i) {
643
+ if (controlled_values[i]) {
644
+ controlPerm = controlPerm | Qrack::pow2(i);
645
+ }
646
+ }
647
+ qsim->UCMtrx(dev_controlled_wires, inverse ? inv : mtrx, dev_wires[0U], controlPerm);
648
+ }
649
+ }
650
+ auto Expval(ObsIdType id) -> double override
651
+ {
652
+ if (id >= obs_cache.size()) {
653
+ throw std::invalid_argument("Observable ID not in device cache: " + std::to_string(id));
654
+ }
655
+ const QrackObservable& obs = obs_cache[id];
656
+ return qsim->ExpectationPauliAll(obs.wires, obs.obs);
657
+ }
658
+ auto Var(ObsIdType id) -> double override
659
+ {
660
+ if (id >= obs_cache.size()) {
661
+ throw std::invalid_argument("Observable ID not in device cache: " + std::to_string(id));
662
+ }
663
+ const QrackObservable& obs = obs_cache[id];
664
+ return qsim->VariancePauliAll(obs.wires, obs.obs);
665
+ }
666
+ void State(DataView<std::complex<double>, 1>& sv) override
667
+ {
668
+ RT_FAIL_IF(sv.size() != (size_t)((uint64_t)qsim->GetMaxQPower()), "Invalid size for the pre-allocated state vector");
669
+ reverseWires();
670
+ #if FPPOW == 6
671
+ qsim->GetQuantumState(&(*(sv.begin())));
672
+ #else
673
+ std::unique_ptr<Qrack::complex> _sv(new Qrack::complex[sv.size()]);
674
+ qsim->GetQuantumState(_sv.get());
675
+ std::copy(_sv.get(), _sv.get() + sv.size(), sv.begin());
676
+ #endif
677
+ reverseWires();
678
+ }
679
+ void Probs(DataView<double, 1>& p) override
680
+ {
681
+ RT_FAIL_IF(p.size() != (size_t)((uint64_t)qsim->GetMaxQPower()), "Invalid size for the pre-allocated probabilities vector");
682
+ reverseWires();
683
+ #if FPPOW == 6
684
+ qsim->GetProbs(&(*(p.begin())));
685
+ #else
686
+ std::unique_ptr<Qrack::real1> _p(new Qrack::real1[p.size()]);
687
+ qsim->GetProbs(_p.get());
688
+ std::copy(_p.get(), _p.get() + p.size(), p.begin());
689
+ #endif
690
+ reverseWires();
691
+ }
692
+ void PartialProbs(DataView<double, 1> &p, const std::vector<QubitIdType> &wires) override
693
+ {
694
+ RT_FAIL_IF((size_t)((uint64_t)Qrack::pow2(wires.size())) != p.size(), "Invalid size for the pre-allocated probabilities vector");
695
+ auto &&dev_wires = getDeviceWires(wires);
696
+ std::reverse(dev_wires.begin(), dev_wires.end());
697
+ #if FPPOW == 6
698
+ qsim->ProbBitsAll(dev_wires, &(*(p.begin())));
699
+ #else
700
+ std::unique_ptr<Qrack::real1> _p(new Qrack::real1[p.size()]);
701
+ qsim->ProbBitsAll(dev_wires, _p.get());
702
+ std::copy(_p.get(), _p.get() + p.size(), p.begin());
703
+ #endif
704
+ }
705
+ void _SampleBody(const size_t numQubits, const std::map<bitCapInt, int>& q_samples, DataView<double, 2> &samples)
706
+ {
707
+ auto samplesIter = samples.begin();
708
+ auto q_samplesIter = q_samples.begin();
709
+ for (auto q_samplesIter = q_samples.begin(); q_samplesIter != q_samples.end(); ++q_samplesIter) {
710
+ const bitCapInt sample = q_samplesIter->first;
711
+ int shots = q_samplesIter->second;
712
+ for (; shots > 0; --shots) {
713
+ for (size_t wire = 0U; wire < numQubits; ++wire) {
714
+ *(samplesIter++) = bi_compare_0((sample >> wire) & 1U) ? 1.0 : 0.0;
715
+ }
716
+ }
717
+ }
718
+ }
719
+ void Sample(DataView<double, 2> &samples, size_t shots) override
720
+ {
721
+ RT_FAIL_IF(samples.size() != shots * qsim->GetQubitCount(), "Invalid size for the pre-allocated samples");
722
+
723
+ if (shots == 1U) {
724
+ const bitCapInt rev_sample = qsim->MAll();
725
+ const bitLenInt numQubits = qsim->GetQubitCount();
726
+ bitCapInt sample = Qrack::ZERO_BCI;
727
+ for (bitLenInt i = 0U; i < numQubits; ++i) {
728
+ if (bi_compare_0(rev_sample & Qrack::pow2(i)) != 0) {
729
+ sample = sample | Qrack::pow2(numQubits - (i + 1U));
730
+ }
731
+ }
732
+ const std::map<bitCapInt, int> q_samples{{sample, 1}};
733
+ _SampleBody(numQubits, q_samples, samples);
734
+ return;
735
+ }
736
+
737
+ std::vector<bitCapInt> qPowers(qsim->GetQubitCount());
738
+ for (bitLenInt i = 0U; i < qPowers.size(); ++i) {
739
+ qPowers[i] = Qrack::pow2(i);
740
+ }
741
+ const std::map<bitCapInt, int> q_samples = qsim->MultiShotMeasureMask(qPowers, shots);
742
+ _SampleBody(qPowers.size(), q_samples, samples);
743
+ }
744
+ void PartialSample(DataView<double, 2> &samples, const std::vector<QubitIdType> &wires, size_t shots) override
745
+ {
746
+ RT_FAIL_IF(samples.size() != shots * wires.size(), "Invalid size for the pre-allocated samples");
747
+
748
+ auto &&dev_wires = getDeviceWires(wires);
749
+
750
+ if (shots == 1U) {
751
+ const bitCapInt rev_sample = qsim->MAll();
752
+ const bitLenInt numQubits = dev_wires.size();
753
+ bitCapInt sample = Qrack::ZERO_BCI;
754
+ for (bitLenInt i = 0U; i < numQubits; ++i) {
755
+ if (bi_compare_0(rev_sample & Qrack::pow2(dev_wires[i])) != 0) {
756
+ sample = sample | Qrack::pow2(numQubits - (i + 1U));
757
+ }
758
+ }
759
+ const std::map<bitCapInt, int> q_samples{{sample, 1}};
760
+ _SampleBody(numQubits, q_samples, samples);
761
+ return;
762
+ }
763
+
764
+ std::vector<bitCapInt> qPowers(dev_wires.size());
765
+ for (size_t i = 0U; i < qPowers.size(); ++i) {
766
+ qPowers[i] = Qrack::pow2(dev_wires[i]);
767
+ }
768
+ const std::map<bitCapInt, int> q_samples = qsim->MultiShotMeasureMask(qPowers, shots);
769
+ _SampleBody(qPowers.size(), q_samples, samples);
770
+ }
771
+ void _CountsBody(const size_t numQubits, const std::map<bitCapInt, int>& q_samples, DataView<int64_t, 1> &counts)
772
+ {
773
+ for (auto q_samplesIter = q_samples.begin(); q_samplesIter != q_samples.end(); ++q_samplesIter) {
774
+ const bitCapInt sample = q_samplesIter->first;
775
+ int shots = q_samplesIter->second;
776
+ std::bitset<1U << QBCAPPOW> basisState;
777
+ for (size_t wire = 0; wire < numQubits; wire++) {
778
+ basisState[wire] = bi_compare_0((sample >> wire) & 1U);
779
+ }
780
+ for (; shots > 0; --shots) {
781
+ ++counts(static_cast<size_t>(basisState.to_ulong()));
782
+ }
783
+ }
784
+ }
785
+ void Counts(DataView<double, 1> &eigvals, DataView<int64_t, 1> &counts,
786
+ size_t shots) override
787
+ {
788
+ const size_t numQubits = qsim->GetQubitCount();
789
+ const size_t numElements = 1U << numQubits;
790
+
791
+ RT_FAIL_IF(eigvals.size() != numElements || counts.size() != numElements,
792
+ "Invalid size for the pre-allocated counts");
793
+
794
+ std::map<bitCapInt, int> q_samples;
795
+ if (shots == 1U) {
796
+ const bitCapInt rev_sample = qsim->MAll();
797
+ const bitLenInt numQubits = qsim->GetQubitCount();
798
+ bitCapInt sample = Qrack::ZERO_BCI;
799
+ for (bitLenInt i = 0U; i < numQubits; ++i) {
800
+ if (bi_compare_0(rev_sample & Qrack::pow2(i)) != 0) {
801
+ sample = sample | Qrack::pow2(numQubits - (i + 1U));
802
+ }
803
+ }
804
+ q_samples[sample] = 1;
805
+ } else {
806
+ std::vector<bitCapInt> qPowers(numQubits);
807
+ for (bitLenInt i = 0U; i < qPowers.size(); ++i) {
808
+ qPowers[i] = Qrack::pow2(i);
809
+ }
810
+ q_samples = qsim->MultiShotMeasureMask(qPowers, shots);
811
+ }
812
+
813
+ std::iota(eigvals.begin(), eigvals.end(), 0);
814
+ std::fill(counts.begin(), counts.end(), 0);
815
+
816
+ _CountsBody(numQubits, q_samples, counts);
817
+ }
818
+
819
+ void PartialCounts(DataView<double, 1> &eigvals, DataView<int64_t, 1> &counts,
820
+ const std::vector<QubitIdType> &wires, size_t shots) override
821
+ {
822
+ const size_t numQubits = wires.size();
823
+ const size_t numElements = 1U << numQubits;
824
+
825
+ RT_FAIL_IF(eigvals.size() != numElements || counts.size() != numElements,
826
+ "Invalid size for the pre-allocated counts");
827
+
828
+ auto &&dev_wires = getDeviceWires(wires);
829
+
830
+ std::map<bitCapInt, int> q_samples;
831
+ if (shots == 1U) {
832
+ const bitCapInt rev_sample = qsim->MAll();
833
+ const bitLenInt numQubits = dev_wires.size();
834
+ bitCapInt sample = Qrack::ZERO_BCI;
835
+ for (bitLenInt i = 0U; i < numQubits; ++i) {
836
+ if (bi_compare_0(rev_sample & Qrack::pow2(dev_wires[i])) != 0) {
837
+ sample = sample | Qrack::pow2(numQubits - (i + 1U));
838
+ }
839
+ }
840
+ q_samples[sample] = 1;
841
+ } else {
842
+ std::vector<bitCapInt> qPowers(dev_wires.size());
843
+ for (size_t i = 0U; i < qPowers.size(); ++i) {
844
+ qPowers[i] = Qrack::pow2(dev_wires[qPowers.size() - (i + 1U)]);
845
+ }
846
+ q_samples = qsim->MultiShotMeasureMask(qPowers, shots);
847
+ }
848
+
849
+ std::iota(eigvals.begin(), eigvals.end(), 0);
850
+ std::fill(counts.begin(), counts.end(), 0);
851
+
852
+ _CountsBody(numQubits, q_samples, counts);
853
+ }
854
+
855
+ void Gradient(std::vector<DataView<double, 1>> &, const std::vector<size_t> &) override {}
856
+ };
857
+
858
+ GENERATE_DEVICE_FACTORY(QrackDevice, QrackDevice);