pyqrack-complex128 2.18.2__tar.gz → 2.18.4__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.18.2/pyqrack_complex128.egg-info → pyqrack_complex128-2.18.4}/PKG-INFO +1 -1
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.4}/pyqrack/qrack_ace_backend.py +286 -205
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.4/pyqrack_complex128.egg-info}/PKG-INFO +1 -1
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.4}/setup.py +1 -1
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.4}/LICENSE +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.4}/MANIFEST.in +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.4}/Makefile +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.4}/README.md +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.4}/pyproject.toml +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.4}/pyqrack/__init__.py +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.4}/pyqrack/neuron_activation_fn.py +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.4}/pyqrack/pauli.py +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.4}/pyqrack/qrack_circuit.py +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.4}/pyqrack/qrack_near_clifford_qec_backend.py +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.4}/pyqrack/qrack_neuron.py +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.4}/pyqrack/qrack_neuron_torch_layer.py +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.4}/pyqrack/qrack_simulator.py +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.4}/pyqrack/qrack_stabilizer.py +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.4}/pyqrack/qrack_system/__init__.py +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.4}/pyqrack/qrack_system/qrack_system.py +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.4}/pyqrack/quimb_circuit_type.py +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.4}/pyqrack/stats/__init__.py +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.4}/pyqrack/stats/load_quantized_data.py +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.4}/pyqrack/stats/quantize_by_range.py +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.4}/pyqrack_complex128.egg-info/SOURCES.txt +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.4}/pyqrack_complex128.egg-info/dependency_links.txt +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.4}/pyqrack_complex128.egg-info/not-zip-safe +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.4}/pyqrack_complex128.egg-info/requires.txt +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.4}/pyqrack_complex128.egg-info/top_level.txt +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.4}/setup.cfg +0 -0
|
@@ -274,11 +274,11 @@ class QrackAceBackend:
|
|
|
274
274
|
if "QRACK_FPPOW" in os.environ:
|
|
275
275
|
fppow = int(os.environ.get("QRACK_FPPOW"))
|
|
276
276
|
if fppow < 5:
|
|
277
|
-
self._epsilon = 2**-
|
|
277
|
+
self._epsilon = 2**-8
|
|
278
278
|
elif fppow > 5:
|
|
279
|
-
self._epsilon = 2**-
|
|
279
|
+
self._epsilon = 2**-50
|
|
280
280
|
else:
|
|
281
|
-
self._epsilon = 2**-
|
|
281
|
+
self._epsilon = 2**-21
|
|
282
282
|
|
|
283
283
|
self._coupling_map = None
|
|
284
284
|
|
|
@@ -406,18 +406,57 @@ class QrackAceBackend:
|
|
|
406
406
|
sim_counts.append(boundary_count)
|
|
407
407
|
|
|
408
408
|
# Error-detection gadget (IBM-style detect-and-post-select, not
|
|
409
|
-
# correction):
|
|
409
|
+
# correction): two shared ancillae per simulator -- every patch AND
|
|
410
410
|
# the boundary crossbar sim alike, "a single ancilla per patch,
|
|
411
411
|
# overall" -- reused across every real (same-simulator) coupling
|
|
412
412
|
# gate that touches it, never allocated per-gate or per-qubit.
|
|
413
413
|
# See _apply_coupling for the actual gadget; this block only does
|
|
414
414
|
# the qubit-index bookkeeping, in the same style as every other
|
|
415
415
|
# per-simulator allocation above.
|
|
416
|
-
self.
|
|
416
|
+
self._detect_ancilla1 = []
|
|
417
|
+
self._detect_ancilla2 = []
|
|
417
418
|
if self.is_error_detection:
|
|
418
419
|
for i in range(len(sim_counts)):
|
|
419
|
-
self.
|
|
420
|
+
self._detect_ancilla1.append(sim_counts[i])
|
|
420
421
|
sim_counts[i] += 1
|
|
422
|
+
self._detect_ancilla2.append(sim_counts[i])
|
|
423
|
+
sim_counts[i] += 1
|
|
424
|
+
|
|
425
|
+
# Logical-qubit wrapper around each patch's detection ancilla,
|
|
426
|
+
# so it's addressable via the ORDINARY cx()/_cpauli() machinery
|
|
427
|
+
# (self.cx(lq1, anc)), not just raw (sim, idx) tuples. Appended
|
|
428
|
+
# PAST the user-facing qubit range (num_qubits() is computed
|
|
429
|
+
# independently from the grid dimensions, not from len(
|
|
430
|
+
# self._qubits), so these extra entries are never visible or
|
|
431
|
+
# reachable through the normal API) -- a simple, single-replica
|
|
432
|
+
# (bulk-style) logical qubit per simulator, one entry in
|
|
433
|
+
# self._qubits pointing at the same physical qubit _detect_
|
|
434
|
+
# ancilla already allocated there. See _apply_coupling for why
|
|
435
|
+
# this needs to be a genuine logical qubit and not just a raw
|
|
436
|
+
# physical capture: capturing lq1's LOGICAL value via a real,
|
|
437
|
+
# nested cx(lq1, anc) call goes through the full _correct()/
|
|
438
|
+
# shadow-coupling machinery robustly, the same way any other
|
|
439
|
+
# logical qubit's value would be determined -- watching one
|
|
440
|
+
# specific physical replica directly (an earlier version of
|
|
441
|
+
# this) isn't a valid invariant, since _correct() can
|
|
442
|
+
# legitimately need to change any single replica's own value.
|
|
443
|
+
self._detect_ancilla1_lq = []
|
|
444
|
+
self._detect_ancilla2_lq = []
|
|
445
|
+
if self.is_error_detection:
|
|
446
|
+
for sim_id, phys_idx in enumerate(self._detect_ancilla1):
|
|
447
|
+
lq_idx = len(self._qubits)
|
|
448
|
+
self._qubits.append([(sim_id, phys_idx)])
|
|
449
|
+
self._detect_ancilla1_lq.append(lq_idx)
|
|
450
|
+
for sim_id, phys_idx in enumerate(self._detect_ancilla2):
|
|
451
|
+
lq_idx = len(self._qubits)
|
|
452
|
+
self._qubits.append([(sim_id, phys_idx)])
|
|
453
|
+
self._detect_ancilla2_lq.append(lq_idx)
|
|
454
|
+
# Reentrancy guard for the gadget's own nested cx(lq1, anc)
|
|
455
|
+
# capture calls (see _apply_coupling): those calls go through
|
|
456
|
+
# the ordinary _cpauli -> _apply_coupling path themselves, which
|
|
457
|
+
# would otherwise try to wrap ITSELF in another capture,
|
|
458
|
+
# recursing without end.
|
|
459
|
+
self._in_gadget_capture = False
|
|
421
460
|
|
|
422
461
|
if "QRACK_QUNIT_SEPARABILITY_THRESHOLD" in os.environ:
|
|
423
462
|
self._sdrp = min(1, float(os.environ["QRACK_QUNIT_SEPARABILITY_THRESHOLD"]))
|
|
@@ -632,51 +671,12 @@ class QrackAceBackend:
|
|
|
632
671
|
t.adjs()
|
|
633
672
|
|
|
634
673
|
def _detect_swap(self, sim_id, idx1, idx2):
|
|
635
|
-
|
|
636
|
-
gate branch, generalized to SWAP's own invariant instead of
|
|
637
|
-
CNOT's. SWAP exchanges two qubits' values, but their XOR
|
|
638
|
-
(parity) is exactly conserved in the noiseless case:
|
|
639
|
-
idx1_before ^ idx2_before == idx1_after ^ idx2_after, regardless
|
|
640
|
-
of either qubit's individual value -- captured the same way,
|
|
641
|
-
just with two capture-CNOTs (one per qubit) into the shared
|
|
642
|
-
per-simulator ancilla instead of one.
|
|
643
|
-
"""
|
|
644
|
-
sim = self.sim[sim_id]
|
|
645
|
-
if not self.is_error_detection:
|
|
646
|
-
sim.swap(idx1, idx2)
|
|
647
|
-
return
|
|
648
|
-
anc = self._detect_ancilla[sim_id]
|
|
649
|
-
sim.mcx([idx1], anc)
|
|
650
|
-
sim.mcx([idx2], anc)
|
|
651
|
-
sim.swap(idx1, idx2)
|
|
652
|
-
sim.mcx([idx1], anc)
|
|
653
|
-
sim.mcx([idx2], anc)
|
|
654
|
-
sim.force_m(anc, False)
|
|
674
|
+
self.sim[sim_id].swap(idx1, idx2)
|
|
655
675
|
|
|
656
676
|
def _anti_shadow_wrap(self, c, t, middle_fn):
|
|
657
|
-
"""Shared gadget for _anti_c{x,y,z}_shadow: all three bracket
|
|
658
|
-
their genuinely cross-sim shadow call with two _qec_x(c) calls,
|
|
659
|
-
and nothing else ever touches c in between -- the shadow call
|
|
660
|
-
underneath only ever reads c via .prob(), never gates it (same
|
|
661
|
-
property _apply_coupling's real branch relies on for its own
|
|
662
|
-
control-invariance check). So X;X on c should net to identity,
|
|
663
|
-
in the noiseless case, regardless of c's current value or basis
|
|
664
|
-
-- exactly as protectable as the CNOT-sandwich case, just with
|
|
665
|
-
X instead of CNOT as the bracketing gate.
|
|
666
|
-
"""
|
|
667
|
-
if not (self.is_error_detection and isinstance(c, tuple)):
|
|
668
|
-
self._qec_x(c)
|
|
669
|
-
middle_fn(c, t)
|
|
670
|
-
self._qec_x(c)
|
|
671
|
-
return
|
|
672
|
-
anc = self._detect_ancilla[c[0]]
|
|
673
|
-
sim = self.sim[c[0]]
|
|
674
|
-
sim.mcx([c[1]], anc)
|
|
675
677
|
self._qec_x(c)
|
|
676
678
|
middle_fn(c, t)
|
|
677
679
|
self._qec_x(c)
|
|
678
|
-
sim.mcx([c[1]], anc)
|
|
679
|
-
sim.force_m(anc, False)
|
|
680
680
|
|
|
681
681
|
def _anti_cz_shadow(self, c, t):
|
|
682
682
|
self._anti_shadow_wrap(c, t, self._cz_shadow)
|
|
@@ -1179,9 +1179,7 @@ class QrackAceBackend:
|
|
|
1179
1179
|
self.sim[b[0]].h(b[1])
|
|
1180
1180
|
return
|
|
1181
1181
|
|
|
1182
|
-
self._correct(lq)
|
|
1183
1182
|
skip = self._invalidate_for_gate(lq)
|
|
1184
|
-
|
|
1185
1183
|
qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
|
|
1186
1184
|
|
|
1187
1185
|
for q in qb:
|
|
@@ -1444,6 +1442,56 @@ class QrackAceBackend:
|
|
|
1444
1442
|
# bounded window, no reactive invalidation
|
|
1445
1443
|
# needed for these -- see
|
|
1446
1444
|
# _resolve_witnessed_shadow.
|
|
1445
|
+
|
|
1446
|
+
# Logical-level error-detection gadget: captures lq1's LOGICAL
|
|
1447
|
+
# value (and lq2's too, for CZ specifically -- see below) into a
|
|
1448
|
+
# dedicated hidden ancilla via a genuine, nested cx() call --
|
|
1449
|
+
# NOT a raw capture of one specific physical replica. This
|
|
1450
|
+
# matters: gate_fn/shadow_fn below never gate the control (only
|
|
1451
|
+
# ever read it via .prob()), so lq1's LOGICAL value is
|
|
1452
|
+
# structurally guaranteed not to change across this loop --
|
|
1453
|
+
# that invariant holds regardless of what _correct() may have
|
|
1454
|
+
# legitimately done to any INDIVIDUAL replica beforehand,
|
|
1455
|
+
# because the nested cx(lq1, anc) call determines lq1's value
|
|
1456
|
+
# the same robust way any other logical coupling would, not by
|
|
1457
|
+
# trusting one physical replica directly (an earlier version of
|
|
1458
|
+
# this tried exactly that and crashed: _correct() can
|
|
1459
|
+
# legitimately need to change hq1[0]'s own value, so "hq1[0]
|
|
1460
|
+
# never changes" isn't actually a valid invariant, even though
|
|
1461
|
+
# "lq1's logical value never changes here" is).
|
|
1462
|
+
#
|
|
1463
|
+
# The genuine syndrome signal comes from the capture itself: the
|
|
1464
|
+
# nested cx() goes through the same _correct()/shadow-coupling
|
|
1465
|
+
# machinery as any other logical operation, which can introduce
|
|
1466
|
+
# some imprecision between the "before" and "after" capture even
|
|
1467
|
+
# though lq1's true value didn't change -- that imprecision is
|
|
1468
|
+
# exactly this architecture's own intrinsic (not noise=p)
|
|
1469
|
+
# approximation error, which is what's worth catching.
|
|
1470
|
+
#
|
|
1471
|
+
# CZ also captures lq2 into the SAME ancilla: unlike CX/CY,
|
|
1472
|
+
# where the target's value legitimately, intentionally changes
|
|
1473
|
+
# (that's the entangling operation, not an error), CZ never
|
|
1474
|
+
# changes either participant's Z-population on any input, so
|
|
1475
|
+
# lq2's logical value is an equally valid invariant here, for
|
|
1476
|
+
# this gate family only -- same reasoning as the earlier
|
|
1477
|
+
# single-replica CZ dual-check, just applied at the logical
|
|
1478
|
+
# level now.
|
|
1479
|
+
anc1, anc2 = None, None
|
|
1480
|
+
if self.is_error_detection and not self._in_gadget_capture and ((len(hq1) > 1) or (len(hq2) > 1)):
|
|
1481
|
+
anc1 = self._detect_ancilla1_lq[hq1[0][0]]
|
|
1482
|
+
self._in_gadget_capture = True
|
|
1483
|
+
# Control bit-flip
|
|
1484
|
+
self.cx(lq1, anc1)
|
|
1485
|
+
if len(hq1) > 1:
|
|
1486
|
+
anc2 = self._detect_ancilla2_lq[hq1[0][0]]
|
|
1487
|
+
# XOR on target
|
|
1488
|
+
self.cx(lq2, anc2)
|
|
1489
|
+
if anti:
|
|
1490
|
+
self.acx(lq1, anc2)
|
|
1491
|
+
else:
|
|
1492
|
+
self.cx(lq1, anc2)
|
|
1493
|
+
self._in_gadget_capture = False
|
|
1494
|
+
|
|
1447
1495
|
for q1 in qb1:
|
|
1448
1496
|
b1 = hq1[q1]
|
|
1449
1497
|
gate_fn, shadow_fn = self._get_gate(pauli, anti, b1[0])
|
|
@@ -1455,88 +1503,56 @@ class QrackAceBackend:
|
|
|
1455
1503
|
for q2 in qb2:
|
|
1456
1504
|
b2 = hq2[q2]
|
|
1457
1505
|
if b1[0] == b2[0]:
|
|
1458
|
-
|
|
1459
|
-
# Single-patch, not multi-patch: force_m's
|
|
1460
|
-
# post-selection isn't local to the one
|
|
1461
|
-
# simulator it's physically called on -- the
|
|
1462
|
-
# projection it performs reflects the "lab
|
|
1463
|
-
# frame" ground truth for whatever it's checking,
|
|
1464
|
-
# not an independent per-patch fact. Since every
|
|
1465
|
-
# replica of a boundary qubit is meant to
|
|
1466
|
-
# represent the SAME logical value, checking that
|
|
1467
|
-
# invariant via any one patch that has a real
|
|
1468
|
-
# gate touching it already captures the full
|
|
1469
|
-
# syndrome -- separately re-checking lq1's other
|
|
1470
|
-
# replicas in their own patches doesn't add new
|
|
1471
|
-
# independent information, it just adds their
|
|
1472
|
-
# own extra gate exposure for no additional
|
|
1473
|
-
# benefit. So: XOR b1's value into ITS OWN
|
|
1474
|
-
# patch's ancilla immediately before the gate,
|
|
1475
|
-
# XOR it in again immediately after, force_m that
|
|
1476
|
-
# one ancilla -- b1 only, not every replica of
|
|
1477
|
-
# lq1.
|
|
1478
|
-
#
|
|
1479
|
-
# Also worth being explicit about: what this
|
|
1480
|
-
# catches is NOT the same thing as the `noise=`
|
|
1481
|
-
# parameter's injected depolarizing/Pauli channel.
|
|
1482
|
-
# It's this architecture's own INTRINSIC error --
|
|
1483
|
-
# the approximation inherent in the elided
|
|
1484
|
-
# shadow-coupling / replica-consistency scheme,
|
|
1485
|
-
# present even at noise=0. Testing this gadget
|
|
1486
|
-
# against `noise=p` instead measures something
|
|
1487
|
-
# different (and actively penalizes it, since the
|
|
1488
|
-
# sandwiching CNOTs are then subject to that same
|
|
1489
|
-
# injected noise too) -- see conversation record.
|
|
1490
|
-
#
|
|
1491
|
-
# Scope: still only the mcx/mcy/mcz path
|
|
1492
|
-
# (_cpauli / _apply_coupling); swap/iswap/etc.
|
|
1493
|
-
# get their own dedicated gadget (_detect_swap),
|
|
1494
|
-
# and the anti-controlled shadow variants get
|
|
1495
|
-
# theirs (_anti_shadow_wrap) -- see those.
|
|
1496
|
-
#
|
|
1497
|
-
# CZ specifically also checks b2, not just b1:
|
|
1498
|
-
# unlike CX/CY, where the target's Z-population
|
|
1499
|
-
# legitimately, intentionally changes (that IS
|
|
1500
|
-
# the entangling operation -- not an error), CZ
|
|
1501
|
-
# is diagonal in the computational basis and
|
|
1502
|
-
# NEVER changes either qubit's Z-population, only
|
|
1503
|
-
# a relative phase. So b2 is an equally valid
|
|
1504
|
-
# invariance check here, for this gate family
|
|
1505
|
-
# only. Combined as one XOR parity (b1^b2), same
|
|
1506
|
-
# pattern as the SWAP gadget: catches either
|
|
1507
|
-
# participant's first-order (O(p)) disturbance,
|
|
1508
|
-
# only misses the second-order (O(p^2)) case
|
|
1509
|
-
# where both are disturbed simultaneously in a
|
|
1510
|
-
# way that cancels in the combined parity.
|
|
1511
|
-
# (Considered and rejected checking b1 in BOTH
|
|
1512
|
-
# Z and X bases instead, to catch phase-type
|
|
1513
|
-
# errors on the control too -- doesn't work: CX/CY
|
|
1514
|
-
# genuinely, intentionally entangle control with
|
|
1515
|
-
# target, which legitimately degrades the
|
|
1516
|
-
# control's own X/Y-basis coherence as correct
|
|
1517
|
-
# gate behavior, not error. A detector for that
|
|
1518
|
-
# would flag every successful entangling gate and
|
|
1519
|
-
# destroy the entanglement the gate was supposed
|
|
1520
|
-
# to create.)
|
|
1521
|
-
check_b2_too = pauli == Pauli.PauliZ
|
|
1522
|
-
anc = self._detect_ancilla[b1[0]]
|
|
1523
|
-
sim = self.sim[b1[0]]
|
|
1524
|
-
sim.mcx([b1[1]], anc)
|
|
1525
|
-
if check_b2_too:
|
|
1526
|
-
sim.mcx([b2[1]], anc)
|
|
1527
|
-
gate_fn([b1[1]], b2[1])
|
|
1528
|
-
sim.mcx([b1[1]], anc)
|
|
1529
|
-
if check_b2_too:
|
|
1530
|
-
sim.mcx([b2[1]], anc)
|
|
1531
|
-
sim.force_m(anc, False)
|
|
1532
|
-
else:
|
|
1533
|
-
gate_fn([b1[1]], b2[1])
|
|
1506
|
+
gate_fn([b1[1]], b2[1])
|
|
1534
1507
|
elif lq1_lr or (b1[1] == b2[1]) or ((len(qb1) == 2) and (b1[1] == (b2[1] & 1))):
|
|
1535
1508
|
shadow_fn(b1, b2)
|
|
1536
1509
|
shadow_targets.append(b2)
|
|
1537
1510
|
if witness is not None and witness != b2:
|
|
1538
1511
|
witnessed_targets.append((b2, witness))
|
|
1539
1512
|
|
|
1513
|
+
if anc1 is not None:
|
|
1514
|
+
self._in_gadget_capture = True
|
|
1515
|
+
self.cx(lq1, anc1)
|
|
1516
|
+
# Check prob() before forcing: force_m(anc, False)
|
|
1517
|
+
# assumes the "agree" branch has nonzero probability,
|
|
1518
|
+
# which usually holds (that's the whole invariant this
|
|
1519
|
+
# gadget relies on) but isn't guaranteed -- if the
|
|
1520
|
+
# architecture's own approximation genuinely, confidently
|
|
1521
|
+
# drove lq1 (or lq2) to a different value between the
|
|
1522
|
+
# two captures, the "agree" branch can have exactly zero
|
|
1523
|
+
# probability, and forcing it crashes outright (verified
|
|
1524
|
+
# directly: reproducible RuntimeError under realistic
|
|
1525
|
+
# circuit depth). Accept that a genuine, confident
|
|
1526
|
+
# disagreement occurred instead of forcing the
|
|
1527
|
+
# impossible branch: force the TRUE condition (safe,
|
|
1528
|
+
# since that branch is the one that actually carries the
|
|
1529
|
+
# probability), reset the ancilla back to |0>, and apply
|
|
1530
|
+
# a corrective X to lq1 to restore the detected
|
|
1531
|
+
# invariant.
|
|
1532
|
+
anc_sim, anc_idx = self._qubits[anc1][0]
|
|
1533
|
+
p = self.sim[anc_sim].prob(anc_idx)
|
|
1534
|
+
if p > 0.5:
|
|
1535
|
+
self.force_m(anc1, True)
|
|
1536
|
+
self.x(anc1)
|
|
1537
|
+
self.x(lq1)
|
|
1538
|
+
else:
|
|
1539
|
+
self.force_m(anc1, False)
|
|
1540
|
+
self._in_gadget_capture = False
|
|
1541
|
+
|
|
1542
|
+
if anc2 is not None:
|
|
1543
|
+
self._in_gadget_capture = True
|
|
1544
|
+
# XOR check
|
|
1545
|
+
self.cx(lq2, anc2)
|
|
1546
|
+
anc_sim, anc_idx = self._qubits[anc2][0]
|
|
1547
|
+
p = self.sim[anc_sim].prob(anc_idx)
|
|
1548
|
+
if p > 0.5:
|
|
1549
|
+
self.force_m(anc2, True)
|
|
1550
|
+
self.x(anc2)
|
|
1551
|
+
self.x(lq2)
|
|
1552
|
+
else:
|
|
1553
|
+
self.force_m(anc2, False)
|
|
1554
|
+
self._in_gadget_capture = False
|
|
1555
|
+
|
|
1540
1556
|
if lq2 is not None and witnessed_targets and self._witness_map is not None:
|
|
1541
1557
|
wmap = self._witness_map.setdefault(lq2, {})
|
|
1542
1558
|
for target_replica, witness_replica in witnessed_targets:
|
|
@@ -1621,61 +1637,81 @@ class QrackAceBackend:
|
|
|
1621
1637
|
self._cpauli(lq1, lq2, False, Pauli.PauliX)
|
|
1622
1638
|
|
|
1623
1639
|
def cy(self, lq1, lq2):
|
|
1624
|
-
self.
|
|
1640
|
+
if self.is_error_detection and ((len(self._unpack(lq1)) > 1) or (len(self._unpack(lq2)) > 1)):
|
|
1641
|
+
self.adjs(lq2)
|
|
1642
|
+
self.cx(lq1, lq2)
|
|
1643
|
+
self.s(lq2)
|
|
1644
|
+
else:
|
|
1645
|
+
self._cpauli(lq1, lq2, False, Pauli.PauliY)
|
|
1625
1646
|
|
|
1626
1647
|
def cz(self, lq1, lq2):
|
|
1627
|
-
self.
|
|
1648
|
+
if self.is_error_detection and ((len(self._unpack(lq1)) > 1) or (len(self._unpack(lq2)) > 1)):
|
|
1649
|
+
self.h(lq2)
|
|
1650
|
+
self.cx(lq1, lq2)
|
|
1651
|
+
self.h(lq2)
|
|
1652
|
+
else:
|
|
1653
|
+
self._cpauli(lq1, lq2, False, Pauli.PauliZ)
|
|
1628
1654
|
|
|
1629
1655
|
def acx(self, lq1, lq2):
|
|
1630
1656
|
self._cpauli(lq1, lq2, True, Pauli.PauliX)
|
|
1631
1657
|
|
|
1632
1658
|
def acy(self, lq1, lq2):
|
|
1633
|
-
self.
|
|
1659
|
+
if self.is_error_detection and ((len(self._unpack(lq1)) > 1) or (len(self._unpack(lq2)) > 1)):
|
|
1660
|
+
self.adjs(lq2)
|
|
1661
|
+
self.acx(lq1, lq2)
|
|
1662
|
+
self.s(lq2)
|
|
1663
|
+
else:
|
|
1664
|
+
self._cpauli(lq1, lq2, True, Pauli.PauliY)
|
|
1634
1665
|
|
|
1635
1666
|
def acz(self, lq1, lq2):
|
|
1636
|
-
self.
|
|
1667
|
+
if self.is_error_detection and ((len(self._unpack(lq1)) > 1) or (len(self._unpack(lq2)) > 1)):
|
|
1668
|
+
self.h(lq2)
|
|
1669
|
+
self.acx(lq1, lq2)
|
|
1670
|
+
self.h(lq2)
|
|
1671
|
+
else:
|
|
1672
|
+
self._cpauli(lq1, lq2, True, Pauli.PauliZ)
|
|
1637
1673
|
|
|
1638
1674
|
def mcx(self, lq1, lq2):
|
|
1639
1675
|
if len(lq1) > 1:
|
|
1640
1676
|
raise RuntimeError(
|
|
1641
1677
|
"QrackAceBackend.mcx() is provided for syntax convenience and only supports 1 control qubit!"
|
|
1642
1678
|
)
|
|
1643
|
-
self.
|
|
1679
|
+
self.cx(lq1[0], lq2)
|
|
1644
1680
|
|
|
1645
1681
|
def mcy(self, lq1, lq2):
|
|
1646
1682
|
if len(lq1) > 1:
|
|
1647
1683
|
raise RuntimeError(
|
|
1648
1684
|
"QrackAceBackend.mcy() is provided for syntax convenience and only supports 1 control qubit!"
|
|
1649
1685
|
)
|
|
1650
|
-
self.
|
|
1686
|
+
self.cy(lq1[0], lq2)
|
|
1651
1687
|
|
|
1652
1688
|
def mcz(self, lq1, lq2):
|
|
1653
1689
|
if len(lq1) > 1:
|
|
1654
1690
|
raise RuntimeError(
|
|
1655
1691
|
"QrackAceBackend.mcz() is provided for syntax convenience and only supports 1 control qubit!"
|
|
1656
1692
|
)
|
|
1657
|
-
self.
|
|
1693
|
+
self.cz(lq1[0], lq2)
|
|
1658
1694
|
|
|
1659
1695
|
def macx(self, lq1, lq2):
|
|
1660
1696
|
if len(lq1) > 1:
|
|
1661
1697
|
raise RuntimeError(
|
|
1662
1698
|
"QrackAceBackend.macx() is provided for syntax convenience and only supports 1 control qubit!"
|
|
1663
1699
|
)
|
|
1664
|
-
self.
|
|
1700
|
+
self.acx(lq1[0], lq2)
|
|
1665
1701
|
|
|
1666
1702
|
def macy(self, lq1, lq2):
|
|
1667
1703
|
if len(lq1) > 1:
|
|
1668
1704
|
raise RuntimeError(
|
|
1669
1705
|
"QrackAceBackend.macy() is provided for syntax convenience and only supports 1 control qubit!"
|
|
1670
1706
|
)
|
|
1671
|
-
self.
|
|
1707
|
+
self.acy(lq1[0], lq2)
|
|
1672
1708
|
|
|
1673
1709
|
def macz(self, lq1, lq2):
|
|
1674
1710
|
if len(lq1) > 1:
|
|
1675
1711
|
raise RuntimeError(
|
|
1676
1712
|
"QrackAceBackend.macz() is provided for syntax convenience and only supports 1 control qubit!"
|
|
1677
1713
|
)
|
|
1678
|
-
self.
|
|
1714
|
+
self.acz(lq1[0], lq2)
|
|
1679
1715
|
|
|
1680
1716
|
def swap(self, lq1, lq2):
|
|
1681
1717
|
hq1 = self._unpack(lq1)
|
|
@@ -1745,6 +1781,18 @@ class QrackAceBackend:
|
|
|
1745
1781
|
# single-replica (bulk) qubits, so this is always safe to call, and
|
|
1746
1782
|
# only actually matters (and was being silently skipped) whenever
|
|
1747
1783
|
# either side is a multi-replica boundary qubit.
|
|
1784
|
+
#
|
|
1785
|
+
# Deliberately NOT wrapped with the same logical-level gadget
|
|
1786
|
+
# _cpauli uses below this branch: unlike a controlled-Pauli gate,
|
|
1787
|
+
# where the control is structurally invariant, SWAP genuinely,
|
|
1788
|
+
# intentionally exchanges both sides' values -- an independent
|
|
1789
|
+
# "did home1/home2 change" check would incorrectly flag a
|
|
1790
|
+
# legitimate swap as an error and try to veto it. The correct
|
|
1791
|
+
# invariant here is the COMBINED parity (home1 XOR home2), not
|
|
1792
|
+
# independent per-side checks, and getting that right when the
|
|
1793
|
+
# two sides can land on different simulators (the partial-match
|
|
1794
|
+
# branches below) needs separate, careful treatment -- not
|
|
1795
|
+
# reused as-is from _cpauli's helper.
|
|
1748
1796
|
self._correct(lq1)
|
|
1749
1797
|
self._correct(lq2)
|
|
1750
1798
|
|
|
@@ -1760,6 +1808,19 @@ class QrackAceBackend:
|
|
|
1760
1808
|
# with or without this call).
|
|
1761
1809
|
return
|
|
1762
1810
|
|
|
1811
|
+
anc1, anc2 = None, None
|
|
1812
|
+
if self.is_error_detection and not self._in_gadget_capture and ((len(hq1) > 1) or (len(hq2) > 1)):
|
|
1813
|
+
if len(hq2) == 1:
|
|
1814
|
+
anc1 = self._detect_ancilla1_lq[hq1[0][0]]
|
|
1815
|
+
anc2 = self._detect_ancilla2_lq[hq1[0][0]]
|
|
1816
|
+
else:
|
|
1817
|
+
anc1 = self._detect_ancilla1_lq[hq2[0][0]]
|
|
1818
|
+
anc2 = self._detect_ancilla2_lq[hq2[0][0]]
|
|
1819
|
+
self._in_gadget_capture = True
|
|
1820
|
+
self.cx(lq1, anc1)
|
|
1821
|
+
self.cx(lq2, anc2)
|
|
1822
|
+
self._in_gadget_capture = False
|
|
1823
|
+
|
|
1763
1824
|
# Partial-match cases: one side is a simple (single-replica) qubit,
|
|
1764
1825
|
# the other has multiple replicas. For the multi-replica side's
|
|
1765
1826
|
# non-matching replicas, the correct decomposition (verified
|
|
@@ -1791,9 +1852,7 @@ class QrackAceBackend:
|
|
|
1791
1852
|
# error is already Z-basis-population-visible and doesn't need
|
|
1792
1853
|
# an H-rotation first to become detectable.
|
|
1793
1854
|
self._correct(lq2)
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
if len(hq2) == 1 and any(h[0] == hq2[0][0] for h in hq1):
|
|
1855
|
+
elif len(hq2) == 1 and any(h[0] == hq2[0][0] for h in hq1):
|
|
1797
1856
|
_hq2 = hq2[0]
|
|
1798
1857
|
non_matching = [h for h in hq1 if h[0] != _hq2[0]]
|
|
1799
1858
|
matching = [h for h in hq1 if h[0] == _hq2[0]]
|
|
@@ -1804,18 +1863,39 @@ class QrackAceBackend:
|
|
|
1804
1863
|
for _hq1 in non_matching:
|
|
1805
1864
|
self._cx_shadow(_hq2, _hq1)
|
|
1806
1865
|
self._correct(lq1)
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1866
|
+
else:
|
|
1867
|
+
# General case: no shared simulator at all (e.g. two simple
|
|
1868
|
+
# qubits on entirely different sims -- nothing to anchor an exact
|
|
1869
|
+
# swap on, verified the sandwich branches above give WRONG,
|
|
1870
|
+
# deterministic results here: 100/100 failures vs 0/15 for this
|
|
1871
|
+
# fallback), or both sides multi-replica and not fully matched:
|
|
1872
|
+
# cx() already wraps itself with _correct() via _cpauli, so no
|
|
1873
|
+
# extra wrapping needed here.
|
|
1874
|
+
self.cx(lq1, lq2)
|
|
1875
|
+
self.cx(lq2, lq1)
|
|
1876
|
+
self.cx(lq1, lq2)
|
|
1877
|
+
|
|
1878
|
+
if anc1 is not None:
|
|
1879
|
+
self._in_gadget_capture = True
|
|
1880
|
+
self.cx(lq1, anc2)
|
|
1881
|
+
self.cx(lq2, anc1)
|
|
1882
|
+
anc_sim, anc_idx = self._qubits[anc1][0]
|
|
1883
|
+
p = self.sim[anc_sim].prob(anc_idx)
|
|
1884
|
+
if p > 0.5:
|
|
1885
|
+
self.force_m(anc1, True)
|
|
1886
|
+
self.x(anc1)
|
|
1887
|
+
self.x(lq1)
|
|
1888
|
+
else:
|
|
1889
|
+
self.force_m(anc1, False)
|
|
1890
|
+
anc_sim, anc_idx = self._qubits[anc2][0]
|
|
1891
|
+
p = self.sim[anc_sim].prob(anc_idx)
|
|
1892
|
+
if p > 0.5:
|
|
1893
|
+
self.force_m(anc2, True)
|
|
1894
|
+
self.x(anc2)
|
|
1895
|
+
self.x(lq2)
|
|
1896
|
+
else:
|
|
1897
|
+
self.force_m(anc2, False)
|
|
1898
|
+
self._in_gadget_capture = False
|
|
1819
1899
|
|
|
1820
1900
|
def iswap(self, lq1, lq2):
|
|
1821
1901
|
self.swap(lq1, lq2)
|
|
@@ -2431,35 +2511,32 @@ class QrackAceBackend:
|
|
|
2431
2511
|
|
|
2432
2512
|
coupling_map = self.get_logical_coupling_map()
|
|
2433
2513
|
|
|
2434
|
-
# Error-detection damping (cx/cy/cz
|
|
2435
|
-
#
|
|
2436
|
-
#
|
|
2437
|
-
#
|
|
2438
|
-
#
|
|
2439
|
-
#
|
|
2440
|
-
#
|
|
2441
|
-
#
|
|
2442
|
-
#
|
|
2443
|
-
#
|
|
2444
|
-
#
|
|
2445
|
-
#
|
|
2446
|
-
#
|
|
2447
|
-
#
|
|
2448
|
-
#
|
|
2449
|
-
#
|
|
2450
|
-
#
|
|
2451
|
-
#
|
|
2452
|
-
#
|
|
2453
|
-
#
|
|
2454
|
-
#
|
|
2455
|
-
#
|
|
2456
|
-
#
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
if _uncommon_sim_fraction(lq1, lq2) == 0:
|
|
2461
|
-
has_real_partner.add(lq1)
|
|
2462
|
-
has_real_partner.add(lq2)
|
|
2514
|
+
# Error-detection damping (cx/cy/cz; swap/iswap handled
|
|
2515
|
+
# separately below): the current gadget structurally requires
|
|
2516
|
+
# lq1 (== "a" for a given coupling_map entry, since a always
|
|
2517
|
+
# plays the control's role for that entry -- confirmed against
|
|
2518
|
+
# _cpauli's own convention) to be a multi-replica (boundary)
|
|
2519
|
+
# qubit -- see _cpauli: anc1 (the control-invariance capture) is
|
|
2520
|
+
# unconditional but structurally trivial for a single-replica
|
|
2521
|
+
# control (_correct() is already a no-op there, nothing for the
|
|
2522
|
+
# gadget to protect against), and anc2 (the target-value
|
|
2523
|
+
# verification against the CX/ACX-predicted output) is
|
|
2524
|
+
# explicitly gated on len(hq1) > 1. Both halves are gated the
|
|
2525
|
+
# same, simple way: whether a itself is boundary. This replaces
|
|
2526
|
+
# an older version keyed on _uncommon_sim_fraction(lq1,lq2)==0
|
|
2527
|
+
# ("does this pair share a simulator"), which matched an EARLIER
|
|
2528
|
+
# gadget that wrapped gate_fn alone and only ever ran on the
|
|
2529
|
+
# real (same-sim) branch -- that gadget was subsequently proven
|
|
2530
|
+
# a mathematically guaranteed no-op (gate_fn structurally never
|
|
2531
|
+
# touches its control's Z-value regardless of the control's
|
|
2532
|
+
# incoming state, verified directly to floating-point
|
|
2533
|
+
# precision) and replaced with the current mechanism, which
|
|
2534
|
+
# doesn't care whether a and b share a simulator at all: the
|
|
2535
|
+
# nested cx(lq1, anc) capture goes through the full logical
|
|
2536
|
+
# coupling machinery (shadow or real, whichever applies) and
|
|
2537
|
+
# works the same way regardless. So this is checked directly,
|
|
2538
|
+
# per pair, from len(self._qubits[a]) -- no precomputed set
|
|
2539
|
+
# needed.
|
|
2463
2540
|
|
|
2464
2541
|
# This factor is a theory-derived placeholder, not an empirical
|
|
2465
2542
|
# fit -- deliberately, per instruction not to chase a regression
|
|
@@ -2503,13 +2580,13 @@ class QrackAceBackend:
|
|
|
2503
2580
|
# Pauli-string convention is big-endian relative to the qubit
|
|
2504
2581
|
# list: the LAST qubit corresponds to the LEFTMOST character).
|
|
2505
2582
|
p_shadow_ccz = 1 - sp * p
|
|
2506
|
-
if a
|
|
2583
|
+
if self.is_error_detection and len(self._qubits[a]) > 1:
|
|
2507
2584
|
# a is the control for this pair (_cpauli(lq1=a, lq2=b,
|
|
2508
|
-
# ...) is always called with a as the coupling source)
|
|
2509
|
-
#
|
|
2510
|
-
#
|
|
2511
|
-
#
|
|
2512
|
-
#
|
|
2585
|
+
# ...) is always called with a as the coupling source).
|
|
2586
|
+
# Both gadget halves that protect this pair (anc1 on a,
|
|
2587
|
+
# anc2 verifying b) are structurally live exactly when a
|
|
2588
|
+
# itself is boundary -- see the comment above this loop.
|
|
2589
|
+
# Damp accordingly.
|
|
2513
2590
|
p_shadow_ccz *= y
|
|
2514
2591
|
noise_model.add_quantum_error(
|
|
2515
2592
|
pauli_error([("ZI", p_shadow_ccz), ("II", 1 - p_shadow_ccz)]), "cz", [a, b]
|
|
@@ -2563,21 +2640,22 @@ class QrackAceBackend:
|
|
|
2563
2640
|
# same condition to stay consistent with what the gate does.
|
|
2564
2641
|
if (is_a_simple != is_b_simple) and has_match:
|
|
2565
2642
|
p_net_swap = 2 * p * (1 - p)
|
|
2566
|
-
# swap()
|
|
2567
|
-
#
|
|
2568
|
-
#
|
|
2569
|
-
#
|
|
2570
|
-
#
|
|
2571
|
-
#
|
|
2572
|
-
#
|
|
2573
|
-
#
|
|
2574
|
-
#
|
|
2575
|
-
#
|
|
2576
|
-
#
|
|
2577
|
-
#
|
|
2578
|
-
#
|
|
2579
|
-
|
|
2580
|
-
|
|
2643
|
+
# swap()'s own anc1/anc2 cross-check (verifying lq1's new
|
|
2644
|
+
# value against lq2's old, and vice versa -- see swap())
|
|
2645
|
+
# wraps the ENTIRE call, every branch, not specifically
|
|
2646
|
+
# this matching-replica step -- _detect_swap itself is
|
|
2647
|
+
# just a plain swap(), carrying no gadget of its own
|
|
2648
|
+
# (an earlier per-replica parity gadget living there was
|
|
2649
|
+
# proven a mathematically guaranteed no-op, the same way
|
|
2650
|
+
# the old cx/cy/cz gadget was, and was simplified away).
|
|
2651
|
+
# So this IS damped, unlike the stale "swap/iswap
|
|
2652
|
+
# deliberately not damped" note this replaced -- but
|
|
2653
|
+
# unconditionally so, not gated on any structural
|
|
2654
|
+
# per-qubit condition: swap()'s gadget captures both
|
|
2655
|
+
# sides whenever is_error_detection is on, full stop, no
|
|
2656
|
+
# len(hq)>1-style precondition the way _cpauli's anc2
|
|
2657
|
+
# has.
|
|
2658
|
+
if self.is_error_detection:
|
|
2581
2659
|
p_net_swap *= y
|
|
2582
2660
|
if is_a_simple:
|
|
2583
2661
|
# a is bulk, b is boundary -> error lands on b
|
|
@@ -2610,12 +2688,15 @@ class QrackAceBackend:
|
|
|
2610
2688
|
# previous formula.
|
|
2611
2689
|
if (is_a_simple != is_b_simple) and has_match:
|
|
2612
2690
|
p_cz = 1 - sp * p # same per-call formula as standalone cz
|
|
2613
|
-
if
|
|
2614
|
-
# this cz component is
|
|
2615
|
-
#
|
|
2616
|
-
#
|
|
2617
|
-
#
|
|
2618
|
-
#
|
|
2691
|
+
if self.is_error_detection and len(self._qubits[a]) > 1:
|
|
2692
|
+
# this cz component is routed through cx() when
|
|
2693
|
+
# is_error_detection is on (see cz()'s own wrapper),
|
|
2694
|
+
# so it's the SAME kind of shadow-driven term, gated
|
|
2695
|
+
# the SAME way, as the main cx/cy/cz loop above (see
|
|
2696
|
+
# that comment for the mechanism); p_net_swap was
|
|
2697
|
+
# already damped above, independently, since it comes
|
|
2698
|
+
# from a different gate (the now-protected native
|
|
2699
|
+
# swap, not
|
|
2619
2700
|
# this cz component).
|
|
2620
2701
|
p_cz *= y
|
|
2621
2702
|
p_i = (1 - p_net_swap) * (1 - p_cz)
|
|
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.18.2 → pyqrack_complex128-2.18.4}/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.18.2 → pyqrack_complex128-2.18.4}/pyqrack/qrack_system/qrack_system.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.4}/pyqrack/stats/load_quantized_data.py
RENAMED
|
File without changes
|
|
File without changes
|
{pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.4}/pyqrack_complex128.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.4}/pyqrack_complex128.egg-info/not-zip-safe
RENAMED
|
File without changes
|
{pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.4}/pyqrack_complex128.egg-info/requires.txt
RENAMED
|
File without changes
|
{pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.4}/pyqrack_complex128.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|