pyqrack-complex128 2.18.2__tar.gz → 2.18.3__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.3}/PKG-INFO +1 -1
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.3}/pyqrack/qrack_ace_backend.py +229 -147
- pyqrack_complex128-2.18.3/pyqrack/qrack_system/qrack_cl_precompile/qrack_cl_precompile +0 -0
- pyqrack_complex128-2.18.3/pyqrack/qrack_system/qrack_lib/libqrack_pinvoke.so +0 -0
- pyqrack_complex128-2.18.3/pyqrack/qrack_system/qrack_lib/libqrack_pinvoke.so.10.13.0 +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.3/pyqrack_complex128.egg-info}/PKG-INFO +1 -1
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.3}/pyqrack_complex128.egg-info/SOURCES.txt +3 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.3}/setup.py +1 -1
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.3}/LICENSE +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.3}/MANIFEST.in +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.3}/Makefile +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.3}/README.md +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.3}/pyproject.toml +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.3}/pyqrack/__init__.py +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.3}/pyqrack/neuron_activation_fn.py +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.3}/pyqrack/pauli.py +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.3}/pyqrack/qrack_circuit.py +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.3}/pyqrack/qrack_near_clifford_qec_backend.py +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.3}/pyqrack/qrack_neuron.py +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.3}/pyqrack/qrack_neuron_torch_layer.py +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.3}/pyqrack/qrack_simulator.py +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.3}/pyqrack/qrack_stabilizer.py +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.3}/pyqrack/qrack_system/__init__.py +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.3}/pyqrack/qrack_system/qrack_system.py +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.3}/pyqrack/quimb_circuit_type.py +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.3}/pyqrack/stats/__init__.py +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.3}/pyqrack/stats/load_quantized_data.py +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.3}/pyqrack/stats/quantize_by_range.py +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.3}/pyqrack_complex128.egg-info/dependency_links.txt +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.3}/pyqrack_complex128.egg-info/not-zip-safe +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.3}/pyqrack_complex128.egg-info/requires.txt +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.3}/pyqrack_complex128.egg-info/top_level.txt +0 -0
- {pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.3}/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)
|
|
@@ -1444,6 +1444,56 @@ class QrackAceBackend:
|
|
|
1444
1444
|
# bounded window, no reactive invalidation
|
|
1445
1445
|
# needed for these -- see
|
|
1446
1446
|
# _resolve_witnessed_shadow.
|
|
1447
|
+
|
|
1448
|
+
# Logical-level error-detection gadget: captures lq1's LOGICAL
|
|
1449
|
+
# value (and lq2's too, for CZ specifically -- see below) into a
|
|
1450
|
+
# dedicated hidden ancilla via a genuine, nested cx() call --
|
|
1451
|
+
# NOT a raw capture of one specific physical replica. This
|
|
1452
|
+
# matters: gate_fn/shadow_fn below never gate the control (only
|
|
1453
|
+
# ever read it via .prob()), so lq1's LOGICAL value is
|
|
1454
|
+
# structurally guaranteed not to change across this loop --
|
|
1455
|
+
# that invariant holds regardless of what _correct() may have
|
|
1456
|
+
# legitimately done to any INDIVIDUAL replica beforehand,
|
|
1457
|
+
# because the nested cx(lq1, anc) call determines lq1's value
|
|
1458
|
+
# the same robust way any other logical coupling would, not by
|
|
1459
|
+
# trusting one physical replica directly (an earlier version of
|
|
1460
|
+
# this tried exactly that and crashed: _correct() can
|
|
1461
|
+
# legitimately need to change hq1[0]'s own value, so "hq1[0]
|
|
1462
|
+
# never changes" isn't actually a valid invariant, even though
|
|
1463
|
+
# "lq1's logical value never changes here" is).
|
|
1464
|
+
#
|
|
1465
|
+
# The genuine syndrome signal comes from the capture itself: the
|
|
1466
|
+
# nested cx() goes through the same _correct()/shadow-coupling
|
|
1467
|
+
# machinery as any other logical operation, which can introduce
|
|
1468
|
+
# some imprecision between the "before" and "after" capture even
|
|
1469
|
+
# though lq1's true value didn't change -- that imprecision is
|
|
1470
|
+
# exactly this architecture's own intrinsic (not noise=p)
|
|
1471
|
+
# approximation error, which is what's worth catching.
|
|
1472
|
+
#
|
|
1473
|
+
# CZ also captures lq2 into the SAME ancilla: unlike CX/CY,
|
|
1474
|
+
# where the target's value legitimately, intentionally changes
|
|
1475
|
+
# (that's the entangling operation, not an error), CZ never
|
|
1476
|
+
# changes either participant's Z-population on any input, so
|
|
1477
|
+
# lq2's logical value is an equally valid invariant here, for
|
|
1478
|
+
# this gate family only -- same reasoning as the earlier
|
|
1479
|
+
# single-replica CZ dual-check, just applied at the logical
|
|
1480
|
+
# level now.
|
|
1481
|
+
anc1, anc2 = None, None
|
|
1482
|
+
if self.is_error_detection and not self._in_gadget_capture:
|
|
1483
|
+
anc1 = self._detect_ancilla1_lq[hq1[0][0]]
|
|
1484
|
+
self._in_gadget_capture = True
|
|
1485
|
+
# Control bit-flip
|
|
1486
|
+
self.cx(lq1, anc1)
|
|
1487
|
+
if len(hq1) > 1:
|
|
1488
|
+
anc2 = self._detect_ancilla2_lq[hq1[0][0]]
|
|
1489
|
+
# XOR on target
|
|
1490
|
+
self.cx(lq2, anc2)
|
|
1491
|
+
if anti:
|
|
1492
|
+
self.acx(lq1, anc2)
|
|
1493
|
+
else:
|
|
1494
|
+
self.cx(lq1, anc2)
|
|
1495
|
+
self._in_gadget_capture = False
|
|
1496
|
+
|
|
1447
1497
|
for q1 in qb1:
|
|
1448
1498
|
b1 = hq1[q1]
|
|
1449
1499
|
gate_fn, shadow_fn = self._get_gate(pauli, anti, b1[0])
|
|
@@ -1455,88 +1505,56 @@ class QrackAceBackend:
|
|
|
1455
1505
|
for q2 in qb2:
|
|
1456
1506
|
b2 = hq2[q2]
|
|
1457
1507
|
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])
|
|
1508
|
+
gate_fn([b1[1]], b2[1])
|
|
1534
1509
|
elif lq1_lr or (b1[1] == b2[1]) or ((len(qb1) == 2) and (b1[1] == (b2[1] & 1))):
|
|
1535
1510
|
shadow_fn(b1, b2)
|
|
1536
1511
|
shadow_targets.append(b2)
|
|
1537
1512
|
if witness is not None and witness != b2:
|
|
1538
1513
|
witnessed_targets.append((b2, witness))
|
|
1539
1514
|
|
|
1515
|
+
if anc1 is not None:
|
|
1516
|
+
self._in_gadget_capture = True
|
|
1517
|
+
self.cx(lq1, anc1)
|
|
1518
|
+
# Check prob() before forcing: force_m(anc, False)
|
|
1519
|
+
# assumes the "agree" branch has nonzero probability,
|
|
1520
|
+
# which usually holds (that's the whole invariant this
|
|
1521
|
+
# gadget relies on) but isn't guaranteed -- if the
|
|
1522
|
+
# architecture's own approximation genuinely, confidently
|
|
1523
|
+
# drove lq1 (or lq2) to a different value between the
|
|
1524
|
+
# two captures, the "agree" branch can have exactly zero
|
|
1525
|
+
# probability, and forcing it crashes outright (verified
|
|
1526
|
+
# directly: reproducible RuntimeError under realistic
|
|
1527
|
+
# circuit depth). Accept that a genuine, confident
|
|
1528
|
+
# disagreement occurred instead of forcing the
|
|
1529
|
+
# impossible branch: force the TRUE condition (safe,
|
|
1530
|
+
# since that branch is the one that actually carries the
|
|
1531
|
+
# probability), reset the ancilla back to |0>, and apply
|
|
1532
|
+
# a corrective X to lq1 to restore the detected
|
|
1533
|
+
# invariant.
|
|
1534
|
+
anc_sim, anc_idx = self._qubits[anc1][0]
|
|
1535
|
+
p = self.sim[anc_sim].prob(anc_idx)
|
|
1536
|
+
if p > 0.5:
|
|
1537
|
+
self.force_m(anc1, True)
|
|
1538
|
+
self.x(anc1)
|
|
1539
|
+
self.x(lq1)
|
|
1540
|
+
else:
|
|
1541
|
+
self.force_m(anc1, False)
|
|
1542
|
+
self._in_gadget_capture = False
|
|
1543
|
+
|
|
1544
|
+
if anc2 is not None:
|
|
1545
|
+
self._in_gadget_capture = True
|
|
1546
|
+
# XOR check
|
|
1547
|
+
self.cx(lq2, anc2)
|
|
1548
|
+
anc_sim, anc_idx = self._qubits[anc2][0]
|
|
1549
|
+
p = self.sim[anc_sim].prob(anc_idx)
|
|
1550
|
+
if p > 0.5:
|
|
1551
|
+
self.force_m(anc2, True)
|
|
1552
|
+
self.x(anc2)
|
|
1553
|
+
self.x(lq2)
|
|
1554
|
+
else:
|
|
1555
|
+
self.force_m(anc2, False)
|
|
1556
|
+
self._in_gadget_capture = False
|
|
1557
|
+
|
|
1540
1558
|
if lq2 is not None and witnessed_targets and self._witness_map is not None:
|
|
1541
1559
|
wmap = self._witness_map.setdefault(lq2, {})
|
|
1542
1560
|
for target_replica, witness_replica in witnessed_targets:
|
|
@@ -1621,61 +1639,81 @@ class QrackAceBackend:
|
|
|
1621
1639
|
self._cpauli(lq1, lq2, False, Pauli.PauliX)
|
|
1622
1640
|
|
|
1623
1641
|
def cy(self, lq1, lq2):
|
|
1624
|
-
self.
|
|
1642
|
+
if self.is_error_detection:
|
|
1643
|
+
self.adjs(lq2)
|
|
1644
|
+
self.cx(lq1, lq2)
|
|
1645
|
+
self.s(lq2)
|
|
1646
|
+
else:
|
|
1647
|
+
self._cpauli(lq1, lq2, False, Pauli.PauliY)
|
|
1625
1648
|
|
|
1626
1649
|
def cz(self, lq1, lq2):
|
|
1627
|
-
self.
|
|
1650
|
+
if self.is_error_detection:
|
|
1651
|
+
self.h(lq2)
|
|
1652
|
+
self.cx(lq1, lq2)
|
|
1653
|
+
self.h(lq2)
|
|
1654
|
+
else:
|
|
1655
|
+
self._cpauli(lq1, lq2, False, Pauli.PauliZ)
|
|
1628
1656
|
|
|
1629
1657
|
def acx(self, lq1, lq2):
|
|
1630
1658
|
self._cpauli(lq1, lq2, True, Pauli.PauliX)
|
|
1631
1659
|
|
|
1632
1660
|
def acy(self, lq1, lq2):
|
|
1633
|
-
self.
|
|
1661
|
+
if self.is_error_detection:
|
|
1662
|
+
self.adjs(lq2)
|
|
1663
|
+
self.acx(lq1, lq2)
|
|
1664
|
+
self.s(lq2)
|
|
1665
|
+
else:
|
|
1666
|
+
self._cpauli(lq1, lq2, True, Pauli.PauliY)
|
|
1634
1667
|
|
|
1635
1668
|
def acz(self, lq1, lq2):
|
|
1636
|
-
self.
|
|
1669
|
+
if self.is_error_detection:
|
|
1670
|
+
self.h(lq2)
|
|
1671
|
+
self.acx(lq1, lq2)
|
|
1672
|
+
self.h(lq2)
|
|
1673
|
+
else:
|
|
1674
|
+
self._cpauli(lq1, lq2, True, Pauli.PauliZ)
|
|
1637
1675
|
|
|
1638
1676
|
def mcx(self, lq1, lq2):
|
|
1639
1677
|
if len(lq1) > 1:
|
|
1640
1678
|
raise RuntimeError(
|
|
1641
1679
|
"QrackAceBackend.mcx() is provided for syntax convenience and only supports 1 control qubit!"
|
|
1642
1680
|
)
|
|
1643
|
-
self.
|
|
1681
|
+
self.cx(lq1[0], lq2)
|
|
1644
1682
|
|
|
1645
1683
|
def mcy(self, lq1, lq2):
|
|
1646
1684
|
if len(lq1) > 1:
|
|
1647
1685
|
raise RuntimeError(
|
|
1648
1686
|
"QrackAceBackend.mcy() is provided for syntax convenience and only supports 1 control qubit!"
|
|
1649
1687
|
)
|
|
1650
|
-
self.
|
|
1688
|
+
self.cy(lq1[0], lq2)
|
|
1651
1689
|
|
|
1652
1690
|
def mcz(self, lq1, lq2):
|
|
1653
1691
|
if len(lq1) > 1:
|
|
1654
1692
|
raise RuntimeError(
|
|
1655
1693
|
"QrackAceBackend.mcz() is provided for syntax convenience and only supports 1 control qubit!"
|
|
1656
1694
|
)
|
|
1657
|
-
self.
|
|
1695
|
+
self.cz(lq1[0], lq2)
|
|
1658
1696
|
|
|
1659
1697
|
def macx(self, lq1, lq2):
|
|
1660
1698
|
if len(lq1) > 1:
|
|
1661
1699
|
raise RuntimeError(
|
|
1662
1700
|
"QrackAceBackend.macx() is provided for syntax convenience and only supports 1 control qubit!"
|
|
1663
1701
|
)
|
|
1664
|
-
self.
|
|
1702
|
+
self.acx(lq1[0], lq2)
|
|
1665
1703
|
|
|
1666
1704
|
def macy(self, lq1, lq2):
|
|
1667
1705
|
if len(lq1) > 1:
|
|
1668
1706
|
raise RuntimeError(
|
|
1669
1707
|
"QrackAceBackend.macy() is provided for syntax convenience and only supports 1 control qubit!"
|
|
1670
1708
|
)
|
|
1671
|
-
self.
|
|
1709
|
+
self.acy(lq1[0], lq2)
|
|
1672
1710
|
|
|
1673
1711
|
def macz(self, lq1, lq2):
|
|
1674
1712
|
if len(lq1) > 1:
|
|
1675
1713
|
raise RuntimeError(
|
|
1676
1714
|
"QrackAceBackend.macz() is provided for syntax convenience and only supports 1 control qubit!"
|
|
1677
1715
|
)
|
|
1678
|
-
self.
|
|
1716
|
+
self.acz(lq1[0], lq2)
|
|
1679
1717
|
|
|
1680
1718
|
def swap(self, lq1, lq2):
|
|
1681
1719
|
hq1 = self._unpack(lq1)
|
|
@@ -1745,6 +1783,18 @@ class QrackAceBackend:
|
|
|
1745
1783
|
# single-replica (bulk) qubits, so this is always safe to call, and
|
|
1746
1784
|
# only actually matters (and was being silently skipped) whenever
|
|
1747
1785
|
# either side is a multi-replica boundary qubit.
|
|
1786
|
+
#
|
|
1787
|
+
# Deliberately NOT wrapped with the same logical-level gadget
|
|
1788
|
+
# _cpauli uses below this branch: unlike a controlled-Pauli gate,
|
|
1789
|
+
# where the control is structurally invariant, SWAP genuinely,
|
|
1790
|
+
# intentionally exchanges both sides' values -- an independent
|
|
1791
|
+
# "did home1/home2 change" check would incorrectly flag a
|
|
1792
|
+
# legitimate swap as an error and try to veto it. The correct
|
|
1793
|
+
# invariant here is the COMBINED parity (home1 XOR home2), not
|
|
1794
|
+
# independent per-side checks, and getting that right when the
|
|
1795
|
+
# two sides can land on different simulators (the partial-match
|
|
1796
|
+
# branches below) needs separate, careful treatment -- not
|
|
1797
|
+
# reused as-is from _cpauli's helper.
|
|
1748
1798
|
self._correct(lq1)
|
|
1749
1799
|
self._correct(lq2)
|
|
1750
1800
|
|
|
@@ -1760,6 +1810,19 @@ class QrackAceBackend:
|
|
|
1760
1810
|
# with or without this call).
|
|
1761
1811
|
return
|
|
1762
1812
|
|
|
1813
|
+
anc1, anc2 = None, None
|
|
1814
|
+
if self.is_error_detection and not self._in_gadget_capture:
|
|
1815
|
+
if len(hq2) == 1:
|
|
1816
|
+
anc1 = self._detect_ancilla1_lq[hq1[0][0]]
|
|
1817
|
+
anc2 = self._detect_ancilla2_lq[hq1[0][0]]
|
|
1818
|
+
else:
|
|
1819
|
+
anc1 = self._detect_ancilla1_lq[hq2[0][0]]
|
|
1820
|
+
anc2 = self._detect_ancilla2_lq[hq2[0][0]]
|
|
1821
|
+
self._in_gadget_capture = True
|
|
1822
|
+
self.cx(lq1, anc1)
|
|
1823
|
+
self.cx(lq2, anc2)
|
|
1824
|
+
self._in_gadget_capture = False
|
|
1825
|
+
|
|
1763
1826
|
# Partial-match cases: one side is a simple (single-replica) qubit,
|
|
1764
1827
|
# the other has multiple replicas. For the multi-replica side's
|
|
1765
1828
|
# non-matching replicas, the correct decomposition (verified
|
|
@@ -1791,9 +1854,7 @@ class QrackAceBackend:
|
|
|
1791
1854
|
# error is already Z-basis-population-visible and doesn't need
|
|
1792
1855
|
# an H-rotation first to become detectable.
|
|
1793
1856
|
self._correct(lq2)
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
if len(hq2) == 1 and any(h[0] == hq2[0][0] for h in hq1):
|
|
1857
|
+
elif len(hq2) == 1 and any(h[0] == hq2[0][0] for h in hq1):
|
|
1797
1858
|
_hq2 = hq2[0]
|
|
1798
1859
|
non_matching = [h for h in hq1 if h[0] != _hq2[0]]
|
|
1799
1860
|
matching = [h for h in hq1 if h[0] == _hq2[0]]
|
|
@@ -1804,18 +1865,39 @@ class QrackAceBackend:
|
|
|
1804
1865
|
for _hq1 in non_matching:
|
|
1805
1866
|
self._cx_shadow(_hq2, _hq1)
|
|
1806
1867
|
self._correct(lq1)
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1868
|
+
else:
|
|
1869
|
+
# General case: no shared simulator at all (e.g. two simple
|
|
1870
|
+
# qubits on entirely different sims -- nothing to anchor an exact
|
|
1871
|
+
# swap on, verified the sandwich branches above give WRONG,
|
|
1872
|
+
# deterministic results here: 100/100 failures vs 0/15 for this
|
|
1873
|
+
# fallback), or both sides multi-replica and not fully matched:
|
|
1874
|
+
# cx() already wraps itself with _correct() via _cpauli, so no
|
|
1875
|
+
# extra wrapping needed here.
|
|
1876
|
+
self.cx(lq1, lq2)
|
|
1877
|
+
self.cx(lq2, lq1)
|
|
1878
|
+
self.cx(lq1, lq2)
|
|
1879
|
+
|
|
1880
|
+
if anc1 is not None:
|
|
1881
|
+
self._in_gadget_capture = True
|
|
1882
|
+
self.cx(lq1, anc2)
|
|
1883
|
+
self.cx(lq2, anc1)
|
|
1884
|
+
anc_sim, anc_idx = self._qubits[anc1][0]
|
|
1885
|
+
p = self.sim[anc_sim].prob(anc_idx)
|
|
1886
|
+
if p > 0.5:
|
|
1887
|
+
self.force_m(anc1, True)
|
|
1888
|
+
self.x(anc1)
|
|
1889
|
+
self.x(lq1)
|
|
1890
|
+
else:
|
|
1891
|
+
self.force_m(anc1, False)
|
|
1892
|
+
anc_sim, anc_idx = self._qubits[anc2][0]
|
|
1893
|
+
p = self.sim[anc_sim].prob(anc_idx)
|
|
1894
|
+
if p > 0.5:
|
|
1895
|
+
self.force_m(anc2, True)
|
|
1896
|
+
self.x(anc2)
|
|
1897
|
+
self.x(lq2)
|
|
1898
|
+
else:
|
|
1899
|
+
self.force_m(anc2, False)
|
|
1900
|
+
self._in_gadget_capture = False
|
|
1819
1901
|
|
|
1820
1902
|
def iswap(self, lq1, lq2):
|
|
1821
1903
|
self.swap(lq1, lq2)
|
|
Binary file
|
|
Binary file
|
{pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.3}/pyqrack_complex128.egg-info/SOURCES.txt
RENAMED
|
@@ -17,6 +17,9 @@ pyqrack/qrack_stabilizer.py
|
|
|
17
17
|
pyqrack/quimb_circuit_type.py
|
|
18
18
|
pyqrack/qrack_system/__init__.py
|
|
19
19
|
pyqrack/qrack_system/qrack_system.py
|
|
20
|
+
pyqrack/qrack_system/qrack_cl_precompile/qrack_cl_precompile
|
|
21
|
+
pyqrack/qrack_system/qrack_lib/libqrack_pinvoke.so
|
|
22
|
+
pyqrack/qrack_system/qrack_lib/libqrack_pinvoke.so.10.13.0
|
|
20
23
|
pyqrack/stats/__init__.py
|
|
21
24
|
pyqrack/stats/load_quantized_data.py
|
|
22
25
|
pyqrack/stats/quantize_by_range.py
|
|
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.3}/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.3}/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.3}/pyqrack/stats/load_quantized_data.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.3}/pyqrack_complex128.egg-info/not-zip-safe
RENAMED
|
File without changes
|
{pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.3}/pyqrack_complex128.egg-info/requires.txt
RENAMED
|
File without changes
|
{pyqrack_complex128-2.18.2 → pyqrack_complex128-2.18.3}/pyqrack_complex128.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|