pyqrack-complex128 2.28.2__tar.gz → 2.29.1__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_complex128-2.28.2/pyqrack_complex128.egg-info → pyqrack_complex128-2.29.1}/PKG-INFO +1 -1
- {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack/qrack_ace_backend.py +122 -207
- {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1/pyqrack_complex128.egg-info}/PKG-INFO +1 -1
- {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/setup.py +1 -1
- {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/LICENSE +0 -0
- {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/MANIFEST.in +0 -0
- {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/Makefile +0 -0
- {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/README.md +0 -0
- {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyproject.toml +0 -0
- {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack/__init__.py +0 -0
- {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack/neuron_activation_fn.py +0 -0
- {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack/pauli.py +0 -0
- {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack/qrack_ace_mps_backend.py +0 -0
- {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack/qrack_circuit.py +0 -0
- {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack/qrack_near_clifford_qec_backend.py +0 -0
- {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack/qrack_neuron.py +0 -0
- {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack/qrack_neuron_torch_layer.py +0 -0
- {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack/qrack_simulator.py +0 -0
- {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack/qrack_stabilizer.py +0 -0
- {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack/qrack_system/__init__.py +0 -0
- {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack/qrack_system/qrack_system.py +0 -0
- {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack/quimb_circuit_type.py +0 -0
- {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack/stats/__init__.py +0 -0
- {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack/stats/load_quantized_data.py +0 -0
- {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack/stats/quantize_by_range.py +0 -0
- {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack_complex128.egg-info/SOURCES.txt +0 -0
- {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack_complex128.egg-info/dependency_links.txt +0 -0
- {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack_complex128.egg-info/not-zip-safe +0 -0
- {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack_complex128.egg-info/requires.txt +0 -0
- {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack_complex128.egg-info/top_level.txt +0 -0
- {pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/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?
|
|
@@ -306,7 +317,7 @@ class QrackAceBackend:
|
|
|
306
317
|
# between "more separable" (rotation toward consensus is real,
|
|
307
318
|
# useful reconciliation) and "more entangled" (rotation only
|
|
308
319
|
# scrambles a real correlation with another logical qubit's
|
|
309
|
-
# replica -- see
|
|
320
|
+
# replica -- see correct). Default is the point where a single
|
|
310
321
|
# qubit's linear entropy is half its maximum.
|
|
311
322
|
self._rot_epsilon = (1.0 - 1.0 / math.sqrt(2)) / 2
|
|
312
323
|
|
|
@@ -329,7 +340,7 @@ class QrackAceBackend:
|
|
|
329
340
|
# rather than surgically suppressing the wrap step further
|
|
330
341
|
# down (which was tried and reverted -- it produced replica
|
|
331
342
|
# counts, e.g. 2, that the rest of the class was never built
|
|
332
|
-
# to handle, via _get_qb_lhv_indices/
|
|
343
|
+
# to handle, via _get_qb_lhv_indices/correct, which assume
|
|
333
344
|
# counts of 1, 3, or 5 only), means every code path that fires
|
|
334
345
|
# for is_torus=False is one that already exists and is already
|
|
335
346
|
# tested for is_torus=True; only WHICH positions take which
|
|
@@ -360,17 +371,17 @@ 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
|
-
# drops to being only the last-resort tie-breaker
|
|
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
|
|
367
378
|
# it off, the proxy is promoted to cover the crossbar's old
|
|
368
379
|
# tie-breaking role directly.
|
|
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,25 +452,25 @@ 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,
|
|
451
|
-
# nested cx(lq1, anc) call goes through the full
|
|
462
|
+
# nested cx(lq1, anc) call goes through the full correct()/
|
|
452
463
|
# shadow-coupling machinery robustly, the same way any other
|
|
453
464
|
# logical qubit's value would be determined -- watching one
|
|
454
465
|
# specific physical replica directly (an earlier version of
|
|
455
|
-
# this) isn't a valid invariant, since
|
|
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
|
|
@@ -866,7 +877,7 @@ class QrackAceBackend:
|
|
|
866
877
|
|
|
867
878
|
sim.mtrx([m00, m01, m10, m11])
|
|
868
879
|
|
|
869
|
-
def
|
|
880
|
+
def correct(self, lq, phase=False, skip_rotation=False):
|
|
870
881
|
hq = self._unpack(lq)
|
|
871
882
|
|
|
872
883
|
if len(hq) == 1:
|
|
@@ -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,11 +1333,11 @@ 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
|
|
|
1329
|
-
# Correction deferred to next 2-qubit gate (_cpauli calls
|
|
1340
|
+
# Correction deferred to next 2-qubit gate (_cpauli calls correct)
|
|
1330
1341
|
|
|
1331
1342
|
def r(self, p, th, lq):
|
|
1332
1343
|
hq = self._unpack(lq)
|
|
@@ -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)
|
|
@@ -1350,7 +1361,7 @@ class QrackAceBackend:
|
|
|
1350
1361
|
elif p == Pauli.PauliZ:
|
|
1351
1362
|
lhv.rz(th)
|
|
1352
1363
|
|
|
1353
|
-
# Correction deferred to next 2-qubit gate (_cpauli calls
|
|
1364
|
+
# Correction deferred to next 2-qubit gate (_cpauli calls correct)
|
|
1354
1365
|
|
|
1355
1366
|
def h(self, lq):
|
|
1356
1367
|
hq = self._unpack(lq)
|
|
@@ -1365,11 +1376,11 @@ 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
|
|
|
1372
|
-
# Correction deferred to next 2-qubit gate (_cpauli calls
|
|
1383
|
+
# Correction deferred to next 2-qubit gate (_cpauli calls correct)
|
|
1373
1384
|
|
|
1374
1385
|
def s(self, lq):
|
|
1375
1386
|
hq = self._unpack(lq)
|
|
@@ -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)
|
|
@@ -1636,8 +1647,8 @@ class QrackAceBackend:
|
|
|
1636
1647
|
self._in_gadget_capture = False
|
|
1637
1648
|
|
|
1638
1649
|
if not self._in_gadget_capture:
|
|
1639
|
-
self.
|
|
1640
|
-
self.
|
|
1650
|
+
self.correct(lq1)
|
|
1651
|
+
self.correct(lq2)
|
|
1641
1652
|
|
|
1642
1653
|
# A target replica (q2) that shares a simulator with ANY control
|
|
1643
1654
|
# replica in qb1 is going to receive a REAL, exact gate from that
|
|
@@ -1651,7 +1662,7 @@ class QrackAceBackend:
|
|
|
1651
1662
|
# invert the correlation sign the real CNOT is about to
|
|
1652
1663
|
# establish. Confirmed directly: for a boundary-to-boundary
|
|
1653
1664
|
# coupling, measuring the raw physical replica pairs that DO
|
|
1654
|
-
# share a simulator (bypassing all of
|
|
1665
|
+
# share a simulator (bypassing all of correct/m's classical
|
|
1655
1666
|
# reconciliation, and with is_error_detection off) showed near-
|
|
1656
1667
|
# perfect agreement for the one pair untouched by any earlier
|
|
1657
1668
|
# shadow write, but only ~50-60% agreement (chance level) for
|
|
@@ -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:
|
|
@@ -1693,15 +1704,15 @@ class QrackAceBackend:
|
|
|
1693
1704
|
|
|
1694
1705
|
if not self._in_gadget_capture:
|
|
1695
1706
|
if pauli != Pauli.PauliZ:
|
|
1696
|
-
self.
|
|
1707
|
+
self.correct(lq2, False, pauli != Pauli.PauliX)
|
|
1697
1708
|
if pauli != Pauli.PauliX:
|
|
1698
|
-
self.
|
|
1709
|
+
self.correct(lq2, True)
|
|
1699
1710
|
|
|
1700
1711
|
if anc is not None:
|
|
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)
|
|
@@ -2011,10 +2022,10 @@ class QrackAceBackend:
|
|
|
2011
2022
|
# decomposition for SDRP to truncate. This is the case the noise
|
|
2012
2023
|
# model's common-fraction term assumes is error-free.
|
|
2013
2024
|
|
|
2014
|
-
# _cpauli normally wraps every coupling gate with
|
|
2025
|
+
# _cpauli normally wraps every coupling gate with correct() before
|
|
2015
2026
|
# and after, to reconcile multi-replica consensus. This method
|
|
2016
2027
|
# calls the underlying simulators (and _cx_shadow) directly,
|
|
2017
|
-
# bypassing that wrapper entirely --
|
|
2028
|
+
# bypassing that wrapper entirely -- correct() is a no-op for
|
|
2018
2029
|
# single-replica (bulk) qubits, so this is always safe to call, and
|
|
2019
2030
|
# only actually matters (and was being silently skipped) whenever
|
|
2020
2031
|
# either side is a multi-replica boundary qubit.
|
|
@@ -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
|
|
|
@@ -2081,8 +2092,8 @@ class QrackAceBackend:
|
|
|
2081
2092
|
self.cx(lq2, lq1)
|
|
2082
2093
|
return
|
|
2083
2094
|
|
|
2084
|
-
self.
|
|
2085
|
-
self.
|
|
2095
|
+
self.correct(lq1)
|
|
2096
|
+
self.correct(lq2)
|
|
2086
2097
|
|
|
2087
2098
|
# Partial-match cases: one side is a simple (single-replica) qubit,
|
|
2088
2099
|
# the other has multiple replicas. For the multi-replica side's
|
|
@@ -2109,12 +2120,12 @@ class QrackAceBackend:
|
|
|
2109
2120
|
for _hq2 in non_matching:
|
|
2110
2121
|
self._cx_shadow(_hq1, _hq2)
|
|
2111
2122
|
# No correction on lq1 (bulk, single-replica): already a
|
|
2112
|
-
# structural no-op via
|
|
2123
|
+
# structural no-op via correct's own len(hq)==1 guard.
|
|
2113
2124
|
# lq2 (boundary) DOES take real error here (X-type, verified),
|
|
2114
2125
|
# so it keeps a correction -- phase=False, not True, since the
|
|
2115
2126
|
# error is already Z-basis-population-visible and doesn't need
|
|
2116
2127
|
# an H-rotation first to become detectable.
|
|
2117
|
-
self.
|
|
2128
|
+
self.correct(lq2)
|
|
2118
2129
|
elif len(hq2) == 1 and any(h[0] == hq2[0][0] for h in hq1):
|
|
2119
2130
|
_hq2 = hq2[0]
|
|
2120
2131
|
non_matching = [h for h in hq1 if h[0] != _hq2[0]]
|
|
@@ -2125,14 +2136,14 @@ class QrackAceBackend:
|
|
|
2125
2136
|
self.sim[_hq2[0]].swap(_hq2[1], _hq1[1])
|
|
2126
2137
|
for _hq1 in non_matching:
|
|
2127
2138
|
self._cx_shadow(_hq2, _hq1)
|
|
2128
|
-
self.
|
|
2139
|
+
self.correct(lq1)
|
|
2129
2140
|
else:
|
|
2130
2141
|
# General case: no shared simulator at all (e.g. two simple
|
|
2131
2142
|
# qubits on entirely different sims -- nothing to anchor an exact
|
|
2132
2143
|
# swap on, verified the sandwich branches above give WRONG,
|
|
2133
2144
|
# deterministic results here: 100/100 failures vs 0/15 for this
|
|
2134
2145
|
# fallback), or both sides multi-replica and not fully matched:
|
|
2135
|
-
# cx() already wraps itself with
|
|
2146
|
+
# cx() already wraps itself with correct() via _cpauli, so no
|
|
2136
2147
|
# extra wrapping needed here.
|
|
2137
2148
|
self.cx(lq1, lq2)
|
|
2138
2149
|
self.cx(lq2, lq1)
|
|
@@ -2159,65 +2170,6 @@ class QrackAceBackend:
|
|
|
2159
2170
|
self.mcx([c1, c2], t)
|
|
2160
2171
|
return
|
|
2161
2172
|
|
|
2162
|
-
# AND-invariant, generalized from _apply_coupling's CX/CZ XOR
|
|
2163
|
-
# check to the Toffoli's actual relationship: t should end up as
|
|
2164
|
-
# t_before XOR (c1 AND c2), not just t_before XOR c1 or XOR c2
|
|
2165
|
-
# individually. mcx is the native AND-conditioned flip, so it
|
|
2166
|
-
# plays the same role here that a plain cx(control, anc) played
|
|
2167
|
-
# in the 2-qubit case: capture t's pre-gate value, then fold in
|
|
2168
|
-
# c1 AND c2 via a genuine nested mcx() (which goes through the
|
|
2169
|
-
# same shadow/_correct machinery as any other logical gate, so
|
|
2170
|
-
# this is exposed to the same class of intrinsic approximation
|
|
2171
|
-
# error the CX/CZ check catches, not a raw single-replica read).
|
|
2172
|
-
# Uses t's own patch ancilla2 array, since t itself is guaranteed
|
|
2173
|
-
# bulk on this path (the len(hqt) > 1 case returns earlier,
|
|
2174
|
-
# above) and nothing else in this method uses that array anymore.
|
|
2175
|
-
anc_and = None
|
|
2176
|
-
if self.is_error_detection and not self._in_gadget_capture and ((len(hqt) > 1) or (len(hq1) > 1) or (len(hq2) > 1)):
|
|
2177
|
-
found = False
|
|
2178
|
-
anc_sim = hq1[0][0]
|
|
2179
|
-
t_sims = {y[0] for y in hqt if y[0] >= 0}
|
|
2180
|
-
for x in hq1:
|
|
2181
|
-
if (x[0] < 0) or (x[0] not in t_sims):
|
|
2182
|
-
continue
|
|
2183
|
-
anc_sim = x[0]
|
|
2184
|
-
for y in hq2:
|
|
2185
|
-
if (y[0] < 0) or (y[0] not in t_sims):
|
|
2186
|
-
continue
|
|
2187
|
-
if anc_sim == y[0]:
|
|
2188
|
-
found = True
|
|
2189
|
-
break
|
|
2190
|
-
if found:
|
|
2191
|
-
break
|
|
2192
|
-
self._in_gadget_capture = True
|
|
2193
|
-
anc_and = self._detect_ancilla_lq[anc_sim]
|
|
2194
|
-
self.cx(t, anc_and)
|
|
2195
|
-
# self.mcx() only supports a single control at the logical
|
|
2196
|
-
# level (a syntax-convenience wrapper, confirmed directly --
|
|
2197
|
-
# it raises for 2 controls), so it can't do the AND capture
|
|
2198
|
-
# itself. When c1 and c2 both happen to have a physical
|
|
2199
|
-
# replica in t's own home-patch simulator (the same one
|
|
2200
|
-
# anc_and lives in), use a genuine native mcx there directly
|
|
2201
|
-
# -- exact, and a clean, independent reference to compare the
|
|
2202
|
-
# real Toffoli against. Otherwise, fall back to a recursive
|
|
2203
|
-
# ccx(c1, c2, anc_and): safe from infinite recursion here
|
|
2204
|
-
# specifically because _in_gadget_capture is already True, so
|
|
2205
|
-
# the recursive call's own "not self._in_gadget_capture" gate
|
|
2206
|
-
# skips setting up a second anc_and, and just performs the
|
|
2207
|
-
# ordinary native-witness-or-shadow Toffoli computation on
|
|
2208
|
-
# anc_and instead.
|
|
2209
|
-
c1_idx = next((r[1] for r in hq1 if r[0] == anc_sim), None)
|
|
2210
|
-
c2_idx = next((r[1] for r in hq2 if r[0] == anc_sim), None)
|
|
2211
|
-
if (c1_idx is not None) and (c2_idx is not None):
|
|
2212
|
-
_, anc_and_idx = self._qubits[anc_and][0]
|
|
2213
|
-
self.sim[anc_sim].mcx([c1_idx, c2_idx], anc_and_idx)
|
|
2214
|
-
else:
|
|
2215
|
-
self.ccx(c1, c2, anc_and)
|
|
2216
|
-
self._in_gadget_capture = False
|
|
2217
|
-
|
|
2218
|
-
self._correct(c1)
|
|
2219
|
-
self._correct(c2)
|
|
2220
|
-
|
|
2221
2173
|
qb1, _ = QrackAceBackend._get_qb_lhv_indices(hq1)
|
|
2222
2174
|
qb2, _ = QrackAceBackend._get_qb_lhv_indices(hq2)
|
|
2223
2175
|
qbt, _ = QrackAceBackend._get_qb_lhv_indices(hqt)
|
|
@@ -2242,28 +2194,6 @@ class QrackAceBackend:
|
|
|
2242
2194
|
witnesses.append(witness)
|
|
2243
2195
|
|
|
2244
2196
|
if needs_shadow:
|
|
2245
|
-
# Confirmed directly: whenever even one replica of t lacks a
|
|
2246
|
-
# native witness shared with c1 and c2, the per-replica
|
|
2247
|
-
# fallback to _ccx_shadow craters fidelity specifically when
|
|
2248
|
-
# either control is in genuine superposition -- 0.64-0.68
|
|
2249
|
-
# vs. 0.999+ for every other input tested, stable from 100 to
|
|
2250
|
-
# 2500 shots, so not shot noise. Diverting the WHOLE
|
|
2251
|
-
# operation (not just the unwitnessed replica) to the general
|
|
2252
|
-
# 6-CX decomposition instead: every step here is a genuine
|
|
2253
|
-
# logical cx()/h()/t()/adjt() call, which updates every
|
|
2254
|
-
# replica of t through the same _apply_coupling boundary-
|
|
2255
|
-
# crossing machinery already validated elsewhere in this
|
|
2256
|
-
# file, rather than the lower-level per-replica _cx_shadow
|
|
2257
|
-
# calls _ccx_shadow uses for just the unwitnessed ones.
|
|
2258
|
-
#
|
|
2259
|
-
# Wrapped in _in_gadget_capture, same as established earlier:
|
|
2260
|
-
# without it, each individual cx() here would independently
|
|
2261
|
-
# trigger its own XOR-invariant gadget setup, which corrupts
|
|
2262
|
-
# the precise T/Tdg phase relationships this decomposition
|
|
2263
|
-
# depends on (confirmed directly, the first time this
|
|
2264
|
-
# decomposition was used unconditionally -- corrupted even
|
|
2265
|
-
# c1/c2, which the per-replica path never touched at all).
|
|
2266
|
-
self._in_gadget_capture = True
|
|
2267
2197
|
self.h(t)
|
|
2268
2198
|
self.cx(c2, t)
|
|
2269
2199
|
self.adjt(t)
|
|
@@ -2279,29 +2209,14 @@ class QrackAceBackend:
|
|
|
2279
2209
|
self.t(c1)
|
|
2280
2210
|
self.adjt(c2)
|
|
2281
2211
|
self.cx(c1, c2)
|
|
2282
|
-
self._in_gadget_capture = False
|
|
2283
2212
|
else:
|
|
2213
|
+
self.correct(c1)
|
|
2214
|
+
self.correct(c2)
|
|
2215
|
+
|
|
2284
2216
|
for (b1, b2, bt) in witnesses:
|
|
2285
2217
|
self.sim[b1[0]].mcx([b1[1], b2[1]], bt[1])
|
|
2286
2218
|
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
self._in_gadget_capture = True
|
|
2290
|
-
|
|
2291
|
-
if anc_and is not None:
|
|
2292
|
-
self.cx(t, anc_and)
|
|
2293
|
-
anc_sim, anc_idx = self._qubits[anc_and][0]
|
|
2294
|
-
p = self.sim[anc_sim].prob(anc_idx)
|
|
2295
|
-
p = 1.0 - p
|
|
2296
|
-
if self._ps_epsilon >= p:
|
|
2297
|
-
b = self.m(anc_and)
|
|
2298
|
-
else:
|
|
2299
|
-
b = self.force_m(anc_and, False)
|
|
2300
|
-
if b:
|
|
2301
|
-
self.x(anc_and)
|
|
2302
|
-
self.x(t)
|
|
2303
|
-
|
|
2304
|
-
self._in_gadget_capture = False
|
|
2219
|
+
self.correct(t)
|
|
2305
2220
|
|
|
2306
2221
|
def ccz(self, c1, c2, t):
|
|
2307
2222
|
self.h(t)
|
|
@@ -2319,9 +2234,9 @@ class QrackAceBackend:
|
|
|
2319
2234
|
b = hq[0]
|
|
2320
2235
|
return self.sim[b[0]].prob(b[1])
|
|
2321
2236
|
|
|
2322
|
-
self.
|
|
2237
|
+
self.correct(lq)
|
|
2323
2238
|
# The real replicas are already mutually consistent here (the
|
|
2324
|
-
#
|
|
2239
|
+
# correct() call above already ran the appropriate tie-breaking
|
|
2325
2240
|
# cascade for however many replicas hq has -- 2, 3, 4, or 5,
|
|
2326
2241
|
# depending on self.use_crossbars and whether this is an edge or
|
|
2327
2242
|
# corner boundary qubit -- using the LHV proxy and/or the
|
|
@@ -2330,7 +2245,7 @@ class QrackAceBackend:
|
|
|
2330
2245
|
# correct, already-decided answer, regardless of replica count:
|
|
2331
2246
|
# re-weighting any particular replica specially here would
|
|
2332
2247
|
# reintroduce the same center-dragging distortion the cascade in
|
|
2333
|
-
#
|
|
2248
|
+
# correct() was built to avoid.
|
|
2334
2249
|
p = [self.sim[b[0]].prob(b[1]) for b in hq]
|
|
2335
2250
|
n = len(p)
|
|
2336
2251
|
prms = math.sqrt(sum(x**2 for x in p) / n)
|
|
@@ -2358,7 +2273,7 @@ class QrackAceBackend:
|
|
|
2358
2273
|
else:
|
|
2359
2274
|
self.sim[b[0]].force_m(b[1], result)
|
|
2360
2275
|
|
|
2361
|
-
lhv = self.
|
|
2276
|
+
lhv = self.lhv.get(lq)
|
|
2362
2277
|
if lhv is not None:
|
|
2363
2278
|
lhv.reset()
|
|
2364
2279
|
if result:
|
|
@@ -2372,7 +2287,7 @@ class QrackAceBackend:
|
|
|
2372
2287
|
b = hq[0]
|
|
2373
2288
|
return self.sim[b[0]].force_m(b[1], result)
|
|
2374
2289
|
|
|
2375
|
-
self.
|
|
2290
|
+
self.correct(lq)
|
|
2376
2291
|
|
|
2377
2292
|
qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
|
|
2378
2293
|
|
|
@@ -2385,7 +2300,7 @@ class QrackAceBackend:
|
|
|
2385
2300
|
else:
|
|
2386
2301
|
self.sim[b[0]].force_m(b[1], result)
|
|
2387
2302
|
|
|
2388
|
-
lhv = self.
|
|
2303
|
+
lhv = self.lhv.get(lq)
|
|
2389
2304
|
if lhv is not None:
|
|
2390
2305
|
lhv.reset()
|
|
2391
2306
|
if result:
|
|
@@ -2782,7 +2697,7 @@ class QrackAceBackend:
|
|
|
2782
2697
|
# keep patching individual arithmetic mistakes in a function whose
|
|
2783
2698
|
# intended row/col semantics were genuinely unclear on inspection,
|
|
2784
2699
|
# this is grounded directly in the one thing that's true by
|
|
2785
|
-
# construction: self.
|
|
2700
|
+
# construction: self.qubits, the actual replica structure built
|
|
2786
2701
|
# during __init__. Two logical qubits are coupled if and only if
|
|
2787
2702
|
# they share ANY simulator id among their own replicas -- this
|
|
2788
2703
|
# cannot disagree with the real adjacency, because it doesn't
|
|
@@ -2791,7 +2706,7 @@ class QrackAceBackend:
|
|
|
2791
2706
|
n = self.num_qubits()
|
|
2792
2707
|
sim_to_qubits = {}
|
|
2793
2708
|
for lq in range(n):
|
|
2794
|
-
for sim_id, _ in self.
|
|
2709
|
+
for sim_id, _ in self.qubits[lq]:
|
|
2795
2710
|
sim_to_qubits.setdefault(sim_id, []).append(lq)
|
|
2796
2711
|
for sim_id, qubits_here in sim_to_qubits.items():
|
|
2797
2712
|
for a in qubits_here:
|
|
@@ -2822,7 +2737,7 @@ class QrackAceBackend:
|
|
|
2822
2737
|
# are untouched -- a bulk qubit's single simulator membership
|
|
2823
2738
|
# was never ambiguous.
|
|
2824
2739
|
def is_boundary(lq):
|
|
2825
|
-
return len(self.
|
|
2740
|
+
return len(self.qubits[lq]) > 1
|
|
2826
2741
|
|
|
2827
2742
|
def same_single_side(idx_a, idx_b, long_range_arr, length):
|
|
2828
2743
|
if idx_a == idx_b:
|
|
@@ -3008,8 +2923,8 @@ class QrackAceBackend:
|
|
|
3008
2923
|
noise_model = NoiseModel()
|
|
3009
2924
|
|
|
3010
2925
|
def _uncommon_sim_fraction(lq1, lq2):
|
|
3011
|
-
sims1 = {qb[0] for qb in self.
|
|
3012
|
-
sims2 = {qb[0] for qb in self.
|
|
2926
|
+
sims1 = {qb[0] for qb in self.qubits[lq1]}
|
|
2927
|
+
sims2 = {qb[0] for qb in self.qubits[lq2]}
|
|
3013
2928
|
|
|
3014
2929
|
return len(sims1 ^ sims2) / len(sims1.union(sims2))
|
|
3015
2930
|
|
|
@@ -3022,7 +2937,7 @@ class QrackAceBackend:
|
|
|
3022
2937
|
# _cpauli's own convention) to be a multi-replica (boundary)
|
|
3023
2938
|
# qubit -- see _cpauli: anc1 (the control-invariance capture) is
|
|
3024
2939
|
# unconditional but structurally trivial for a single-replica
|
|
3025
|
-
# control (
|
|
2940
|
+
# control (correct() is already a no-op there, nothing for the
|
|
3026
2941
|
# gadget to protect against), and anc2 (the target-value
|
|
3027
2942
|
# verification against the CX/ACX-predicted output) is
|
|
3028
2943
|
# explicitly gated on len(hq1) > 1. Both halves are gated the
|
|
@@ -3039,7 +2954,7 @@ class QrackAceBackend:
|
|
|
3039
2954
|
# nested cx(lq1, anc) capture goes through the full logical
|
|
3040
2955
|
# coupling machinery (shadow or real, whichever applies) and
|
|
3041
2956
|
# works the same way regardless. So this is checked directly,
|
|
3042
|
-
# per pair, from len(self.
|
|
2957
|
+
# per pair, from len(self.qubits[a]) -- no precomputed set
|
|
3043
2958
|
# needed.
|
|
3044
2959
|
|
|
3045
2960
|
# This factor is a theory-derived placeholder, not an empirical
|
|
@@ -3085,7 +3000,7 @@ class QrackAceBackend:
|
|
|
3085
3000
|
# Pauli-string convention is big-endian relative to the qubit
|
|
3086
3001
|
# list: the LAST qubit corresponds to the LEFTMOST character).
|
|
3087
3002
|
p_shadow_ccz = 1 - sp * p
|
|
3088
|
-
if self.is_error_detection and len(self.
|
|
3003
|
+
if self.is_error_detection and len(self.qubits[a]) > 1:
|
|
3089
3004
|
# a is the control for this pair (_cpauli(lq1=a, lq2=b,
|
|
3090
3005
|
# ...) is always called with a as the coupling source).
|
|
3091
3006
|
# Both gadget halves that protect this pair (anc1 on a,
|
|
@@ -3119,13 +3034,13 @@ class QrackAceBackend:
|
|
|
3119
3034
|
# Error type re-verified directly against THIS file (the
|
|
3120
3035
|
# gadget mechanics changed substantially since the type was
|
|
3121
3036
|
# first derived -- swap()'s own cross-check gadget, the
|
|
3122
|
-
#
|
|
3037
|
+
# correct()-skip-during-capture fix, and the post-selection
|
|
3123
3038
|
# floor threshold are all new), to make sure none of that
|
|
3124
3039
|
# changed the underlying picture. It hasn't: _cx_shadow
|
|
3125
3040
|
# itself is untouched by any of it. Reading the LOGICAL
|
|
3126
3041
|
# prob(lq) still tells you nothing (0.00000 deviation in
|
|
3127
3042
|
# both bases on a single isolated swap, gadget on or off --
|
|
3128
|
-
# it calls
|
|
3043
|
+
# it calls correct() internally and reconciles across every
|
|
3129
3044
|
# replica, the same masking this file has run into more than
|
|
3130
3045
|
# once). Reading the RAW, non-matching replica directly
|
|
3131
3046
|
# (bypassing that reconciliation) gives a clean, deterministic
|
|
@@ -3160,12 +3075,12 @@ class QrackAceBackend:
|
|
|
3160
3075
|
# error character isn't captured by this specific derivation;
|
|
3161
3076
|
# that case keeps the previous symmetric depolarizing
|
|
3162
3077
|
# treatment below as a fallback.
|
|
3163
|
-
len_a = len(self.
|
|
3164
|
-
len_b = len(self.
|
|
3078
|
+
len_a = len(self.qubits[a])
|
|
3079
|
+
len_b = len(self.qubits[b])
|
|
3165
3080
|
is_a_simple = len_a == 1
|
|
3166
3081
|
is_b_simple = len_b == 1
|
|
3167
|
-
sims_a = [qb[0] for qb in self.
|
|
3168
|
-
sims_b = [qb[0] for qb in self.
|
|
3082
|
+
sims_a = [qb[0] for qb in self.qubits[a]]
|
|
3083
|
+
sims_b = [qb[0] for qb in self.qubits[b]]
|
|
3169
3084
|
has_match = any(s in sims_b for s in sims_a)
|
|
3170
3085
|
|
|
3171
3086
|
if (is_a_simple != is_b_simple) and has_match:
|
|
@@ -3213,7 +3128,7 @@ class QrackAceBackend:
|
|
|
3213
3128
|
# 4-term (I,X,Z,Y) channel down to a plain 2-term (I,Z) one.
|
|
3214
3129
|
if (is_a_simple != is_b_simple) and has_match:
|
|
3215
3130
|
p_cz = 1 - sp * p # same per-call formula as standalone cz
|
|
3216
|
-
if self.is_error_detection and len(self.
|
|
3131
|
+
if self.is_error_detection and len(self.qubits[a]) > 1:
|
|
3217
3132
|
p_cz *= d
|
|
3218
3133
|
p_net_iswap = p_net_swap * (1 - p_cz) + p_cz * (1 - p_net_swap)
|
|
3219
3134
|
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
|
{pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack/qrack_near_clifford_qec_backend.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack/qrack_system/qrack_system.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack/stats/load_quantized_data.py
RENAMED
|
File without changes
|
|
File without changes
|
{pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack_complex128.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack_complex128.egg-info/not-zip-safe
RENAMED
|
File without changes
|
{pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack_complex128.egg-info/requires.txt
RENAMED
|
File without changes
|
{pyqrack_complex128-2.28.2 → pyqrack_complex128-2.29.1}/pyqrack_complex128.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|