pyqrack-cpu-complex128 2.19.3__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.3 → pyqrack_cpu_complex128-2.20.0}/Makefile +1 -1
- {pyqrack_cpu_complex128-2.19.3/pyqrack_cpu_complex128.egg-info → pyqrack_cpu_complex128-2.20.0}/PKG-INFO +1 -1
- {pyqrack_cpu_complex128-2.19.3 → pyqrack_cpu_complex128-2.20.0}/pyqrack/qrack_ace_backend.py +441 -248
- {pyqrack_cpu_complex128-2.19.3 → pyqrack_cpu_complex128-2.20.0/pyqrack_cpu_complex128.egg-info}/PKG-INFO +1 -1
- {pyqrack_cpu_complex128-2.19.3 → pyqrack_cpu_complex128-2.20.0}/setup.py +1 -1
- {pyqrack_cpu_complex128-2.19.3 → pyqrack_cpu_complex128-2.20.0}/LICENSE +0 -0
- {pyqrack_cpu_complex128-2.19.3 → pyqrack_cpu_complex128-2.20.0}/MANIFEST.in +0 -0
- {pyqrack_cpu_complex128-2.19.3 → pyqrack_cpu_complex128-2.20.0}/README.md +0 -0
- {pyqrack_cpu_complex128-2.19.3 → pyqrack_cpu_complex128-2.20.0}/pyproject.toml +0 -0
- {pyqrack_cpu_complex128-2.19.3 → pyqrack_cpu_complex128-2.20.0}/pyqrack/__init__.py +0 -0
- {pyqrack_cpu_complex128-2.19.3 → pyqrack_cpu_complex128-2.20.0}/pyqrack/neuron_activation_fn.py +0 -0
- {pyqrack_cpu_complex128-2.19.3 → pyqrack_cpu_complex128-2.20.0}/pyqrack/pauli.py +0 -0
- {pyqrack_cpu_complex128-2.19.3 → pyqrack_cpu_complex128-2.20.0}/pyqrack/qrack_circuit.py +0 -0
- {pyqrack_cpu_complex128-2.19.3 → pyqrack_cpu_complex128-2.20.0}/pyqrack/qrack_near_clifford_qec_backend.py +0 -0
- {pyqrack_cpu_complex128-2.19.3 → pyqrack_cpu_complex128-2.20.0}/pyqrack/qrack_neuron.py +0 -0
- {pyqrack_cpu_complex128-2.19.3 → pyqrack_cpu_complex128-2.20.0}/pyqrack/qrack_neuron_torch_layer.py +0 -0
- {pyqrack_cpu_complex128-2.19.3 → pyqrack_cpu_complex128-2.20.0}/pyqrack/qrack_simulator.py +0 -0
- {pyqrack_cpu_complex128-2.19.3 → pyqrack_cpu_complex128-2.20.0}/pyqrack/qrack_stabilizer.py +0 -0
- {pyqrack_cpu_complex128-2.19.3 → pyqrack_cpu_complex128-2.20.0}/pyqrack/qrack_system/__init__.py +0 -0
- {pyqrack_cpu_complex128-2.19.3 → pyqrack_cpu_complex128-2.20.0}/pyqrack/qrack_system/qrack_system.py +0 -0
- {pyqrack_cpu_complex128-2.19.3 → pyqrack_cpu_complex128-2.20.0}/pyqrack/quimb_circuit_type.py +0 -0
- {pyqrack_cpu_complex128-2.19.3 → pyqrack_cpu_complex128-2.20.0}/pyqrack/stats/__init__.py +0 -0
- {pyqrack_cpu_complex128-2.19.3 → pyqrack_cpu_complex128-2.20.0}/pyqrack/stats/load_quantized_data.py +0 -0
- {pyqrack_cpu_complex128-2.19.3 → pyqrack_cpu_complex128-2.20.0}/pyqrack/stats/quantize_by_range.py +0 -0
- {pyqrack_cpu_complex128-2.19.3 → pyqrack_cpu_complex128-2.20.0}/pyqrack_cpu_complex128.egg-info/SOURCES.txt +0 -0
- {pyqrack_cpu_complex128-2.19.3 → pyqrack_cpu_complex128-2.20.0}/pyqrack_cpu_complex128.egg-info/dependency_links.txt +0 -0
- {pyqrack_cpu_complex128-2.19.3 → pyqrack_cpu_complex128-2.20.0}/pyqrack_cpu_complex128.egg-info/not-zip-safe +0 -0
- {pyqrack_cpu_complex128-2.19.3 → pyqrack_cpu_complex128-2.20.0}/pyqrack_cpu_complex128.egg-info/requires.txt +0 -0
- {pyqrack_cpu_complex128-2.19.3 → pyqrack_cpu_complex128-2.20.0}/pyqrack_cpu_complex128.egg-info/top_level.txt +0 -0
- {pyqrack_cpu_complex128-2.19.3 → 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.3 → 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
|
|
@@ -635,30 +693,21 @@ class QrackAceBackend:
|
|
|
635
693
|
p2 = self.sim[q2[0]].prob(q2[1]) if isinstance(q2, tuple) else q2.prob()
|
|
636
694
|
target = q2
|
|
637
695
|
|
|
638
|
-
# 0/1-BALANCE FIX: a near-tie
|
|
639
|
-
#
|
|
640
|
-
#
|
|
641
|
-
#
|
|
642
|
-
#
|
|
643
|
-
#
|
|
644
|
-
#
|
|
645
|
-
#
|
|
646
|
-
#
|
|
647
|
-
#
|
|
648
|
-
#
|
|
649
|
-
#
|
|
650
|
-
#
|
|
651
|
-
# deterministic bias toward |1>.
|
|
652
|
-
#
|
|
653
|
-
# Fix: when the two readings are genuinely tied, the DECISION of
|
|
654
|
-
# whether to apply Z must itself be the random draw (not merely
|
|
655
|
-
# which value gets compared against the threshold, which never
|
|
656
|
-
# changed the deterministic outcome). When the readings are
|
|
657
|
-
# NOT tied, the existing decisive-threshold logic is unambiguous
|
|
658
|
-
# and unchanged.
|
|
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.
|
|
659
709
|
if (abs(p1 - 0.5) <= self._epsilon) and (abs(p2 - 0.5) <= self._epsilon):
|
|
660
710
|
apply = random.random() < 0.5
|
|
661
|
-
target = q1 if (random.random() < 0.5) else q2
|
|
662
711
|
else:
|
|
663
712
|
# Symmetric-in-control-vs-target CZ shadow, by design: pick
|
|
664
713
|
# whichever of p1/p2 is more DECISIVE (further from 0.5, i.e.
|
|
@@ -670,6 +719,13 @@ class QrackAceBackend:
|
|
|
670
719
|
# control definitively |0> paired with a freshly-H'd target
|
|
671
720
|
# sitting at p=0.5, where the old logic fired regardless of
|
|
672
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.
|
|
673
729
|
d1 = abs(p1 - 0.5)
|
|
674
730
|
d2 = abs(p2 - 0.5)
|
|
675
731
|
if d1 >= d2:
|
|
@@ -685,29 +741,55 @@ class QrackAceBackend:
|
|
|
685
741
|
else:
|
|
686
742
|
target.z()
|
|
687
743
|
|
|
688
|
-
def
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
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()
|
|
693
772
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
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
|
|
697
779
|
else:
|
|
698
|
-
|
|
780
|
+
apply = p > 0.5
|
|
699
781
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
782
|
+
if apply:
|
|
783
|
+
if isinstance(t, tuple):
|
|
784
|
+
getattr(self.sim[t[0]], gate_name)(t[1])
|
|
785
|
+
else:
|
|
786
|
+
getattr(t, gate_name)()
|
|
705
787
|
|
|
706
|
-
def
|
|
707
|
-
if isinstance(
|
|
708
|
-
self.sim[
|
|
788
|
+
def _qec_x(self, c):
|
|
789
|
+
if isinstance(c, tuple):
|
|
790
|
+
self.sim[c[0]].x(c[1])
|
|
709
791
|
else:
|
|
710
|
-
|
|
792
|
+
c.x()
|
|
711
793
|
|
|
712
794
|
def _detect_swap(self, sim_id, idx1, idx2):
|
|
713
795
|
self.sim[sim_id].swap(idx1, idx2)
|
|
@@ -721,31 +803,13 @@ class QrackAceBackend:
|
|
|
721
803
|
self._anti_shadow_wrap(c, t, self._cz_shadow)
|
|
722
804
|
|
|
723
805
|
def _cx_shadow(self, c, t):
|
|
724
|
-
|
|
725
|
-
if abs(p - 0.5) <= self._epsilon:
|
|
726
|
-
apply = random.random() < 0.5
|
|
727
|
-
else:
|
|
728
|
-
apply = p > 0.5
|
|
729
|
-
if apply:
|
|
730
|
-
if isinstance(t, tuple):
|
|
731
|
-
self.sim[t[0]].x(t[1])
|
|
732
|
-
else:
|
|
733
|
-
t.x()
|
|
806
|
+
self._shadow_control_apply(c, t, "x")
|
|
734
807
|
|
|
735
808
|
def _anti_cx_shadow(self, c, t):
|
|
736
809
|
self._anti_shadow_wrap(c, t, self._cx_shadow)
|
|
737
810
|
|
|
738
811
|
def _cy_shadow(self, c, t):
|
|
739
|
-
|
|
740
|
-
if abs(p - 0.5) <= self._epsilon:
|
|
741
|
-
apply = random.random() < 0.5
|
|
742
|
-
else:
|
|
743
|
-
apply = p > 0.5
|
|
744
|
-
if apply:
|
|
745
|
-
if isinstance(t, tuple):
|
|
746
|
-
self.sim[t[0]].y(t[1])
|
|
747
|
-
else:
|
|
748
|
-
t.y()
|
|
812
|
+
self._shadow_control_apply(c, t, "y")
|
|
749
813
|
|
|
750
814
|
def _anti_cy_shadow(self, c, t):
|
|
751
815
|
self._anti_shadow_wrap(c, t, self._cy_shadow)
|
|
@@ -1109,6 +1173,26 @@ class QrackAceBackend:
|
|
|
1109
1173
|
math.atan(math.tan(i) * b) - i,
|
|
1110
1174
|
)
|
|
1111
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
|
+
|
|
1112
1196
|
def u(self, lq, th, ph, lm):
|
|
1113
1197
|
hq = self._unpack(lq)
|
|
1114
1198
|
if len(hq) < 2:
|
|
@@ -1129,6 +1213,8 @@ class QrackAceBackend:
|
|
|
1129
1213
|
if lhv is not None:
|
|
1130
1214
|
lhv.u(th, ph, lm)
|
|
1131
1215
|
|
|
1216
|
+
self._rep_code_apply_gate(lq, "u", th, ph, lm)
|
|
1217
|
+
|
|
1132
1218
|
# Correction deferred to next 2-qubit gate (_cpauli calls _correct)
|
|
1133
1219
|
|
|
1134
1220
|
def _revert_shadow_commitment(self, lq1):
|
|
@@ -1222,6 +1308,11 @@ class QrackAceBackend:
|
|
|
1222
1308
|
elif p == Pauli.PauliZ:
|
|
1223
1309
|
lhv.rz(th)
|
|
1224
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
|
+
|
|
1225
1316
|
# Correction deferred to next 2-qubit gate (_cpauli calls _correct)
|
|
1226
1317
|
|
|
1227
1318
|
def h(self, lq):
|
|
@@ -1244,6 +1335,8 @@ class QrackAceBackend:
|
|
|
1244
1335
|
if lhv is not None:
|
|
1245
1336
|
lhv.h()
|
|
1246
1337
|
|
|
1338
|
+
self._rep_code_apply_gate(lq, "h")
|
|
1339
|
+
|
|
1247
1340
|
# Correction deferred to next 2-qubit gate (_cpauli calls _correct)
|
|
1248
1341
|
|
|
1249
1342
|
def s(self, lq):
|
|
@@ -1266,6 +1359,8 @@ class QrackAceBackend:
|
|
|
1266
1359
|
if lhv is not None:
|
|
1267
1360
|
lhv.s()
|
|
1268
1361
|
|
|
1362
|
+
self._rep_code_apply_gate(lq, "s")
|
|
1363
|
+
|
|
1269
1364
|
def adjs(self, lq):
|
|
1270
1365
|
hq = self._unpack(lq)
|
|
1271
1366
|
if len(hq) < 2:
|
|
@@ -1286,6 +1381,8 @@ class QrackAceBackend:
|
|
|
1286
1381
|
if lhv is not None:
|
|
1287
1382
|
lhv.adjs()
|
|
1288
1383
|
|
|
1384
|
+
self._rep_code_apply_gate(lq, "adjs")
|
|
1385
|
+
|
|
1289
1386
|
def sx(self, lq):
|
|
1290
1387
|
hq = self._unpack(lq)
|
|
1291
1388
|
if len(hq) < 2:
|
|
@@ -1306,6 +1403,8 @@ class QrackAceBackend:
|
|
|
1306
1403
|
if lhv is not None:
|
|
1307
1404
|
lhv.sx()
|
|
1308
1405
|
|
|
1406
|
+
self._rep_code_apply_gate(lq, "sx")
|
|
1407
|
+
|
|
1309
1408
|
def adjsx(self, lq):
|
|
1310
1409
|
hq = self._unpack(lq)
|
|
1311
1410
|
if len(hq) < 2:
|
|
@@ -1326,129 +1425,147 @@ class QrackAceBackend:
|
|
|
1326
1425
|
if lhv is not None:
|
|
1327
1426
|
lhv.adjsx()
|
|
1328
1427
|
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
feature is off
|
|
1337
|
-
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.
|
|
1338
1436
|
"""
|
|
1339
1437
|
if not self.is_boundary_repetition_code:
|
|
1340
1438
|
return []
|
|
1341
|
-
|
|
1342
|
-
if
|
|
1439
|
+
partners = self._rep_code_partners.get(lq)
|
|
1440
|
+
if not partners:
|
|
1343
1441
|
return []
|
|
1344
|
-
|
|
1442
|
+
hq = self._qubits[lq]
|
|
1443
|
+
pairs = []
|
|
1444
|
+
p_i = 0
|
|
1345
1445
|
for replica in hq:
|
|
1346
1446
|
if self._boundary_sim_id is not None and replica[0] == self._boundary_sim_id:
|
|
1347
1447
|
continue
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
return
|
|
1351
|
-
|
|
1352
|
-
def
|
|
1353
|
-
"""
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
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.
|
|
1359
1459
|
"""
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
self.
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
the
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
"""
|
|
1376
|
-
for _, anc1_lq, _ in triples:
|
|
1377
|
-
hq_a = self._qubits[anc1_lq]
|
|
1378
|
-
qb_a, _ = QrackAceBackend._get_qb_lhv_indices(hq_a)
|
|
1379
|
-
self._apply_coupling(pauli, anti, qb1, hq1, qb_a, hq_a, lq1_lr, lq1, anc1_lq)
|
|
1380
|
-
|
|
1381
|
-
def _rep_code_decode_and_correct(self, lq, triples, phase=False):
|
|
1382
|
-
"""Steps 3-5: bit-flip syndrome-check-and-correct (using
|
|
1383
|
-
ancilla2 per patch), THEN the final decode CNOT (lq's replica
|
|
1384
|
-
-> ancilla1 again), which -- once any disagreement has already
|
|
1385
|
-
been corrected -- returns ancilla1 exactly to |0>, ready for
|
|
1386
|
-
the next boundary qubit's coupling event to reuse. An explicit
|
|
1387
|
-
measure-and-reset backstops the decode CNOT for the rare
|
|
1388
|
-
(corner-only) multi-disagreement case left uncorrected below,
|
|
1389
|
-
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.
|
|
1390
1475
|
|
|
1391
1476
|
Same "try to force error-free, fall back to an unforced Born-
|
|
1392
1477
|
rule read, then it's obvious by elimination" decoding pattern
|
|
1393
|
-
used elsewhere in this file
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
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.
|
|
1404
1488
|
"""
|
|
1405
|
-
if not
|
|
1489
|
+
if not pairs:
|
|
1406
1490
|
return
|
|
1407
1491
|
|
|
1408
1492
|
syndrome = []
|
|
1409
|
-
for (sim_id, b_idx),
|
|
1493
|
+
for (sim_id, b_idx), p_lq, syn_lq in pairs:
|
|
1410
1494
|
sim = self.sim[sim_id]
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
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)
|
|
1419
1516
|
if bit:
|
|
1420
|
-
sim.x(
|
|
1517
|
+
sim.x(syn_idx) # reset the shared syndrome ancilla for reuse
|
|
1421
1518
|
|
|
1422
|
-
syndrome.append((bit, sim_id,
|
|
1519
|
+
syndrome.append((bit, sim_id, p_idx))
|
|
1423
1520
|
|
|
1424
1521
|
n = len(syndrome)
|
|
1425
1522
|
disagree = sum(1 for bit, _, _ in syndrome if bit)
|
|
1426
|
-
if disagree
|
|
1427
|
-
|
|
1428
|
-
|
|
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:
|
|
1429
1535
|
if phase:
|
|
1430
1536
|
self.z(lq)
|
|
1431
1537
|
else:
|
|
1432
1538
|
self.x(lq)
|
|
1433
|
-
|
|
1434
|
-
|
|
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:
|
|
1435
1544
|
if bit:
|
|
1436
1545
|
if phase:
|
|
1437
|
-
self.sim[sim_id].z(
|
|
1546
|
+
self.sim[sim_id].z(p_idx)
|
|
1438
1547
|
else:
|
|
1439
|
-
self.sim[sim_id].x(
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
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)
|
|
1452
1569
|
|
|
1453
1570
|
def x(self, lq):
|
|
1454
1571
|
hq = self._unpack(lq)
|
|
@@ -1470,6 +1587,8 @@ class QrackAceBackend:
|
|
|
1470
1587
|
if lhv is not None:
|
|
1471
1588
|
lhv.x()
|
|
1472
1589
|
|
|
1590
|
+
self._rep_code_apply_gate(lq, "x")
|
|
1591
|
+
|
|
1473
1592
|
def y(self, lq):
|
|
1474
1593
|
hq = self._unpack(lq)
|
|
1475
1594
|
if len(hq) < 2:
|
|
@@ -1490,6 +1609,8 @@ class QrackAceBackend:
|
|
|
1490
1609
|
if lhv is not None:
|
|
1491
1610
|
lhv.y()
|
|
1492
1611
|
|
|
1612
|
+
self._rep_code_apply_gate(lq, "y")
|
|
1613
|
+
|
|
1493
1614
|
def z(self, lq):
|
|
1494
1615
|
hq = self._unpack(lq)
|
|
1495
1616
|
if len(hq) < 2:
|
|
@@ -1510,6 +1631,8 @@ class QrackAceBackend:
|
|
|
1510
1631
|
if lhv is not None:
|
|
1511
1632
|
lhv.z()
|
|
1512
1633
|
|
|
1634
|
+
self._rep_code_apply_gate(lq, "z")
|
|
1635
|
+
|
|
1513
1636
|
def t(self, lq):
|
|
1514
1637
|
hq = self._unpack(lq)
|
|
1515
1638
|
if len(hq) < 2:
|
|
@@ -1530,6 +1653,8 @@ class QrackAceBackend:
|
|
|
1530
1653
|
if lhv is not None:
|
|
1531
1654
|
lhv.t()
|
|
1532
1655
|
|
|
1656
|
+
self._rep_code_apply_gate(lq, "t")
|
|
1657
|
+
|
|
1533
1658
|
def adjt(self, lq):
|
|
1534
1659
|
hq = self._unpack(lq)
|
|
1535
1660
|
if len(hq) < 2:
|
|
@@ -1550,6 +1675,8 @@ class QrackAceBackend:
|
|
|
1550
1675
|
if lhv is not None:
|
|
1551
1676
|
lhv.adjt()
|
|
1552
1677
|
|
|
1678
|
+
self._rep_code_apply_gate(lq, "adjt")
|
|
1679
|
+
|
|
1553
1680
|
def _get_gate(self, pauli, anti, sim_id):
|
|
1554
1681
|
gate = None
|
|
1555
1682
|
shadow = None
|
|
@@ -1790,40 +1917,34 @@ class QrackAceBackend:
|
|
|
1790
1917
|
self._correct(lq1)
|
|
1791
1918
|
self._correct(lq2)
|
|
1792
1919
|
|
|
1793
|
-
# Boundary repetition code,
|
|
1794
|
-
#
|
|
1795
|
-
#
|
|
1796
|
-
#
|
|
1797
|
-
#
|
|
1798
|
-
#
|
|
1799
|
-
#
|
|
1800
|
-
#
|
|
1801
|
-
#
|
|
1802
|
-
#
|
|
1803
|
-
# no-op anyway), but the CONTROL side could still be a protected
|
|
1804
|
-
# boundary qubit, and re-running its own encode/check/decode
|
|
1805
|
-
# cycle on every nested capture would be wasteful and redundant
|
|
1806
|
-
# 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.
|
|
1807
1930
|
#
|
|
1808
|
-
#
|
|
1809
|
-
#
|
|
1810
|
-
#
|
|
1811
|
-
#
|
|
1812
|
-
#
|
|
1813
|
-
#
|
|
1814
|
-
#
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
t2 = self._rep_code_encode(lq2)
|
|
1826
|
-
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:
|
|
1827
1948
|
# Only defined for "patch-local" connectivity, the only
|
|
1828
1949
|
# officially supported connectivity model here (get_
|
|
1829
1950
|
# logical_coupling_map() only ever returns pairs that
|
|
@@ -1831,7 +1952,7 @@ class QrackAceBackend:
|
|
|
1831
1952
|
# share NO simulator at all) only exists as a fallback
|
|
1832
1953
|
# for users who ignore that map, and this repetition-
|
|
1833
1954
|
# code layer doesn't try to support it: raise plainly
|
|
1834
|
-
# instead of silently coupling
|
|
1955
|
+
# instead of silently coupling partners through an
|
|
1835
1956
|
# undefined/unsupported path.
|
|
1836
1957
|
sims1 = {b[0] for b in hq1}
|
|
1837
1958
|
sims2 = {b[0] for b in hq2}
|
|
@@ -1849,8 +1970,8 @@ class QrackAceBackend:
|
|
|
1849
1970
|
# qubits, which now live as real qubits in the shared boundary sim.
|
|
1850
1971
|
self._apply_coupling(pauli, anti, qb1, hq1, qb2, hq2, lq1_lr, lq1, lq2)
|
|
1851
1972
|
|
|
1852
|
-
if
|
|
1853
|
-
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)
|
|
1854
1975
|
|
|
1855
1976
|
if lq2 in self._lhv:
|
|
1856
1977
|
ctrl_prob = self.sim[hq1[0][0]].prob(hq1[0][1])
|
|
@@ -1859,21 +1980,16 @@ class QrackAceBackend:
|
|
|
1859
1980
|
if self._in_gadget_capture:
|
|
1860
1981
|
return
|
|
1861
1982
|
|
|
1862
|
-
# Repetition-code
|
|
1983
|
+
# Repetition-code syndrome-check for lq2 happens HERE,
|
|
1863
1984
|
# immediately after the coupler+extension, in the SAME frame
|
|
1864
1985
|
# they just ran in -- deliberately BEFORE _correct()'s own,
|
|
1865
1986
|
# separate soft reconciliation below, which can itself rotate
|
|
1866
|
-
# lq2 (the phase=True call) and would
|
|
1867
|
-
#
|
|
1868
|
-
#
|
|
1869
|
-
#
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
# never spanning an external rotation the way permanently-
|
|
1873
|
-
# synced partners, checked from a separate top-level call, used
|
|
1874
|
-
# to.
|
|
1875
|
-
if self.is_boundary_repetition_code:
|
|
1876
|
-
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)
|
|
1877
1993
|
|
|
1878
1994
|
# No post-coupling correction on lq1 (the control): _cz_shadow only
|
|
1879
1995
|
# ever calls .z() on the target, never gates the control -- only
|
|
@@ -1894,16 +2010,74 @@ class QrackAceBackend:
|
|
|
1894
2010
|
def cy(self, lq1, lq2):
|
|
1895
2011
|
if self.is_error_detection and ((len(self._unpack(lq1)) > 1) or (len(self._unpack(lq2)) > 1)):
|
|
1896
2012
|
self.adjs(lq2)
|
|
1897
|
-
self.
|
|
2013
|
+
self._rep_code_suppress_check = True
|
|
2014
|
+
try:
|
|
2015
|
+
self.cx(lq1, lq2)
|
|
2016
|
+
finally:
|
|
2017
|
+
self._rep_code_suppress_check = False
|
|
1898
2018
|
self.s(lq2)
|
|
2019
|
+
self._rep_code_check_pair(lq1, lq2)
|
|
1899
2020
|
else:
|
|
1900
2021
|
self._cpauli(lq1, lq2, False, Pauli.PauliY)
|
|
1901
2022
|
|
|
1902
2023
|
def cz(self, lq1, lq2):
|
|
1903
2024
|
if self.is_error_detection and ((len(self._unpack(lq1)) > 1) or (len(self._unpack(lq2)) > 1)):
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
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)
|
|
1907
2081
|
else:
|
|
1908
2082
|
self._cpauli(lq1, lq2, False, Pauli.PauliZ)
|
|
1909
2083
|
|
|
@@ -1913,16 +2087,26 @@ class QrackAceBackend:
|
|
|
1913
2087
|
def acy(self, lq1, lq2):
|
|
1914
2088
|
if self.is_error_detection and ((len(self._unpack(lq1)) > 1) or (len(self._unpack(lq2)) > 1)):
|
|
1915
2089
|
self.adjs(lq2)
|
|
1916
|
-
self.
|
|
2090
|
+
self._rep_code_suppress_check = True
|
|
2091
|
+
try:
|
|
2092
|
+
self.acx(lq1, lq2)
|
|
2093
|
+
finally:
|
|
2094
|
+
self._rep_code_suppress_check = False
|
|
1917
2095
|
self.s(lq2)
|
|
2096
|
+
self._rep_code_check_pair(lq1, lq2)
|
|
1918
2097
|
else:
|
|
1919
2098
|
self._cpauli(lq1, lq2, True, Pauli.PauliY)
|
|
1920
2099
|
|
|
1921
2100
|
def acz(self, lq1, lq2):
|
|
1922
2101
|
if self.is_error_detection and ((len(self._unpack(lq1)) > 1) or (len(self._unpack(lq2)) > 1)):
|
|
1923
2102
|
self.h(lq2)
|
|
1924
|
-
self.
|
|
2103
|
+
self._rep_code_suppress_check = True
|
|
2104
|
+
try:
|
|
2105
|
+
self.acx(lq1, lq2)
|
|
2106
|
+
finally:
|
|
2107
|
+
self._rep_code_suppress_check = False
|
|
1925
2108
|
self.h(lq2)
|
|
2109
|
+
self._rep_code_check_pair(lq1, lq2)
|
|
1926
2110
|
else:
|
|
1927
2111
|
self._cpauli(lq1, lq2, True, Pauli.PauliZ)
|
|
1928
2112
|
|
|
@@ -2021,13 +2205,22 @@ class QrackAceBackend:
|
|
|
2021
2205
|
if lhv1 is not None:
|
|
2022
2206
|
self._lhv[lq2] = lhv1
|
|
2023
2207
|
|
|
2024
|
-
# Boundary repetition code
|
|
2025
|
-
#
|
|
2026
|
-
#
|
|
2027
|
-
#
|
|
2028
|
-
#
|
|
2029
|
-
#
|
|
2030
|
-
#
|
|
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
|
|
2031
2224
|
return
|
|
2032
2225
|
|
|
2033
2226
|
# Boundary repetition code: redirect entirely to the 3-CNOT
|
|
@@ -2035,12 +2228,12 @@ class QrackAceBackend:
|
|
|
2035
2228
|
# side is multi-replica (repetition-code eligible), rather than
|
|
2036
2229
|
# extending each of the specialized fast/partial-match paths
|
|
2037
2230
|
# below individually with new, separately-unverified encode/
|
|
2038
|
-
# decode logic. self.cx() already handles
|
|
2039
|
-
#
|
|
2040
|
-
#
|
|
2041
|
-
#
|
|
2042
|
-
#
|
|
2043
|
-
#
|
|
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
|
|
2044
2237
|
# the fast/exact-match path additionally requires lq1 and lq2 to
|
|
2045
2238
|
# be TWO boundary qubits with identically-matching patch
|
|
2046
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.3 → 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.3 → 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.3 → pyqrack_cpu_complex128-2.20.0}/pyqrack/qrack_system/__init__.py
RENAMED
|
File without changes
|
{pyqrack_cpu_complex128-2.19.3 → pyqrack_cpu_complex128-2.20.0}/pyqrack/qrack_system/qrack_system.py
RENAMED
|
File without changes
|
{pyqrack_cpu_complex128-2.19.3 → pyqrack_cpu_complex128-2.20.0}/pyqrack/quimb_circuit_type.py
RENAMED
|
File without changes
|
|
File without changes
|
{pyqrack_cpu_complex128-2.19.3 → pyqrack_cpu_complex128-2.20.0}/pyqrack/stats/load_quantized_data.py
RENAMED
|
File without changes
|
{pyqrack_cpu_complex128-2.19.3 → 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
|