pyqrack-cpu-complex128 1.58.0__py3-none-manylinux_2_35_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.

Potentially problematic release.


This version of pyqrack-cpu-complex128 might be problematic. Click here for more details.

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