pyqrack-cpu 2.28.2__tar.gz → 2.29.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyqrack_cpu-2.28.2/pyqrack_cpu.egg-info → pyqrack_cpu-2.29.0}/PKG-INFO +1 -1
- {pyqrack_cpu-2.28.2 → pyqrack_cpu-2.29.0}/pyqrack/qrack_ace_backend.py +92 -81
- {pyqrack_cpu-2.28.2 → pyqrack_cpu-2.29.0/pyqrack_cpu.egg-info}/PKG-INFO +1 -1
- {pyqrack_cpu-2.28.2 → pyqrack_cpu-2.29.0}/setup.py +1 -1
- {pyqrack_cpu-2.28.2 → pyqrack_cpu-2.29.0}/LICENSE +0 -0
- {pyqrack_cpu-2.28.2 → pyqrack_cpu-2.29.0}/MANIFEST.in +0 -0
- {pyqrack_cpu-2.28.2 → pyqrack_cpu-2.29.0}/Makefile +0 -0
- {pyqrack_cpu-2.28.2 → pyqrack_cpu-2.29.0}/README.md +0 -0
- {pyqrack_cpu-2.28.2 → pyqrack_cpu-2.29.0}/pyproject.toml +0 -0
- {pyqrack_cpu-2.28.2 → pyqrack_cpu-2.29.0}/pyqrack/__init__.py +0 -0
- {pyqrack_cpu-2.28.2 → pyqrack_cpu-2.29.0}/pyqrack/neuron_activation_fn.py +0 -0
- {pyqrack_cpu-2.28.2 → pyqrack_cpu-2.29.0}/pyqrack/pauli.py +0 -0
- {pyqrack_cpu-2.28.2 → pyqrack_cpu-2.29.0}/pyqrack/qrack_ace_mps_backend.py +0 -0
- {pyqrack_cpu-2.28.2 → pyqrack_cpu-2.29.0}/pyqrack/qrack_circuit.py +0 -0
- {pyqrack_cpu-2.28.2 → pyqrack_cpu-2.29.0}/pyqrack/qrack_near_clifford_qec_backend.py +0 -0
- {pyqrack_cpu-2.28.2 → pyqrack_cpu-2.29.0}/pyqrack/qrack_neuron.py +0 -0
- {pyqrack_cpu-2.28.2 → pyqrack_cpu-2.29.0}/pyqrack/qrack_neuron_torch_layer.py +0 -0
- {pyqrack_cpu-2.28.2 → pyqrack_cpu-2.29.0}/pyqrack/qrack_simulator.py +0 -0
- {pyqrack_cpu-2.28.2 → pyqrack_cpu-2.29.0}/pyqrack/qrack_stabilizer.py +0 -0
- {pyqrack_cpu-2.28.2 → pyqrack_cpu-2.29.0}/pyqrack/qrack_system/__init__.py +0 -0
- {pyqrack_cpu-2.28.2 → pyqrack_cpu-2.29.0}/pyqrack/qrack_system/qrack_system.py +0 -0
- {pyqrack_cpu-2.28.2 → pyqrack_cpu-2.29.0}/pyqrack/quimb_circuit_type.py +0 -0
- {pyqrack_cpu-2.28.2 → pyqrack_cpu-2.29.0}/pyqrack/stats/__init__.py +0 -0
- {pyqrack_cpu-2.28.2 → pyqrack_cpu-2.29.0}/pyqrack/stats/load_quantized_data.py +0 -0
- {pyqrack_cpu-2.28.2 → pyqrack_cpu-2.29.0}/pyqrack/stats/quantize_by_range.py +0 -0
- {pyqrack_cpu-2.28.2 → pyqrack_cpu-2.29.0}/pyqrack_cpu.egg-info/SOURCES.txt +0 -0
- {pyqrack_cpu-2.28.2 → pyqrack_cpu-2.29.0}/pyqrack_cpu.egg-info/dependency_links.txt +0 -0
- {pyqrack_cpu-2.28.2 → pyqrack_cpu-2.29.0}/pyqrack_cpu.egg-info/not-zip-safe +0 -0
- {pyqrack_cpu-2.28.2 → pyqrack_cpu-2.29.0}/pyqrack_cpu.egg-info/requires.txt +0 -0
- {pyqrack_cpu-2.28.2 → pyqrack_cpu-2.29.0}/pyqrack_cpu.egg-info/top_level.txt +0 -0
- {pyqrack_cpu-2.28.2 → pyqrack_cpu-2.29.0}/setup.cfg +0 -0
|
@@ -218,8 +218,8 @@ def _cpauli_lhv(prob, targ, axis, anti, theta=math.pi):
|
|
|
218
218
|
class QrackAceBackend:
|
|
219
219
|
"""A back end for elided quantum error correction
|
|
220
220
|
|
|
221
|
-
This back end uses elided repetition code on a nearest-neighbor topology
|
|
222
|
-
a utility-scale superconducting chip quantum computer in very little memory.
|
|
221
|
+
This back end uses elided repetition code on a nearest-neighbor topology (but fully-connected
|
|
222
|
+
within "patches") to emulate a utility-scale superconducting chip quantum computer in very little memory.
|
|
223
223
|
|
|
224
224
|
The backend was originally designed assuming an (orbifolded) 2D qubit grid like 2019 Sycamore.
|
|
225
225
|
However, it quickly became apparent that users can basically design their own connectivity topologies,
|
|
@@ -229,6 +229,17 @@ class QrackAceBackend:
|
|
|
229
229
|
(If you have 3+ patches, maybe your discrete GPU can do multiple patches in the time it takes an Intel HD
|
|
230
230
|
to do one patch worth of work!)
|
|
231
231
|
|
|
232
|
+
Raw state-vector qubits can be directly manipulated through "sim" and "qubits" properties (with boundary
|
|
233
|
+
"tie-breaker" local hidden variables qubits in "lhv"). "sim" entries in the "qubits" map expose the full
|
|
234
|
+
QrackSimulator gate API.
|
|
235
|
+
|
|
236
|
+
For irregularly shaped "cutout geometries," allocate a super set of the geometric layout with this class,
|
|
237
|
+
and simply do not use unneeded qubits (and strike them from the Qiskit coupling map, for transpilation).
|
|
238
|
+
Qrack will not allocate space for any qubit that never actually becomes involved in any entangling gate.
|
|
239
|
+
|
|
240
|
+
For expected XEB across different 2D nearest-neighbor experiments, see the data set, regression model,
|
|
241
|
+
and calculator at https://tinyurl.com/qrackacexeb.
|
|
242
|
+
|
|
232
243
|
Attributes:
|
|
233
244
|
sim(QrackSimulator): Array of simulators corresponding to "patches" between boundary rows.
|
|
234
245
|
long_range_columns(int): How many ideal rows between QEC boundary rows?
|
|
@@ -360,7 +371,7 @@ class QrackAceBackend:
|
|
|
360
371
|
# grows with total boundary-qubit count rather than patch count,
|
|
361
372
|
# so it doesn't scale -- off by default, and worth turning on only
|
|
362
373
|
# for a handful of patches. Every boundary qubit ALSO still gets
|
|
363
|
-
# its classical LHVQubit proxy (self.
|
|
374
|
+
# its classical LHVQubit proxy (self.lhv, below) regardless, same
|
|
364
375
|
# as when this option is off: with the crossbar on, the proxy
|
|
365
376
|
# drops to being only the last-resort tie-breaker _correct() falls
|
|
366
377
|
# back to when even the crossbar replica is itself ambiguous; with
|
|
@@ -369,8 +380,8 @@ class QrackAceBackend:
|
|
|
369
380
|
boundary_sim_id = sim_count
|
|
370
381
|
boundary_count = 0
|
|
371
382
|
|
|
372
|
-
self.
|
|
373
|
-
self.
|
|
383
|
+
self.qubits = []
|
|
384
|
+
self.lhv = {}
|
|
374
385
|
sim_counts = [0] * sim_count
|
|
375
386
|
sim_id = 0
|
|
376
387
|
tot_qubits = 0
|
|
@@ -388,9 +399,9 @@ class QrackAceBackend:
|
|
|
388
399
|
qubit.append((boundary_sim_id, boundary_count))
|
|
389
400
|
boundary_count += 1
|
|
390
401
|
|
|
391
|
-
if (not to_clone) or (tot_qubits in to_clone.
|
|
392
|
-
self.
|
|
393
|
-
to_clone=(to_clone.
|
|
402
|
+
if (not to_clone) or (tot_qubits in to_clone.lhv):
|
|
403
|
+
self.lhv[tot_qubits] = LHVQubit(
|
|
404
|
+
to_clone=(to_clone.lhv[tot_qubits] if to_clone else None)
|
|
394
405
|
)
|
|
395
406
|
|
|
396
407
|
if (not c) and (not r):
|
|
@@ -405,8 +416,8 @@ class QrackAceBackend:
|
|
|
405
416
|
if not c:
|
|
406
417
|
sim_id = (sim_id + 1) % sim_count
|
|
407
418
|
|
|
408
|
-
self.
|
|
409
|
-
list(to_clone.
|
|
419
|
+
self.qubits.append(
|
|
420
|
+
list(to_clone.qubits[tot_qubits]) if to_clone else qubit
|
|
410
421
|
)
|
|
411
422
|
tot_qubits += 1
|
|
412
423
|
|
|
@@ -430,10 +441,10 @@ class QrackAceBackend:
|
|
|
430
441
|
# See _apply_coupling for the actual gadget; this block only does
|
|
431
442
|
# the qubit-index bookkeeping, in the same style as every other
|
|
432
443
|
# per-simulator allocation above.
|
|
433
|
-
|
|
444
|
+
detect_ancilla = []
|
|
434
445
|
if self.is_error_detection:
|
|
435
446
|
for i in range(len(sim_counts)):
|
|
436
|
-
|
|
447
|
+
detect_ancilla.append(sim_counts[i])
|
|
437
448
|
sim_counts[i] += 1
|
|
438
449
|
|
|
439
450
|
# Logical-qubit wrapper around each patch's detection ancilla,
|
|
@@ -441,10 +452,10 @@ class QrackAceBackend:
|
|
|
441
452
|
# (self.cx(lq1, anc)), not just raw (sim, idx) tuples. Appended
|
|
442
453
|
# PAST the user-facing qubit range (num_qubits() is computed
|
|
443
454
|
# independently from the grid dimensions, not from len(
|
|
444
|
-
# self.
|
|
455
|
+
# self.qubits), so these extra entries are never visible or
|
|
445
456
|
# reachable through the normal API) -- a simple, single-replica
|
|
446
457
|
# (bulk-style) logical qubit per simulator, one entry in
|
|
447
|
-
# self.
|
|
458
|
+
# self.qubits pointing at the same physical qubit _detect_
|
|
448
459
|
# ancilla already allocated there. See _apply_coupling for why
|
|
449
460
|
# this needs to be a genuine logical qubit and not just a raw
|
|
450
461
|
# physical capture: capturing lq1's LOGICAL value via a real,
|
|
@@ -454,12 +465,12 @@ class QrackAceBackend:
|
|
|
454
465
|
# specific physical replica directly (an earlier version of
|
|
455
466
|
# this) isn't a valid invariant, since _correct() can
|
|
456
467
|
# legitimately need to change any single replica's own value.
|
|
457
|
-
self.
|
|
468
|
+
self.detect_ancilla_lq = []
|
|
458
469
|
if self.is_error_detection:
|
|
459
|
-
for sim_id, phys_idx in enumerate(
|
|
460
|
-
lq_idx = len(self.
|
|
461
|
-
self.
|
|
462
|
-
self.
|
|
470
|
+
for sim_id, phys_idx in enumerate(detect_ancilla):
|
|
471
|
+
lq_idx = len(self.qubits)
|
|
472
|
+
self.qubits.append([(sim_id, phys_idx)])
|
|
473
|
+
self.detect_ancilla_lq.append(lq_idx)
|
|
463
474
|
# Boundary repetition code, on-demand design: only the couplers
|
|
464
475
|
# are actually noisy here -- single-qubit gates are already
|
|
465
476
|
# exactly transversal, per-replica, with zero error, so there's
|
|
@@ -473,18 +484,18 @@ class QrackAceBackend:
|
|
|
473
484
|
# See _cpauli for the actual encode/couple/decode/correct cycle
|
|
474
485
|
# built around these; this block is just the qubit-index
|
|
475
486
|
# bookkeeping, in the same style as _detect_ancilla above.
|
|
476
|
-
|
|
487
|
+
rep_code_ancilla = []
|
|
477
488
|
if self.is_boundary_repetition_code:
|
|
478
489
|
for i in range(len(sim_counts)):
|
|
479
|
-
|
|
490
|
+
rep_code_ancilla.append(sim_counts[i])
|
|
480
491
|
sim_counts[i] += 1
|
|
481
492
|
|
|
482
|
-
self.
|
|
493
|
+
self.rep_code_ancilla_lq = []
|
|
483
494
|
if self.is_boundary_repetition_code:
|
|
484
|
-
for sim_id, phys_idx in enumerate(
|
|
485
|
-
lq_idx = len(self.
|
|
486
|
-
self.
|
|
487
|
-
self.
|
|
495
|
+
for sim_id, phys_idx in enumerate(rep_code_ancilla):
|
|
496
|
+
lq_idx = len(self.qubits)
|
|
497
|
+
self.qubits.append([(sim_id, phys_idx)])
|
|
498
|
+
self.rep_code_ancilla_lq.append(lq_idx)
|
|
488
499
|
|
|
489
500
|
# capture calls (see _apply_coupling): those calls go through
|
|
490
501
|
# the ordinary _cpauli -> _apply_coupling path themselves, which
|
|
@@ -772,7 +783,7 @@ class QrackAceBackend:
|
|
|
772
783
|
self._cx_shadow(c1, c2)
|
|
773
784
|
|
|
774
785
|
def _unpack(self, lq):
|
|
775
|
-
return self.
|
|
786
|
+
return self.qubits[lq]
|
|
776
787
|
|
|
777
788
|
@staticmethod
|
|
778
789
|
def _get_qb_lhv_indices(hq):
|
|
@@ -783,7 +794,7 @@ class QrackAceBackend:
|
|
|
783
794
|
# diagonal-patch); with use_crossbars=True, 3 (home + adjacent-
|
|
784
795
|
# patch + shared crossbar) or 5 (home + adjacent-patch + shared
|
|
785
796
|
# crossbar + 2 diagonal-patch). None of them are virtual/LHV-
|
|
786
|
-
# backed slots -- the classical LHVQubit proxy (self.
|
|
797
|
+
# backed slots -- the classical LHVQubit proxy (self.lhv) is
|
|
787
798
|
# tracked entirely separately from hq, not as one of its indices
|
|
788
799
|
# -- so qb is simply every index, in order, regardless of which
|
|
789
800
|
# of those counts applies. We keep this helper's name and
|
|
@@ -878,8 +889,8 @@ class QrackAceBackend:
|
|
|
878
889
|
for q in qb:
|
|
879
890
|
b = hq[q]
|
|
880
891
|
self.sim[b[0]].h(b[1])
|
|
881
|
-
if lq in self.
|
|
882
|
-
self.
|
|
892
|
+
if lq in self.lhv:
|
|
893
|
+
self.lhv[lq].h()
|
|
883
894
|
|
|
884
895
|
if len(hq) == 4:
|
|
885
896
|
# Corner-boundary qubit: 4 real replicas remain (home,
|
|
@@ -894,7 +905,7 @@ class QrackAceBackend:
|
|
|
894
905
|
p1 = self.sim[hq[1][0]].prob(hq[1][1])
|
|
895
906
|
p2 = self.sim[hq[2][0]].prob(hq[2][1])
|
|
896
907
|
p3 = self.sim[hq[3][0]].prob(hq[3][1])
|
|
897
|
-
lhv = self.
|
|
908
|
+
lhv = self.lhv.get(lq)
|
|
898
909
|
|
|
899
910
|
end_caps = [p0, p1, p2, p3]
|
|
900
911
|
# Classify each end-cap as decisively high / decisively low /
|
|
@@ -1010,7 +1021,7 @@ class QrackAceBackend:
|
|
|
1010
1021
|
p2 = self.sim[hq[2][0]].prob(hq[2][1])
|
|
1011
1022
|
p3 = self.sim[hq[3][0]].prob(hq[3][1])
|
|
1012
1023
|
p4 = self.sim[hq[4][0]].prob(hq[4][1])
|
|
1013
|
-
lhv = self.
|
|
1024
|
+
lhv = self.lhv.get(lq)
|
|
1014
1025
|
|
|
1015
1026
|
end_caps = [p0, p1, p3, p4]
|
|
1016
1027
|
high_count = sum(1 for x in end_caps if x > (0.5 + self._epsilon))
|
|
@@ -1076,7 +1087,7 @@ class QrackAceBackend:
|
|
|
1076
1087
|
p0 = self.sim[hq[0][0]].prob(hq[0][1])
|
|
1077
1088
|
p1 = self.sim[hq[1][0]].prob(hq[1][1])
|
|
1078
1089
|
p2 = self.sim[hq[2][0]].prob(hq[2][1])
|
|
1079
|
-
lhv = self.
|
|
1090
|
+
lhv = self.lhv.get(lq)
|
|
1080
1091
|
|
|
1081
1092
|
p1_high = p1 > (0.5 + self._epsilon)
|
|
1082
1093
|
p1_low = p1 < (0.5 - self._epsilon)
|
|
@@ -1159,7 +1170,7 @@ class QrackAceBackend:
|
|
|
1159
1170
|
# sit at old-index-2 is gone. The classical LHV proxy takes
|
|
1160
1171
|
# over the crossbar's old role as "slot2" in the conditional
|
|
1161
1172
|
# tie-breaking cascade below.
|
|
1162
|
-
lhv = self.
|
|
1173
|
+
lhv = self.lhv.get(lq)
|
|
1163
1174
|
p0 = self.sim[hq[0][0]].prob(hq[0][1])
|
|
1164
1175
|
p1 = self.sim[hq[1][0]].prob(hq[1][1])
|
|
1165
1176
|
|
|
@@ -1292,8 +1303,8 @@ class QrackAceBackend:
|
|
|
1292
1303
|
for q in qb:
|
|
1293
1304
|
b = hq[q]
|
|
1294
1305
|
self.sim[b[0]].h(b[1])
|
|
1295
|
-
if lq in self.
|
|
1296
|
-
self.
|
|
1306
|
+
if lq in self.lhv:
|
|
1307
|
+
self.lhv[lq].h()
|
|
1297
1308
|
|
|
1298
1309
|
def apply_magnetic_bias(self, q, b):
|
|
1299
1310
|
if b == 0:
|
|
@@ -1322,7 +1333,7 @@ class QrackAceBackend:
|
|
|
1322
1333
|
b = hq[q]
|
|
1323
1334
|
self.sim[b[0]].u(b[1], th, ph, lm)
|
|
1324
1335
|
|
|
1325
|
-
lhv = self.
|
|
1336
|
+
lhv = self.lhv.get(lq)
|
|
1326
1337
|
if lhv is not None:
|
|
1327
1338
|
lhv.u(th, ph, lm)
|
|
1328
1339
|
|
|
@@ -1341,7 +1352,7 @@ class QrackAceBackend:
|
|
|
1341
1352
|
b = hq[q]
|
|
1342
1353
|
self.sim[b[0]].r(p, th, b[1])
|
|
1343
1354
|
|
|
1344
|
-
lhv = self.
|
|
1355
|
+
lhv = self.lhv.get(lq)
|
|
1345
1356
|
if lhv is not None:
|
|
1346
1357
|
if p == Pauli.PauliX:
|
|
1347
1358
|
lhv.rx(th)
|
|
@@ -1365,7 +1376,7 @@ class QrackAceBackend:
|
|
|
1365
1376
|
b = hq[q]
|
|
1366
1377
|
self.sim[b[0]].h(b[1])
|
|
1367
1378
|
|
|
1368
|
-
lhv = self.
|
|
1379
|
+
lhv = self.lhv.get(lq)
|
|
1369
1380
|
if lhv is not None:
|
|
1370
1381
|
lhv.h()
|
|
1371
1382
|
|
|
@@ -1384,7 +1395,7 @@ class QrackAceBackend:
|
|
|
1384
1395
|
b = hq[q]
|
|
1385
1396
|
self.sim[b[0]].s(b[1])
|
|
1386
1397
|
|
|
1387
|
-
lhv = self.
|
|
1398
|
+
lhv = self.lhv.get(lq)
|
|
1388
1399
|
if lhv is not None:
|
|
1389
1400
|
lhv.s()
|
|
1390
1401
|
|
|
@@ -1401,7 +1412,7 @@ class QrackAceBackend:
|
|
|
1401
1412
|
b = hq[q]
|
|
1402
1413
|
self.sim[b[0]].adjs(b[1])
|
|
1403
1414
|
|
|
1404
|
-
lhv = self.
|
|
1415
|
+
lhv = self.lhv.get(lq)
|
|
1405
1416
|
if lhv is not None:
|
|
1406
1417
|
lhv.adjs()
|
|
1407
1418
|
|
|
@@ -1418,7 +1429,7 @@ class QrackAceBackend:
|
|
|
1418
1429
|
b = hq[q]
|
|
1419
1430
|
self.sim[b[0]].sx(b[1])
|
|
1420
1431
|
|
|
1421
|
-
lhv = self.
|
|
1432
|
+
lhv = self.lhv.get(lq)
|
|
1422
1433
|
if lhv is not None:
|
|
1423
1434
|
lhv.sx()
|
|
1424
1435
|
|
|
@@ -1435,7 +1446,7 @@ class QrackAceBackend:
|
|
|
1435
1446
|
b = hq[q]
|
|
1436
1447
|
self.sim[b[0]].adjsx(b[1])
|
|
1437
1448
|
|
|
1438
|
-
lhv = self.
|
|
1449
|
+
lhv = self.lhv.get(lq)
|
|
1439
1450
|
if lhv is not None:
|
|
1440
1451
|
lhv.adjsx()
|
|
1441
1452
|
|
|
@@ -1452,7 +1463,7 @@ class QrackAceBackend:
|
|
|
1452
1463
|
b = hq[q]
|
|
1453
1464
|
self.sim[b[0]].x(b[1])
|
|
1454
1465
|
|
|
1455
|
-
lhv = self.
|
|
1466
|
+
lhv = self.lhv.get(lq)
|
|
1456
1467
|
if lhv is not None:
|
|
1457
1468
|
lhv.x()
|
|
1458
1469
|
|
|
@@ -1469,7 +1480,7 @@ class QrackAceBackend:
|
|
|
1469
1480
|
b = hq[q]
|
|
1470
1481
|
self.sim[b[0]].y(b[1])
|
|
1471
1482
|
|
|
1472
|
-
lhv = self.
|
|
1483
|
+
lhv = self.lhv.get(lq)
|
|
1473
1484
|
if lhv is not None:
|
|
1474
1485
|
lhv.y()
|
|
1475
1486
|
|
|
@@ -1486,7 +1497,7 @@ class QrackAceBackend:
|
|
|
1486
1497
|
b = hq[q]
|
|
1487
1498
|
self.sim[b[0]].z(b[1])
|
|
1488
1499
|
|
|
1489
|
-
lhv = self.
|
|
1500
|
+
lhv = self.lhv.get(lq)
|
|
1490
1501
|
if lhv is not None:
|
|
1491
1502
|
lhv.z()
|
|
1492
1503
|
|
|
@@ -1503,7 +1514,7 @@ class QrackAceBackend:
|
|
|
1503
1514
|
b = hq[q]
|
|
1504
1515
|
self.sim[b[0]].t(b[1])
|
|
1505
1516
|
|
|
1506
|
-
lhv = self.
|
|
1517
|
+
lhv = self.lhv.get(lq)
|
|
1507
1518
|
if lhv is not None:
|
|
1508
1519
|
lhv.t()
|
|
1509
1520
|
|
|
@@ -1520,7 +1531,7 @@ class QrackAceBackend:
|
|
|
1520
1531
|
b = hq[q]
|
|
1521
1532
|
self.sim[b[0]].adjt(b[1])
|
|
1522
1533
|
|
|
1523
|
-
lhv = self.
|
|
1534
|
+
lhv = self.lhv.get(lq)
|
|
1524
1535
|
if lhv is not None:
|
|
1525
1536
|
lhv.adjt()
|
|
1526
1537
|
|
|
@@ -1625,7 +1636,7 @@ class QrackAceBackend:
|
|
|
1625
1636
|
if x[0] in t_sims:
|
|
1626
1637
|
anc_sim = x[0]
|
|
1627
1638
|
break
|
|
1628
|
-
anc = self.
|
|
1639
|
+
anc = self.detect_ancilla_lq[anc_sim]
|
|
1629
1640
|
self._in_gadget_capture = True
|
|
1630
1641
|
# XOR on target
|
|
1631
1642
|
self.cx(lq2, anc)
|
|
@@ -1681,7 +1692,7 @@ class QrackAceBackend:
|
|
|
1681
1692
|
# `(len(qb1) == 2) and (b1[1] == (b2[1] & 1))` as a
|
|
1682
1693
|
# parity-matched fallback. _get_qb_lhv_indices only ever
|
|
1683
1694
|
# returns length 1, 3, or 5 (bulk / edge-boundary /
|
|
1684
|
-
# corner-boundary), matching self.
|
|
1695
|
+
# corner-boundary), matching self.qubits' own replica
|
|
1685
1696
|
# counts by construction -- len(qb1) == 2 is therefore
|
|
1686
1697
|
# unreachable, and that branch was permanently dead code
|
|
1687
1698
|
# (harmless, but confusing). Removed rather than kept:
|
|
@@ -1701,7 +1712,7 @@ class QrackAceBackend:
|
|
|
1701
1712
|
self._in_gadget_capture = True
|
|
1702
1713
|
# XOR check
|
|
1703
1714
|
self.cx(lq2, anc)
|
|
1704
|
-
anc_sim, anc_idx = self.
|
|
1715
|
+
anc_sim, anc_idx = self.qubits[anc][0]
|
|
1705
1716
|
p = self.sim[anc_sim].prob(anc_idx)
|
|
1706
1717
|
p = 1.0 - p
|
|
1707
1718
|
if self._ps_epsilon >= p:
|
|
@@ -1753,7 +1764,7 @@ class QrackAceBackend:
|
|
|
1753
1764
|
if x[0] in t_sims:
|
|
1754
1765
|
anc_sim = x[0]
|
|
1755
1766
|
break
|
|
1756
|
-
anc = self.
|
|
1767
|
+
anc = self.rep_code_ancilla_lq[anc_sim]
|
|
1757
1768
|
self._in_gadget_capture = True
|
|
1758
1769
|
self.cx(lq2, anc)
|
|
1759
1770
|
self._in_gadget_capture = False
|
|
@@ -1769,9 +1780,9 @@ class QrackAceBackend:
|
|
|
1769
1780
|
qb2, _ = QrackAceBackend._get_qb_lhv_indices(h2)
|
|
1770
1781
|
self._apply_coupling(pauli, anti, qb1, hq1, qb2, h2, lq1_lr, lq1, l2)
|
|
1771
1782
|
|
|
1772
|
-
if lq2 in self.
|
|
1783
|
+
if lq2 in self.lhv:
|
|
1773
1784
|
ctrl_prob = self.sim[hq1[0][0]].prob(hq1[0][1])
|
|
1774
|
-
_cpauli_lhv(ctrl_prob, self.
|
|
1785
|
+
_cpauli_lhv(ctrl_prob, self.lhv[lq2], pauli, anti)
|
|
1775
1786
|
|
|
1776
1787
|
if anc is not None:
|
|
1777
1788
|
# Decode:
|
|
@@ -1782,7 +1793,7 @@ class QrackAceBackend:
|
|
|
1782
1793
|
# After we corrected any mismatch above, it's unlikely
|
|
1783
1794
|
# that we can't post-select no-error on this ancilla.
|
|
1784
1795
|
|
|
1785
|
-
anc_sim, anc_idx = self.
|
|
1796
|
+
anc_sim, anc_idx = self.qubits[anc][0]
|
|
1786
1797
|
p = self.sim[anc_sim].prob(anc_idx)
|
|
1787
1798
|
if self._ps_epsilon >= (1.0 - p):
|
|
1788
1799
|
b = self.m(anc)
|
|
@@ -2048,24 +2059,24 @@ class QrackAceBackend:
|
|
|
2048
2059
|
|
|
2049
2060
|
if sims1.isdisjoint(sims2):
|
|
2050
2061
|
# Once fully resolved, the swap itself is nothing but a
|
|
2051
|
-
# reference exchange: self.
|
|
2062
|
+
# reference exchange: self.qubits[lq] is a reference to the
|
|
2052
2063
|
# replica-list object, so swapping which index points to
|
|
2053
2064
|
# which object IS the swap -- no separate content to copy.
|
|
2054
|
-
self.
|
|
2065
|
+
self.qubits[lq1], self.qubits[lq2] = self.qubits[lq2], self.qubits[lq1]
|
|
2055
2066
|
|
|
2056
|
-
lhv1 = self.
|
|
2057
|
-
lhv2 = self.
|
|
2067
|
+
lhv1 = self.lhv.pop(lq1, None)
|
|
2068
|
+
lhv2 = self.lhv.pop(lq2, None)
|
|
2058
2069
|
if lhv2 is not None:
|
|
2059
|
-
self.
|
|
2070
|
+
self.lhv[lq1] = lhv2
|
|
2060
2071
|
if lhv1 is not None:
|
|
2061
|
-
self.
|
|
2072
|
+
self.lhv[lq2] = lhv1
|
|
2062
2073
|
|
|
2063
2074
|
# Boundary repetition code needs no bookkeeping here, unlike
|
|
2064
|
-
# self.
|
|
2075
|
+
# self.qubits/self.lhv just above: it's on-demand now, not
|
|
2065
2076
|
# a permanent, per-logical-index dict of pre-allocated
|
|
2066
|
-
# partners -- eligibility is just len(self.
|
|
2077
|
+
# partners -- eligibility is just len(self.qubits[lq]) > 1,
|
|
2067
2078
|
# computed fresh on every coupling event, so the reference
|
|
2068
|
-
# swap of self.
|
|
2079
|
+
# swap of self.qubits above already, automatically carries
|
|
2069
2080
|
# the right eligibility with it. Nothing else to move.
|
|
2070
2081
|
return
|
|
2071
2082
|
|
|
@@ -2190,7 +2201,7 @@ class QrackAceBackend:
|
|
|
2190
2201
|
if found:
|
|
2191
2202
|
break
|
|
2192
2203
|
self._in_gadget_capture = True
|
|
2193
|
-
anc_and = self.
|
|
2204
|
+
anc_and = self.detect_ancilla_lq[anc_sim]
|
|
2194
2205
|
self.cx(t, anc_and)
|
|
2195
2206
|
# self.mcx() only supports a single control at the logical
|
|
2196
2207
|
# level (a syntax-convenience wrapper, confirmed directly --
|
|
@@ -2209,7 +2220,7 @@ class QrackAceBackend:
|
|
|
2209
2220
|
c1_idx = next((r[1] for r in hq1 if r[0] == anc_sim), None)
|
|
2210
2221
|
c2_idx = next((r[1] for r in hq2 if r[0] == anc_sim), None)
|
|
2211
2222
|
if (c1_idx is not None) and (c2_idx is not None):
|
|
2212
|
-
_, anc_and_idx = self.
|
|
2223
|
+
_, anc_and_idx = self.qubits[anc_and][0]
|
|
2213
2224
|
self.sim[anc_sim].mcx([c1_idx, c2_idx], anc_and_idx)
|
|
2214
2225
|
else:
|
|
2215
2226
|
self.ccx(c1, c2, anc_and)
|
|
@@ -2290,7 +2301,7 @@ class QrackAceBackend:
|
|
|
2290
2301
|
|
|
2291
2302
|
if anc_and is not None:
|
|
2292
2303
|
self.cx(t, anc_and)
|
|
2293
|
-
anc_sim, anc_idx = self.
|
|
2304
|
+
anc_sim, anc_idx = self.qubits[anc_and][0]
|
|
2294
2305
|
p = self.sim[anc_sim].prob(anc_idx)
|
|
2295
2306
|
p = 1.0 - p
|
|
2296
2307
|
if self._ps_epsilon >= p:
|
|
@@ -2358,7 +2369,7 @@ class QrackAceBackend:
|
|
|
2358
2369
|
else:
|
|
2359
2370
|
self.sim[b[0]].force_m(b[1], result)
|
|
2360
2371
|
|
|
2361
|
-
lhv = self.
|
|
2372
|
+
lhv = self.lhv.get(lq)
|
|
2362
2373
|
if lhv is not None:
|
|
2363
2374
|
lhv.reset()
|
|
2364
2375
|
if result:
|
|
@@ -2385,7 +2396,7 @@ class QrackAceBackend:
|
|
|
2385
2396
|
else:
|
|
2386
2397
|
self.sim[b[0]].force_m(b[1], result)
|
|
2387
2398
|
|
|
2388
|
-
lhv = self.
|
|
2399
|
+
lhv = self.lhv.get(lq)
|
|
2389
2400
|
if lhv is not None:
|
|
2390
2401
|
lhv.reset()
|
|
2391
2402
|
if result:
|
|
@@ -2782,7 +2793,7 @@ class QrackAceBackend:
|
|
|
2782
2793
|
# keep patching individual arithmetic mistakes in a function whose
|
|
2783
2794
|
# intended row/col semantics were genuinely unclear on inspection,
|
|
2784
2795
|
# this is grounded directly in the one thing that's true by
|
|
2785
|
-
# construction: self.
|
|
2796
|
+
# construction: self.qubits, the actual replica structure built
|
|
2786
2797
|
# during __init__. Two logical qubits are coupled if and only if
|
|
2787
2798
|
# they share ANY simulator id among their own replicas -- this
|
|
2788
2799
|
# cannot disagree with the real adjacency, because it doesn't
|
|
@@ -2791,7 +2802,7 @@ class QrackAceBackend:
|
|
|
2791
2802
|
n = self.num_qubits()
|
|
2792
2803
|
sim_to_qubits = {}
|
|
2793
2804
|
for lq in range(n):
|
|
2794
|
-
for sim_id, _ in self.
|
|
2805
|
+
for sim_id, _ in self.qubits[lq]:
|
|
2795
2806
|
sim_to_qubits.setdefault(sim_id, []).append(lq)
|
|
2796
2807
|
for sim_id, qubits_here in sim_to_qubits.items():
|
|
2797
2808
|
for a in qubits_here:
|
|
@@ -2822,7 +2833,7 @@ class QrackAceBackend:
|
|
|
2822
2833
|
# are untouched -- a bulk qubit's single simulator membership
|
|
2823
2834
|
# was never ambiguous.
|
|
2824
2835
|
def is_boundary(lq):
|
|
2825
|
-
return len(self.
|
|
2836
|
+
return len(self.qubits[lq]) > 1
|
|
2826
2837
|
|
|
2827
2838
|
def same_single_side(idx_a, idx_b, long_range_arr, length):
|
|
2828
2839
|
if idx_a == idx_b:
|
|
@@ -3008,8 +3019,8 @@ class QrackAceBackend:
|
|
|
3008
3019
|
noise_model = NoiseModel()
|
|
3009
3020
|
|
|
3010
3021
|
def _uncommon_sim_fraction(lq1, lq2):
|
|
3011
|
-
sims1 = {qb[0] for qb in self.
|
|
3012
|
-
sims2 = {qb[0] for qb in self.
|
|
3022
|
+
sims1 = {qb[0] for qb in self.qubits[lq1]}
|
|
3023
|
+
sims2 = {qb[0] for qb in self.qubits[lq2]}
|
|
3013
3024
|
|
|
3014
3025
|
return len(sims1 ^ sims2) / len(sims1.union(sims2))
|
|
3015
3026
|
|
|
@@ -3039,7 +3050,7 @@ class QrackAceBackend:
|
|
|
3039
3050
|
# nested cx(lq1, anc) capture goes through the full logical
|
|
3040
3051
|
# coupling machinery (shadow or real, whichever applies) and
|
|
3041
3052
|
# works the same way regardless. So this is checked directly,
|
|
3042
|
-
# per pair, from len(self.
|
|
3053
|
+
# per pair, from len(self.qubits[a]) -- no precomputed set
|
|
3043
3054
|
# needed.
|
|
3044
3055
|
|
|
3045
3056
|
# This factor is a theory-derived placeholder, not an empirical
|
|
@@ -3085,7 +3096,7 @@ class QrackAceBackend:
|
|
|
3085
3096
|
# Pauli-string convention is big-endian relative to the qubit
|
|
3086
3097
|
# list: the LAST qubit corresponds to the LEFTMOST character).
|
|
3087
3098
|
p_shadow_ccz = 1 - sp * p
|
|
3088
|
-
if self.is_error_detection and len(self.
|
|
3099
|
+
if self.is_error_detection and len(self.qubits[a]) > 1:
|
|
3089
3100
|
# a is the control for this pair (_cpauli(lq1=a, lq2=b,
|
|
3090
3101
|
# ...) is always called with a as the coupling source).
|
|
3091
3102
|
# Both gadget halves that protect this pair (anc1 on a,
|
|
@@ -3160,12 +3171,12 @@ class QrackAceBackend:
|
|
|
3160
3171
|
# error character isn't captured by this specific derivation;
|
|
3161
3172
|
# that case keeps the previous symmetric depolarizing
|
|
3162
3173
|
# treatment below as a fallback.
|
|
3163
|
-
len_a = len(self.
|
|
3164
|
-
len_b = len(self.
|
|
3174
|
+
len_a = len(self.qubits[a])
|
|
3175
|
+
len_b = len(self.qubits[b])
|
|
3165
3176
|
is_a_simple = len_a == 1
|
|
3166
3177
|
is_b_simple = len_b == 1
|
|
3167
|
-
sims_a = [qb[0] for qb in self.
|
|
3168
|
-
sims_b = [qb[0] for qb in self.
|
|
3178
|
+
sims_a = [qb[0] for qb in self.qubits[a]]
|
|
3179
|
+
sims_b = [qb[0] for qb in self.qubits[b]]
|
|
3169
3180
|
has_match = any(s in sims_b for s in sims_a)
|
|
3170
3181
|
|
|
3171
3182
|
if (is_a_simple != is_b_simple) and has_match:
|
|
@@ -3213,7 +3224,7 @@ class QrackAceBackend:
|
|
|
3213
3224
|
# 4-term (I,X,Z,Y) channel down to a plain 2-term (I,Z) one.
|
|
3214
3225
|
if (is_a_simple != is_b_simple) and has_match:
|
|
3215
3226
|
p_cz = 1 - sp * p # same per-call formula as standalone cz
|
|
3216
|
-
if self.is_error_detection and len(self.
|
|
3227
|
+
if self.is_error_detection and len(self.qubits[a]) > 1:
|
|
3217
3228
|
p_cz *= d
|
|
3218
3229
|
p_net_iswap = p_net_swap * (1 - p_cz) + p_cz * (1 - p_net_swap)
|
|
3219
3230
|
if is_a_simple:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|