pyqrack-cpu-complex128 2.19.2__tar.gz → 2.20.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyqrack_cpu_complex128-2.19.2 → pyqrack_cpu_complex128-2.20.0}/Makefile +1 -1
- {pyqrack_cpu_complex128-2.19.2/pyqrack_cpu_complex128.egg-info → pyqrack_cpu_complex128-2.20.0}/PKG-INFO +1 -1
- {pyqrack_cpu_complex128-2.19.2 → pyqrack_cpu_complex128-2.20.0}/pyqrack/qrack_ace_backend.py +452 -241
- {pyqrack_cpu_complex128-2.19.2 → pyqrack_cpu_complex128-2.20.0/pyqrack_cpu_complex128.egg-info}/PKG-INFO +1 -1
- {pyqrack_cpu_complex128-2.19.2 → pyqrack_cpu_complex128-2.20.0}/setup.py +1 -1
- {pyqrack_cpu_complex128-2.19.2 → pyqrack_cpu_complex128-2.20.0}/LICENSE +0 -0
- {pyqrack_cpu_complex128-2.19.2 → pyqrack_cpu_complex128-2.20.0}/MANIFEST.in +0 -0
- {pyqrack_cpu_complex128-2.19.2 → pyqrack_cpu_complex128-2.20.0}/README.md +0 -0
- {pyqrack_cpu_complex128-2.19.2 → pyqrack_cpu_complex128-2.20.0}/pyproject.toml +0 -0
- {pyqrack_cpu_complex128-2.19.2 → pyqrack_cpu_complex128-2.20.0}/pyqrack/__init__.py +0 -0
- {pyqrack_cpu_complex128-2.19.2 → pyqrack_cpu_complex128-2.20.0}/pyqrack/neuron_activation_fn.py +0 -0
- {pyqrack_cpu_complex128-2.19.2 → pyqrack_cpu_complex128-2.20.0}/pyqrack/pauli.py +0 -0
- {pyqrack_cpu_complex128-2.19.2 → pyqrack_cpu_complex128-2.20.0}/pyqrack/qrack_circuit.py +0 -0
- {pyqrack_cpu_complex128-2.19.2 → pyqrack_cpu_complex128-2.20.0}/pyqrack/qrack_near_clifford_qec_backend.py +0 -0
- {pyqrack_cpu_complex128-2.19.2 → pyqrack_cpu_complex128-2.20.0}/pyqrack/qrack_neuron.py +0 -0
- {pyqrack_cpu_complex128-2.19.2 → pyqrack_cpu_complex128-2.20.0}/pyqrack/qrack_neuron_torch_layer.py +0 -0
- {pyqrack_cpu_complex128-2.19.2 → pyqrack_cpu_complex128-2.20.0}/pyqrack/qrack_simulator.py +0 -0
- {pyqrack_cpu_complex128-2.19.2 → pyqrack_cpu_complex128-2.20.0}/pyqrack/qrack_stabilizer.py +0 -0
- {pyqrack_cpu_complex128-2.19.2 → pyqrack_cpu_complex128-2.20.0}/pyqrack/qrack_system/__init__.py +0 -0
- {pyqrack_cpu_complex128-2.19.2 → pyqrack_cpu_complex128-2.20.0}/pyqrack/qrack_system/qrack_system.py +0 -0
- {pyqrack_cpu_complex128-2.19.2 → pyqrack_cpu_complex128-2.20.0}/pyqrack/quimb_circuit_type.py +0 -0
- {pyqrack_cpu_complex128-2.19.2 → pyqrack_cpu_complex128-2.20.0}/pyqrack/stats/__init__.py +0 -0
- {pyqrack_cpu_complex128-2.19.2 → pyqrack_cpu_complex128-2.20.0}/pyqrack/stats/load_quantized_data.py +0 -0
- {pyqrack_cpu_complex128-2.19.2 → pyqrack_cpu_complex128-2.20.0}/pyqrack/stats/quantize_by_range.py +0 -0
- {pyqrack_cpu_complex128-2.19.2 → pyqrack_cpu_complex128-2.20.0}/pyqrack_cpu_complex128.egg-info/SOURCES.txt +0 -0
- {pyqrack_cpu_complex128-2.19.2 → pyqrack_cpu_complex128-2.20.0}/pyqrack_cpu_complex128.egg-info/dependency_links.txt +0 -0
- {pyqrack_cpu_complex128-2.19.2 → pyqrack_cpu_complex128-2.20.0}/pyqrack_cpu_complex128.egg-info/not-zip-safe +0 -0
- {pyqrack_cpu_complex128-2.19.2 → pyqrack_cpu_complex128-2.20.0}/pyqrack_cpu_complex128.egg-info/requires.txt +0 -0
- {pyqrack_cpu_complex128-2.19.2 → pyqrack_cpu_complex128-2.20.0}/pyqrack_cpu_complex128.egg-info/top_level.txt +0 -0
- {pyqrack_cpu_complex128-2.19.2 → pyqrack_cpu_complex128-2.20.0}/setup.cfg +0 -0
|
@@ -30,7 +30,7 @@ build-deps:
|
|
|
30
30
|
rm -rf pyqrack/qrack_system/qrack_cl_precompile
|
|
31
31
|
ifneq ($(OS),Windows_NT)
|
|
32
32
|
ifeq ($(QRACK_PRESENT),)
|
|
33
|
-
git clone https://github.com/unitaryfund/qrack.git; cd qrack; git checkout
|
|
33
|
+
git clone https://github.com/unitaryfund/qrack.git; cd qrack; git checkout 697ff96c730f27fa7d1ed4502b1044b13598ea1a; cd ..
|
|
34
34
|
endif
|
|
35
35
|
mkdir -p qrack/build
|
|
36
36
|
ifeq ($(UNAME_S),Linux)
|
{pyqrack_cpu_complex128-2.19.2 → pyqrack_cpu_complex128-2.20.0}/pyqrack/qrack_ace_backend.py
RENAMED
|
@@ -453,39 +453,97 @@ class QrackAceBackend:
|
|
|
453
453
|
lq_idx = len(self._qubits)
|
|
454
454
|
self._qubits.append([(sim_id, phys_idx)])
|
|
455
455
|
self._detect_ancilla2_lq.append(lq_idx)
|
|
456
|
-
# Boundary repetition code,
|
|
457
|
-
#
|
|
458
|
-
#
|
|
459
|
-
#
|
|
460
|
-
#
|
|
461
|
-
#
|
|
462
|
-
#
|
|
463
|
-
#
|
|
464
|
-
#
|
|
465
|
-
#
|
|
466
|
-
#
|
|
467
|
-
#
|
|
468
|
-
#
|
|
469
|
-
|
|
470
|
-
|
|
456
|
+
# Boundary repetition code, PERMANENT design: with is_1d_chain
|
|
457
|
+
# and/or the "sacrifice one ideal qubit per patch to double
|
|
458
|
+
# patch count on the same hardware budget" tradeoff, patches
|
|
459
|
+
# are smaller and more numerous than when this was first tried,
|
|
460
|
+
# making permanently-held partners affordable in a way they
|
|
461
|
+
# weren't before -- this is the original proposal, revisited.
|
|
462
|
+
# Unlike the on-demand design just above/below this comment
|
|
463
|
+
# historically lived (fresh encode/extend/decode per coupling
|
|
464
|
+
# event), these partners are kept CONTINUOUSLY, transversally
|
|
465
|
+
# in sync with their boundary qubit: single-qubit gates are
|
|
466
|
+
# already exact, per-replica, so syncing partners through them
|
|
467
|
+
# costs nothing beyond the extra qubits themselves (see the
|
|
468
|
+
# _rep_code_apply_gate hook threaded through every transversal
|
|
469
|
+
# gate method below). Only the SYNDROME-CHECK ancilla is
|
|
470
|
+
# shared, one per patch, reused across every boundary qubit
|
|
471
|
+
# that patch protects -- not one dedicated ancilla per boundary
|
|
472
|
+
# qubit, which would scale with boundary-qubit count instead of
|
|
473
|
+
# patch count. Kept separate from _detect_ancilla1/2 above, to
|
|
474
|
+
# avoid any contention with the is_error_detection gadget
|
|
475
|
+
# within the same coupling event, since both are meant to work
|
|
476
|
+
# in concert, not compete for the same physical ancilla.
|
|
477
|
+
self._rep_code_syn_ancilla = []
|
|
471
478
|
if self.is_boundary_repetition_code:
|
|
472
479
|
for i in range(len(sim_counts)):
|
|
473
|
-
self.
|
|
474
|
-
sim_counts[i] += 1
|
|
475
|
-
self._rep_code_ancilla2.append(sim_counts[i])
|
|
480
|
+
self._rep_code_syn_ancilla.append(sim_counts[i])
|
|
476
481
|
sim_counts[i] += 1
|
|
477
482
|
|
|
478
|
-
self.
|
|
479
|
-
self._rep_code_ancilla2_lq = []
|
|
483
|
+
self._rep_code_syn_ancilla_lq = []
|
|
480
484
|
if self.is_boundary_repetition_code:
|
|
481
|
-
for sim_id, phys_idx in enumerate(self.
|
|
482
|
-
lq_idx = len(self._qubits)
|
|
483
|
-
self._qubits.append([(sim_id, phys_idx)])
|
|
484
|
-
self._rep_code_ancilla1_lq.append(lq_idx)
|
|
485
|
-
for sim_id, phys_idx in enumerate(self._rep_code_ancilla2):
|
|
485
|
+
for sim_id, phys_idx in enumerate(self._rep_code_syn_ancilla):
|
|
486
486
|
lq_idx = len(self._qubits)
|
|
487
487
|
self._qubits.append([(sim_id, phys_idx)])
|
|
488
|
-
self.
|
|
488
|
+
self._rep_code_syn_ancilla_lq.append(lq_idx)
|
|
489
|
+
|
|
490
|
+
# PERMANENT partners: one hidden, single-replica "ideal" qubit
|
|
491
|
+
# per non-crossbar patch each boundary qubit touches -- 2 for
|
|
492
|
+
# an edge qubit (every boundary qubit, when is_1d_chain=True,
|
|
493
|
+
# since a 1D chain has no row-direction structure and hence no
|
|
494
|
+
# corners at all), 4 for a 2D corner. The crossbar replica is
|
|
495
|
+
# excluded as a partner leg for the same reason as everywhere
|
|
496
|
+
# else in this file: it has no adjacent patch bulk of its own
|
|
497
|
+
# to pair with. Allocated by iterating self._qubits AFTER the
|
|
498
|
+
# ancilla allocation above, so appending mid-scan can't shift
|
|
499
|
+
# anything already recorded (range() below captures the
|
|
500
|
+
# original length once, before any of this loop's own
|
|
501
|
+
# appends -- a Python quirk relied on deliberately here, not
|
|
502
|
+
# accidentally).
|
|
503
|
+
self._rep_code_partners = {}
|
|
504
|
+
if self.is_boundary_repetition_code:
|
|
505
|
+
for lq in range(len(self._qubits)):
|
|
506
|
+
hq = self._qubits[lq]
|
|
507
|
+
if len(hq) < 2:
|
|
508
|
+
continue
|
|
509
|
+
partner_sims = []
|
|
510
|
+
for replica in hq:
|
|
511
|
+
if has_boundary and replica[0] == boundary_sim_id:
|
|
512
|
+
continue
|
|
513
|
+
partner_sims.append(replica[0])
|
|
514
|
+
resolved_partners = []
|
|
515
|
+
for sim_id in partner_sims:
|
|
516
|
+
idx = sim_counts[sim_id]
|
|
517
|
+
sim_counts[sim_id] += 1
|
|
518
|
+
p_lq = len(self._qubits)
|
|
519
|
+
self._qubits.append([(sim_id, idx)])
|
|
520
|
+
resolved_partners.append(p_lq)
|
|
521
|
+
self._rep_code_partners[lq] = resolved_partners
|
|
522
|
+
|
|
523
|
+
# Suppresses partner re-sync (via _rep_code_apply_gate) while a
|
|
524
|
+
# correction is itself in progress, applying x()/z() to lq
|
|
525
|
+
# directly -- otherwise that correction would recursively
|
|
526
|
+
# re-trigger a partner sync, undoing the very thing the
|
|
527
|
+
# correction just decided about the partners.
|
|
528
|
+
self._rep_code_correcting = False
|
|
529
|
+
|
|
530
|
+
# Suppresses the coupling-extension and syndrome-check logic in
|
|
531
|
+
# _cpauli specifically (NOT the transversal gate sync, which
|
|
532
|
+
# stays on) during cy()/cz()'s own internal H-sandwich. Found
|
|
533
|
+
# necessary directly, not preemptively: cz()'s CZ-symmetric
|
|
534
|
+
# direction choice can put a protected boundary qubit as the
|
|
535
|
+
# TARGET of the sandwich's inner cx() call, controlled by
|
|
536
|
+
# whatever the OTHER operand of the original cz() was -- not
|
|
537
|
+
# the user's original control at all -- while both the boundary
|
|
538
|
+
# qubit and its partners sit in the sandwich's temporarily
|
|
539
|
+
# H-rotated frame, where every reading is legitimately near 0.5.
|
|
540
|
+
# Checking syndrome agreement there compares two independently
|
|
541
|
+
# shadow-approximated near-0.5 values, which disagree often
|
|
542
|
+
# enough by pure chance to trigger real, wrong corrections; the
|
|
543
|
+
# sandwich's own outer H() calls already correctly sync
|
|
544
|
+
# partners transversally, so nothing is lost by skipping the
|
|
545
|
+
# coupling-extension/syndrome step for just this inner call.
|
|
546
|
+
self._rep_code_suppress_check = False
|
|
489
547
|
|
|
490
548
|
# capture calls (see _apply_coupling): those calls go through
|
|
491
549
|
# the ordinary _cpauli -> _apply_coupling path themselves, which
|
|
@@ -633,29 +691,22 @@ class QrackAceBackend:
|
|
|
633
691
|
def _cz_shadow(self, q1, q2):
|
|
634
692
|
p1 = self.sim[q1[0]].prob(q1[1]) if isinstance(q1, tuple) else q1.prob()
|
|
635
693
|
p2 = self.sim[q2[0]].prob(q2[1]) if isinstance(q2, tuple) else q2.prob()
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
#
|
|
639
|
-
#
|
|
640
|
-
#
|
|
641
|
-
#
|
|
642
|
-
#
|
|
643
|
-
#
|
|
644
|
-
#
|
|
645
|
-
#
|
|
646
|
-
#
|
|
647
|
-
#
|
|
648
|
-
#
|
|
649
|
-
#
|
|
650
|
-
#
|
|
651
|
-
|
|
652
|
-
# Fix: when the two readings are genuinely tied, the DECISION of
|
|
653
|
-
# whether to apply Z must itself be the random draw (not merely
|
|
654
|
-
# which value gets compared against the threshold, which never
|
|
655
|
-
# changed the deterministic outcome). When the readings are
|
|
656
|
-
# NOT tied, the existing decisive-threshold logic is unambiguous
|
|
657
|
-
# and unchanged.
|
|
658
|
-
if abs(p1 - p2) <= self._epsilon:
|
|
694
|
+
target = q2
|
|
695
|
+
|
|
696
|
+
# 0/1-BALANCE FIX: a near-tie carries no informative signal
|
|
697
|
+
# about which basis state to commit to. The correct test for
|
|
698
|
+
# that is whether EACH reading, individually, is itself near
|
|
699
|
+
# 0.5 -- not whether the two readings are near EACH OTHER.
|
|
700
|
+
# Comparing p1 to p2 directly was wrong: two readings can
|
|
701
|
+
# agree, decisively and exactly (e.g. two replicas genuinely
|
|
702
|
+
# tied to the same value, both confidently |1>), and that
|
|
703
|
+
# agreement is exactly the strongest, most reliable signal
|
|
704
|
+
# this shadow ever sees -- treating it as "no information" and
|
|
705
|
+
# coin-flipping anyway threw away the best case, not the worst
|
|
706
|
+
# one. Confirmed empirically: forcing random.random() and
|
|
707
|
+
# counting calls showed the coin-flip branch firing for
|
|
708
|
+
# p1=p2=1.0 exactly, which should be fully deterministic.
|
|
709
|
+
if (abs(p1 - 0.5) <= self._epsilon) and (abs(p2 - 0.5) <= self._epsilon):
|
|
659
710
|
apply = random.random() < 0.5
|
|
660
711
|
else:
|
|
661
712
|
# Symmetric-in-control-vs-target CZ shadow, by design: pick
|
|
@@ -668,18 +719,71 @@ class QrackAceBackend:
|
|
|
668
719
|
# control definitively |0> paired with a freshly-H'd target
|
|
669
720
|
# sitting at p=0.5, where the old logic fired regardless of
|
|
670
721
|
# the control's actual value.
|
|
722
|
+
#
|
|
723
|
+
# The Z then goes to whichever qubit was NOT read: applying
|
|
724
|
+
# it back onto the qubit whose own probability just decided
|
|
725
|
+
# the outcome is not what a genuinely symmetric CZ calls
|
|
726
|
+
# for. Confirmed empirically: with q1 mixed and q2 the
|
|
727
|
+
# decisive one, the Z landed nowhere at all under the old
|
|
728
|
+
# "always q2" rule, when it should have landed on q1.
|
|
671
729
|
d1 = abs(p1 - 0.5)
|
|
672
730
|
d2 = abs(p2 - 0.5)
|
|
673
|
-
|
|
731
|
+
if d1 >= d2:
|
|
732
|
+
decisive = p1
|
|
733
|
+
else:
|
|
734
|
+
decisive = p2
|
|
735
|
+
target = q1
|
|
674
736
|
apply = decisive >= (0.5 - self._epsilon)
|
|
675
737
|
|
|
676
|
-
# The Z gate always targets q2 (the actual shadow target) --
|
|
677
|
-
# never q1 (the control) -- per the earlier phase-kickback fix.
|
|
678
738
|
if apply:
|
|
679
|
-
if isinstance(
|
|
680
|
-
self.sim[
|
|
739
|
+
if isinstance(target, tuple):
|
|
740
|
+
self.sim[target[0]].z(target[1])
|
|
741
|
+
else:
|
|
742
|
+
target.z()
|
|
743
|
+
|
|
744
|
+
def _shadow_control_apply(self, c, t, gate_name):
|
|
745
|
+
"""Non-symmetric, control-only shadow: the decision is based
|
|
746
|
+
ONLY on the control's own probability, never on whichever of
|
|
747
|
+
control/target happens to be more polarized, and the payload
|
|
748
|
+
gate (gate_name -- "x" for CX, "y" for CY) is applied directly
|
|
749
|
+
to the target with NO conjugation at all.
|
|
750
|
+
|
|
751
|
+
This is the hot path for CX and CY -- NOT for a genuinely,
|
|
752
|
+
directly requested CZ, which keeps using the fully symmetric
|
|
753
|
+
_cz_shadow above, unchanged, and NOT for CZ's own "hot path"
|
|
754
|
+
reformulation in cz() either, which keeps its dynamic, CZ-
|
|
755
|
+
symmetric choice of which side to wrap (see cz()).
|
|
756
|
+
|
|
757
|
+
The earlier design conjugated the target with H (for CX) or S
|
|
758
|
+
(for CY, itself built from an H-conjugated CX) specifically so
|
|
759
|
+
the SAME symmetric CZ logic could serve every gate type by
|
|
760
|
+
letting the shadow read either side. Once the decision is
|
|
761
|
+
control-only (see the CX/CZ-symmetry fix above), that
|
|
762
|
+
conjugation no longer accomplishes anything: H.Z.H = X and
|
|
763
|
+
S.X.Sdag = Y exactly (verified directly), and the identity
|
|
764
|
+
no-fire case collapses to a genuine no-op either way. Applying
|
|
765
|
+
the payload gate directly, only when warranted, skips two now-
|
|
766
|
+
pointless gate calls per shadow decision on what is likely the
|
|
767
|
+
hottest path in the whole architecture -- CX is typically the
|
|
768
|
+
most common two-qubit gate, and CY/CZ often reduce to it here
|
|
769
|
+
anyway.
|
|
770
|
+
"""
|
|
771
|
+
p = self.sim[c[0]].prob(c[1]) if isinstance(c, tuple) else c.prob()
|
|
772
|
+
|
|
773
|
+
# Same "genuine uncertainty gets a genuine coin flip" principle
|
|
774
|
+
# as _cz_shadow above, just against the control's own distance
|
|
775
|
+
# from 0.5 rather than a control-vs-target comparison, since
|
|
776
|
+
# there's only one reading in play here.
|
|
777
|
+
if abs(p - 0.5) <= self._epsilon:
|
|
778
|
+
apply = random.random() < 0.5
|
|
779
|
+
else:
|
|
780
|
+
apply = p > 0.5
|
|
781
|
+
|
|
782
|
+
if apply:
|
|
783
|
+
if isinstance(t, tuple):
|
|
784
|
+
getattr(self.sim[t[0]], gate_name)(t[1])
|
|
681
785
|
else:
|
|
682
|
-
|
|
786
|
+
getattr(t, gate_name)()
|
|
683
787
|
|
|
684
788
|
def _qec_x(self, c):
|
|
685
789
|
if isinstance(c, tuple):
|
|
@@ -687,24 +791,6 @@ class QrackAceBackend:
|
|
|
687
791
|
else:
|
|
688
792
|
c.x()
|
|
689
793
|
|
|
690
|
-
def _qec_h(self, t):
|
|
691
|
-
if isinstance(t, tuple):
|
|
692
|
-
self.sim[t[0]].h(t[1])
|
|
693
|
-
else:
|
|
694
|
-
t.h()
|
|
695
|
-
|
|
696
|
-
def _qec_s(self, t):
|
|
697
|
-
if isinstance(t, tuple):
|
|
698
|
-
self.sim[t[0]].s(t[1])
|
|
699
|
-
else:
|
|
700
|
-
t.s()
|
|
701
|
-
|
|
702
|
-
def _qec_adjs(self, t):
|
|
703
|
-
if isinstance(t, tuple):
|
|
704
|
-
self.sim[t[0]].adjs(t[1])
|
|
705
|
-
else:
|
|
706
|
-
t.adjs()
|
|
707
|
-
|
|
708
794
|
def _detect_swap(self, sim_id, idx1, idx2):
|
|
709
795
|
self.sim[sim_id].swap(idx1, idx2)
|
|
710
796
|
|
|
@@ -717,17 +803,13 @@ class QrackAceBackend:
|
|
|
717
803
|
self._anti_shadow_wrap(c, t, self._cz_shadow)
|
|
718
804
|
|
|
719
805
|
def _cx_shadow(self, c, t):
|
|
720
|
-
self.
|
|
721
|
-
self._cz_shadow(c, t)
|
|
722
|
-
self._qec_h(t)
|
|
806
|
+
self._shadow_control_apply(c, t, "x")
|
|
723
807
|
|
|
724
808
|
def _anti_cx_shadow(self, c, t):
|
|
725
809
|
self._anti_shadow_wrap(c, t, self._cx_shadow)
|
|
726
810
|
|
|
727
811
|
def _cy_shadow(self, c, t):
|
|
728
|
-
self.
|
|
729
|
-
self._cx_shadow(c, t)
|
|
730
|
-
self._qec_s(t)
|
|
812
|
+
self._shadow_control_apply(c, t, "y")
|
|
731
813
|
|
|
732
814
|
def _anti_cy_shadow(self, c, t):
|
|
733
815
|
self._anti_shadow_wrap(c, t, self._cy_shadow)
|
|
@@ -1091,6 +1173,26 @@ class QrackAceBackend:
|
|
|
1091
1173
|
math.atan(math.tan(i) * b) - i,
|
|
1092
1174
|
)
|
|
1093
1175
|
|
|
1176
|
+
def _rep_code_apply_gate(self, lq, gate_name, *args):
|
|
1177
|
+
"""Apply the same single-qubit gate, with the same arguments, to
|
|
1178
|
+
every permanent repetition-code partner of lq -- keeps partners
|
|
1179
|
+
continuously, transversally in sync with their boundary qubit.
|
|
1180
|
+
Called from the tail of every transversal single-qubit gate
|
|
1181
|
+
method below. Safe to call unconditionally: a no-op whenever
|
|
1182
|
+
the feature is off, lq has no partners (bulk qubits), or a
|
|
1183
|
+
correction is currently in progress (see _rep_code_correcting),
|
|
1184
|
+
since that correction's own x()/z() call on lq would otherwise
|
|
1185
|
+
recursively re-trigger a partner sync mid-correction.
|
|
1186
|
+
"""
|
|
1187
|
+
if not self.is_boundary_repetition_code or self._rep_code_correcting:
|
|
1188
|
+
return
|
|
1189
|
+
partners = self._rep_code_partners.get(lq)
|
|
1190
|
+
if not partners:
|
|
1191
|
+
return
|
|
1192
|
+
for p_lq in partners:
|
|
1193
|
+
sim_id, idx = self._qubits[p_lq][0]
|
|
1194
|
+
getattr(self.sim[sim_id], gate_name)(idx, *args)
|
|
1195
|
+
|
|
1094
1196
|
def u(self, lq, th, ph, lm):
|
|
1095
1197
|
hq = self._unpack(lq)
|
|
1096
1198
|
if len(hq) < 2:
|
|
@@ -1111,6 +1213,8 @@ class QrackAceBackend:
|
|
|
1111
1213
|
if lhv is not None:
|
|
1112
1214
|
lhv.u(th, ph, lm)
|
|
1113
1215
|
|
|
1216
|
+
self._rep_code_apply_gate(lq, "u", th, ph, lm)
|
|
1217
|
+
|
|
1114
1218
|
# Correction deferred to next 2-qubit gate (_cpauli calls _correct)
|
|
1115
1219
|
|
|
1116
1220
|
def _revert_shadow_commitment(self, lq1):
|
|
@@ -1204,6 +1308,11 @@ class QrackAceBackend:
|
|
|
1204
1308
|
elif p == Pauli.PauliZ:
|
|
1205
1309
|
lhv.rz(th)
|
|
1206
1310
|
|
|
1311
|
+
if self.is_boundary_repetition_code and not self._rep_code_correcting:
|
|
1312
|
+
for p_lq in self._rep_code_partners.get(lq, ()):
|
|
1313
|
+
sim_id, idx = self._qubits[p_lq][0]
|
|
1314
|
+
self.sim[sim_id].r(p, th, idx) # r()'s idx is LAST, unlike every other gate
|
|
1315
|
+
|
|
1207
1316
|
# Correction deferred to next 2-qubit gate (_cpauli calls _correct)
|
|
1208
1317
|
|
|
1209
1318
|
def h(self, lq):
|
|
@@ -1226,6 +1335,8 @@ class QrackAceBackend:
|
|
|
1226
1335
|
if lhv is not None:
|
|
1227
1336
|
lhv.h()
|
|
1228
1337
|
|
|
1338
|
+
self._rep_code_apply_gate(lq, "h")
|
|
1339
|
+
|
|
1229
1340
|
# Correction deferred to next 2-qubit gate (_cpauli calls _correct)
|
|
1230
1341
|
|
|
1231
1342
|
def s(self, lq):
|
|
@@ -1248,6 +1359,8 @@ class QrackAceBackend:
|
|
|
1248
1359
|
if lhv is not None:
|
|
1249
1360
|
lhv.s()
|
|
1250
1361
|
|
|
1362
|
+
self._rep_code_apply_gate(lq, "s")
|
|
1363
|
+
|
|
1251
1364
|
def adjs(self, lq):
|
|
1252
1365
|
hq = self._unpack(lq)
|
|
1253
1366
|
if len(hq) < 2:
|
|
@@ -1268,6 +1381,8 @@ class QrackAceBackend:
|
|
|
1268
1381
|
if lhv is not None:
|
|
1269
1382
|
lhv.adjs()
|
|
1270
1383
|
|
|
1384
|
+
self._rep_code_apply_gate(lq, "adjs")
|
|
1385
|
+
|
|
1271
1386
|
def sx(self, lq):
|
|
1272
1387
|
hq = self._unpack(lq)
|
|
1273
1388
|
if len(hq) < 2:
|
|
@@ -1288,6 +1403,8 @@ class QrackAceBackend:
|
|
|
1288
1403
|
if lhv is not None:
|
|
1289
1404
|
lhv.sx()
|
|
1290
1405
|
|
|
1406
|
+
self._rep_code_apply_gate(lq, "sx")
|
|
1407
|
+
|
|
1291
1408
|
def adjsx(self, lq):
|
|
1292
1409
|
hq = self._unpack(lq)
|
|
1293
1410
|
if len(hq) < 2:
|
|
@@ -1308,129 +1425,147 @@ class QrackAceBackend:
|
|
|
1308
1425
|
if lhv is not None:
|
|
1309
1426
|
lhv.adjsx()
|
|
1310
1427
|
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
feature is off
|
|
1319
|
-
or nowhere to protect it.
|
|
1428
|
+
self._rep_code_apply_gate(lq, "adjsx")
|
|
1429
|
+
|
|
1430
|
+
def _rep_code_patch_pairs(self, lq):
|
|
1431
|
+
"""(patch_replica, partner_lq, syn_ancilla_lq) triples for lq --
|
|
1432
|
+
one per PERMANENT partner (see the constructor allocation),
|
|
1433
|
+
i.e. one per non-crossbar patch lq touches: 2 for an edge
|
|
1434
|
+
qubit, 4 for a 2D corner. Empty if lq is bulk (single-replica)
|
|
1435
|
+
or the feature is off.
|
|
1320
1436
|
"""
|
|
1321
1437
|
if not self.is_boundary_repetition_code:
|
|
1322
1438
|
return []
|
|
1323
|
-
|
|
1324
|
-
if
|
|
1439
|
+
partners = self._rep_code_partners.get(lq)
|
|
1440
|
+
if not partners:
|
|
1325
1441
|
return []
|
|
1326
|
-
|
|
1442
|
+
hq = self._qubits[lq]
|
|
1443
|
+
pairs = []
|
|
1444
|
+
p_i = 0
|
|
1327
1445
|
for replica in hq:
|
|
1328
1446
|
if self._boundary_sim_id is not None and replica[0] == self._boundary_sim_id:
|
|
1329
1447
|
continue
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
return
|
|
1333
|
-
|
|
1334
|
-
def
|
|
1335
|
-
"""
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1448
|
+
pairs.append((replica, partners[p_i], self._rep_code_syn_ancilla_lq[replica[0]]))
|
|
1449
|
+
p_i += 1
|
|
1450
|
+
return pairs
|
|
1451
|
+
|
|
1452
|
+
def _rep_code_extend_coupling(self, pauli, anti, qb1, hq1, lq1_lr, lq1, pairs):
|
|
1453
|
+
"""Extends the SAME coupling (same control) to each PERMANENT
|
|
1454
|
+
partner -- reuses _apply_coupling directly, the same real/
|
|
1455
|
+
shadow dispatch every other target already gets. Only
|
|
1456
|
+
meaningful for a TARGET's pairs: a CONTROL's own value is
|
|
1457
|
+
never touched by gate_fn/shadow_fn in the first place, so
|
|
1458
|
+
there's nothing for a control's partners to need extended to.
|
|
1341
1459
|
"""
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
self.
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
the
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
"""
|
|
1358
|
-
for _, anc1_lq, _ in triples:
|
|
1359
|
-
hq_a = self._qubits[anc1_lq]
|
|
1360
|
-
qb_a, _ = QrackAceBackend._get_qb_lhv_indices(hq_a)
|
|
1361
|
-
self._apply_coupling(pauli, anti, qb1, hq1, qb_a, hq_a, lq1_lr, lq1, anc1_lq)
|
|
1362
|
-
|
|
1363
|
-
def _rep_code_decode_and_correct(self, lq, triples, phase=False):
|
|
1364
|
-
"""Steps 3-5: bit-flip syndrome-check-and-correct (using
|
|
1365
|
-
ancilla2 per patch), THEN the final decode CNOT (lq's replica
|
|
1366
|
-
-> ancilla1 again), which -- once any disagreement has already
|
|
1367
|
-
been corrected -- returns ancilla1 exactly to |0>, ready for
|
|
1368
|
-
the next boundary qubit's coupling event to reuse. An explicit
|
|
1369
|
-
measure-and-reset backstops the decode CNOT for the rare
|
|
1370
|
-
(corner-only) multi-disagreement case left uncorrected below,
|
|
1371
|
-
so ancilla1 is unconditionally clean afterward either way.
|
|
1460
|
+
for _, p_lq, _ in pairs:
|
|
1461
|
+
hq_p = self._qubits[p_lq]
|
|
1462
|
+
qb_p, _ = QrackAceBackend._get_qb_lhv_indices(hq_p)
|
|
1463
|
+
self._apply_coupling(pauli, anti, qb1, hq1, qb_p, hq_p, lq1_lr, lq1, p_lq)
|
|
1464
|
+
|
|
1465
|
+
def _rep_code_syndrome_correct(self, lq, pairs, phase=False):
|
|
1466
|
+
"""Syndrome-check-and-correct against lq's PERMANENT partners,
|
|
1467
|
+
using the shared, per-patch syndrome ancilla. Unlike the
|
|
1468
|
+
earlier on-demand design, there is no encode/decode step here:
|
|
1469
|
+
the partners are already continuously, transversally in sync
|
|
1470
|
+
with lq (see _rep_code_apply_gate), so this only needs to
|
|
1471
|
+
check whether the coupling that just touched lq landed
|
|
1472
|
+
consistently on lq and every partner, and reset the shared
|
|
1473
|
+
syndrome ancilla for the next boundary qubit to reuse -- the
|
|
1474
|
+
partners themselves are never reset, since they're permanent.
|
|
1372
1475
|
|
|
1373
1476
|
Same "try to force error-free, fall back to an unforced Born-
|
|
1374
1477
|
rule read, then it's obvious by elimination" decoding pattern
|
|
1375
|
-
used elsewhere in this file
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
happened instead.
|
|
1478
|
+
used elsewhere in this file: read prob() first; force_m to
|
|
1479
|
+
whichever branch is already within _ps_epsilon of certain;
|
|
1480
|
+
only fall through to a genuine, unforced measurement when
|
|
1481
|
+
neither branch is that confident. With N syndrome bits (2 for
|
|
1482
|
+
an edge qubit, 4 for a corner): zero disagreeing partners means
|
|
1483
|
+
no error; exactly one disagreeing partner unambiguously
|
|
1484
|
+
identifies that partner as the error; a strict majority
|
|
1485
|
+
(including unanimity) identifies lq itself as the outlier. A
|
|
1486
|
+
strict minority greater than one is not explainable by any
|
|
1487
|
+
single-qubit error and is left uncorrected.
|
|
1386
1488
|
"""
|
|
1387
|
-
if not
|
|
1489
|
+
if not pairs:
|
|
1388
1490
|
return
|
|
1389
1491
|
|
|
1390
1492
|
syndrome = []
|
|
1391
|
-
for (sim_id, b_idx),
|
|
1493
|
+
for (sim_id, b_idx), p_lq, syn_lq in pairs:
|
|
1392
1494
|
sim = self.sim[sim_id]
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1495
|
+
p_idx = self._qubits[p_lq][0][1]
|
|
1496
|
+
syn_idx = self._qubits[syn_lq][0][1]
|
|
1497
|
+
|
|
1498
|
+
if phase:
|
|
1499
|
+
sim.h(b_idx)
|
|
1500
|
+
sim.h(p_idx)
|
|
1501
|
+
sim.mcx([b_idx], syn_idx)
|
|
1502
|
+
sim.mcx([p_idx], syn_idx)
|
|
1503
|
+
if phase:
|
|
1504
|
+
sim.h(b_idx)
|
|
1505
|
+
sim.h(p_idx)
|
|
1506
|
+
|
|
1507
|
+
p = sim.prob(syn_idx)
|
|
1508
|
+
if p < self._ps_epsilon:
|
|
1509
|
+
sim.force_m(syn_idx, False)
|
|
1510
|
+
bit = False
|
|
1511
|
+
elif p >= (1.0 - self._ps_epsilon):
|
|
1512
|
+
sim.force_m(syn_idx, True)
|
|
1513
|
+
bit = True
|
|
1514
|
+
else:
|
|
1515
|
+
bit = sim.m(syn_idx)
|
|
1401
1516
|
if bit:
|
|
1402
|
-
sim.x(
|
|
1517
|
+
sim.x(syn_idx) # reset the shared syndrome ancilla for reuse
|
|
1403
1518
|
|
|
1404
|
-
syndrome.append((bit, sim_id,
|
|
1519
|
+
syndrome.append((bit, sim_id, p_idx))
|
|
1405
1520
|
|
|
1406
1521
|
n = len(syndrome)
|
|
1407
1522
|
disagree = sum(1 for bit, _, _ in syndrome if bit)
|
|
1408
|
-
if disagree
|
|
1409
|
-
|
|
1410
|
-
|
|
1523
|
+
if disagree == 0:
|
|
1524
|
+
return
|
|
1525
|
+
|
|
1526
|
+
if disagree > n - disagree:
|
|
1527
|
+
# Strict majority (including unanimity): lq itself is the
|
|
1528
|
+
# outlier. Correct via the ordinary logical gate, so it
|
|
1529
|
+
# transversally touches every one of lq's own replicas the
|
|
1530
|
+
# way any other x()/z() call would -- suppressing the
|
|
1531
|
+
# partner re-sync (see _rep_code_apply_gate) since the
|
|
1532
|
+
# partners were just independently verified correct.
|
|
1533
|
+
self._rep_code_correcting = True
|
|
1534
|
+
try:
|
|
1411
1535
|
if phase:
|
|
1412
1536
|
self.z(lq)
|
|
1413
1537
|
else:
|
|
1414
1538
|
self.x(lq)
|
|
1415
|
-
|
|
1416
|
-
|
|
1539
|
+
finally:
|
|
1540
|
+
self._rep_code_correcting = False
|
|
1541
|
+
elif disagree == 1:
|
|
1542
|
+
# exactly one partner disagrees -- unambiguously that partner
|
|
1543
|
+
for bit, sim_id, p_idx in syndrome:
|
|
1417
1544
|
if bit:
|
|
1418
1545
|
if phase:
|
|
1419
|
-
self.sim[sim_id].z(
|
|
1546
|
+
self.sim[sim_id].z(p_idx)
|
|
1420
1547
|
else:
|
|
1421
|
-
self.sim[sim_id].x(
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1548
|
+
self.sim[sim_id].x(p_idx)
|
|
1549
|
+
# else: strict minority > 1 (only possible with the corner
|
|
1550
|
+
# code) -- not explainable by any single-qubit error, left
|
|
1551
|
+
# uncorrected rather than guessed at.
|
|
1552
|
+
|
|
1553
|
+
def _rep_code_check_pair(self, lq1, lq2):
|
|
1554
|
+
"""Runs the syndrome check on both operands directly, bypassing
|
|
1555
|
+
_cpauli entirely. Used after cy()/cz()/acy()/acz()'s H-sandwich
|
|
1556
|
+
fully unwinds, to make up for _rep_code_suppress_check having
|
|
1557
|
+
skipped the check during the sandwich's own internal coupling
|
|
1558
|
+
call -- the check needs to happen exactly once, after the
|
|
1559
|
+
user's full requested operation, not mid-sandwich (see
|
|
1560
|
+
_rep_code_suppress_check for why mid-sandwich specifically goes
|
|
1561
|
+
wrong) and not zero times either.
|
|
1562
|
+
"""
|
|
1563
|
+
if not self.is_boundary_repetition_code:
|
|
1564
|
+
return
|
|
1565
|
+
pairs1 = self._rep_code_patch_pairs(lq1)
|
|
1566
|
+
self._rep_code_syndrome_correct(lq1, pairs1)
|
|
1567
|
+
pairs2 = self._rep_code_patch_pairs(lq2)
|
|
1568
|
+
self._rep_code_syndrome_correct(lq2, pairs2)
|
|
1434
1569
|
|
|
1435
1570
|
def x(self, lq):
|
|
1436
1571
|
hq = self._unpack(lq)
|
|
@@ -1452,6 +1587,8 @@ class QrackAceBackend:
|
|
|
1452
1587
|
if lhv is not None:
|
|
1453
1588
|
lhv.x()
|
|
1454
1589
|
|
|
1590
|
+
self._rep_code_apply_gate(lq, "x")
|
|
1591
|
+
|
|
1455
1592
|
def y(self, lq):
|
|
1456
1593
|
hq = self._unpack(lq)
|
|
1457
1594
|
if len(hq) < 2:
|
|
@@ -1472,6 +1609,8 @@ class QrackAceBackend:
|
|
|
1472
1609
|
if lhv is not None:
|
|
1473
1610
|
lhv.y()
|
|
1474
1611
|
|
|
1612
|
+
self._rep_code_apply_gate(lq, "y")
|
|
1613
|
+
|
|
1475
1614
|
def z(self, lq):
|
|
1476
1615
|
hq = self._unpack(lq)
|
|
1477
1616
|
if len(hq) < 2:
|
|
@@ -1492,6 +1631,8 @@ class QrackAceBackend:
|
|
|
1492
1631
|
if lhv is not None:
|
|
1493
1632
|
lhv.z()
|
|
1494
1633
|
|
|
1634
|
+
self._rep_code_apply_gate(lq, "z")
|
|
1635
|
+
|
|
1495
1636
|
def t(self, lq):
|
|
1496
1637
|
hq = self._unpack(lq)
|
|
1497
1638
|
if len(hq) < 2:
|
|
@@ -1512,6 +1653,8 @@ class QrackAceBackend:
|
|
|
1512
1653
|
if lhv is not None:
|
|
1513
1654
|
lhv.t()
|
|
1514
1655
|
|
|
1656
|
+
self._rep_code_apply_gate(lq, "t")
|
|
1657
|
+
|
|
1515
1658
|
def adjt(self, lq):
|
|
1516
1659
|
hq = self._unpack(lq)
|
|
1517
1660
|
if len(hq) < 2:
|
|
@@ -1532,6 +1675,8 @@ class QrackAceBackend:
|
|
|
1532
1675
|
if lhv is not None:
|
|
1533
1676
|
lhv.adjt()
|
|
1534
1677
|
|
|
1678
|
+
self._rep_code_apply_gate(lq, "adjt")
|
|
1679
|
+
|
|
1535
1680
|
def _get_gate(self, pauli, anti, sim_id):
|
|
1536
1681
|
gate = None
|
|
1537
1682
|
shadow = None
|
|
@@ -1772,40 +1917,34 @@ class QrackAceBackend:
|
|
|
1772
1917
|
self._correct(lq1)
|
|
1773
1918
|
self._correct(lq2)
|
|
1774
1919
|
|
|
1775
|
-
# Boundary repetition code,
|
|
1776
|
-
#
|
|
1777
|
-
#
|
|
1778
|
-
#
|
|
1779
|
-
#
|
|
1780
|
-
#
|
|
1781
|
-
#
|
|
1782
|
-
#
|
|
1783
|
-
#
|
|
1784
|
-
#
|
|
1785
|
-
# no-op anyway), but the CONTROL side could still be a protected
|
|
1786
|
-
# boundary qubit, and re-running its own encode/check/decode
|
|
1787
|
-
# cycle on every nested capture would be wasteful and redundant
|
|
1788
|
-
# with the outer call already doing it once.
|
|
1920
|
+
# Boundary repetition code, PERMANENT partners: unlike the
|
|
1921
|
+
# earlier on-demand design (fresh encode/decode wrapped tightly
|
|
1922
|
+
# around just the coupler), partners here are ALREADY,
|
|
1923
|
+
# continuously kept in sync via every transversal gate (see
|
|
1924
|
+
# _rep_code_apply_gate) -- so there's no encode step needed at
|
|
1925
|
+
# all, just: (1) verify/correct lq1's reliability right before
|
|
1926
|
+
# the coupler reads it, since the control is never gated by
|
|
1927
|
+
# gate_fn/shadow_fn itself but its CURRENT value still needs to
|
|
1928
|
+
# be trustworthy; (2) extend the coupler's effect on lq2 to its
|
|
1929
|
+
# partners too, then check the result.
|
|
1789
1930
|
#
|
|
1790
|
-
#
|
|
1791
|
-
#
|
|
1792
|
-
#
|
|
1793
|
-
#
|
|
1794
|
-
#
|
|
1795
|
-
#
|
|
1796
|
-
#
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
t2 = self._rep_code_encode(lq2)
|
|
1808
|
-
if t2:
|
|
1931
|
+
# This is safe even mid-H-sandwich (cy()/cz()'s internal
|
|
1932
|
+
# h();cx();h(), which reaches this same _cpauli call) for a
|
|
1933
|
+
# different reason than the on-demand design's safety: partners
|
|
1934
|
+
# receive every transversal gate lq2 does, INCLUDING that
|
|
1935
|
+
# sandwich's own h() calls, so by the time this inner coupling
|
|
1936
|
+
# runs, lq2 and its partners are already, consistently, TOGETHER
|
|
1937
|
+
# in whatever frame the sandwich has rotated into -- the
|
|
1938
|
+
# comparison never spans a rotation either side of it missed.
|
|
1939
|
+
# Verified directly against exactly this scenario.
|
|
1940
|
+
if self.is_boundary_repetition_code and not self._in_gadget_capture and not self._rep_code_suppress_check:
|
|
1941
|
+
pairs1 = self._rep_code_patch_pairs(lq1)
|
|
1942
|
+
self._rep_code_syndrome_correct(lq1, pairs1)
|
|
1943
|
+
|
|
1944
|
+
pairs2 = []
|
|
1945
|
+
if self.is_boundary_repetition_code and not self._in_gadget_capture and not self._rep_code_suppress_check:
|
|
1946
|
+
pairs2 = self._rep_code_patch_pairs(lq2)
|
|
1947
|
+
if pairs2:
|
|
1809
1948
|
# Only defined for "patch-local" connectivity, the only
|
|
1810
1949
|
# officially supported connectivity model here (get_
|
|
1811
1950
|
# logical_coupling_map() only ever returns pairs that
|
|
@@ -1813,7 +1952,7 @@ class QrackAceBackend:
|
|
|
1813
1952
|
# share NO simulator at all) only exists as a fallback
|
|
1814
1953
|
# for users who ignore that map, and this repetition-
|
|
1815
1954
|
# code layer doesn't try to support it: raise plainly
|
|
1816
|
-
# instead of silently coupling
|
|
1955
|
+
# instead of silently coupling partners through an
|
|
1817
1956
|
# undefined/unsupported path.
|
|
1818
1957
|
sims1 = {b[0] for b in hq1}
|
|
1819
1958
|
sims2 = {b[0] for b in hq2}
|
|
@@ -1831,8 +1970,8 @@ class QrackAceBackend:
|
|
|
1831
1970
|
# qubits, which now live as real qubits in the shared boundary sim.
|
|
1832
1971
|
self._apply_coupling(pauli, anti, qb1, hq1, qb2, hq2, lq1_lr, lq1, lq2)
|
|
1833
1972
|
|
|
1834
|
-
if
|
|
1835
|
-
self._rep_code_extend_coupling(pauli, anti, qb1, hq1, lq1_lr, lq1,
|
|
1973
|
+
if pairs2:
|
|
1974
|
+
self._rep_code_extend_coupling(pauli, anti, qb1, hq1, lq1_lr, lq1, pairs2)
|
|
1836
1975
|
|
|
1837
1976
|
if lq2 in self._lhv:
|
|
1838
1977
|
ctrl_prob = self.sim[hq1[0][0]].prob(hq1[0][1])
|
|
@@ -1841,21 +1980,16 @@ class QrackAceBackend:
|
|
|
1841
1980
|
if self._in_gadget_capture:
|
|
1842
1981
|
return
|
|
1843
1982
|
|
|
1844
|
-
# Repetition-code
|
|
1983
|
+
# Repetition-code syndrome-check for lq2 happens HERE,
|
|
1845
1984
|
# immediately after the coupler+extension, in the SAME frame
|
|
1846
1985
|
# they just ran in -- deliberately BEFORE _correct()'s own,
|
|
1847
1986
|
# separate soft reconciliation below, which can itself rotate
|
|
1848
|
-
# lq2 (the phase=True call) and would
|
|
1849
|
-
#
|
|
1850
|
-
#
|
|
1851
|
-
#
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
# never spanning an external rotation the way permanently-
|
|
1855
|
-
# synced partners, checked from a separate top-level call, used
|
|
1856
|
-
# to.
|
|
1857
|
-
if self.is_boundary_repetition_code:
|
|
1858
|
-
self._rep_code_decode_and_correct(lq2, t2)
|
|
1987
|
+
# lq2 (the phase=True call) and would pull it out of sync with
|
|
1988
|
+
# partners that don't get that same call routed to them
|
|
1989
|
+
# (partners only track lq2's OWN transversal gates, not
|
|
1990
|
+
# _correct()'s internal bookkeeping).
|
|
1991
|
+
if self.is_boundary_repetition_code and not self._rep_code_suppress_check:
|
|
1992
|
+
self._rep_code_syndrome_correct(lq2, pairs2)
|
|
1859
1993
|
|
|
1860
1994
|
# No post-coupling correction on lq1 (the control): _cz_shadow only
|
|
1861
1995
|
# ever calls .z() on the target, never gates the control -- only
|
|
@@ -1876,16 +2010,74 @@ class QrackAceBackend:
|
|
|
1876
2010
|
def cy(self, lq1, lq2):
|
|
1877
2011
|
if self.is_error_detection and ((len(self._unpack(lq1)) > 1) or (len(self._unpack(lq2)) > 1)):
|
|
1878
2012
|
self.adjs(lq2)
|
|
1879
|
-
self.
|
|
2013
|
+
self._rep_code_suppress_check = True
|
|
2014
|
+
try:
|
|
2015
|
+
self.cx(lq1, lq2)
|
|
2016
|
+
finally:
|
|
2017
|
+
self._rep_code_suppress_check = False
|
|
1880
2018
|
self.s(lq2)
|
|
2019
|
+
self._rep_code_check_pair(lq1, lq2)
|
|
1881
2020
|
else:
|
|
1882
2021
|
self._cpauli(lq1, lq2, False, Pauli.PauliY)
|
|
1883
2022
|
|
|
1884
2023
|
def cz(self, lq1, lq2):
|
|
1885
2024
|
if self.is_error_detection and ((len(self._unpack(lq1)) > 1) or (len(self._unpack(lq2)) > 1)):
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
2025
|
+
# CZ's own exchange symmetry (CZ(a,b)=CZ(b,a)) means either
|
|
2026
|
+
# H-sandwich direction below gives the IDENTICAL, correct
|
|
2027
|
+
# CZ unitary -- so pick whichever leaves the underlying
|
|
2028
|
+
# cx()'s shadow reading the more decisively polarized
|
|
2029
|
+
# qubit, the same principle _cz_shadow itself already
|
|
2030
|
+
# applies directly for a genuinely, directly requested CZ
|
|
2031
|
+
# (the "else" branch below). This is the "hot path" fix:
|
|
2032
|
+
# without it, this branch (is_error_detection AND either
|
|
2033
|
+
# side boundary -- the common, practical case) always read
|
|
2034
|
+
# lq1 regardless of which side was actually more decisive,
|
|
2035
|
+
# since _cx_shadow's control-only basis (see
|
|
2036
|
+
# _shadow_control_apply) no longer inherits _cz_shadow's own
|
|
2037
|
+
# symmetric picking the way it used to.
|
|
2038
|
+
#
|
|
2039
|
+
# Does NOT extend to acz() below: anti-control isn't
|
|
2040
|
+
# symmetric the same way CZ is -- verified directly,
|
|
2041
|
+
# X-conjugating the control vs the target gives two
|
|
2042
|
+
# different gate matrices, not the same one -- so acz() has
|
|
2043
|
+
# no analogous second choice to pick between; lq1 must stay
|
|
2044
|
+
# the one read, since it's specifically the anti-controlled
|
|
2045
|
+
# qubit by the gate's own definition.
|
|
2046
|
+
#
|
|
2047
|
+
# The inner cx() here runs with the repetition code's
|
|
2048
|
+
# coupling-extension/syndrome-check suppressed (see
|
|
2049
|
+
# _rep_code_suppress_check): found directly, not
|
|
2050
|
+
# preemptively, that this symmetric direction choice can
|
|
2051
|
+
# put a protected boundary qubit as the inner cx()'s
|
|
2052
|
+
# TARGET, controlled by the OTHER operand of this cz() call
|
|
2053
|
+
# rather than the user's original control, while both sit
|
|
2054
|
+
# in the sandwich's temporarily H-rotated frame where every
|
|
2055
|
+
# reading is legitimately near 0.5 -- checking syndrome
|
|
2056
|
+
# agreement there compares two independently
|
|
2057
|
+
# shadow-approximated near-0.5 values, which disagreed
|
|
2058
|
+
# often enough by pure chance to trigger real, wrong
|
|
2059
|
+
# corrections. The transversal H() calls below still
|
|
2060
|
+
# correctly sync partners either way; _rep_code_check_pair
|
|
2061
|
+
# after the sandwich fully unwinds makes up for the
|
|
2062
|
+
# suppressed check, running exactly once instead of zero
|
|
2063
|
+
# times or mid-rotation.
|
|
2064
|
+
if abs(self.prob(lq1) - 0.5) >= abs(self.prob(lq2) - 0.5):
|
|
2065
|
+
self.h(lq2)
|
|
2066
|
+
self._rep_code_suppress_check = True
|
|
2067
|
+
try:
|
|
2068
|
+
self.cx(lq1, lq2)
|
|
2069
|
+
finally:
|
|
2070
|
+
self._rep_code_suppress_check = False
|
|
2071
|
+
self.h(lq2)
|
|
2072
|
+
else:
|
|
2073
|
+
self.h(lq1)
|
|
2074
|
+
self._rep_code_suppress_check = True
|
|
2075
|
+
try:
|
|
2076
|
+
self.cx(lq2, lq1)
|
|
2077
|
+
finally:
|
|
2078
|
+
self._rep_code_suppress_check = False
|
|
2079
|
+
self.h(lq1)
|
|
2080
|
+
self._rep_code_check_pair(lq1, lq2)
|
|
1889
2081
|
else:
|
|
1890
2082
|
self._cpauli(lq1, lq2, False, Pauli.PauliZ)
|
|
1891
2083
|
|
|
@@ -1895,16 +2087,26 @@ class QrackAceBackend:
|
|
|
1895
2087
|
def acy(self, lq1, lq2):
|
|
1896
2088
|
if self.is_error_detection and ((len(self._unpack(lq1)) > 1) or (len(self._unpack(lq2)) > 1)):
|
|
1897
2089
|
self.adjs(lq2)
|
|
1898
|
-
self.
|
|
2090
|
+
self._rep_code_suppress_check = True
|
|
2091
|
+
try:
|
|
2092
|
+
self.acx(lq1, lq2)
|
|
2093
|
+
finally:
|
|
2094
|
+
self._rep_code_suppress_check = False
|
|
1899
2095
|
self.s(lq2)
|
|
2096
|
+
self._rep_code_check_pair(lq1, lq2)
|
|
1900
2097
|
else:
|
|
1901
2098
|
self._cpauli(lq1, lq2, True, Pauli.PauliY)
|
|
1902
2099
|
|
|
1903
2100
|
def acz(self, lq1, lq2):
|
|
1904
2101
|
if self.is_error_detection and ((len(self._unpack(lq1)) > 1) or (len(self._unpack(lq2)) > 1)):
|
|
1905
2102
|
self.h(lq2)
|
|
1906
|
-
self.
|
|
2103
|
+
self._rep_code_suppress_check = True
|
|
2104
|
+
try:
|
|
2105
|
+
self.acx(lq1, lq2)
|
|
2106
|
+
finally:
|
|
2107
|
+
self._rep_code_suppress_check = False
|
|
1907
2108
|
self.h(lq2)
|
|
2109
|
+
self._rep_code_check_pair(lq1, lq2)
|
|
1908
2110
|
else:
|
|
1909
2111
|
self._cpauli(lq1, lq2, True, Pauli.PauliZ)
|
|
1910
2112
|
|
|
@@ -2003,13 +2205,22 @@ class QrackAceBackend:
|
|
|
2003
2205
|
if lhv1 is not None:
|
|
2004
2206
|
self._lhv[lq2] = lhv1
|
|
2005
2207
|
|
|
2006
|
-
# Boundary repetition code
|
|
2007
|
-
#
|
|
2008
|
-
#
|
|
2009
|
-
#
|
|
2010
|
-
#
|
|
2011
|
-
#
|
|
2012
|
-
#
|
|
2208
|
+
# Boundary repetition code: partners are tied to a PHYSICAL
|
|
2209
|
+
# patch location, allocated once at construction and never
|
|
2210
|
+
# moved -- exactly like self._qubits and self._lhv just
|
|
2211
|
+
# above, which this mirrors. After swapping WHICH logical
|
|
2212
|
+
# index points to which physical replica list, the partner
|
|
2213
|
+
# mapping needs the same treatment, or lq2 (now pointing at
|
|
2214
|
+
# lq1's old physical hardware) would keep lq2's OLD
|
|
2215
|
+
# partners, checked against physically the wrong patches
|
|
2216
|
+
# from then on.
|
|
2217
|
+
if self.is_boundary_repetition_code:
|
|
2218
|
+
p1 = self._rep_code_partners.pop(lq1, None)
|
|
2219
|
+
p2 = self._rep_code_partners.pop(lq2, None)
|
|
2220
|
+
if p2 is not None:
|
|
2221
|
+
self._rep_code_partners[lq1] = p2
|
|
2222
|
+
if p1 is not None:
|
|
2223
|
+
self._rep_code_partners[lq2] = p1
|
|
2013
2224
|
return
|
|
2014
2225
|
|
|
2015
2226
|
# Boundary repetition code: redirect entirely to the 3-CNOT
|
|
@@ -2017,12 +2228,12 @@ class QrackAceBackend:
|
|
|
2017
2228
|
# side is multi-replica (repetition-code eligible), rather than
|
|
2018
2229
|
# extending each of the specialized fast/partial-match paths
|
|
2019
2230
|
# below individually with new, separately-unverified encode/
|
|
2020
|
-
# decode logic. self.cx() already handles
|
|
2021
|
-
#
|
|
2022
|
-
#
|
|
2023
|
-
#
|
|
2024
|
-
#
|
|
2025
|
-
#
|
|
2231
|
+
# decode logic. self.cx() already handles PERMANENT partners
|
|
2232
|
+
# correctly (transversal sync, coupling extension, syndrome
|
|
2233
|
+
# check all live there) -- so this reuses that rather than
|
|
2234
|
+
# re-deriving equivalent logic three more times under the same
|
|
2235
|
+
# risk of another subtle mismatch. Costs the fast-path
|
|
2236
|
+
# optimizations below for this specific case; given
|
|
2026
2237
|
# the fast/exact-match path additionally requires lq1 and lq2 to
|
|
2027
2238
|
# be TWO boundary qubits with identically-matching patch
|
|
2028
2239
|
# structure -- rare on its own -- the case being deferred is
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyqrack_cpu_complex128-2.19.2 → pyqrack_cpu_complex128-2.20.0}/pyqrack/neuron_activation_fn.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyqrack_cpu_complex128-2.19.2 → pyqrack_cpu_complex128-2.20.0}/pyqrack/qrack_neuron_torch_layer.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyqrack_cpu_complex128-2.19.2 → pyqrack_cpu_complex128-2.20.0}/pyqrack/qrack_system/__init__.py
RENAMED
|
File without changes
|
{pyqrack_cpu_complex128-2.19.2 → pyqrack_cpu_complex128-2.20.0}/pyqrack/qrack_system/qrack_system.py
RENAMED
|
File without changes
|
{pyqrack_cpu_complex128-2.19.2 → pyqrack_cpu_complex128-2.20.0}/pyqrack/quimb_circuit_type.py
RENAMED
|
File without changes
|
|
File without changes
|
{pyqrack_cpu_complex128-2.19.2 → pyqrack_cpu_complex128-2.20.0}/pyqrack/stats/load_quantized_data.py
RENAMED
|
File without changes
|
{pyqrack_cpu_complex128-2.19.2 → pyqrack_cpu_complex128-2.20.0}/pyqrack/stats/quantize_by_range.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|