pyqrack-cuda 1.44.0__tar.gz → 1.44.2__tar.gz
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.
- {pyqrack_cuda-1.44.0/pyqrack_cuda.egg-info → pyqrack_cuda-1.44.2}/PKG-INFO +1 -1
- pyqrack_cuda-1.44.2/pyqrack/qrack_ace_backend.py +602 -0
- {pyqrack_cuda-1.44.0 → pyqrack_cuda-1.44.2}/pyqrack/qrack_simulator.py +1 -1
- {pyqrack_cuda-1.44.0 → pyqrack_cuda-1.44.2/pyqrack_cuda.egg-info}/PKG-INFO +1 -1
- {pyqrack_cuda-1.44.0 → pyqrack_cuda-1.44.2}/setup.py +1 -1
- pyqrack_cuda-1.44.0/pyqrack/qrack_ace_backend.py +0 -261
- {pyqrack_cuda-1.44.0 → pyqrack_cuda-1.44.2}/LICENSE +0 -0
- {pyqrack_cuda-1.44.0 → pyqrack_cuda-1.44.2}/MANIFEST.in +0 -0
- {pyqrack_cuda-1.44.0 → pyqrack_cuda-1.44.2}/Makefile +0 -0
- {pyqrack_cuda-1.44.0 → pyqrack_cuda-1.44.2}/README.md +0 -0
- {pyqrack_cuda-1.44.0 → pyqrack_cuda-1.44.2}/pyproject.toml +0 -0
- {pyqrack_cuda-1.44.0 → pyqrack_cuda-1.44.2}/pyqrack/__init__.py +0 -0
- {pyqrack_cuda-1.44.0 → pyqrack_cuda-1.44.2}/pyqrack/neuron_activation_fn.py +0 -0
- {pyqrack_cuda-1.44.0 → pyqrack_cuda-1.44.2}/pyqrack/pauli.py +0 -0
- {pyqrack_cuda-1.44.0 → pyqrack_cuda-1.44.2}/pyqrack/qrack_circuit.py +0 -0
- {pyqrack_cuda-1.44.0 → pyqrack_cuda-1.44.2}/pyqrack/qrack_neuron.py +0 -0
- {pyqrack_cuda-1.44.0 → pyqrack_cuda-1.44.2}/pyqrack/qrack_neuron_torch_layer.py +0 -0
- {pyqrack_cuda-1.44.0 → pyqrack_cuda-1.44.2}/pyqrack/qrack_stabilizer.py +0 -0
- {pyqrack_cuda-1.44.0 → pyqrack_cuda-1.44.2}/pyqrack/qrack_system/__init__.py +0 -0
- {pyqrack_cuda-1.44.0 → pyqrack_cuda-1.44.2}/pyqrack/qrack_system/qrack_system.py +0 -0
- {pyqrack_cuda-1.44.0 → pyqrack_cuda-1.44.2}/pyqrack/quimb_circuit_type.py +0 -0
- {pyqrack_cuda-1.44.0 → pyqrack_cuda-1.44.2}/pyqrack/stats/__init__.py +0 -0
- {pyqrack_cuda-1.44.0 → pyqrack_cuda-1.44.2}/pyqrack/stats/load_quantized_data.py +0 -0
- {pyqrack_cuda-1.44.0 → pyqrack_cuda-1.44.2}/pyqrack/stats/quantize_by_range.py +0 -0
- {pyqrack_cuda-1.44.0 → pyqrack_cuda-1.44.2}/pyqrack_cuda.egg-info/SOURCES.txt +0 -0
- {pyqrack_cuda-1.44.0 → pyqrack_cuda-1.44.2}/pyqrack_cuda.egg-info/dependency_links.txt +0 -0
- {pyqrack_cuda-1.44.0 → pyqrack_cuda-1.44.2}/pyqrack_cuda.egg-info/not-zip-safe +0 -0
- {pyqrack_cuda-1.44.0 → pyqrack_cuda-1.44.2}/pyqrack_cuda.egg-info/requires.txt +0 -0
- {pyqrack_cuda-1.44.0 → pyqrack_cuda-1.44.2}/pyqrack_cuda.egg-info/top_level.txt +0 -0
- {pyqrack_cuda-1.44.0 → pyqrack_cuda-1.44.2}/setup.cfg +0 -0
@@ -0,0 +1,602 @@
|
|
1
|
+
# (C) Daniel Strano and the Qrack contributors 2017-2025. All rights reserved.
|
2
|
+
#
|
3
|
+
# Use of this source code is governed by an MIT-style license that can be
|
4
|
+
# found in the LICENSE file or at https://opensource.org/licenses/MIT.
|
5
|
+
import math
|
6
|
+
import random
|
7
|
+
import sys
|
8
|
+
import time
|
9
|
+
|
10
|
+
from .qrack_simulator import QrackSimulator
|
11
|
+
from .pauli import Pauli
|
12
|
+
|
13
|
+
|
14
|
+
_IS_QISKIT_AVAILABLE = True
|
15
|
+
try:
|
16
|
+
from qiskit.circuit.quantumcircuit import QuantumCircuit
|
17
|
+
from qiskit.compiler import transpile
|
18
|
+
from qiskit.quantum_info.operators.symplectic.clifford import Clifford
|
19
|
+
except ImportError:
|
20
|
+
_IS_QISKIT_AVAILABLE = False
|
21
|
+
|
22
|
+
|
23
|
+
class QrackAceBackend:
|
24
|
+
"""A back end for elided quantum error correction
|
25
|
+
|
26
|
+
This back end uses elided repetition code on a nearest-neighbor topology to emulate
|
27
|
+
a utility-scale superconducting chip quantum computer in very little memory.
|
28
|
+
|
29
|
+
The backend was originally designed assuming a 2D qubit grid like 2019 Sycamore.
|
30
|
+
However, it quickly became apparent that users can basically design their own
|
31
|
+
connectivity topologies, without breaking the concept. (Not all will work equally well.)
|
32
|
+
|
33
|
+
Attributes:
|
34
|
+
sim(QrackSimulator): Corresponding simulator.
|
35
|
+
"""
|
36
|
+
|
37
|
+
def __init__(
|
38
|
+
self,
|
39
|
+
qubit_count=-1,
|
40
|
+
toClone=None
|
41
|
+
):
|
42
|
+
self.sim = toClone.sim.clone() if toClone else QrackSimulator(3 * qubit_count)
|
43
|
+
|
44
|
+
|
45
|
+
def _ct_pair_prob(self, q1, q2):
|
46
|
+
p1 = self.sim.prob(q1)
|
47
|
+
p2 = self.sim.prob(q2)
|
48
|
+
|
49
|
+
if p1 < p2:
|
50
|
+
return p2, q1
|
51
|
+
|
52
|
+
return p1, q2
|
53
|
+
|
54
|
+
|
55
|
+
def _cz_shadow(self, q1, q2):
|
56
|
+
prob_max, t = self._ct_pair_prob(q1, q2)
|
57
|
+
if prob_max > 0.5:
|
58
|
+
self.sim.z(t)
|
59
|
+
|
60
|
+
|
61
|
+
def _anti_cz_shadow(self, q1, q2):
|
62
|
+
self.sim.x(q1)
|
63
|
+
self._cz_shadow(q1, q2)
|
64
|
+
self.sim.x(q1)
|
65
|
+
|
66
|
+
|
67
|
+
def _cx_shadow(self, c, t):
|
68
|
+
self.sim.h(t)
|
69
|
+
self._cz_shadow(c, t)
|
70
|
+
self.sim.h(t)
|
71
|
+
|
72
|
+
|
73
|
+
def _anti_cx_shadow(self, c, t):
|
74
|
+
self.sim.x(t)
|
75
|
+
self._cx_shadow(c, t)
|
76
|
+
self.sim.x(t)
|
77
|
+
|
78
|
+
|
79
|
+
def _cy_shadow(self, c, t):
|
80
|
+
self.sim.adjs(t)
|
81
|
+
self._cx_shadow(c, t)
|
82
|
+
self.sim.s(t)
|
83
|
+
|
84
|
+
|
85
|
+
def _anti_cy_shadow(self, c, t):
|
86
|
+
self.sim.x(t)
|
87
|
+
self._cy_shadow(c, t)
|
88
|
+
self.sim.x(t)
|
89
|
+
|
90
|
+
|
91
|
+
def _unpack(self, lq, reverse = False):
|
92
|
+
return [3 * lq + 2, 3 * lq + 1, 3 * lq] if reverse else [3 * lq, 3 * lq + 1, 3 * lq + 2]
|
93
|
+
|
94
|
+
|
95
|
+
def _encode(self, hq, reverse = False):
|
96
|
+
if reverse:
|
97
|
+
self._cx_shadow(hq[0], hq[1])
|
98
|
+
self.sim.mcx([hq[1]], hq[2])
|
99
|
+
else:
|
100
|
+
self.sim.mcx([hq[0]], hq[1])
|
101
|
+
self._cx_shadow(hq[1], hq[2])
|
102
|
+
|
103
|
+
|
104
|
+
def _decode(self, hq, reverse = False):
|
105
|
+
if reverse:
|
106
|
+
self.sim.mcx([hq[1]], hq[2])
|
107
|
+
self._cx_shadow(hq[0], hq[1])
|
108
|
+
else:
|
109
|
+
self._cx_shadow(hq[1], hq[2])
|
110
|
+
self.sim.mcx([hq[0]], hq[1])
|
111
|
+
|
112
|
+
|
113
|
+
def u(self, th, ph, lm, lq):
|
114
|
+
hq = self._unpack(lq)
|
115
|
+
self._decode(hq)
|
116
|
+
self.sim.u(hq[0], th, ph, lm)
|
117
|
+
self._encode(hq)
|
118
|
+
|
119
|
+
|
120
|
+
def r(self, p, th, lq):
|
121
|
+
hq = self._unpack(lq)
|
122
|
+
self._decode(hq)
|
123
|
+
self.sim.r(p, th, hq[0])
|
124
|
+
self._encode(hq)
|
125
|
+
|
126
|
+
|
127
|
+
def s(self, lq):
|
128
|
+
hq = self._unpack(lq)
|
129
|
+
self._decode(hq)
|
130
|
+
self.sim.s(hq[0])
|
131
|
+
self._encode(hq)
|
132
|
+
|
133
|
+
|
134
|
+
def adjs(self, lq):
|
135
|
+
hq = self._unpack(lq)
|
136
|
+
self._decode(hq)
|
137
|
+
self.sim.adjs(hq[0])
|
138
|
+
self._encode(hq)
|
139
|
+
|
140
|
+
|
141
|
+
def x(self, lq):
|
142
|
+
hq = self._unpack(lq)
|
143
|
+
self._decode(hq)
|
144
|
+
self.sim.x(hq[0])
|
145
|
+
self._encode(hq)
|
146
|
+
|
147
|
+
|
148
|
+
def y(self, lq):
|
149
|
+
hq = self._unpack(lq)
|
150
|
+
self._decode(hq)
|
151
|
+
self.sim.y(hq[0])
|
152
|
+
self._encode(hq)
|
153
|
+
|
154
|
+
|
155
|
+
def z(self, lq):
|
156
|
+
hq = self._unpack(lq)
|
157
|
+
self._decode(hq)
|
158
|
+
self.sim.z(hq[0])
|
159
|
+
self._encode(hq)
|
160
|
+
|
161
|
+
|
162
|
+
def h(self, lq):
|
163
|
+
hq = self._unpack(lq)
|
164
|
+
self._decode(hq)
|
165
|
+
self.sim.h(hq[0])
|
166
|
+
self._encode(hq)
|
167
|
+
|
168
|
+
|
169
|
+
def t(self, lq):
|
170
|
+
hq = self._unpack(lq)
|
171
|
+
self._decode(hq)
|
172
|
+
self.sim.t(hq[0])
|
173
|
+
self._encode(hq)
|
174
|
+
|
175
|
+
|
176
|
+
def adjt(self, lq):
|
177
|
+
hq = self._unpack(lq)
|
178
|
+
self._decode(hq)
|
179
|
+
self.sim.adjt(hq[0])
|
180
|
+
self._encode(hq)
|
181
|
+
|
182
|
+
|
183
|
+
def _cpauli(self, lq1, lq2, anti, pauli):
|
184
|
+
gate = None
|
185
|
+
if pauli == Pauli.PauliX:
|
186
|
+
gate = self.sim.macx if anti else self.sim.mcx
|
187
|
+
elif pauli == Pauli.PauliY:
|
188
|
+
gate = self.sim.macy if anti else self.sim.mcy
|
189
|
+
elif pauli == Pauli.PauliZ:
|
190
|
+
gate = self.sim.macz if anti else self.sim.mcz
|
191
|
+
else:
|
192
|
+
return
|
193
|
+
|
194
|
+
if (lq2 == (lq1 + 1)) or (lq1 == (lq2 + 1)):
|
195
|
+
hq1 = self._unpack(lq1, True)
|
196
|
+
hq2 = self._unpack(lq2, False)
|
197
|
+
self._decode(hq1, True)
|
198
|
+
self._decode(hq2, False)
|
199
|
+
gate([hq1[0]], hq2[0])
|
200
|
+
self._encode(hq2, False)
|
201
|
+
self._encode(hq1, True)
|
202
|
+
else:
|
203
|
+
hq1 = self._unpack(lq1)
|
204
|
+
hq2 = self._unpack(lq2)
|
205
|
+
gate([hq1[0]], hq2[0])
|
206
|
+
gate([hq1[1]], hq2[1])
|
207
|
+
gate([hq1[2]], hq2[2])
|
208
|
+
|
209
|
+
|
210
|
+
def cx(self, lq1, lq2):
|
211
|
+
self._cpauli(lq1, lq2, False, Pauli.PauliX)
|
212
|
+
|
213
|
+
|
214
|
+
def cy(self, lq1, lq2):
|
215
|
+
self._cpauli(lq1, lq2, False, Pauli.PauliY)
|
216
|
+
|
217
|
+
|
218
|
+
def cz(self, lq1, lq2):
|
219
|
+
self._cpauli(lq1, lq2, False, Pauli.PauliZ)
|
220
|
+
|
221
|
+
|
222
|
+
def acx(self, lq1, lq2):
|
223
|
+
self._cpauli(lq1, lq2, True, Pauli.PauliX)
|
224
|
+
|
225
|
+
|
226
|
+
def acy(self, lq1, lq2):
|
227
|
+
self._cpauli(lq1, lq2, True, Pauli.PauliY)
|
228
|
+
|
229
|
+
|
230
|
+
def acz(self, lq1, lq2):
|
231
|
+
self._cpauli(lq1, lq2, True, Pauli.PauliZ)
|
232
|
+
|
233
|
+
|
234
|
+
def swap(self, lq1, lq2):
|
235
|
+
self.cx(lq1, lq2)
|
236
|
+
self.cx(lq2, lq1)
|
237
|
+
self.cx(lq1, lq2)
|
238
|
+
|
239
|
+
|
240
|
+
def iswap(self, lq1, lq2):
|
241
|
+
self.swap(lq1, lq2)
|
242
|
+
self.cz(lq1, lq2)
|
243
|
+
self.s(lq1)
|
244
|
+
self.s(lq2)
|
245
|
+
|
246
|
+
|
247
|
+
def adjiswap(self, lq1, lq2):
|
248
|
+
self.adjs(lq2)
|
249
|
+
self.adjs(lq1)
|
250
|
+
self.cz(lq1, lq2)
|
251
|
+
self.swap(lq1, lq2)
|
252
|
+
|
253
|
+
|
254
|
+
def m(self, lq):
|
255
|
+
hq = self._unpack(lq)
|
256
|
+
syndrome = 0
|
257
|
+
bits = []
|
258
|
+
for q in hq:
|
259
|
+
bits.append(self.sim.m(q))
|
260
|
+
if bits[-1]:
|
261
|
+
syndrome += 1
|
262
|
+
result = True if (syndrome > 1) else False
|
263
|
+
for i in range(len(hq)):
|
264
|
+
if bits[i] != result:
|
265
|
+
self.sim.x(hq[i])
|
266
|
+
|
267
|
+
return result
|
268
|
+
|
269
|
+
|
270
|
+
def m_all(self):
|
271
|
+
result = 0
|
272
|
+
for lq in range(self.sim.num_qubits() // 3):
|
273
|
+
result <<= 1
|
274
|
+
if self.m(lq):
|
275
|
+
result |= 1
|
276
|
+
|
277
|
+
return result
|
278
|
+
|
279
|
+
|
280
|
+
def measure_shots(self, q, s):
|
281
|
+
_q = []
|
282
|
+
for i in q:
|
283
|
+
_q.append(3 * i)
|
284
|
+
_q.append(3 * i + 1)
|
285
|
+
_q.append(3 * i + 2)
|
286
|
+
|
287
|
+
samples = self.sim.measure_shots(_q, s)
|
288
|
+
|
289
|
+
results = []
|
290
|
+
for sample in samples:
|
291
|
+
logical_sample = 0
|
292
|
+
for i in range(len(q)):
|
293
|
+
logical_sample <<= 1
|
294
|
+
bit_count = 0
|
295
|
+
for _ in range(3):
|
296
|
+
if sample & 1:
|
297
|
+
bit_count += 1
|
298
|
+
sample >>= 1
|
299
|
+
if bit_count > 1:
|
300
|
+
logical_sample |= 1
|
301
|
+
results.append(logical_sample)
|
302
|
+
|
303
|
+
return results
|
304
|
+
|
305
|
+
|
306
|
+
def _apply_op(self, operation):
|
307
|
+
name = operation.name
|
308
|
+
|
309
|
+
if (name == 'id') or (name == 'barrier'):
|
310
|
+
# Skip measurement logic
|
311
|
+
return
|
312
|
+
|
313
|
+
conditional = getattr(operation, 'conditional', None)
|
314
|
+
if isinstance(conditional, int):
|
315
|
+
conditional_bit_set = (self._classical_register >> conditional) & 1
|
316
|
+
if not conditional_bit_set:
|
317
|
+
return
|
318
|
+
elif conditional is not None:
|
319
|
+
mask = int(conditional.mask, 16)
|
320
|
+
if mask > 0:
|
321
|
+
value = self._classical_memory & mask
|
322
|
+
while (mask & 0x1) == 0:
|
323
|
+
mask >>= 1
|
324
|
+
value >>= 1
|
325
|
+
if value != int(conditional.val, 16):
|
326
|
+
return
|
327
|
+
|
328
|
+
if (name == 'u1') or (name == 'p'):
|
329
|
+
self._sim.u(0, 0, float(operation.params[0]), operation.qubits[0]._index)
|
330
|
+
elif name == 'u2':
|
331
|
+
self._sim.u(
|
332
|
+
math.pi / 2,
|
333
|
+
float(operation.params[0]),
|
334
|
+
float(operation.params[1]),
|
335
|
+
operation.qubits[0]._index
|
336
|
+
)
|
337
|
+
elif (name == 'u3') or (name == 'u'):
|
338
|
+
self._sim.u(
|
339
|
+
float(operation.params[0]),
|
340
|
+
float(operation.params[1]),
|
341
|
+
float(operation.params[2]),
|
342
|
+
operation.qubits[0]._index
|
343
|
+
)
|
344
|
+
elif name == 'r':
|
345
|
+
self._sim.u(
|
346
|
+
float(operation.params[0]),
|
347
|
+
float(operation.params[1]) - math.pi / 2,
|
348
|
+
(-1 * float(operation.params[1])) + math.pi / 2,
|
349
|
+
operation.qubits[0]._index
|
350
|
+
)
|
351
|
+
elif name == 'rx':
|
352
|
+
self._sim.r(Pauli.PauliX, float(operation.params[0]), operation.qubits[0]._index)
|
353
|
+
elif name == 'ry':
|
354
|
+
self._sim.r(Pauli.PauliY, float(operation.params[0]), operation.qubits[0]._index)
|
355
|
+
elif name == 'rz':
|
356
|
+
self._sim.r(Pauli.PauliZ, float(operation.params[0]), operation.qubits[0]._index)
|
357
|
+
elif name == 'h':
|
358
|
+
self._sim.h(operation.qubits[0]._index)
|
359
|
+
elif name == 'x':
|
360
|
+
self._sim.x(operation.qubits[0]._index)
|
361
|
+
elif name == 'y':
|
362
|
+
self._sim.y(operation.qubits[0]._index)
|
363
|
+
elif name == 'z':
|
364
|
+
self._sim.z(operation.qubits[0]._index)
|
365
|
+
elif name == 's':
|
366
|
+
self._sim.s(operation.qubits[0]._index)
|
367
|
+
elif name == 'sdg':
|
368
|
+
self._sim.adjs(operation.qubits[0]._index)
|
369
|
+
elif name == 't':
|
370
|
+
self._sim.t(operation.qubits[0]._index)
|
371
|
+
elif name == 'tdg':
|
372
|
+
self._sim.adjt(operation.qubits[0]._index)
|
373
|
+
elif name == 'cx':
|
374
|
+
self._sim.cx(operation.qubits[0]._index, operation.qubits[1]._index)
|
375
|
+
elif name == 'cy':
|
376
|
+
self._sim.cy(operation.qubits[0]._index, operation.qubits[1]._index)
|
377
|
+
elif name == 'cz':
|
378
|
+
self._sim.cz(operation.qubits[0]._index, operation.qubits[1]._index)
|
379
|
+
elif name == 'dcx':
|
380
|
+
self._sim.mcx(operation.qubits[0]._index, operation.qubits[1]._index)
|
381
|
+
self._sim.mcx(operation.qubits[1]._index, operation.qubits[0]._index)
|
382
|
+
elif name == 'swap':
|
383
|
+
self._sim.swap(operation.qubits[0]._index, operation.qubits[1]._index)
|
384
|
+
elif name == 'iswap':
|
385
|
+
self._sim.iswap(operation.qubits[0]._index, operation.qubits[1]._index)
|
386
|
+
elif name == 'iswap_dg':
|
387
|
+
self._sim.adjiswap(operation.qubits[0]._index, operation.qubits[1]._index)
|
388
|
+
elif name == 'reset':
|
389
|
+
qubits = operation.qubits
|
390
|
+
for qubit in qubits:
|
391
|
+
if self._sim.m(qubit._index):
|
392
|
+
self._sim.x(qubit._index)
|
393
|
+
elif name == 'measure':
|
394
|
+
qubits = operation.qubits
|
395
|
+
clbits = operation.clbits
|
396
|
+
cregbits = (
|
397
|
+
operation.register
|
398
|
+
if hasattr(operation, 'register')
|
399
|
+
else len(operation.qubits) * [-1]
|
400
|
+
)
|
401
|
+
|
402
|
+
self._sample_qubits += qubits
|
403
|
+
self._sample_clbits += clbits
|
404
|
+
self._sample_cregbits += cregbits
|
405
|
+
|
406
|
+
if not self._sample_measure:
|
407
|
+
for index in range(len(qubits)):
|
408
|
+
qubit_outcome = self._sim.m(qubits[index]._index)
|
409
|
+
|
410
|
+
clbit = clbits[index]
|
411
|
+
clmask = 1 << clbit
|
412
|
+
self._classical_memory = (self._classical_memory & (~clmask)) | (
|
413
|
+
qubit_outcome << clbit
|
414
|
+
)
|
415
|
+
|
416
|
+
cregbit = cregbits[index]
|
417
|
+
if cregbit < 0:
|
418
|
+
cregbit = clbit
|
419
|
+
|
420
|
+
regbit = 1 << cregbit
|
421
|
+
self._classical_register = (
|
422
|
+
self._classical_register & (~regbit)
|
423
|
+
) | (qubit_outcome << cregbit)
|
424
|
+
|
425
|
+
elif name == 'bfunc':
|
426
|
+
mask = int(operation.mask, 16)
|
427
|
+
relation = operation.relation
|
428
|
+
val = int(operation.val, 16)
|
429
|
+
|
430
|
+
cregbit = operation.register
|
431
|
+
cmembit = operation.memory if hasattr(operation, 'memory') else None
|
432
|
+
|
433
|
+
compared = (self._classical_register & mask) - val
|
434
|
+
|
435
|
+
if relation == '==':
|
436
|
+
outcome = compared == 0
|
437
|
+
elif relation == '!=':
|
438
|
+
outcome = compared != 0
|
439
|
+
elif relation == '<':
|
440
|
+
outcome = compared < 0
|
441
|
+
elif relation == '<=':
|
442
|
+
outcome = compared <= 0
|
443
|
+
elif relation == '>':
|
444
|
+
outcome = compared > 0
|
445
|
+
elif relation == '>=':
|
446
|
+
outcome = compared >= 0
|
447
|
+
else:
|
448
|
+
raise QrackError('Invalid boolean function relation.')
|
449
|
+
|
450
|
+
# Store outcome in register and optionally memory slot
|
451
|
+
regbit = 1 << cregbit
|
452
|
+
self._classical_register = (self._classical_register & (~regbit)) | (
|
453
|
+
int(outcome) << cregbit
|
454
|
+
)
|
455
|
+
if cmembit is not None:
|
456
|
+
membit = 1 << cmembit
|
457
|
+
self._classical_memory = (self._classical_memory & (~membit)) | (
|
458
|
+
int(outcome) << cmembit
|
459
|
+
)
|
460
|
+
else:
|
461
|
+
err_msg = 'QrackAceBackend encountered unrecognized operation "{0}"'
|
462
|
+
raise RuntimeError(err_msg.format(operation))
|
463
|
+
|
464
|
+
def _add_sample_measure(self, sample_qubits, sample_clbits, num_samples):
|
465
|
+
"""Generate data samples from current statevector.
|
466
|
+
|
467
|
+
Taken almost straight from the terra source code.
|
468
|
+
|
469
|
+
Args:
|
470
|
+
measure_params (list): List of (qubit, clbit) values for
|
471
|
+
measure instructions to sample.
|
472
|
+
num_samples (int): The number of data samples to generate.
|
473
|
+
|
474
|
+
Returns:
|
475
|
+
list: A list of data values in hex format.
|
476
|
+
"""
|
477
|
+
# Get unique qubits that are actually measured
|
478
|
+
measure_qubit = [qubit for qubit in sample_qubits]
|
479
|
+
measure_clbit = [clbit for clbit in sample_clbits]
|
480
|
+
|
481
|
+
# Sample and convert to bit-strings
|
482
|
+
if num_samples == 1:
|
483
|
+
sample = self._sim.m_all()
|
484
|
+
result = 0
|
485
|
+
for index in range(len(measure_qubit)):
|
486
|
+
qubit = measure_qubit[index]._index
|
487
|
+
qubit_outcome = (sample >> qubit) & 1
|
488
|
+
result |= qubit_outcome << index
|
489
|
+
measure_results = [result]
|
490
|
+
else:
|
491
|
+
measure_results = self._sim.measure_shots([q._index for q in measure_qubit], num_samples)
|
492
|
+
|
493
|
+
data = []
|
494
|
+
for sample in measure_results:
|
495
|
+
for index in range(len(measure_qubit)):
|
496
|
+
qubit_outcome = (sample >> index) & 1
|
497
|
+
clbit = measure_clbit[index]._index
|
498
|
+
clmask = 1 << clbit
|
499
|
+
self._classical_memory = (self._classical_memory & (~clmask)) | (
|
500
|
+
qubit_outcome << clbit
|
501
|
+
)
|
502
|
+
|
503
|
+
data.append(bin(self._classical_memory)[2:].zfill(self.num_qubits()))
|
504
|
+
|
505
|
+
return data
|
506
|
+
|
507
|
+
def run_qiskit_circuit(self, experiment, shots=1):
|
508
|
+
if not _IS_QISKIT_AVAILABLE:
|
509
|
+
raise RuntimeError(
|
510
|
+
"Before trying to run_qiskit_circuit() with QrackAceBackend, you must install Qiskit!"
|
511
|
+
)
|
512
|
+
|
513
|
+
instructions = []
|
514
|
+
if isinstance(experiment, QuantumCircuit):
|
515
|
+
instructions = experiment.data
|
516
|
+
else:
|
517
|
+
raise RuntimeError('Unrecognized "run_input" argument specified for run().')
|
518
|
+
|
519
|
+
self._shots = shots
|
520
|
+
self._sample_qubits = []
|
521
|
+
self._sample_clbits = []
|
522
|
+
self._sample_cregbits = []
|
523
|
+
self._sample_measure = True
|
524
|
+
_data = []
|
525
|
+
shotLoopMax = 1
|
526
|
+
|
527
|
+
is_initializing = True
|
528
|
+
boundary_start = -1
|
529
|
+
|
530
|
+
for opcount in range(len(instructions)):
|
531
|
+
operation = instructions[opcount]
|
532
|
+
|
533
|
+
if operation.name == 'id' or operation.name == 'barrier':
|
534
|
+
continue
|
535
|
+
|
536
|
+
if is_initializing and (
|
537
|
+
(operation.name == 'measure') or (operation.name == 'reset')
|
538
|
+
):
|
539
|
+
continue
|
540
|
+
|
541
|
+
is_initializing = False
|
542
|
+
|
543
|
+
if (operation.name == 'measure') or (operation.name == 'reset'):
|
544
|
+
if boundary_start == -1:
|
545
|
+
boundary_start = opcount
|
546
|
+
|
547
|
+
if (boundary_start != -1) and (operation.name != 'measure'):
|
548
|
+
shotsPerLoop = 1
|
549
|
+
shotLoopMax = self._shots
|
550
|
+
self._sample_measure = False
|
551
|
+
break
|
552
|
+
|
553
|
+
preamble_memory = 0
|
554
|
+
preamble_register = 0
|
555
|
+
preamble_sim = None
|
556
|
+
|
557
|
+
if self._sample_measure or boundary_start <= 0:
|
558
|
+
boundary_start = 0
|
559
|
+
self._sample_measure = True
|
560
|
+
shotsPerLoop = self._shots
|
561
|
+
shotLoopMax = 1
|
562
|
+
else:
|
563
|
+
boundary_start -= 1
|
564
|
+
if boundary_start > 0:
|
565
|
+
self._sim = self
|
566
|
+
self._classical_memory = 0
|
567
|
+
self._classical_register = 0
|
568
|
+
|
569
|
+
for operation in instructions[:boundary_start]:
|
570
|
+
self._apply_op(operation)
|
571
|
+
|
572
|
+
preamble_memory = self._classical_memory
|
573
|
+
preamble_register = self._classical_register
|
574
|
+
preamble_sim = self._sim
|
575
|
+
|
576
|
+
for shot in range(shotLoopMax):
|
577
|
+
if preamble_sim is None:
|
578
|
+
self._sim = self
|
579
|
+
self._classical_memory = 0
|
580
|
+
self._classical_register = 0
|
581
|
+
else:
|
582
|
+
self._sim = QrackAceBackend(toClone=preamble_sim)
|
583
|
+
self._classical_memory = preamble_memory
|
584
|
+
self._classical_register = preamble_register
|
585
|
+
|
586
|
+
for operation in instructions[boundary_start:]:
|
587
|
+
self._apply_op(operation)
|
588
|
+
|
589
|
+
if not self._sample_measure and (len(self._sample_qubits) > 0):
|
590
|
+
_data += [bin(self._classical_memory)[2:].zfill(self.num_qubits())]
|
591
|
+
self._sample_qubits = []
|
592
|
+
self._sample_clbits = []
|
593
|
+
self._sample_cregbits = []
|
594
|
+
|
595
|
+
if self._sample_measure and (len(self._sample_qubits) > 0):
|
596
|
+
_data = self._add_sample_measure(
|
597
|
+
self._sample_qubits, self._sample_clbits, self._shots
|
598
|
+
)
|
599
|
+
|
600
|
+
del self._sim
|
601
|
+
|
602
|
+
return _data
|
@@ -3843,7 +3843,7 @@ class QrackSimulator:
|
|
3843
3843
|
|
3844
3844
|
if not self._sample_measure:
|
3845
3845
|
for index in range(len(qubits)):
|
3846
|
-
qubit_outcome = self._sim.m(qubits[index])
|
3846
|
+
qubit_outcome = self._sim.m(qubits[index]._index)
|
3847
3847
|
|
3848
3848
|
clbit = clbits[index]
|
3849
3849
|
clmask = 1 << clbit
|
@@ -1,261 +0,0 @@
|
|
1
|
-
# (C) Daniel Strano and the Qrack contributors 2017-2025. All rights reserved.
|
2
|
-
#
|
3
|
-
# Use of this source code is governed by an MIT-style license that can be
|
4
|
-
# found in the LICENSE file or at https://opensource.org/licenses/MIT.
|
5
|
-
import math
|
6
|
-
import random
|
7
|
-
import sys
|
8
|
-
import time
|
9
|
-
|
10
|
-
from .qrack_simulator import QrackSimulator
|
11
|
-
from .pauli import Pauli
|
12
|
-
|
13
|
-
|
14
|
-
class QrackAceBackend:
|
15
|
-
"""A back end for elided quantum error correction
|
16
|
-
|
17
|
-
This back end uses elided repetition code on a nearest-neighbor topology to emulate
|
18
|
-
a utility-scale superconducting chip quantum computer in very little memory.
|
19
|
-
|
20
|
-
The backend was originally designed assuming a 2D qubit grid like 2019 Sycamore.
|
21
|
-
However, it quickly became apparent that users can basically design their own
|
22
|
-
connectivity topologies, without breaking the concept. (Not all will work equally well.)
|
23
|
-
|
24
|
-
Attributes:
|
25
|
-
sim(QrackSimulator): Corresponding simulator.
|
26
|
-
"""
|
27
|
-
|
28
|
-
def __init__(
|
29
|
-
self,
|
30
|
-
qubit_count=-1,
|
31
|
-
):
|
32
|
-
self.sim = QrackSimulator(3 * qubit_count)
|
33
|
-
|
34
|
-
|
35
|
-
def _ct_pair_prob(self, q1, q2):
|
36
|
-
p1 = self.sim.prob(q1)
|
37
|
-
p2 = self.sim.prob(q2)
|
38
|
-
|
39
|
-
if p1 < p2:
|
40
|
-
return p2, q1
|
41
|
-
|
42
|
-
return p1, q2
|
43
|
-
|
44
|
-
|
45
|
-
def _cz_shadow(self, q1, q2):
|
46
|
-
prob_max, t = self._ct_pair_prob(q1, q2)
|
47
|
-
if prob_max > 0.5:
|
48
|
-
self.sim.z(t)
|
49
|
-
|
50
|
-
|
51
|
-
def _anti_cz_shadow(self, q1, q2):
|
52
|
-
self.sim.x(q1)
|
53
|
-
self._cz_shadow(q1, q2)
|
54
|
-
self.sim.x(q1)
|
55
|
-
|
56
|
-
|
57
|
-
def _cx_shadow(self, c, t):
|
58
|
-
self.sim.h(t)
|
59
|
-
self._cz_shadow(c, t)
|
60
|
-
self.sim.h(t)
|
61
|
-
|
62
|
-
|
63
|
-
def _anti_cx_shadow(self, c, t):
|
64
|
-
self.sim.x(t)
|
65
|
-
self._cx_shadow(c, t)
|
66
|
-
self.sim.x(t)
|
67
|
-
|
68
|
-
|
69
|
-
def _cy_shadow(self, c, t):
|
70
|
-
self.sim.adjs(t)
|
71
|
-
self._cx_shadow(c, t)
|
72
|
-
self.sim.s(t)
|
73
|
-
|
74
|
-
|
75
|
-
def _anti_cy_shadow(self, c, t):
|
76
|
-
self.sim.x(t)
|
77
|
-
self._cy_shadow(c, t)
|
78
|
-
self.sim.x(t)
|
79
|
-
|
80
|
-
|
81
|
-
def _unpack(self, lq, reverse = False):
|
82
|
-
return [3 * lq + 2, 3 * lq + 1, 3 * lq] if reverse else [3 * lq, 3 * lq + 1, 3 * lq + 2]
|
83
|
-
|
84
|
-
|
85
|
-
def _encode(self, hq, reverse = False):
|
86
|
-
if reverse:
|
87
|
-
self._cx_shadow(hq[0], hq[1])
|
88
|
-
self.sim.mcx([hq[1]], hq[2])
|
89
|
-
else:
|
90
|
-
self.sim.mcx([hq[0]], hq[1])
|
91
|
-
self._cx_shadow(hq[1], hq[2])
|
92
|
-
|
93
|
-
|
94
|
-
def _decode(self, hq, reverse = False):
|
95
|
-
if reverse:
|
96
|
-
self.sim.mcx([hq[1]], hq[2])
|
97
|
-
self._cx_shadow(hq[0], hq[1])
|
98
|
-
else:
|
99
|
-
self._cx_shadow(hq[1], hq[2])
|
100
|
-
self.sim.mcx([hq[0]], hq[1])
|
101
|
-
|
102
|
-
|
103
|
-
def u(self, th, ph, lm, lq):
|
104
|
-
hq = self._unpack(lq)
|
105
|
-
self._decode(hq)
|
106
|
-
self.sim.u(hq[0], th, ph, lm)
|
107
|
-
self._encode(hq)
|
108
|
-
|
109
|
-
|
110
|
-
def s(self, lq):
|
111
|
-
hq = self._unpack(lq)
|
112
|
-
self._decode(hq)
|
113
|
-
self.sim.s(hq[0])
|
114
|
-
self._encode(hq)
|
115
|
-
|
116
|
-
|
117
|
-
def adjs(self, lq):
|
118
|
-
hq = self._unpack(lq)
|
119
|
-
self._decode(hq)
|
120
|
-
self.sim.adjs(hq[0])
|
121
|
-
self._encode(hq)
|
122
|
-
|
123
|
-
|
124
|
-
def x(self, lq):
|
125
|
-
hq = self._unpack(lq)
|
126
|
-
self._decode(hq)
|
127
|
-
self.sim.x(hq[0])
|
128
|
-
self._encode(hq)
|
129
|
-
|
130
|
-
|
131
|
-
def y(self, lq):
|
132
|
-
hq = self._unpack(lq)
|
133
|
-
self._decode(hq)
|
134
|
-
self.sim.y(hq[0])
|
135
|
-
self._encode(hq)
|
136
|
-
|
137
|
-
|
138
|
-
def z(self, lq):
|
139
|
-
hq = self._unpack(lq)
|
140
|
-
self._decode(hq)
|
141
|
-
self.sim.z(hq[0])
|
142
|
-
self._encode(hq)
|
143
|
-
|
144
|
-
|
145
|
-
def h(self, lq):
|
146
|
-
hq = self._unpack(lq)
|
147
|
-
self._decode(hq)
|
148
|
-
self.sim.h(hq[0])
|
149
|
-
self._encode(hq)
|
150
|
-
|
151
|
-
|
152
|
-
def t(self, lq):
|
153
|
-
hq = self._unpack(lq)
|
154
|
-
self._decode(hq)
|
155
|
-
self.sim.t(hq[0])
|
156
|
-
self._encode(hq)
|
157
|
-
|
158
|
-
|
159
|
-
def adjt(self, lq):
|
160
|
-
hq = self._unpack(lq)
|
161
|
-
self._decode(hq)
|
162
|
-
self.sim.adjt(hq[0])
|
163
|
-
self._encode(hq)
|
164
|
-
|
165
|
-
|
166
|
-
def _cpauli(self, lq1, lq2, anti, pauli):
|
167
|
-
gate = None
|
168
|
-
if pauli == Pauli.PauliX:
|
169
|
-
gate = self.sim.macx if anti else self.sim.mcx
|
170
|
-
elif pauli == Pauli.PauliY:
|
171
|
-
gate = self.sim.macy if anti else self.sim.mcy
|
172
|
-
elif pauli == Pauli.PauliZ:
|
173
|
-
gate = self.sim.macz if anti else self.sim.mcz
|
174
|
-
else:
|
175
|
-
return
|
176
|
-
|
177
|
-
if (lq2 == (lq1 + 1)) or (lq1 == (lq2 + 1)):
|
178
|
-
hq1 = self._unpack(lq1, True)
|
179
|
-
hq2 = self._unpack(lq2, False)
|
180
|
-
self._decode(hq1, True)
|
181
|
-
self._decode(hq2, False)
|
182
|
-
gate([hq1[0]], hq2[0])
|
183
|
-
self._encode(hq2, False)
|
184
|
-
self._encode(hq1, True)
|
185
|
-
else:
|
186
|
-
hq1 = self._unpack(lq1)
|
187
|
-
hq2 = self._unpack(lq2)
|
188
|
-
gate([hq1[0]], hq2[0])
|
189
|
-
gate([hq1[1]], hq2[1])
|
190
|
-
gate([hq1[2]], hq2[2])
|
191
|
-
|
192
|
-
|
193
|
-
def cx(self, lq1, lq2):
|
194
|
-
self._cpauli(lq1, lq2, False, Pauli.PauliX)
|
195
|
-
|
196
|
-
|
197
|
-
def cy(self, lq1, lq2):
|
198
|
-
self._cpauli(lq1, lq2, False, Pauli.PauliY)
|
199
|
-
|
200
|
-
|
201
|
-
def cz(self, lq1, lq2):
|
202
|
-
self._cpauli(lq1, lq2, False, Pauli.PauliZ)
|
203
|
-
|
204
|
-
|
205
|
-
def acx(self, lq1, lq2):
|
206
|
-
self._cpauli(lq1, lq2, True, Pauli.PauliX)
|
207
|
-
|
208
|
-
|
209
|
-
def acy(self, lq1, lq2):
|
210
|
-
self._cpauli(lq1, lq2, True, Pauli.PauliY)
|
211
|
-
|
212
|
-
|
213
|
-
def acz(self, lq1, lq2):
|
214
|
-
self._cpauli(lq1, lq2, True, Pauli.PauliZ)
|
215
|
-
|
216
|
-
|
217
|
-
def swap(self, lq1, lq2):
|
218
|
-
self.cx(lq1, lq2)
|
219
|
-
self.cx(lq2, lq1)
|
220
|
-
self.cx(lq1, lq2)
|
221
|
-
|
222
|
-
|
223
|
-
def iswap(self, lq1, lq2):
|
224
|
-
self.swap(lq1, lq2)
|
225
|
-
self.cz(lq1, lq2)
|
226
|
-
self.s(lq1)
|
227
|
-
self.s(lq2)
|
228
|
-
|
229
|
-
|
230
|
-
def adjiswap(self, lq1, lq2):
|
231
|
-
self.adjs(lq2)
|
232
|
-
self.adjs(lq1)
|
233
|
-
self.cz(lq1, lq2)
|
234
|
-
self.swap(lq1, lq2)
|
235
|
-
|
236
|
-
|
237
|
-
def m(self, lq):
|
238
|
-
hq = self._unpack(lq)
|
239
|
-
syndrome = 0
|
240
|
-
bits = []
|
241
|
-
for q in hq:
|
242
|
-
bits.append(self.sim.m(q))
|
243
|
-
if bits[-1]:
|
244
|
-
syndrome += 1
|
245
|
-
result = True if (syndrome > 1) else False
|
246
|
-
for i in range(len(hq)):
|
247
|
-
if bits[i] != result:
|
248
|
-
self.sim.x(hq[i])
|
249
|
-
|
250
|
-
return result
|
251
|
-
|
252
|
-
|
253
|
-
def m_all(self):
|
254
|
-
result = 0
|
255
|
-
for lq in range(self.sim.num_qubits() // 3):
|
256
|
-
result <<= 1
|
257
|
-
if self.m(lq):
|
258
|
-
result |= 1
|
259
|
-
|
260
|
-
return result
|
261
|
-
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|