pyqrack 1.70.0__py3-none-macosx_13_0_x86_64.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.
- pyqrack/__init__.py +20 -0
- pyqrack/neuron_activation_fn.py +21 -0
- pyqrack/pauli.py +19 -0
- pyqrack/qrack_ace_backend.py +1544 -0
- pyqrack/qrack_circuit.py +584 -0
- pyqrack/qrack_neuron.py +262 -0
- pyqrack/qrack_neuron_torch_layer.py +170 -0
- pyqrack/qrack_simulator.py +4498 -0
- pyqrack/qrack_stabilizer.py +58 -0
- pyqrack/qrack_system/__init__.py +9 -0
- pyqrack/qrack_system/qrack_cl_precompile/qrack_cl_precompile +0 -0
- pyqrack/qrack_system/qrack_lib/libqrack_pinvoke.9.29.0.dylib +0 -0
- pyqrack/qrack_system/qrack_lib/libqrack_pinvoke.dylib +0 -0
- pyqrack/qrack_system/qrack_system.py +1351 -0
- pyqrack/quimb_circuit_type.py +17 -0
- pyqrack/stats/__init__.py +6 -0
- pyqrack/stats/load_quantized_data.py +35 -0
- pyqrack/stats/quantize_by_range.py +56 -0
- pyqrack-1.70.0.dist-info/LICENSE +21 -0
- pyqrack-1.70.0.dist-info/METADATA +82 -0
- pyqrack-1.70.0.dist-info/RECORD +23 -0
- pyqrack-1.70.0.dist-info/WHEEL +5 -0
- pyqrack-1.70.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1544 @@
|
|
|
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 os
|
|
7
|
+
import random
|
|
8
|
+
import sys
|
|
9
|
+
import time
|
|
10
|
+
|
|
11
|
+
from .qrack_simulator import QrackSimulator
|
|
12
|
+
from .pauli import Pauli
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
_IS_QISKIT_AVAILABLE = True
|
|
16
|
+
try:
|
|
17
|
+
from qiskit.circuit.quantumcircuit import QuantumCircuit
|
|
18
|
+
from qiskit.compiler import transpile
|
|
19
|
+
from qiskit.quantum_info.operators.symplectic.clifford import Clifford
|
|
20
|
+
except ImportError:
|
|
21
|
+
_IS_QISKIT_AVAILABLE = False
|
|
22
|
+
|
|
23
|
+
_IS_QISKIT_AER_AVAILABLE = True
|
|
24
|
+
try:
|
|
25
|
+
from qiskit_aer.noise import NoiseModel, depolarizing_error
|
|
26
|
+
except ImportError:
|
|
27
|
+
_IS_QISKIT_AER_AVAILABLE = False
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# Initial stub and concept produced through conversation with Elara
|
|
31
|
+
# (the custom OpenAI GPT)
|
|
32
|
+
class LHVQubit:
|
|
33
|
+
def __init__(self, toClone=None):
|
|
34
|
+
# Initial state in "Bloch vector" terms, defaults to |0⟩
|
|
35
|
+
if toClone:
|
|
36
|
+
self.bloch = toClone.bloch.copy()
|
|
37
|
+
else:
|
|
38
|
+
self.reset()
|
|
39
|
+
|
|
40
|
+
def reset(self):
|
|
41
|
+
self.bloch = [0.0, 0.0, 1.0]
|
|
42
|
+
|
|
43
|
+
def h(self):
|
|
44
|
+
# Hadamard: rotate around Y-axis then X-axis (simplified for LHV)
|
|
45
|
+
x, y, z = self.bloch
|
|
46
|
+
self.bloch = [(x + z) / math.sqrt(2), y, (z - x) / math.sqrt(2)]
|
|
47
|
+
|
|
48
|
+
def x(self):
|
|
49
|
+
x, y, z = self.bloch
|
|
50
|
+
self.bloch = [x, y, -z]
|
|
51
|
+
|
|
52
|
+
def y(self):
|
|
53
|
+
x, y, z = self.bloch
|
|
54
|
+
self.bloch = [-x, y, z]
|
|
55
|
+
|
|
56
|
+
def z(self):
|
|
57
|
+
x, y, z = self.bloch
|
|
58
|
+
self.bloch = [x, -y, z]
|
|
59
|
+
|
|
60
|
+
def rx(self, theta):
|
|
61
|
+
# Rotate Bloch vector around X-axis by angle theta
|
|
62
|
+
x, y, z = self.bloch
|
|
63
|
+
cos_theta = math.cos(theta)
|
|
64
|
+
sin_theta = math.sin(theta)
|
|
65
|
+
new_y = cos_theta * y - sin_theta * z
|
|
66
|
+
new_z = sin_theta * y + cos_theta * z
|
|
67
|
+
self.bloch = [x, new_y, new_z]
|
|
68
|
+
|
|
69
|
+
def ry(self, theta):
|
|
70
|
+
# Rotate Bloch vector around Y-axis by angle theta
|
|
71
|
+
x, y, z = self.bloch
|
|
72
|
+
cos_theta = math.cos(theta)
|
|
73
|
+
sin_theta = math.sin(theta)
|
|
74
|
+
new_x = cos_theta * x + sin_theta * z
|
|
75
|
+
new_z = -sin_theta * x + cos_theta * z
|
|
76
|
+
self.bloch = [new_x, y, new_z]
|
|
77
|
+
|
|
78
|
+
def rz(self, theta):
|
|
79
|
+
# Rotate Bloch vector around Z-axis by angle theta (in radians)
|
|
80
|
+
x, y, z = self.bloch
|
|
81
|
+
cos_theta = math.cos(theta)
|
|
82
|
+
sin_theta = math.sin(theta)
|
|
83
|
+
new_x = cos_theta * x - sin_theta * y
|
|
84
|
+
new_y = sin_theta * x + cos_theta * y
|
|
85
|
+
self.bloch = [new_x, new_y, z]
|
|
86
|
+
|
|
87
|
+
def s(self):
|
|
88
|
+
self.rz(math.pi / 2)
|
|
89
|
+
|
|
90
|
+
def adjs(self):
|
|
91
|
+
self.rz(-math.pi / 2)
|
|
92
|
+
|
|
93
|
+
def t(self):
|
|
94
|
+
self.rz(math.pi / 4)
|
|
95
|
+
|
|
96
|
+
def adjt(self):
|
|
97
|
+
self.rz(-math.pi / 4)
|
|
98
|
+
|
|
99
|
+
def u(self, theta, phi, lam):
|
|
100
|
+
# Apply general single-qubit unitary gate
|
|
101
|
+
self.rz(lam)
|
|
102
|
+
self.ry(theta)
|
|
103
|
+
self.rz(phi)
|
|
104
|
+
|
|
105
|
+
# Provided verbatim by Elara (the custom OpenAI GPT):
|
|
106
|
+
def mtrx(self, matrix):
|
|
107
|
+
"""
|
|
108
|
+
Apply a 2x2 unitary matrix to the LHV Bloch vector using only standard math/cmath.
|
|
109
|
+
Matrix format: [a, b, c, d] for [[a, b], [c, d]]
|
|
110
|
+
"""
|
|
111
|
+
a, b, c, d = matrix
|
|
112
|
+
|
|
113
|
+
# Current Bloch vector
|
|
114
|
+
x, y, z = self.bloch
|
|
115
|
+
|
|
116
|
+
# Convert to density matrix ρ = ½ (I + xσx + yσy + zσz)
|
|
117
|
+
rho = [[(1 + z) / 2, (x - 1j * y) / 2], [(x + 1j * y) / 2, (1 - z) / 2]]
|
|
118
|
+
|
|
119
|
+
# Compute U * ρ
|
|
120
|
+
u_rho = [
|
|
121
|
+
[a * rho[0][0] + b * rho[1][0], a * rho[0][1] + b * rho[1][1]],
|
|
122
|
+
[c * rho[0][0] + d * rho[1][0], c * rho[0][1] + d * rho[1][1]],
|
|
123
|
+
]
|
|
124
|
+
|
|
125
|
+
# Compute (U * ρ) * U†
|
|
126
|
+
rho_prime = [
|
|
127
|
+
[
|
|
128
|
+
u_rho[0][0] * a.conjugate() + u_rho[0][1] * b.conjugate(),
|
|
129
|
+
u_rho[0][0] * c.conjugate() + u_rho[0][1] * d.conjugate(),
|
|
130
|
+
],
|
|
131
|
+
[
|
|
132
|
+
u_rho[1][0] * a.conjugate() + u_rho[1][1] * b.conjugate(),
|
|
133
|
+
u_rho[1][0] * c.conjugate() + u_rho[1][1] * d.conjugate(),
|
|
134
|
+
],
|
|
135
|
+
]
|
|
136
|
+
|
|
137
|
+
# Extract Bloch components: Tr(ρ'σi) = 2 * Re[...]
|
|
138
|
+
new_x = 2 * rho_prime[0][1].real + 2 * rho_prime[1][0].real
|
|
139
|
+
new_y = 2 * (rho_prime[0][1].imag - rho_prime[1][0].imag)
|
|
140
|
+
new_z = 2 * rho_prime[0][0].real - 1 # since Tr(ρ') = 1
|
|
141
|
+
|
|
142
|
+
p = math.sqrt(new_x**2 + new_y**2 + new_z**2)
|
|
143
|
+
|
|
144
|
+
new_x /= p
|
|
145
|
+
new_y /= p
|
|
146
|
+
new_z /= p
|
|
147
|
+
|
|
148
|
+
self.bloch = [new_x, new_y, new_z]
|
|
149
|
+
|
|
150
|
+
def prob(self, basis=Pauli.PauliZ):
|
|
151
|
+
"""Sample a classical outcome from the current 'quantum' state"""
|
|
152
|
+
if basis == Pauli.PauliZ:
|
|
153
|
+
prob_1 = (1 - self.bloch[2]) / 2
|
|
154
|
+
elif basis == Pauli.PauliX:
|
|
155
|
+
prob_1 = (1 - self.bloch[0]) / 2
|
|
156
|
+
elif basis == Pauli.PauliY:
|
|
157
|
+
prob_1 = (1 - self.bloch[1]) / 2
|
|
158
|
+
else:
|
|
159
|
+
raise ValueError(f"Unsupported basis: {basis}")
|
|
160
|
+
return prob_1
|
|
161
|
+
|
|
162
|
+
def m(self):
|
|
163
|
+
result = random.random() < self.prob()
|
|
164
|
+
self.reset()
|
|
165
|
+
if result:
|
|
166
|
+
self.x()
|
|
167
|
+
return result
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
# Provided by Elara (the custom OpenAI GPT)
|
|
171
|
+
def _cpauli_lhv(prob, targ, axis, anti, theta=math.pi):
|
|
172
|
+
"""
|
|
173
|
+
Apply a 'soft' controlled-Pauli gate: rotate target qubit
|
|
174
|
+
proportionally to control's Z expectation value.
|
|
175
|
+
|
|
176
|
+
theta: full rotation angle if control in |1⟩
|
|
177
|
+
"""
|
|
178
|
+
# Control influence is (1 - ctrl.bloch[2]) / 2 = P(|1⟩)
|
|
179
|
+
# BUT we avoid collapse by using the expectation value:
|
|
180
|
+
control_influence = (1 - prob) if anti else prob
|
|
181
|
+
|
|
182
|
+
effective_theta = control_influence * theta
|
|
183
|
+
|
|
184
|
+
# Apply partial rotation to target qubit:
|
|
185
|
+
if axis == Pauli.PauliX:
|
|
186
|
+
targ.rx(effective_theta)
|
|
187
|
+
elif axis == Pauli.PauliY:
|
|
188
|
+
targ.ry(effective_theta)
|
|
189
|
+
elif axis == Pauli.PauliZ:
|
|
190
|
+
targ.rz(effective_theta)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class QrackAceBackend:
|
|
194
|
+
"""A back end for elided quantum error correction
|
|
195
|
+
|
|
196
|
+
This back end uses elided repetition code on a nearest-neighbor topology to emulate
|
|
197
|
+
a utility-scale superconducting chip quantum computer in very little memory.4
|
|
198
|
+
|
|
199
|
+
The backend was originally designed assuming an (orbifolded) 2D qubit grid like 2019 Sycamore.
|
|
200
|
+
However, it quickly became apparent that users can basically design their own connectivity topologies,
|
|
201
|
+
without breaking the concept. (Not all will work equally well.)
|
|
202
|
+
|
|
203
|
+
Consider distributing the different "patches" to different GPUs with self.sim[sim_id].set_device(gpu_id)!
|
|
204
|
+
(If you have 3+ patches, maybe your discrete GPU can do multiple patches in the time it takes an Intel HD
|
|
205
|
+
to do one patch worth of work!)
|
|
206
|
+
|
|
207
|
+
Attributes:
|
|
208
|
+
sim(QrackSimulator): Array of simulators corresponding to "patches" between boundary rows.
|
|
209
|
+
long_range_columns(int): How many ideal rows between QEC boundary rows?
|
|
210
|
+
is_transpose(bool): Rows are long if False, columns are long if True
|
|
211
|
+
"""
|
|
212
|
+
|
|
213
|
+
def __init__(
|
|
214
|
+
self,
|
|
215
|
+
qubit_count=1,
|
|
216
|
+
long_range_columns=4,
|
|
217
|
+
long_range_rows=4,
|
|
218
|
+
is_transpose=False,
|
|
219
|
+
isTensorNetwork=False,
|
|
220
|
+
isSchmidtDecomposeMulti=False,
|
|
221
|
+
isSchmidtDecompose=True,
|
|
222
|
+
isStabilizerHybrid=False,
|
|
223
|
+
isBinaryDecisionTree=False,
|
|
224
|
+
isPaged=True,
|
|
225
|
+
isCpuGpuHybrid=True,
|
|
226
|
+
isOpenCL=True,
|
|
227
|
+
isHostPointer=(
|
|
228
|
+
True if os.environ.get("PYQRACK_HOST_POINTER_DEFAULT_ON") else False
|
|
229
|
+
),
|
|
230
|
+
noise=0,
|
|
231
|
+
toClone=None,
|
|
232
|
+
):
|
|
233
|
+
if toClone:
|
|
234
|
+
qubit_count = toClone.num_qubits()
|
|
235
|
+
long_range_columns = toClone.long_range_columns
|
|
236
|
+
long_range_rows = toClone.long_range_rows
|
|
237
|
+
is_transpose = toClone.is_transpose
|
|
238
|
+
if qubit_count < 0:
|
|
239
|
+
qubit_count = 0
|
|
240
|
+
if long_range_columns < 0:
|
|
241
|
+
long_range_columns = 0
|
|
242
|
+
|
|
243
|
+
self._factor_width(qubit_count, is_transpose)
|
|
244
|
+
self.long_range_columns = long_range_columns
|
|
245
|
+
self.long_range_rows = long_range_rows
|
|
246
|
+
self.is_transpose = is_transpose
|
|
247
|
+
|
|
248
|
+
fppow = 5
|
|
249
|
+
if "QRACK_FPPOW" in os.environ:
|
|
250
|
+
fppow = int(os.environ.get("QRACK_FPPOW"))
|
|
251
|
+
if fppow < 5:
|
|
252
|
+
self._epsilon = 2**-9
|
|
253
|
+
elif fppow > 5:
|
|
254
|
+
self._epsilon = 2**-51
|
|
255
|
+
else:
|
|
256
|
+
self._epsilon = 2**-22
|
|
257
|
+
|
|
258
|
+
self._coupling_map = None
|
|
259
|
+
|
|
260
|
+
# If there's only one or zero "False" columns or rows,
|
|
261
|
+
# the entire simulator is connected, anyway.
|
|
262
|
+
len_col_seq = long_range_columns + 1
|
|
263
|
+
col_patch_count = (self._row_length + len_col_seq - 1) // len_col_seq
|
|
264
|
+
if (self._row_length < 3) or ((long_range_columns + 1) >= self._row_length):
|
|
265
|
+
self._is_col_long_range = [True] * self._row_length
|
|
266
|
+
else:
|
|
267
|
+
col_seq = [True] * long_range_columns + [False]
|
|
268
|
+
self._is_col_long_range = (col_seq * col_patch_count)[: self._row_length]
|
|
269
|
+
if long_range_columns < self._row_length:
|
|
270
|
+
self._is_col_long_range[-1] = False
|
|
271
|
+
len_row_seq = long_range_rows + 1
|
|
272
|
+
row_patch_count = (self._col_length + len_row_seq - 1) // len_row_seq
|
|
273
|
+
if (self._col_length < 3) or ((long_range_rows + 1) >= self._col_length):
|
|
274
|
+
self._is_row_long_range = [True] * self._col_length
|
|
275
|
+
else:
|
|
276
|
+
row_seq = [True] * long_range_rows + [False]
|
|
277
|
+
self._is_row_long_range = (row_seq * row_patch_count)[: self._col_length]
|
|
278
|
+
if long_range_rows < self._col_length:
|
|
279
|
+
self._is_row_long_range[-1] = False
|
|
280
|
+
sim_count = col_patch_count * row_patch_count
|
|
281
|
+
|
|
282
|
+
self._qubits = []
|
|
283
|
+
sim_counts = [0] * sim_count
|
|
284
|
+
sim_id = 0
|
|
285
|
+
tot_qubits = 0
|
|
286
|
+
for r in self._is_row_long_range:
|
|
287
|
+
for c in self._is_col_long_range:
|
|
288
|
+
qubit = [(sim_id, sim_counts[sim_id])]
|
|
289
|
+
sim_counts[sim_id] += 1
|
|
290
|
+
|
|
291
|
+
if (not c) or (not r):
|
|
292
|
+
t_sim_id = (sim_id + 1) % sim_count
|
|
293
|
+
qubit.append((t_sim_id, sim_counts[t_sim_id]))
|
|
294
|
+
sim_counts[t_sim_id] += 1
|
|
295
|
+
|
|
296
|
+
qubit.append(
|
|
297
|
+
LHVQubit(
|
|
298
|
+
toClone=(
|
|
299
|
+
toClone._qubits[tot_qubits][2] if toClone else None
|
|
300
|
+
)
|
|
301
|
+
)
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
if (not c) and (not r):
|
|
305
|
+
t_sim_id = (sim_id + col_patch_count) % sim_count
|
|
306
|
+
qubit.append((t_sim_id, sim_counts[t_sim_id]))
|
|
307
|
+
sim_counts[t_sim_id] += 1
|
|
308
|
+
|
|
309
|
+
t_sim_id = (t_sim_id + 1) % sim_count
|
|
310
|
+
qubit.append((t_sim_id, sim_counts[t_sim_id]))
|
|
311
|
+
sim_counts[t_sim_id] += 1
|
|
312
|
+
|
|
313
|
+
if not c:
|
|
314
|
+
sim_id = (sim_id + 1) % sim_count
|
|
315
|
+
|
|
316
|
+
self._qubits.append(qubit)
|
|
317
|
+
tot_qubits += 1
|
|
318
|
+
|
|
319
|
+
self.sim = []
|
|
320
|
+
for i in range(sim_count):
|
|
321
|
+
self.sim.append(
|
|
322
|
+
toClone.sim[i].clone()
|
|
323
|
+
if toClone
|
|
324
|
+
else QrackSimulator(
|
|
325
|
+
sim_counts[i],
|
|
326
|
+
isTensorNetwork=isTensorNetwork,
|
|
327
|
+
isSchmidtDecomposeMulti=isSchmidtDecomposeMulti,
|
|
328
|
+
isSchmidtDecompose=isSchmidtDecompose,
|
|
329
|
+
isStabilizerHybrid=isStabilizerHybrid,
|
|
330
|
+
isBinaryDecisionTree=isBinaryDecisionTree,
|
|
331
|
+
isPaged=isPaged,
|
|
332
|
+
isCpuGpuHybrid=isCpuGpuHybrid,
|
|
333
|
+
isOpenCL=isOpenCL,
|
|
334
|
+
isHostPointer=isHostPointer,
|
|
335
|
+
noise=noise,
|
|
336
|
+
)
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
# You can still "monkey-patch" this, after the constructor.
|
|
340
|
+
if "QRACK_QUNIT_SEPARABILITY_THRESHOLD" not in os.environ:
|
|
341
|
+
# (1 - 1 / sqrt(2)) / 4 (but empirically tuned)
|
|
342
|
+
self.sim[i].set_sdrp(0.073223304703363119)
|
|
343
|
+
|
|
344
|
+
def clone(self):
|
|
345
|
+
return QrackAceBackend(toClone=self)
|
|
346
|
+
|
|
347
|
+
def num_qubits(self):
|
|
348
|
+
return self._row_length * self._col_length
|
|
349
|
+
|
|
350
|
+
def get_row_length(self):
|
|
351
|
+
return self._row_length
|
|
352
|
+
|
|
353
|
+
def get_column_length(self):
|
|
354
|
+
return self._col_length
|
|
355
|
+
|
|
356
|
+
def _factor_width(self, width, is_transpose=False):
|
|
357
|
+
col_len = math.floor(math.sqrt(width))
|
|
358
|
+
while ((width // col_len) * col_len) != width:
|
|
359
|
+
col_len -= 1
|
|
360
|
+
row_len = width // col_len
|
|
361
|
+
|
|
362
|
+
self._col_length, self._row_length = (
|
|
363
|
+
(row_len, col_len) if is_transpose else (col_len, row_len)
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
def _ct_pair_prob(self, q1, q2):
|
|
367
|
+
p1 = self.sim[q1[0]].prob(q1[1]) if isinstance(q1, tuple) else q1.prob()
|
|
368
|
+
p2 = self.sim[q2[0]].prob(q2[1]) if isinstance(q2, tuple) else q2.prob()
|
|
369
|
+
|
|
370
|
+
if p1 < p2:
|
|
371
|
+
return p2, q1
|
|
372
|
+
|
|
373
|
+
return p1, q2
|
|
374
|
+
|
|
375
|
+
def _cz_shadow(self, q1, q2):
|
|
376
|
+
prob_max, t = self._ct_pair_prob(q1, q2)
|
|
377
|
+
if prob_max > 0.5:
|
|
378
|
+
if isinstance(t, tuple):
|
|
379
|
+
self.sim[t[0]].z(t[1])
|
|
380
|
+
else:
|
|
381
|
+
t.z()
|
|
382
|
+
|
|
383
|
+
def _qec_x(self, c):
|
|
384
|
+
if isinstance(c, tuple):
|
|
385
|
+
self.sim[c[0]].x(c[1])
|
|
386
|
+
else:
|
|
387
|
+
c.x()
|
|
388
|
+
|
|
389
|
+
def _qec_h(self, t):
|
|
390
|
+
if isinstance(t, tuple):
|
|
391
|
+
self.sim[t[0]].h(t[1])
|
|
392
|
+
else:
|
|
393
|
+
t.h()
|
|
394
|
+
|
|
395
|
+
def _qec_s(self, t):
|
|
396
|
+
if isinstance(t, tuple):
|
|
397
|
+
self.sim[t[0]].s(t[1])
|
|
398
|
+
else:
|
|
399
|
+
t.s()
|
|
400
|
+
|
|
401
|
+
def _qec_adjs(self, t):
|
|
402
|
+
if isinstance(t, tuple):
|
|
403
|
+
self.sim[t[0]].adjs(t[1])
|
|
404
|
+
else:
|
|
405
|
+
t.adjs()
|
|
406
|
+
|
|
407
|
+
def _anti_cz_shadow(self, c, t):
|
|
408
|
+
self._qec_x(c)
|
|
409
|
+
self._cz_shadow(c, t)
|
|
410
|
+
self._qec_x(c)
|
|
411
|
+
|
|
412
|
+
def _cx_shadow(self, c, t):
|
|
413
|
+
self._qec_h(t)
|
|
414
|
+
self._cz_shadow(c, t)
|
|
415
|
+
self._qec_h(t)
|
|
416
|
+
|
|
417
|
+
def _anti_cx_shadow(self, c, t):
|
|
418
|
+
self._qec_x(c)
|
|
419
|
+
self._cx_shadow(c, t)
|
|
420
|
+
self._qec_x(c)
|
|
421
|
+
|
|
422
|
+
def _cy_shadow(self, c, t):
|
|
423
|
+
self._qec_adjs(t)
|
|
424
|
+
self._cx_shadow(c, t)
|
|
425
|
+
self._qec_s(t)
|
|
426
|
+
|
|
427
|
+
def _anti_cy_shadow(self, c, t):
|
|
428
|
+
self._qec_x(c)
|
|
429
|
+
self._cy_shadow(c, t)
|
|
430
|
+
self._qec_x(c)
|
|
431
|
+
|
|
432
|
+
def _unpack(self, lq):
|
|
433
|
+
return self._qubits[lq]
|
|
434
|
+
|
|
435
|
+
def _get_qb_lhv_indices(self, hq):
|
|
436
|
+
qb = []
|
|
437
|
+
if len(hq) < 2:
|
|
438
|
+
qb = [0]
|
|
439
|
+
lhv = -1
|
|
440
|
+
elif len(hq) < 4:
|
|
441
|
+
qb = [0, 1]
|
|
442
|
+
lhv = 2
|
|
443
|
+
else:
|
|
444
|
+
qb = [0, 1, 3, 4]
|
|
445
|
+
lhv = 2
|
|
446
|
+
|
|
447
|
+
return qb, lhv
|
|
448
|
+
|
|
449
|
+
def _get_lhv_bloch_angles(self, sim):
|
|
450
|
+
# Z axis
|
|
451
|
+
z = sim.bloch[2]
|
|
452
|
+
|
|
453
|
+
# X axis
|
|
454
|
+
x = sim.bloch[0]
|
|
455
|
+
|
|
456
|
+
# Y axis
|
|
457
|
+
y = sim.bloch[1]
|
|
458
|
+
|
|
459
|
+
inclination = math.atan2(math.sqrt(x**2 + y**2), z)
|
|
460
|
+
azimuth = math.atan2(y, x)
|
|
461
|
+
|
|
462
|
+
return azimuth, inclination
|
|
463
|
+
|
|
464
|
+
def _get_bloch_angles(self, hq):
|
|
465
|
+
sim = self.sim[hq[0]].clone()
|
|
466
|
+
q = hq[1]
|
|
467
|
+
sim.separate([q])
|
|
468
|
+
|
|
469
|
+
# Z axis
|
|
470
|
+
z = 1 - 2 * sim.prob(q)
|
|
471
|
+
|
|
472
|
+
# X axis
|
|
473
|
+
sim.h(q)
|
|
474
|
+
x = 1 - 2 * sim.prob(q)
|
|
475
|
+
sim.h(q)
|
|
476
|
+
|
|
477
|
+
# Y axis
|
|
478
|
+
sim.adjs(q)
|
|
479
|
+
sim.h(q)
|
|
480
|
+
y = 1 - 2 * sim.prob(q)
|
|
481
|
+
sim.h(q)
|
|
482
|
+
sim.s(q)
|
|
483
|
+
|
|
484
|
+
inclination = math.atan2(math.sqrt(x**2 + y**2), z)
|
|
485
|
+
azimuth = math.atan2(y, x)
|
|
486
|
+
|
|
487
|
+
return azimuth, inclination
|
|
488
|
+
|
|
489
|
+
def _rotate_to_bloch(self, hq, delta_azimuth, delta_inclination):
|
|
490
|
+
sim = self.sim[hq[0]]
|
|
491
|
+
q = hq[1]
|
|
492
|
+
|
|
493
|
+
# Apply rotation as "Azimuth, Inclination" (AI)
|
|
494
|
+
cosA = math.cos(delta_azimuth)
|
|
495
|
+
sinA = math.sin(delta_azimuth)
|
|
496
|
+
cosI = math.cos(delta_inclination / 2)
|
|
497
|
+
sinI = math.sin(delta_inclination / 2)
|
|
498
|
+
|
|
499
|
+
m00 = complex(cosI, 0)
|
|
500
|
+
m01 = complex(-cosA, sinA) * sinI
|
|
501
|
+
m10 = complex(cosA, sinA) * sinI
|
|
502
|
+
m11 = complex(cosI, 0)
|
|
503
|
+
|
|
504
|
+
sim.mtrx([m00, m01, m10, m11], q)
|
|
505
|
+
|
|
506
|
+
def _rotate_lhv_to_bloch(self, sim, delta_azimuth, delta_inclination):
|
|
507
|
+
# Apply rotation as "Azimuth, Inclination" (AI)
|
|
508
|
+
cosA = math.cos(delta_azimuth)
|
|
509
|
+
sinA = math.sin(delta_azimuth)
|
|
510
|
+
cosI = math.cos(delta_inclination / 2)
|
|
511
|
+
sinI = math.sin(delta_inclination / 2)
|
|
512
|
+
|
|
513
|
+
m00 = complex(cosI, 0)
|
|
514
|
+
m01 = complex(-cosA, sinA) * sinI
|
|
515
|
+
m10 = complex(cosA, sinA) * sinI
|
|
516
|
+
m11 = complex(cosI, 0)
|
|
517
|
+
|
|
518
|
+
sim.mtrx([m00, m01, m10, m11])
|
|
519
|
+
|
|
520
|
+
def _correct(self, lq, phase=False, skip_rotation=False):
|
|
521
|
+
hq = self._unpack(lq)
|
|
522
|
+
|
|
523
|
+
if len(hq) == 1:
|
|
524
|
+
return
|
|
525
|
+
|
|
526
|
+
qb, lhv = self._get_qb_lhv_indices(hq)
|
|
527
|
+
|
|
528
|
+
if phase:
|
|
529
|
+
for q in qb:
|
|
530
|
+
b = hq[q]
|
|
531
|
+
self.sim[b[0]].h(b[1])
|
|
532
|
+
b = hq[lhv]
|
|
533
|
+
b.h()
|
|
534
|
+
|
|
535
|
+
if len(hq) == 5:
|
|
536
|
+
# RMS
|
|
537
|
+
p = [
|
|
538
|
+
self.sim[hq[0][0]].prob(hq[0][1]),
|
|
539
|
+
self.sim[hq[1][0]].prob(hq[1][1]),
|
|
540
|
+
hq[2].prob(),
|
|
541
|
+
self.sim[hq[3][0]].prob(hq[3][1]),
|
|
542
|
+
self.sim[hq[4][0]].prob(hq[4][1]),
|
|
543
|
+
]
|
|
544
|
+
# Balancing suggestion from Elara (the custom OpenAI GPT)
|
|
545
|
+
prms = math.sqrt(
|
|
546
|
+
(p[0] ** 2 + p[1] ** 2 + 3 * (p[2] ** 2) + p[3] ** 2 + p[4] ** 2) / 7
|
|
547
|
+
)
|
|
548
|
+
qrms = math.sqrt(
|
|
549
|
+
(
|
|
550
|
+
(1 - p[0]) ** 2
|
|
551
|
+
+ (1 - p[1]) ** 2
|
|
552
|
+
+ 3 * ((1 - p[2]) ** 2)
|
|
553
|
+
+ (1 - p[3]) ** 2
|
|
554
|
+
+ (1 - p[4]) ** 2
|
|
555
|
+
)
|
|
556
|
+
/ 7
|
|
557
|
+
)
|
|
558
|
+
result = ((prms + (1 - qrms)) / 2) >= 0.5
|
|
559
|
+
syndrome = (
|
|
560
|
+
[1 - p[0], 1 - p[1], 1 - p[2], 1 - p[3], 1 - p[4]]
|
|
561
|
+
if result
|
|
562
|
+
else [p[0], p[1], p[2], p[3], p[4]]
|
|
563
|
+
)
|
|
564
|
+
for q in range(5):
|
|
565
|
+
if syndrome[q] > (0.5 + self._epsilon):
|
|
566
|
+
if q == 2:
|
|
567
|
+
hq[q].x()
|
|
568
|
+
else:
|
|
569
|
+
self.sim[hq[q][0]].x(hq[q][1])
|
|
570
|
+
|
|
571
|
+
if not skip_rotation:
|
|
572
|
+
a, i = [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]
|
|
573
|
+
a[0], i[0] = self._get_bloch_angles(hq[0])
|
|
574
|
+
a[1], i[1] = self._get_bloch_angles(hq[1])
|
|
575
|
+
a[2], i[2] = self._get_lhv_bloch_angles(hq[2])
|
|
576
|
+
a[3], i[3] = self._get_bloch_angles(hq[3])
|
|
577
|
+
a[4], i[4] = self._get_bloch_angles(hq[4])
|
|
578
|
+
|
|
579
|
+
a_target = 0
|
|
580
|
+
i_target = 0
|
|
581
|
+
for x in range(5):
|
|
582
|
+
if x == 2:
|
|
583
|
+
continue
|
|
584
|
+
a_target += a[x]
|
|
585
|
+
i_target += i[x]
|
|
586
|
+
|
|
587
|
+
a_target /= 5
|
|
588
|
+
i_target /= 5
|
|
589
|
+
for x in range(5):
|
|
590
|
+
if x == 2:
|
|
591
|
+
self._rotate_lhv_to_bloch(
|
|
592
|
+
hq[x], a_target - a[x], i_target - i[x]
|
|
593
|
+
)
|
|
594
|
+
else:
|
|
595
|
+
self._rotate_to_bloch(hq[x], a_target - a[x], i_target - i[x])
|
|
596
|
+
|
|
597
|
+
else:
|
|
598
|
+
# RMS
|
|
599
|
+
p = [
|
|
600
|
+
self.sim[hq[0][0]].prob(hq[0][1]),
|
|
601
|
+
self.sim[hq[1][0]].prob(hq[1][1]),
|
|
602
|
+
hq[2].prob(),
|
|
603
|
+
]
|
|
604
|
+
# Balancing suggestion from Elara (the custom OpenAI GPT)
|
|
605
|
+
prms = math.sqrt((p[0] ** 2 + p[1] ** 2 + p[2] ** 2) / 3)
|
|
606
|
+
qrms = math.sqrt(((1 - p[0]) ** 2 + (1 - p[1]) ** 2 + (1 - p[2]) ** 2) / 3)
|
|
607
|
+
result = ((prms + (1 - qrms)) / 2) >= 0.5
|
|
608
|
+
syndrome = [1 - p[0], 1 - p[1], 1 - p[2]] if result else [p[0], p[1], p[2]]
|
|
609
|
+
for q in range(3):
|
|
610
|
+
if syndrome[q] > (0.5 + self._epsilon):
|
|
611
|
+
if q == 2:
|
|
612
|
+
hq[q].x()
|
|
613
|
+
else:
|
|
614
|
+
self.sim[hq[q][0]].x(hq[q][1])
|
|
615
|
+
|
|
616
|
+
if not skip_rotation:
|
|
617
|
+
a, i = [0, 0, 0], [0, 0, 0]
|
|
618
|
+
a[0], i[0] = self._get_bloch_angles(hq[0])
|
|
619
|
+
a[1], i[1] = self._get_bloch_angles(hq[1])
|
|
620
|
+
a[2], i[2] = self._get_lhv_bloch_angles(hq[2])
|
|
621
|
+
|
|
622
|
+
a_target = 0
|
|
623
|
+
i_target = 0
|
|
624
|
+
for x in range(3):
|
|
625
|
+
if x == 2:
|
|
626
|
+
continue
|
|
627
|
+
a_target += a[x]
|
|
628
|
+
i_target += i[x]
|
|
629
|
+
|
|
630
|
+
a_target /= 3
|
|
631
|
+
i_target /= 3
|
|
632
|
+
for x in range(3):
|
|
633
|
+
if x == 2:
|
|
634
|
+
self._rotate_lhv_to_bloch(
|
|
635
|
+
hq[x], a_target - a[x], i_target - i[x]
|
|
636
|
+
)
|
|
637
|
+
else:
|
|
638
|
+
self._rotate_to_bloch(hq[x], a_target - a[x], i_target - i[x])
|
|
639
|
+
|
|
640
|
+
if phase:
|
|
641
|
+
for q in qb:
|
|
642
|
+
b = hq[q]
|
|
643
|
+
self.sim[b[0]].h(b[1])
|
|
644
|
+
b = hq[lhv]
|
|
645
|
+
b.h()
|
|
646
|
+
|
|
647
|
+
def apply_magnetic_bias(self, q, b):
|
|
648
|
+
if b == 0:
|
|
649
|
+
return
|
|
650
|
+
b = math.exp(b)
|
|
651
|
+
for x in q:
|
|
652
|
+
hq = self._unpack(x)
|
|
653
|
+
for c in range(len(hq)):
|
|
654
|
+
h = hq[c]
|
|
655
|
+
if c == 2:
|
|
656
|
+
a, i = self._get_lhv_bloch_angles(h)
|
|
657
|
+
self._rotate_lhv_to_bloch(
|
|
658
|
+
h,
|
|
659
|
+
math.atan(math.tan(a) * b) - a,
|
|
660
|
+
math.atan(math.tan(i) * b) - i,
|
|
661
|
+
)
|
|
662
|
+
else:
|
|
663
|
+
a, i = self._get_bloch_angles(h)
|
|
664
|
+
self._rotate_to_bloch(
|
|
665
|
+
h,
|
|
666
|
+
math.atan(math.tan(a) * b) - a,
|
|
667
|
+
math.atan(math.tan(i) * b) - i,
|
|
668
|
+
)
|
|
669
|
+
|
|
670
|
+
def u(self, lq, th, ph, lm):
|
|
671
|
+
hq = self._unpack(lq)
|
|
672
|
+
if len(hq) < 2:
|
|
673
|
+
b = hq[0]
|
|
674
|
+
self.sim[b[0]].u(b[1], th, ph, lm)
|
|
675
|
+
return
|
|
676
|
+
|
|
677
|
+
qb, lhv = self._get_qb_lhv_indices(hq)
|
|
678
|
+
|
|
679
|
+
for q in qb:
|
|
680
|
+
b = hq[q]
|
|
681
|
+
self.sim[b[0]].u(b[1], th, ph, lm)
|
|
682
|
+
|
|
683
|
+
b = hq[lhv]
|
|
684
|
+
b.u(th, ph, lm)
|
|
685
|
+
|
|
686
|
+
self._correct(lq, False, True)
|
|
687
|
+
self._correct(lq, True, False)
|
|
688
|
+
|
|
689
|
+
def r(self, p, th, lq):
|
|
690
|
+
hq = self._unpack(lq)
|
|
691
|
+
if len(hq) < 2:
|
|
692
|
+
b = hq[0]
|
|
693
|
+
self.sim[b[0]].r(p, th, b[1])
|
|
694
|
+
return
|
|
695
|
+
|
|
696
|
+
qb, lhv = self._get_qb_lhv_indices(hq)
|
|
697
|
+
|
|
698
|
+
for q in qb:
|
|
699
|
+
b = hq[q]
|
|
700
|
+
self.sim[b[0]].r(p, th, b[1])
|
|
701
|
+
|
|
702
|
+
b = hq[lhv]
|
|
703
|
+
if p == Pauli.PauliX:
|
|
704
|
+
b.rx(th)
|
|
705
|
+
elif p == Pauli.PauliY:
|
|
706
|
+
b.ry(th)
|
|
707
|
+
elif p == Pauli.PauliZ:
|
|
708
|
+
b.rz(th)
|
|
709
|
+
|
|
710
|
+
if p != Pauli.PauliZ:
|
|
711
|
+
self._correct(lq, False, p != Pauli.PauliX)
|
|
712
|
+
if p != Pauli.PauliX:
|
|
713
|
+
self._correct(lq, True)
|
|
714
|
+
|
|
715
|
+
def h(self, lq):
|
|
716
|
+
hq = self._unpack(lq)
|
|
717
|
+
if len(hq) < 2:
|
|
718
|
+
b = hq[0]
|
|
719
|
+
self.sim[b[0]].h(b[1])
|
|
720
|
+
return
|
|
721
|
+
|
|
722
|
+
self._correct(lq)
|
|
723
|
+
|
|
724
|
+
qb, lhv = self._get_qb_lhv_indices(hq)
|
|
725
|
+
|
|
726
|
+
for q in qb:
|
|
727
|
+
b = hq[q]
|
|
728
|
+
self.sim[b[0]].h(b[1])
|
|
729
|
+
|
|
730
|
+
b = hq[lhv]
|
|
731
|
+
b.h()
|
|
732
|
+
|
|
733
|
+
self._correct(lq)
|
|
734
|
+
|
|
735
|
+
def s(self, lq):
|
|
736
|
+
hq = self._unpack(lq)
|
|
737
|
+
if len(hq) < 2:
|
|
738
|
+
b = hq[0]
|
|
739
|
+
self.sim[b[0]].s(b[1])
|
|
740
|
+
return
|
|
741
|
+
|
|
742
|
+
qb, lhv = self._get_qb_lhv_indices(hq)
|
|
743
|
+
|
|
744
|
+
for q in qb:
|
|
745
|
+
b = hq[q]
|
|
746
|
+
self.sim[b[0]].s(b[1])
|
|
747
|
+
|
|
748
|
+
b = hq[lhv]
|
|
749
|
+
b.s()
|
|
750
|
+
|
|
751
|
+
def adjs(self, lq):
|
|
752
|
+
hq = self._unpack(lq)
|
|
753
|
+
if len(hq) < 2:
|
|
754
|
+
b = hq[0]
|
|
755
|
+
self.sim[b[0]].adjs(b[1])
|
|
756
|
+
return
|
|
757
|
+
|
|
758
|
+
qb, lhv = self._get_qb_lhv_indices(hq)
|
|
759
|
+
|
|
760
|
+
for q in qb:
|
|
761
|
+
b = hq[q]
|
|
762
|
+
self.sim[b[0]].adjs(b[1])
|
|
763
|
+
|
|
764
|
+
b = hq[lhv]
|
|
765
|
+
b.adjs()
|
|
766
|
+
|
|
767
|
+
def x(self, lq):
|
|
768
|
+
hq = self._unpack(lq)
|
|
769
|
+
if len(hq) < 2:
|
|
770
|
+
b = hq[0]
|
|
771
|
+
self.sim[b[0]].x(b[1])
|
|
772
|
+
return
|
|
773
|
+
|
|
774
|
+
qb, lhv = self._get_qb_lhv_indices(hq)
|
|
775
|
+
|
|
776
|
+
for q in qb:
|
|
777
|
+
b = hq[q]
|
|
778
|
+
self.sim[b[0]].x(b[1])
|
|
779
|
+
|
|
780
|
+
b = hq[lhv]
|
|
781
|
+
b.x()
|
|
782
|
+
|
|
783
|
+
def y(self, lq):
|
|
784
|
+
hq = self._unpack(lq)
|
|
785
|
+
if len(hq) < 2:
|
|
786
|
+
b = hq[0]
|
|
787
|
+
self.sim[b[0]].y(b[1])
|
|
788
|
+
return
|
|
789
|
+
|
|
790
|
+
qb, lhv = self._get_qb_lhv_indices(hq)
|
|
791
|
+
|
|
792
|
+
for q in qb:
|
|
793
|
+
b = hq[q]
|
|
794
|
+
self.sim[b[0]].y(b[1])
|
|
795
|
+
|
|
796
|
+
b = hq[lhv]
|
|
797
|
+
b.y()
|
|
798
|
+
|
|
799
|
+
def z(self, lq):
|
|
800
|
+
hq = self._unpack(lq)
|
|
801
|
+
if len(hq) < 2:
|
|
802
|
+
b = hq[0]
|
|
803
|
+
self.sim[b[0]].z(b[1])
|
|
804
|
+
return
|
|
805
|
+
|
|
806
|
+
qb, lhv = self._get_qb_lhv_indices(hq)
|
|
807
|
+
|
|
808
|
+
for q in qb:
|
|
809
|
+
b = hq[q]
|
|
810
|
+
self.sim[b[0]].z(b[1])
|
|
811
|
+
|
|
812
|
+
b = hq[lhv]
|
|
813
|
+
b.z()
|
|
814
|
+
|
|
815
|
+
def t(self, lq):
|
|
816
|
+
hq = self._unpack(lq)
|
|
817
|
+
if len(hq) < 2:
|
|
818
|
+
b = hq[0]
|
|
819
|
+
self.sim[b[0]].t(b[1])
|
|
820
|
+
return
|
|
821
|
+
|
|
822
|
+
qb, lhv = self._get_qb_lhv_indices(hq)
|
|
823
|
+
|
|
824
|
+
for q in qb:
|
|
825
|
+
b = hq[q]
|
|
826
|
+
self.sim[b[0]].t(b[1])
|
|
827
|
+
|
|
828
|
+
b = hq[lhv]
|
|
829
|
+
b.t()
|
|
830
|
+
|
|
831
|
+
def adjt(self, lq):
|
|
832
|
+
hq = self._unpack(lq)
|
|
833
|
+
if len(hq) < 2:
|
|
834
|
+
b = hq[0]
|
|
835
|
+
self.sim[b[0]].adjt(b[1])
|
|
836
|
+
return
|
|
837
|
+
|
|
838
|
+
qb, lhv = self._get_qb_lhv_indices(hq)
|
|
839
|
+
|
|
840
|
+
for q in qb:
|
|
841
|
+
b = hq[q]
|
|
842
|
+
self.sim[b[0]].adjt(b[1])
|
|
843
|
+
|
|
844
|
+
b = hq[lhv]
|
|
845
|
+
b.adjt()
|
|
846
|
+
|
|
847
|
+
def _get_gate(self, pauli, anti, sim_id):
|
|
848
|
+
gate = None
|
|
849
|
+
shadow = None
|
|
850
|
+
if pauli == Pauli.PauliX:
|
|
851
|
+
gate = self.sim[sim_id].macx if anti else self.sim[sim_id].mcx
|
|
852
|
+
shadow = self._anti_cx_shadow if anti else self._cx_shadow
|
|
853
|
+
elif pauli == Pauli.PauliY:
|
|
854
|
+
gate = self.sim[sim_id].macy if anti else self.sim[sim_id].mcy
|
|
855
|
+
shadow = self._anti_cy_shadow if anti else self._cy_shadow
|
|
856
|
+
elif pauli == Pauli.PauliZ:
|
|
857
|
+
gate = self.sim[sim_id].macz if anti else self.sim[sim_id].mcz
|
|
858
|
+
shadow = self._anti_cz_shadow if anti else self._cz_shadow
|
|
859
|
+
else:
|
|
860
|
+
raise RuntimeError(
|
|
861
|
+
"QrackAceBackend._get_gate() should never return identity!"
|
|
862
|
+
)
|
|
863
|
+
|
|
864
|
+
return gate, shadow
|
|
865
|
+
|
|
866
|
+
def _get_connected(self, i, is_row):
|
|
867
|
+
long_range = self._is_row_long_range if is_row else self._is_col_long_range
|
|
868
|
+
length = self._col_length if is_row else self._row_length
|
|
869
|
+
|
|
870
|
+
connected = [i]
|
|
871
|
+
c = (i - 1) % length
|
|
872
|
+
while long_range[c] and (len(connected) < length):
|
|
873
|
+
connected.append(c)
|
|
874
|
+
c = (c - 1) % length
|
|
875
|
+
if len(connected) < length:
|
|
876
|
+
connected.append(c)
|
|
877
|
+
boundary = len(connected)
|
|
878
|
+
c = (i + 1) % length
|
|
879
|
+
while long_range[c] and (len(connected) < length):
|
|
880
|
+
connected.append(c)
|
|
881
|
+
c = (c + 1) % length
|
|
882
|
+
if len(connected) < length:
|
|
883
|
+
connected.append(c)
|
|
884
|
+
|
|
885
|
+
return connected, boundary
|
|
886
|
+
|
|
887
|
+
def _apply_coupling(self, pauli, anti, qb1, lhv1, hq1, qb2, lhv2, hq2, lq1_lr):
|
|
888
|
+
for q1 in qb1:
|
|
889
|
+
if q1 == lhv1:
|
|
890
|
+
continue
|
|
891
|
+
b1 = hq1[q1]
|
|
892
|
+
gate_fn, shadow_fn = self._get_gate(pauli, anti, b1[0])
|
|
893
|
+
for q2 in qb2:
|
|
894
|
+
if q2 == lhv2:
|
|
895
|
+
continue
|
|
896
|
+
b2 = hq2[q2]
|
|
897
|
+
if b1[0] == b2[0]:
|
|
898
|
+
gate_fn([b1[1]], b2[1])
|
|
899
|
+
elif (
|
|
900
|
+
lq1_lr
|
|
901
|
+
or (b1[1] == b2[1])
|
|
902
|
+
or ((len(qb1) == 2) and (b1[1] == (b2[1] & 1)))
|
|
903
|
+
):
|
|
904
|
+
shadow_fn(b1, b2)
|
|
905
|
+
|
|
906
|
+
def _cpauli(self, lq1, lq2, anti, pauli):
|
|
907
|
+
lq1_row = lq1 // self._row_length
|
|
908
|
+
lq1_col = lq1 % self._row_length
|
|
909
|
+
lq2_row = lq2 // self._row_length
|
|
910
|
+
lq2_col = lq2 % self._row_length
|
|
911
|
+
|
|
912
|
+
hq1 = self._unpack(lq1)
|
|
913
|
+
hq2 = self._unpack(lq2)
|
|
914
|
+
|
|
915
|
+
lq1_lr = len(hq1) == 1
|
|
916
|
+
lq2_lr = len(hq2) == 1
|
|
917
|
+
|
|
918
|
+
self._correct(lq1)
|
|
919
|
+
|
|
920
|
+
qb1, lhv1 = self._get_qb_lhv_indices(hq1)
|
|
921
|
+
qb2, lhv2 = self._get_qb_lhv_indices(hq2)
|
|
922
|
+
# Apply cross coupling on hardware qubits first
|
|
923
|
+
self._apply_coupling(pauli, anti, qb1, lhv1, hq1, qb2, lhv2, hq2, lq1_lr)
|
|
924
|
+
# Apply coupling to the local-hidden-variable target
|
|
925
|
+
if lhv2 >= 0:
|
|
926
|
+
_cpauli_lhv(
|
|
927
|
+
hq1[lhv1].prob() if lhv1 >= 0 else self.sim[hq1[0][0]].prob(hq1[0][1]),
|
|
928
|
+
hq2[lhv2],
|
|
929
|
+
pauli,
|
|
930
|
+
anti,
|
|
931
|
+
)
|
|
932
|
+
|
|
933
|
+
self._correct(lq1, True)
|
|
934
|
+
if pauli != Pauli.PauliZ:
|
|
935
|
+
self._correct(lq2, False, pauli != Pauli.PauliX)
|
|
936
|
+
if pauli != Pauli.PauliX:
|
|
937
|
+
self._correct(lq2, True)
|
|
938
|
+
|
|
939
|
+
def cx(self, lq1, lq2):
|
|
940
|
+
self._cpauli(lq1, lq2, False, Pauli.PauliX)
|
|
941
|
+
|
|
942
|
+
def cy(self, lq1, lq2):
|
|
943
|
+
self._cpauli(lq1, lq2, False, Pauli.PauliY)
|
|
944
|
+
|
|
945
|
+
def cz(self, lq1, lq2):
|
|
946
|
+
self._cpauli(lq1, lq2, False, Pauli.PauliZ)
|
|
947
|
+
|
|
948
|
+
def acx(self, lq1, lq2):
|
|
949
|
+
self._cpauli(lq1, lq2, True, Pauli.PauliX)
|
|
950
|
+
|
|
951
|
+
def acy(self, lq1, lq2):
|
|
952
|
+
self._cpauli(lq1, lq2, True, Pauli.PauliY)
|
|
953
|
+
|
|
954
|
+
def acz(self, lq1, lq2):
|
|
955
|
+
self._cpauli(lq1, lq2, True, Pauli.PauliZ)
|
|
956
|
+
|
|
957
|
+
def mcx(self, lq1, lq2):
|
|
958
|
+
if len(lq1) > 1:
|
|
959
|
+
raise RuntimeError(
|
|
960
|
+
"QrackAceBackend.mcx() is provided for syntax convenience and only supports 1 control qubit!"
|
|
961
|
+
)
|
|
962
|
+
self._cpauli(lq1[0], lq2, False, Pauli.PauliX)
|
|
963
|
+
|
|
964
|
+
def mcy(self, lq1, lq2):
|
|
965
|
+
if len(lq1) > 1:
|
|
966
|
+
raise RuntimeError(
|
|
967
|
+
"QrackAceBackend.mcy() is provided for syntax convenience and only supports 1 control qubit!"
|
|
968
|
+
)
|
|
969
|
+
self._cpauli(lq1[0], lq2, False, Pauli.PauliY)
|
|
970
|
+
|
|
971
|
+
def mcz(self, lq1, lq2):
|
|
972
|
+
if len(lq1) > 1:
|
|
973
|
+
raise RuntimeError(
|
|
974
|
+
"QrackAceBackend.mcz() is provided for syntax convenience and only supports 1 control qubit!"
|
|
975
|
+
)
|
|
976
|
+
self._cpauli(lq1[0], lq2, False, Pauli.PauliZ)
|
|
977
|
+
|
|
978
|
+
def macx(self, lq1, lq2):
|
|
979
|
+
if len(lq1) > 1:
|
|
980
|
+
raise RuntimeError(
|
|
981
|
+
"QrackAceBackend.macx() is provided for syntax convenience and only supports 1 control qubit!"
|
|
982
|
+
)
|
|
983
|
+
self._cpauli(lq1[0], lq2, True, Pauli.PauliX)
|
|
984
|
+
|
|
985
|
+
def macy(self, lq1, lq2):
|
|
986
|
+
if len(lq1) > 1:
|
|
987
|
+
raise RuntimeError(
|
|
988
|
+
"QrackAceBackend.macy() is provided for syntax convenience and only supports 1 control qubit!"
|
|
989
|
+
)
|
|
990
|
+
self._cpauli(lq1[0], lq2, True, Pauli.PauliY)
|
|
991
|
+
|
|
992
|
+
def macz(self, lq1, lq2):
|
|
993
|
+
if len(lq1) > 1:
|
|
994
|
+
raise RuntimeError(
|
|
995
|
+
"QrackAceBackend.macz() is provided for syntax convenience and only supports 1 control qubit!"
|
|
996
|
+
)
|
|
997
|
+
self._cpauli(lq1[0], lq2, True, Pauli.PauliZ)
|
|
998
|
+
|
|
999
|
+
def swap(self, lq1, lq2):
|
|
1000
|
+
self.cx(lq1, lq2)
|
|
1001
|
+
self.cx(lq2, lq1)
|
|
1002
|
+
self.cx(lq1, lq2)
|
|
1003
|
+
|
|
1004
|
+
def iswap(self, lq1, lq2):
|
|
1005
|
+
self.swap(lq1, lq2)
|
|
1006
|
+
self.cz(lq1, lq2)
|
|
1007
|
+
self.s(lq1)
|
|
1008
|
+
self.s(lq2)
|
|
1009
|
+
|
|
1010
|
+
def adjiswap(self, lq1, lq2):
|
|
1011
|
+
self.adjs(lq2)
|
|
1012
|
+
self.adjs(lq1)
|
|
1013
|
+
self.cz(lq1, lq2)
|
|
1014
|
+
self.swap(lq1, lq2)
|
|
1015
|
+
|
|
1016
|
+
def prob(self, lq):
|
|
1017
|
+
hq = self._unpack(lq)
|
|
1018
|
+
if len(hq) < 2:
|
|
1019
|
+
b = hq[0]
|
|
1020
|
+
return self.sim[b[0]].prob(b[1])
|
|
1021
|
+
|
|
1022
|
+
self._correct(lq)
|
|
1023
|
+
if len(hq) == 5:
|
|
1024
|
+
# RMS
|
|
1025
|
+
p = [
|
|
1026
|
+
self.sim[hq[0][0]].prob(hq[0][1]),
|
|
1027
|
+
self.sim[hq[1][0]].prob(hq[1][1]),
|
|
1028
|
+
hq[2].prob(),
|
|
1029
|
+
self.sim[hq[3][0]].prob(hq[3][1]),
|
|
1030
|
+
self.sim[hq[4][0]].prob(hq[4][1]),
|
|
1031
|
+
]
|
|
1032
|
+
# Balancing suggestion from Elara (the custom OpenAI GPT)
|
|
1033
|
+
prms = math.sqrt(
|
|
1034
|
+
(p[0] ** 2 + p[1] ** 2 + 3 * (p[2] ** 2) + p[3] ** 2 + p[4] ** 2) / 7
|
|
1035
|
+
)
|
|
1036
|
+
qrms = math.sqrt(
|
|
1037
|
+
(
|
|
1038
|
+
(1 - p[0]) ** 2
|
|
1039
|
+
+ (1 - p[1]) ** 2
|
|
1040
|
+
+ 3 * ((1 - p[2]) ** 2)
|
|
1041
|
+
+ (1 - p[3]) ** 2
|
|
1042
|
+
+ (1 - p[4]) ** 2
|
|
1043
|
+
)
|
|
1044
|
+
/ 7
|
|
1045
|
+
)
|
|
1046
|
+
else:
|
|
1047
|
+
# RMS
|
|
1048
|
+
p = [
|
|
1049
|
+
self.sim[hq[0][0]].prob(hq[0][1]),
|
|
1050
|
+
self.sim[hq[1][0]].prob(hq[1][1]),
|
|
1051
|
+
hq[2].prob(),
|
|
1052
|
+
]
|
|
1053
|
+
# Balancing suggestion from Elara (the custom OpenAI GPT)
|
|
1054
|
+
prms = math.sqrt((p[0] ** 2 + p[1] ** 2 + p[2] ** 2) / 3)
|
|
1055
|
+
qrms = math.sqrt(((1 - p[0]) ** 2 + (1 - p[1]) ** 2 + (1 - p[2]) ** 2) / 3)
|
|
1056
|
+
|
|
1057
|
+
return (prms + (1 - qrms)) / 2
|
|
1058
|
+
|
|
1059
|
+
def m(self, lq):
|
|
1060
|
+
hq = self._unpack(lq)
|
|
1061
|
+
if len(hq) < 2:
|
|
1062
|
+
b = hq[0]
|
|
1063
|
+
return self.sim[b[0]].m(b[1])
|
|
1064
|
+
|
|
1065
|
+
p = self.prob(lq)
|
|
1066
|
+
result = ((p + self._epsilon) >= 1) or (random.random() < p)
|
|
1067
|
+
|
|
1068
|
+
qb, lhv = self._get_qb_lhv_indices(hq)
|
|
1069
|
+
|
|
1070
|
+
for q in qb:
|
|
1071
|
+
b = hq[q]
|
|
1072
|
+
p = self.sim[b[0]].prob(b[1]) if result else (1 - self.sim[b[0]].prob(b[1]))
|
|
1073
|
+
if p < self._epsilon:
|
|
1074
|
+
if self.sim[b[0]].m(b[1]) != result:
|
|
1075
|
+
self.sim[b[0]].x(b[1])
|
|
1076
|
+
else:
|
|
1077
|
+
self.sim[b[0]].force_m(b[1], result)
|
|
1078
|
+
|
|
1079
|
+
b = hq[lhv]
|
|
1080
|
+
b.reset()
|
|
1081
|
+
if result:
|
|
1082
|
+
b.x()
|
|
1083
|
+
|
|
1084
|
+
return result
|
|
1085
|
+
|
|
1086
|
+
def force_m(self, lq, result):
|
|
1087
|
+
hq = self._unpack(lq)
|
|
1088
|
+
if len(hq) < 2:
|
|
1089
|
+
b = hq[0]
|
|
1090
|
+
return self.sim[b[0]].force_m(b[1], result)
|
|
1091
|
+
|
|
1092
|
+
self._correct(lq)
|
|
1093
|
+
|
|
1094
|
+
qb, lhv = self._get_qb_lhv_indices(hq)
|
|
1095
|
+
|
|
1096
|
+
for q in qb:
|
|
1097
|
+
b = hq[q]
|
|
1098
|
+
p = self.sim[b[0]].prob(b[1]) if result else (1 - self.sim[b[0]].prob(b[1]))
|
|
1099
|
+
if p < self._epsilon:
|
|
1100
|
+
if self.sim[b[0]].m(b[1]) != result:
|
|
1101
|
+
self.sim[b[0]].x(b[1])
|
|
1102
|
+
else:
|
|
1103
|
+
self.sim[b[0]].force_m(b[1], result)
|
|
1104
|
+
|
|
1105
|
+
b = hq[1]
|
|
1106
|
+
b.reset()
|
|
1107
|
+
if result:
|
|
1108
|
+
b.x()
|
|
1109
|
+
|
|
1110
|
+
return c
|
|
1111
|
+
|
|
1112
|
+
def m_all(self):
|
|
1113
|
+
# Randomize the order of measurement to amortize error.
|
|
1114
|
+
result = 0
|
|
1115
|
+
rows = list(range(self._col_length))
|
|
1116
|
+
random.shuffle(rows)
|
|
1117
|
+
for lq_row in rows:
|
|
1118
|
+
row_offset = lq_row * self._row_length
|
|
1119
|
+
cols = list(range(self._row_length))
|
|
1120
|
+
random.shuffle(cols)
|
|
1121
|
+
for lq_col in cols:
|
|
1122
|
+
lq = row_offset + lq_col
|
|
1123
|
+
if self.m(lq):
|
|
1124
|
+
result |= 1 << lq
|
|
1125
|
+
|
|
1126
|
+
return result
|
|
1127
|
+
|
|
1128
|
+
def measure_shots(self, q, s):
|
|
1129
|
+
samples = []
|
|
1130
|
+
for _ in range(s):
|
|
1131
|
+
clone = self.clone()
|
|
1132
|
+
_sample = clone.m_all()
|
|
1133
|
+
sample = 0
|
|
1134
|
+
for i in range(len(q)):
|
|
1135
|
+
if (_sample >> q[i]) & 1:
|
|
1136
|
+
sample |= 1 << i
|
|
1137
|
+
samples.append(sample)
|
|
1138
|
+
|
|
1139
|
+
return samples
|
|
1140
|
+
|
|
1141
|
+
def _apply_op(self, operation):
|
|
1142
|
+
name = operation.name
|
|
1143
|
+
|
|
1144
|
+
if (name == "id") or (name == "barrier"):
|
|
1145
|
+
# Skip measurement logic
|
|
1146
|
+
return
|
|
1147
|
+
|
|
1148
|
+
conditional = getattr(operation, "conditional", None)
|
|
1149
|
+
if isinstance(conditional, int):
|
|
1150
|
+
conditional_bit_set = (self._classical_register >> conditional) & 1
|
|
1151
|
+
if not conditional_bit_set:
|
|
1152
|
+
return
|
|
1153
|
+
elif conditional is not None:
|
|
1154
|
+
mask = int(conditional.mask, 16)
|
|
1155
|
+
if mask > 0:
|
|
1156
|
+
value = self._classical_memory & mask
|
|
1157
|
+
while (mask & 0x1) == 0:
|
|
1158
|
+
mask >>= 1
|
|
1159
|
+
value >>= 1
|
|
1160
|
+
if value != int(conditional.val, 16):
|
|
1161
|
+
return
|
|
1162
|
+
|
|
1163
|
+
if (name == "u1") or (name == "p"):
|
|
1164
|
+
self._sim.u(operation.qubits[0]._index, 0, 0, float(operation.params[0]))
|
|
1165
|
+
elif name == "u2":
|
|
1166
|
+
self._sim.u(
|
|
1167
|
+
operation.qubits[0]._index,
|
|
1168
|
+
math.pi / 2,
|
|
1169
|
+
float(operation.params[0]),
|
|
1170
|
+
float(operation.params[1]),
|
|
1171
|
+
)
|
|
1172
|
+
elif (name == "u3") or (name == "u"):
|
|
1173
|
+
self._sim.u(
|
|
1174
|
+
operation.qubits[0]._index,
|
|
1175
|
+
float(operation.params[0]),
|
|
1176
|
+
float(operation.params[1]),
|
|
1177
|
+
float(operation.params[2]),
|
|
1178
|
+
)
|
|
1179
|
+
elif name == "r":
|
|
1180
|
+
self._sim.u(
|
|
1181
|
+
operation.qubits[0]._index,
|
|
1182
|
+
float(operation.params[0]),
|
|
1183
|
+
float(operation.params[1]) - math.pi / 2,
|
|
1184
|
+
(-1 * float(operation.params[1])) + math.pi / 2,
|
|
1185
|
+
)
|
|
1186
|
+
elif name == "rx":
|
|
1187
|
+
self._sim.r(
|
|
1188
|
+
Pauli.PauliX, float(operation.params[0]), operation.qubits[0]._index
|
|
1189
|
+
)
|
|
1190
|
+
elif name == "ry":
|
|
1191
|
+
self._sim.r(
|
|
1192
|
+
Pauli.PauliY, float(operation.params[0]), operation.qubits[0]._index
|
|
1193
|
+
)
|
|
1194
|
+
elif name == "rz":
|
|
1195
|
+
self._sim.r(
|
|
1196
|
+
Pauli.PauliZ, float(operation.params[0]), operation.qubits[0]._index
|
|
1197
|
+
)
|
|
1198
|
+
elif name == "h":
|
|
1199
|
+
self._sim.h(operation.qubits[0]._index)
|
|
1200
|
+
elif name == "x":
|
|
1201
|
+
self._sim.x(operation.qubits[0]._index)
|
|
1202
|
+
elif name == "y":
|
|
1203
|
+
self._sim.y(operation.qubits[0]._index)
|
|
1204
|
+
elif name == "z":
|
|
1205
|
+
self._sim.z(operation.qubits[0]._index)
|
|
1206
|
+
elif name == "s":
|
|
1207
|
+
self._sim.s(operation.qubits[0]._index)
|
|
1208
|
+
elif name == "sdg":
|
|
1209
|
+
self._sim.adjs(operation.qubits[0]._index)
|
|
1210
|
+
elif name == "t":
|
|
1211
|
+
self._sim.t(operation.qubits[0]._index)
|
|
1212
|
+
elif name == "tdg":
|
|
1213
|
+
self._sim.adjt(operation.qubits[0]._index)
|
|
1214
|
+
elif name == "cx":
|
|
1215
|
+
self._sim.cx(operation.qubits[0]._index, operation.qubits[1]._index)
|
|
1216
|
+
elif name == "cy":
|
|
1217
|
+
self._sim.cy(operation.qubits[0]._index, operation.qubits[1]._index)
|
|
1218
|
+
elif name == "cz":
|
|
1219
|
+
self._sim.cz(operation.qubits[0]._index, operation.qubits[1]._index)
|
|
1220
|
+
elif name == "dcx":
|
|
1221
|
+
self._sim.mcx(operation.qubits[0]._index, operation.qubits[1]._index)
|
|
1222
|
+
self._sim.mcx(operation.qubits[1]._index, operation.qubits[0]._index)
|
|
1223
|
+
elif name == "swap":
|
|
1224
|
+
self._sim.swap(operation.qubits[0]._index, operation.qubits[1]._index)
|
|
1225
|
+
elif name == "iswap":
|
|
1226
|
+
self._sim.iswap(operation.qubits[0]._index, operation.qubits[1]._index)
|
|
1227
|
+
elif name == "iswap_dg":
|
|
1228
|
+
self._sim.adjiswap(operation.qubits[0]._index, operation.qubits[1]._index)
|
|
1229
|
+
elif name == "reset":
|
|
1230
|
+
qubits = operation.qubits
|
|
1231
|
+
for qubit in qubits:
|
|
1232
|
+
if self._sim.m(qubit._index):
|
|
1233
|
+
self._sim.x(qubit._index)
|
|
1234
|
+
elif name == "measure":
|
|
1235
|
+
qubits = operation.qubits
|
|
1236
|
+
clbits = operation.clbits
|
|
1237
|
+
cregbits = (
|
|
1238
|
+
operation.register
|
|
1239
|
+
if hasattr(operation, "register")
|
|
1240
|
+
else len(operation.qubits) * [-1]
|
|
1241
|
+
)
|
|
1242
|
+
|
|
1243
|
+
self._sample_qubits += qubits
|
|
1244
|
+
self._sample_clbits += clbits
|
|
1245
|
+
self._sample_cregbits += cregbits
|
|
1246
|
+
|
|
1247
|
+
if not self._sample_measure:
|
|
1248
|
+
for index in range(len(qubits)):
|
|
1249
|
+
qubit_outcome = self._sim.m(qubits[index]._index)
|
|
1250
|
+
|
|
1251
|
+
clbit = clbits[index]
|
|
1252
|
+
clmask = 1 << clbit
|
|
1253
|
+
self._classical_memory = (self._classical_memory & (~clmask)) | (
|
|
1254
|
+
qubit_outcome << clbit
|
|
1255
|
+
)
|
|
1256
|
+
|
|
1257
|
+
cregbit = cregbits[index]
|
|
1258
|
+
if cregbit < 0:
|
|
1259
|
+
cregbit = clbit
|
|
1260
|
+
|
|
1261
|
+
regbit = 1 << cregbit
|
|
1262
|
+
self._classical_register = (
|
|
1263
|
+
self._classical_register & (~regbit)
|
|
1264
|
+
) | (qubit_outcome << cregbit)
|
|
1265
|
+
|
|
1266
|
+
elif name == "bfunc":
|
|
1267
|
+
mask = int(operation.mask, 16)
|
|
1268
|
+
relation = operation.relation
|
|
1269
|
+
val = int(operation.val, 16)
|
|
1270
|
+
|
|
1271
|
+
cregbit = operation.register
|
|
1272
|
+
cmembit = operation.memory if hasattr(operation, "memory") else None
|
|
1273
|
+
|
|
1274
|
+
compared = (self._classical_register & mask) - val
|
|
1275
|
+
|
|
1276
|
+
if relation == "==":
|
|
1277
|
+
outcome = compared == 0
|
|
1278
|
+
elif relation == "!=":
|
|
1279
|
+
outcome = compared != 0
|
|
1280
|
+
elif relation == "<":
|
|
1281
|
+
outcome = compared < 0
|
|
1282
|
+
elif relation == "<=":
|
|
1283
|
+
outcome = compared <= 0
|
|
1284
|
+
elif relation == ">":
|
|
1285
|
+
outcome = compared > 0
|
|
1286
|
+
elif relation == ">=":
|
|
1287
|
+
outcome = compared >= 0
|
|
1288
|
+
else:
|
|
1289
|
+
raise QrackError("Invalid boolean function relation.")
|
|
1290
|
+
|
|
1291
|
+
# Store outcome in register and optionally memory slot
|
|
1292
|
+
regbit = 1 << cregbit
|
|
1293
|
+
self._classical_register = (self._classical_register & (~regbit)) | (
|
|
1294
|
+
int(outcome) << cregbit
|
|
1295
|
+
)
|
|
1296
|
+
if cmembit is not None:
|
|
1297
|
+
membit = 1 << cmembit
|
|
1298
|
+
self._classical_memory = (self._classical_memory & (~membit)) | (
|
|
1299
|
+
int(outcome) << cmembit
|
|
1300
|
+
)
|
|
1301
|
+
else:
|
|
1302
|
+
err_msg = 'QrackAceBackend encountered unrecognized operation "{0}"'
|
|
1303
|
+
raise RuntimeError(err_msg.format(operation))
|
|
1304
|
+
|
|
1305
|
+
def _add_sample_measure(self, sample_qubits, sample_clbits, num_samples):
|
|
1306
|
+
"""Generate data samples from current statevector.
|
|
1307
|
+
|
|
1308
|
+
Taken almost straight from the terra source code.
|
|
1309
|
+
|
|
1310
|
+
Args:
|
|
1311
|
+
measure_params (list): List of (qubit, clbit) values for
|
|
1312
|
+
measure instructions to sample.
|
|
1313
|
+
num_samples (int): The number of data samples to generate.
|
|
1314
|
+
|
|
1315
|
+
Returns:
|
|
1316
|
+
list: A list of data values in hex format.
|
|
1317
|
+
"""
|
|
1318
|
+
# Get unique qubits that are actually measured
|
|
1319
|
+
measure_qubit = [qubit for qubit in sample_qubits]
|
|
1320
|
+
measure_clbit = [clbit for clbit in sample_clbits]
|
|
1321
|
+
|
|
1322
|
+
# Sample and convert to bit-strings
|
|
1323
|
+
if num_samples == 1:
|
|
1324
|
+
sample = self._sim.m_all()
|
|
1325
|
+
result = 0
|
|
1326
|
+
for index in range(len(measure_qubit)):
|
|
1327
|
+
qubit = measure_qubit[index]._index
|
|
1328
|
+
qubit_outcome = (sample >> qubit) & 1
|
|
1329
|
+
result |= qubit_outcome << index
|
|
1330
|
+
measure_results = [result]
|
|
1331
|
+
else:
|
|
1332
|
+
measure_results = self._sim.measure_shots(
|
|
1333
|
+
[q._index for q in measure_qubit], num_samples
|
|
1334
|
+
)
|
|
1335
|
+
|
|
1336
|
+
data = []
|
|
1337
|
+
for sample in measure_results:
|
|
1338
|
+
for index in range(len(measure_qubit)):
|
|
1339
|
+
qubit_outcome = (sample >> index) & 1
|
|
1340
|
+
clbit = measure_clbit[index]._index
|
|
1341
|
+
clmask = 1 << clbit
|
|
1342
|
+
self._classical_memory = (self._classical_memory & (~clmask)) | (
|
|
1343
|
+
qubit_outcome << clbit
|
|
1344
|
+
)
|
|
1345
|
+
|
|
1346
|
+
data.append(bin(self._classical_memory)[2:].zfill(self.num_qubits()))
|
|
1347
|
+
|
|
1348
|
+
return data
|
|
1349
|
+
|
|
1350
|
+
def run_qiskit_circuit(self, experiment, shots=1):
|
|
1351
|
+
if not _IS_QISKIT_AVAILABLE:
|
|
1352
|
+
raise RuntimeError(
|
|
1353
|
+
"Before trying to run_qiskit_circuit() with QrackAceBackend, you must install Qiskit!"
|
|
1354
|
+
)
|
|
1355
|
+
|
|
1356
|
+
instructions = []
|
|
1357
|
+
if isinstance(experiment, QuantumCircuit):
|
|
1358
|
+
instructions = experiment.data
|
|
1359
|
+
else:
|
|
1360
|
+
raise RuntimeError('Unrecognized "run_input" argument specified for run().')
|
|
1361
|
+
|
|
1362
|
+
self._shots = shots
|
|
1363
|
+
self._sample_qubits = []
|
|
1364
|
+
self._sample_clbits = []
|
|
1365
|
+
self._sample_cregbits = []
|
|
1366
|
+
self._sample_measure = True
|
|
1367
|
+
_data = []
|
|
1368
|
+
shotLoopMax = 1
|
|
1369
|
+
|
|
1370
|
+
is_initializing = True
|
|
1371
|
+
boundary_start = -1
|
|
1372
|
+
|
|
1373
|
+
for opcount in range(len(instructions)):
|
|
1374
|
+
operation = instructions[opcount]
|
|
1375
|
+
|
|
1376
|
+
if operation.name == "id" or operation.name == "barrier":
|
|
1377
|
+
continue
|
|
1378
|
+
|
|
1379
|
+
if is_initializing and (
|
|
1380
|
+
(operation.name == "measure") or (operation.name == "reset")
|
|
1381
|
+
):
|
|
1382
|
+
continue
|
|
1383
|
+
|
|
1384
|
+
is_initializing = False
|
|
1385
|
+
|
|
1386
|
+
if (operation.name == "measure") or (operation.name == "reset"):
|
|
1387
|
+
if boundary_start == -1:
|
|
1388
|
+
boundary_start = opcount
|
|
1389
|
+
|
|
1390
|
+
if (boundary_start != -1) and (operation.name != "measure"):
|
|
1391
|
+
shotsPerLoop = 1
|
|
1392
|
+
shotLoopMax = self._shots
|
|
1393
|
+
self._sample_measure = False
|
|
1394
|
+
break
|
|
1395
|
+
|
|
1396
|
+
preamble_memory = 0
|
|
1397
|
+
preamble_register = 0
|
|
1398
|
+
preamble_sim = None
|
|
1399
|
+
|
|
1400
|
+
if self._sample_measure or boundary_start <= 0:
|
|
1401
|
+
boundary_start = 0
|
|
1402
|
+
self._sample_measure = True
|
|
1403
|
+
shotsPerLoop = self._shots
|
|
1404
|
+
shotLoopMax = 1
|
|
1405
|
+
else:
|
|
1406
|
+
boundary_start -= 1
|
|
1407
|
+
if boundary_start > 0:
|
|
1408
|
+
self._sim = self
|
|
1409
|
+
self._classical_memory = 0
|
|
1410
|
+
self._classical_register = 0
|
|
1411
|
+
|
|
1412
|
+
for operation in instructions[:boundary_start]:
|
|
1413
|
+
self._apply_op(operation)
|
|
1414
|
+
|
|
1415
|
+
preamble_memory = self._classical_memory
|
|
1416
|
+
preamble_register = self._classical_register
|
|
1417
|
+
preamble_sim = self._sim
|
|
1418
|
+
|
|
1419
|
+
for shot in range(shotLoopMax):
|
|
1420
|
+
if preamble_sim is None:
|
|
1421
|
+
self._sim = self
|
|
1422
|
+
self._classical_memory = 0
|
|
1423
|
+
self._classical_register = 0
|
|
1424
|
+
else:
|
|
1425
|
+
self._sim = QrackAceBackend(toClone=preamble_sim)
|
|
1426
|
+
self._classical_memory = preamble_memory
|
|
1427
|
+
self._classical_register = preamble_register
|
|
1428
|
+
|
|
1429
|
+
for operation in instructions[boundary_start:]:
|
|
1430
|
+
self._apply_op(operation)
|
|
1431
|
+
|
|
1432
|
+
if not self._sample_measure and (len(self._sample_qubits) > 0):
|
|
1433
|
+
_data += [bin(self._classical_memory)[2:].zfill(self.num_qubits())]
|
|
1434
|
+
self._sample_qubits = []
|
|
1435
|
+
self._sample_clbits = []
|
|
1436
|
+
self._sample_cregbits = []
|
|
1437
|
+
|
|
1438
|
+
if self._sample_measure and (len(self._sample_qubits) > 0):
|
|
1439
|
+
_data = self._add_sample_measure(
|
|
1440
|
+
self._sample_qubits, self._sample_clbits, self._shots
|
|
1441
|
+
)
|
|
1442
|
+
|
|
1443
|
+
del self._sim
|
|
1444
|
+
|
|
1445
|
+
return _data
|
|
1446
|
+
|
|
1447
|
+
def get_qiskit_basis_gates():
|
|
1448
|
+
return [
|
|
1449
|
+
"id",
|
|
1450
|
+
"u",
|
|
1451
|
+
"u1",
|
|
1452
|
+
"u2",
|
|
1453
|
+
"u3",
|
|
1454
|
+
"r",
|
|
1455
|
+
"rx",
|
|
1456
|
+
"ry",
|
|
1457
|
+
"rz",
|
|
1458
|
+
"h",
|
|
1459
|
+
"x",
|
|
1460
|
+
"y",
|
|
1461
|
+
"z",
|
|
1462
|
+
"s",
|
|
1463
|
+
"sdg",
|
|
1464
|
+
"sx",
|
|
1465
|
+
"sxdg",
|
|
1466
|
+
"p",
|
|
1467
|
+
"t",
|
|
1468
|
+
"tdg",
|
|
1469
|
+
"cx",
|
|
1470
|
+
"cy",
|
|
1471
|
+
"cz",
|
|
1472
|
+
"swap",
|
|
1473
|
+
"iswap",
|
|
1474
|
+
"reset",
|
|
1475
|
+
"measure",
|
|
1476
|
+
]
|
|
1477
|
+
|
|
1478
|
+
# Mostly written by Dan, but with a little help from Elara (custom OpenAI GPT)
|
|
1479
|
+
def get_logical_coupling_map(self):
|
|
1480
|
+
if self._coupling_map:
|
|
1481
|
+
return self._coupling_map
|
|
1482
|
+
|
|
1483
|
+
coupling_map = set()
|
|
1484
|
+
rows, cols = self._row_length, self._col_length
|
|
1485
|
+
|
|
1486
|
+
# Map each column index to its full list of logical qubit indices
|
|
1487
|
+
def logical_index(row, col):
|
|
1488
|
+
return row * cols + col
|
|
1489
|
+
|
|
1490
|
+
for col in range(cols):
|
|
1491
|
+
connected_cols, _ = self._get_connected(col, False)
|
|
1492
|
+
for row in range(rows):
|
|
1493
|
+
connected_rows, _ = self._get_connected(row, False)
|
|
1494
|
+
a = logical_index(row, col)
|
|
1495
|
+
for c in connected_cols:
|
|
1496
|
+
for r in connected_rows:
|
|
1497
|
+
b = logical_index(r, c)
|
|
1498
|
+
if a != b:
|
|
1499
|
+
coupling_map.add((a, b))
|
|
1500
|
+
|
|
1501
|
+
self._coupling_map = sorted(coupling_map)
|
|
1502
|
+
|
|
1503
|
+
return self._coupling_map
|
|
1504
|
+
|
|
1505
|
+
# Designed by Dan, and implemented by Elara:
|
|
1506
|
+
def create_noise_model(self, x=0.25, y=0.25):
|
|
1507
|
+
if not _IS_QISKIT_AER_AVAILABLE:
|
|
1508
|
+
raise RuntimeError(
|
|
1509
|
+
"Before trying to run_qiskit_circuit() with QrackAceBackend, you must install Qiskit Aer!"
|
|
1510
|
+
)
|
|
1511
|
+
noise_model = NoiseModel()
|
|
1512
|
+
|
|
1513
|
+
for a, b in self.get_logical_coupling_map():
|
|
1514
|
+
col_a, col_b = a % self._row_length, b % self._row_length
|
|
1515
|
+
row_a, row_b = a // self._row_length, b // self._row_length
|
|
1516
|
+
is_long_a = self._is_col_long_range[col_a]
|
|
1517
|
+
is_long_b = self._is_col_long_range[col_b]
|
|
1518
|
+
|
|
1519
|
+
if is_long_a and is_long_b:
|
|
1520
|
+
continue # No noise on long-to-long
|
|
1521
|
+
|
|
1522
|
+
if (col_a == col_b) or (row_a == row_b):
|
|
1523
|
+
continue # No noise for same column
|
|
1524
|
+
|
|
1525
|
+
if is_long_a or is_long_b:
|
|
1526
|
+
y_cy = 1 - (1 - y) ** 2
|
|
1527
|
+
y_swap = 1 - (1 - y) ** 3
|
|
1528
|
+
noise_model.add_quantum_error(depolarizing_error(y, 2), "cx", [a, b])
|
|
1529
|
+
noise_model.add_quantum_error(depolarizing_error(y_cy, 2), "cy", [a, b])
|
|
1530
|
+
noise_model.add_quantum_error(depolarizing_error(y_cy, 2), "cz", [a, b])
|
|
1531
|
+
noise_model.add_quantum_error(
|
|
1532
|
+
depolarizing_error(y_swap, 2), "swap", [a, b]
|
|
1533
|
+
)
|
|
1534
|
+
else:
|
|
1535
|
+
y_cy = 1 - (1 - y) ** 2
|
|
1536
|
+
y_swap = 1 - (1 - y) ** 3
|
|
1537
|
+
noise_model.add_quantum_error(depolarizing_error(y_cy, 2), "cx", [a, b])
|
|
1538
|
+
noise_model.add_quantum_error(depolarizing_error(y_cy, 2), "cy", [a, b])
|
|
1539
|
+
noise_model.add_quantum_error(depolarizing_error(y_cy, 2), "cz", [a, b])
|
|
1540
|
+
noise_model.add_quantum_error(
|
|
1541
|
+
depolarizing_error(y_swap, 2), "swap", [a, b]
|
|
1542
|
+
)
|
|
1543
|
+
|
|
1544
|
+
return noise_model
|