pyqrack-cpu-complex128 2.25.4__tar.gz → 2.26.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.
Files changed (30) hide show
  1. {pyqrack_cpu_complex128-2.25.4/pyqrack_cpu_complex128.egg-info → pyqrack_cpu_complex128-2.26.0}/PKG-INFO +1 -1
  2. {pyqrack_cpu_complex128-2.25.4 → pyqrack_cpu_complex128-2.26.0}/pyqrack/qrack_ace_backend.py +214 -215
  3. {pyqrack_cpu_complex128-2.25.4 → pyqrack_cpu_complex128-2.26.0}/pyqrack/qrack_simulator.py +16 -0
  4. {pyqrack_cpu_complex128-2.25.4 → pyqrack_cpu_complex128-2.26.0/pyqrack_cpu_complex128.egg-info}/PKG-INFO +1 -1
  5. {pyqrack_cpu_complex128-2.25.4 → pyqrack_cpu_complex128-2.26.0}/setup.py +1 -1
  6. {pyqrack_cpu_complex128-2.25.4 → pyqrack_cpu_complex128-2.26.0}/LICENSE +0 -0
  7. {pyqrack_cpu_complex128-2.25.4 → pyqrack_cpu_complex128-2.26.0}/MANIFEST.in +0 -0
  8. {pyqrack_cpu_complex128-2.25.4 → pyqrack_cpu_complex128-2.26.0}/Makefile +0 -0
  9. {pyqrack_cpu_complex128-2.25.4 → pyqrack_cpu_complex128-2.26.0}/README.md +0 -0
  10. {pyqrack_cpu_complex128-2.25.4 → pyqrack_cpu_complex128-2.26.0}/pyproject.toml +0 -0
  11. {pyqrack_cpu_complex128-2.25.4 → pyqrack_cpu_complex128-2.26.0}/pyqrack/__init__.py +0 -0
  12. {pyqrack_cpu_complex128-2.25.4 → pyqrack_cpu_complex128-2.26.0}/pyqrack/neuron_activation_fn.py +0 -0
  13. {pyqrack_cpu_complex128-2.25.4 → pyqrack_cpu_complex128-2.26.0}/pyqrack/pauli.py +0 -0
  14. {pyqrack_cpu_complex128-2.25.4 → pyqrack_cpu_complex128-2.26.0}/pyqrack/qrack_circuit.py +0 -0
  15. {pyqrack_cpu_complex128-2.25.4 → pyqrack_cpu_complex128-2.26.0}/pyqrack/qrack_near_clifford_qec_backend.py +0 -0
  16. {pyqrack_cpu_complex128-2.25.4 → pyqrack_cpu_complex128-2.26.0}/pyqrack/qrack_neuron.py +0 -0
  17. {pyqrack_cpu_complex128-2.25.4 → pyqrack_cpu_complex128-2.26.0}/pyqrack/qrack_neuron_torch_layer.py +0 -0
  18. {pyqrack_cpu_complex128-2.25.4 → pyqrack_cpu_complex128-2.26.0}/pyqrack/qrack_stabilizer.py +0 -0
  19. {pyqrack_cpu_complex128-2.25.4 → pyqrack_cpu_complex128-2.26.0}/pyqrack/qrack_system/__init__.py +0 -0
  20. {pyqrack_cpu_complex128-2.25.4 → pyqrack_cpu_complex128-2.26.0}/pyqrack/qrack_system/qrack_system.py +0 -0
  21. {pyqrack_cpu_complex128-2.25.4 → pyqrack_cpu_complex128-2.26.0}/pyqrack/quimb_circuit_type.py +0 -0
  22. {pyqrack_cpu_complex128-2.25.4 → pyqrack_cpu_complex128-2.26.0}/pyqrack/stats/__init__.py +0 -0
  23. {pyqrack_cpu_complex128-2.25.4 → pyqrack_cpu_complex128-2.26.0}/pyqrack/stats/load_quantized_data.py +0 -0
  24. {pyqrack_cpu_complex128-2.25.4 → pyqrack_cpu_complex128-2.26.0}/pyqrack/stats/quantize_by_range.py +0 -0
  25. {pyqrack_cpu_complex128-2.25.4 → pyqrack_cpu_complex128-2.26.0}/pyqrack_cpu_complex128.egg-info/SOURCES.txt +0 -0
  26. {pyqrack_cpu_complex128-2.25.4 → pyqrack_cpu_complex128-2.26.0}/pyqrack_cpu_complex128.egg-info/dependency_links.txt +0 -0
  27. {pyqrack_cpu_complex128-2.25.4 → pyqrack_cpu_complex128-2.26.0}/pyqrack_cpu_complex128.egg-info/not-zip-safe +0 -0
  28. {pyqrack_cpu_complex128-2.25.4 → pyqrack_cpu_complex128-2.26.0}/pyqrack_cpu_complex128.egg-info/requires.txt +0 -0
  29. {pyqrack_cpu_complex128-2.25.4 → pyqrack_cpu_complex128-2.26.0}/pyqrack_cpu_complex128.egg-info/top_level.txt +0 -0
  30. {pyqrack_cpu_complex128-2.25.4 → pyqrack_cpu_complex128-2.26.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyqrack-cpu-complex128
3
- Version: 2.25.4
3
+ Version: 2.26.0
4
4
  Summary: pyqrack - Pure Python vm6502q/qrack Wrapper
5
5
  Home-page: https://github.com/vm6502q/pyqrack
6
6
  Author: Daniel Strano
@@ -334,18 +334,19 @@ class QrackAceBackend:
334
334
  self._is_row_long_range[-1] = False
335
335
  sim_count = col_patch_count * row_patch_count
336
336
 
337
- # Boundary qubits no longer carry a private classical LHV proxy.
338
- # Instead, every boundary site (row- and/or column-boundary alike)
339
- # gets a real qubit in one single, shared "crossbar" QrackSimulator.
340
- # That simulator's own greedy elision (set_sdrp) is trusted to
341
- # automatically factor apart whatever boundary sites turn out to be
342
- # separable (e.g. disjoint rails, rail intersections), exactly the
343
- # same way it already factors apart unentangled subspaces within
344
- # any other single QrackSimulator instance. We don't need to special
345
- # -case "crossbar intersections" by hand; the elision does it for us.
346
- boundary_sim_id = sim_count
347
- boundary_count = 0
348
-
337
+ # The shared boundary "crossbar" QrackSimulator has been removed.
338
+ # It used to give every boundary site (row- and/or column-boundary
339
+ # alike) one extra real replica in a single simulator shared by ALL
340
+ # boundary qubits, trusting that simulator's own greedy elision
341
+ # (set_sdrp) to automatically factor apart whatever boundary sites
342
+ # turned out to be separable, rather than hand-coding "crossbar
343
+ # intersection" logic. Measurement showed it adds real, and
344
+ # growing-with-patch-count, connectivity value -- but per explicit
345
+ # instruction, it is being ripped out cleanly regardless, to get an
346
+ # empirical read on XEB without it. Every boundary qubit already
347
+ # carries a classical LHVQubit proxy (self._lhv, below) in
348
+ # parallel; _correct() now falls back to that proxy for tie-
349
+ # breaking wherever the crossbar replica used to be consulted.
349
350
  self._qubits = []
350
351
  self._lhv = {}
351
352
  sim_counts = [0] * sim_count
@@ -361,9 +362,6 @@ class QrackAceBackend:
361
362
  qubit.append((t_sim_id, sim_counts[t_sim_id]))
362
363
  sim_counts[t_sim_id] += 1
363
364
 
364
- qubit.append((boundary_sim_id, boundary_count))
365
- boundary_count += 1
366
-
367
365
  if (not to_clone) or (tot_qubits in to_clone._lhv):
368
366
  self._lhv[tot_qubits] = LHVQubit(
369
367
  to_clone=(to_clone._lhv[tot_qubits] if to_clone else None)
@@ -386,22 +384,10 @@ class QrackAceBackend:
386
384
  )
387
385
  tot_qubits += 1
388
386
 
389
- # The crossbar's size is fixed by how many boundary sites exist.
390
- # When there are none (e.g. a grid small enough, relative to
391
- # long_range_rows/columns, that the whole thing is "fully
392
- # connected" with no QEC boundary at all), we must NOT allocate a
393
- # 0-qubit QrackSimulator for the crossbar. The boundary sim is only
394
- # created when boundary_count > 0, exactly mirroring how the original
395
- # LHV-based code never instantiated anything for the boundary case
396
- # when there were no boundary sites.
397
- has_boundary = boundary_count > 0
398
- if has_boundary:
399
- sim_counts.append(boundary_count)
400
-
401
387
  # Error-detection gadget (IBM-style detect-and-post-select, not
402
- # correction): two shared ancillae per simulator -- every patch AND
403
- # the boundary crossbar sim alike, "a single ancilla per patch,
404
- # overall" -- reused across every real (same-simulator) coupling
388
+ # correction): two shared ancillae per simulator -- one per patch,
389
+ # "a single ancilla per patch, overall" -- reused across every real
390
+ # (same-simulator) coupling
405
391
  # gate that touches it, never allocated per-gate or per-qubit.
406
392
  # See _apply_coupling for the actual gadget; this block only does
407
393
  # the qubit-index bookkeeping, in the same style as every other
@@ -483,7 +469,7 @@ class QrackAceBackend:
483
469
  self._sdrp = 0.0
484
470
 
485
471
  self.sim = []
486
- for i in range(sim_count + (1 if has_boundary else 0)):
472
+ for i in range(sim_count):
487
473
  self.sim.append(
488
474
  to_clone.sim[i].clone()
489
475
  if to_clone
@@ -499,8 +485,6 @@ class QrackAceBackend:
499
485
  )
500
486
  )
501
487
 
502
- self._boundary_sim_id = boundary_sim_id if has_boundary else None
503
-
504
488
  def clone(self):
505
489
  return QrackAceBackend(to_clone=self)
506
490
 
@@ -753,19 +737,17 @@ class QrackAceBackend:
753
737
 
754
738
  @staticmethod
755
739
  def _get_qb_lhv_indices(hq):
756
- # Historically, index 2 (when present) pointed at a private
757
- # classical LHVQubit proxy and had to be special-cased everywhere.
758
- # It is now an ordinary (sim_id, idx) tuple into the shared
759
- # boundary "crossbar" QrackSimulator, so it is just one more
760
- # coupling target like every other index. We keep this helper's
761
- # name and signature for minimal call-site churn; "lhv" is now
762
- # always -1 (no index needs special-casing any more).
763
- if len(hq) < 2:
764
- qb = [0]
765
- elif len(hq) < 4:
766
- qb = [0, 1, 2]
767
- else:
768
- qb = [0, 1, 2, 3, 4]
740
+ # With the shared boundary "crossbar" QrackSimulator removed,
741
+ # every entry of hq is again an ordinary (sim_id, idx) tuple into a
742
+ # real physical replica: 1 for a bulk qubit, 2 for an edge-boundary
743
+ # qubit (home + adjacent-patch), or 4 for a corner-boundary qubit
744
+ # (home + adjacent-patch + 2 diagonal-patch). None of them are
745
+ # virtual/LHV-backed slots any more -- the classical LHVQubit
746
+ # proxy (self._lhv) is tracked entirely separately from hq, not as
747
+ # one of its indices -- so qb is simply every index, in order. We
748
+ # keep this helper's name and signature for minimal call-site
749
+ # churn; "lhv" is always -1 (no index needs special-casing).
750
+ qb = list(range(len(hq)))
769
751
  lhv = -1
770
752
 
771
753
  return qb, lhv
@@ -857,26 +839,22 @@ class QrackAceBackend:
857
839
  if lq in self._lhv:
858
840
  self._lhv[lq].h()
859
841
 
860
- if len(hq) == 5:
842
+ if len(hq) == 4:
843
+ # Corner-boundary qubit: 4 real replicas remain (home,
844
+ # adjacent-patch, and the 2 diagonal-patch shadows) now that
845
+ # the crossbar replica that used to sit at old-index-2 is
846
+ # gone. These are exactly the old "end-cap" set
847
+ # [p0, p1, p3, p4] from before removal -- vote via their own
848
+ # RMS pool first, and consult the LHV proxy only as the
849
+ # last-resort tie-breaker on a genuine 2-2 split (the role the
850
+ # crossbar replica used to fill).
861
851
  p0 = self.sim[hq[0][0]].prob(hq[0][1])
862
852
  p1 = self.sim[hq[1][0]].prob(hq[1][1])
863
853
  p2 = self.sim[hq[2][0]].prob(hq[2][1])
864
854
  p3 = self.sim[hq[3][0]].prob(hq[3][1])
865
- p4 = self.sim[hq[4][0]].prob(hq[4][1])
866
855
  lhv = self._lhv.get(lq)
867
856
 
868
- # The 4 "end-cap" replicas (home patch + the 3 patch-partner
869
- # shadow replicas), by analogy with the 3-replica case's
870
- # slot1/slot2: vote first via their own RMS pool, UNLESS they
871
- # are in a genuine 2-vs-2 tie, in which case the crossbar
872
- # replica (hq[2], already weighted specially in the prior
873
- # flat-pool code) breaks the tie; the LHV is consulted only
874
- # as a last-resort fallback if the crossbar itself is
875
- # ambiguous. This is a direct structural analogy to the
876
- # validated 3-replica cascade, not independently re-derived
877
- # for this topology -- carried only as far as that cheap
878
- # analogy supports, per explicit guidance.
879
- end_caps = [p0, p1, p3, p4]
857
+ end_caps = [p0, p1, p2, p3]
880
858
  # Classify each end-cap as decisively high / decisively low /
881
859
  # undecided (within epsilon of 0.5), rather than a bare >=0.5
882
860
  # check -- a replica reading EXACTLY 0.5 was previously always
@@ -884,9 +862,9 @@ class QrackAceBackend:
884
862
  high_count = sum(1 for x in end_caps if x > (0.5 + self._epsilon))
885
863
  low_count = sum(1 for x in end_caps if x < (0.5 - self._epsilon))
886
864
  undecided_count = len(end_caps) - high_count - low_count
887
- # Ambiguous (defer to the crossbar/LHV tie-breaker) on a
888
- # genuine 2-2 split, OR if any end-cap is undecided -- an
889
- # undecided value can't safely be counted toward either side.
865
+ # Ambiguous (defer to the LHV tie-breaker) on a genuine 2-2
866
+ # split, OR if any end-cap is undecided -- an undecided value
867
+ # can't safely be counted toward either side.
890
868
  end_caps_tied = (high_count == 2 and low_count == 2) or (undecided_count > 0)
891
869
 
892
870
  if not end_caps_tied:
@@ -898,8 +876,6 @@ class QrackAceBackend:
898
876
  if abs(eff_prob - 0.5) <= self._epsilon
899
877
  else (eff_prob >= 0.5)
900
878
  )
901
- elif abs(p2 - 0.5) > self._epsilon:
902
- result = p2 >= 0.5
903
879
  elif lhv is not None:
904
880
  p_lhv = lhv.prob()
905
881
  result = (
@@ -910,42 +886,41 @@ class QrackAceBackend:
910
886
  else:
911
887
  result = random.random() < 0.5
912
888
 
913
- p = [p0, p1, p2, p3, p4]
889
+ p = [p0, p1, p2, p3]
914
890
  syndrome = [1 - x for x in p] if result else list(p)
915
- for q in range(5):
891
+ for q in range(4):
916
892
  if syndrome[q] > (0.5 + self._epsilon):
917
893
  self.sim[hq[q][0]].x(hq[q][1])
918
894
 
919
895
  if not skip_rotation:
920
- a, i, w = [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]
896
+ a, i, w = [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]
921
897
  a[0], i[0], r0 = self._get_bloch_angles(hq[0])
922
898
  a[1], i[1], r1 = self._get_bloch_angles(hq[1])
923
899
  a[2], i[2], r2 = self._get_bloch_angles(hq[2])
924
900
  a[3], i[3], r3 = self._get_bloch_angles(hq[3])
925
- a[4], i[4], r4 = self._get_bloch_angles(hq[4])
926
- w = [1 - r0, 1 - r1, 1 - r2, 1 - r3, 1 - r4]
901
+ w = [1 - r0, 1 - r1, 1 - r2, 1 - r3]
927
902
 
928
903
  w_total = sum(w)
929
904
  if w_total > self._epsilon:
930
905
  a_target = sum(wx * ax for wx, ax in zip(w, a)) / w_total
931
906
  i_target = sum(wx * ix for wx, ix in zip(w, i)) / w_total
932
907
  # hq[0] is excluded from the rotation itself, by the
933
- # same reasoning as the 3-replica case above: it's
908
+ # same reasoning as the 2-replica case below: it's
934
909
  # allocated identically (unconditionally, first, in
935
910
  # the qubit's home-patch simulator) before any
936
- # boundary/crossbar extension logic runs, so it's the
937
- # replica most likely to carry real, same-simulator
938
- # coherent entanglement worth protecting from the
939
- # decoherence a physical rotation would cost it.
940
- # Indices 1-4 are all crossbar-extension slots with
941
- # no comparable real entanglement to lose.
911
+ # boundary-extension logic runs, so it's the replica
912
+ # most likely to carry real, same-simulator coherent
913
+ # entanglement worth protecting from the decoherence a
914
+ # physical rotation would cost it. Indices 1-3 are all
915
+ # boundary-extension slots with no comparable real
916
+ # entanglement to lose.
942
917
  # Only rotate a replica that is itself reasonably
943
918
  # separable (its own reduced state still has a
944
919
  # meaningfully-defined Bloch direction). A replica
945
920
  # that's picked up genuine entanglement with another
946
921
  # logical qubit's replica (e.g. via a real gate_fn
947
922
  # coupling in _apply_coupling -- not just slot0; any
948
- # of slots 1-4 can end up real-coupled depending on
923
+ # of slots 1-3 can end up real-coupled depending on
949
924
  # geometry) reads a Bloch vector dominated by
950
925
  # simulator floating-point noise, not real
951
926
  # information. Rotating it "toward consensus" in that
@@ -970,8 +945,8 @@ class QrackAceBackend:
970
945
  # replica is "more entangled than separable" and is
971
946
  # left alone; short of it, the rotation still does
972
947
  # real, useful reconciliation work.
973
- for x in range(1, 5):
974
- if [r0, r1, r2, r3, r4][x] <= self._rot_epsilon:
948
+ for x in range(1, 4):
949
+ if [r0, r1, r2, r3][x] <= self._rot_epsilon:
975
950
  self._rotate_to_bloch(hq[x], a_target - a[x], i_target - i[x])
976
951
  # If every replica reads as maximally mixed (w_total ~ 0),
977
952
  # there is no well-defined direction to rotate toward at
@@ -979,62 +954,61 @@ class QrackAceBackend:
979
954
  # arbitrary/undefined target derived from noise.
980
955
 
981
956
  else:
957
+ # Edge-boundary qubit: only 2 real replicas remain (home +
958
+ # adjacent-patch) now that the crossbar replica that used to
959
+ # sit at old-index-2 is gone. The classical LHV proxy takes
960
+ # over the crossbar's old role as "slot2" in the conditional
961
+ # tie-breaking cascade below.
982
962
  lhv = self._lhv.get(lq)
963
+ p0 = self.sim[hq[0][0]].prob(hq[0][1])
964
+ p1 = self.sim[hq[1][0]].prob(hq[1][1])
965
+
983
966
  if lhv is None:
984
- # RMS
985
- p = [
986
- self.sim[hq[0][0]].prob(hq[0][1]),
987
- self.sim[hq[1][0]].prob(hq[1][1]),
988
- self.sim[hq[2][0]].prob(hq[2][1]),
989
- ]
990
- # Balancing suggestion from Elara (the custom OpenAI GPT)
991
- prms = math.sqrt((p[0] ** 2 + p[1] ** 2 + p[2] ** 2) / 3)
992
- qrms = math.sqrt(((1 - p[0]) ** 2 + (1 - p[1]) ** 2 + (1 - p[2]) ** 2) / 3)
967
+ # No LHV proxy exists (shouldn't normally happen for a
968
+ # boundary qubit, since the constructor always creates
969
+ # one -- handled defensively anyway): fall back to a
970
+ # plain 2-way RMS pool over the two real replicas.
971
+ prms = math.sqrt((p0**2 + p1**2) / 2)
972
+ qrms = math.sqrt(((1 - p0) ** 2 + (1 - p1) ** 2) / 2)
993
973
  eff_prob = (prms + (1 - qrms)) / 2
994
974
  result = (
995
975
  (random.random() < 0.5)
996
976
  if abs(eff_prob - 0.5) <= self._epsilon
997
977
  else (eff_prob >= 0.5)
998
978
  )
999
- syndrome = [1 - p[0], 1 - p[1], 1 - p[2]] if result else [p[0], p[1], p[2]]
1000
- for q in range(3):
979
+ syndrome = [1 - p0, 1 - p1] if result else [p0, p1]
980
+ for q in range(2):
1001
981
  if syndrome[q] > (0.5 + self._epsilon):
1002
982
  self.sim[hq[q][0]].x(hq[q][1])
1003
983
  # end_caps_agree is referenced unconditionally below (to
1004
984
  # decide whether the rotation step is worth doing at all);
1005
985
  # this branch has no separate "end-caps vs slot0" concept
1006
986
  # the way the has-LHV branch does, so the analogous check
1007
- # is simply whether all 3 replicas already decisively
987
+ # is simply whether both replicas already decisively
1008
988
  # agree with each other.
1009
- all_high = all(x > (0.5 + self._epsilon) for x in p)
1010
- all_low = all(x < (0.5 - self._epsilon) for x in p)
989
+ all_high = p0 > (0.5 + self._epsilon) and p1 > (0.5 + self._epsilon)
990
+ all_low = p0 < (0.5 - self._epsilon) and p1 < (0.5 - self._epsilon)
1011
991
  end_caps_agree = all_high or all_low
1012
992
  else:
1013
- # Conditional tie-breaking cascade, NOT a fixed-weight pool.
1014
- # A fixed weight on slot0/lhv pulls every decision toward
1015
- # 0.5 even when slot1 and slot2 already agree confidently,
1016
- # because slot0 and lhv are legitimately, permanently near
1017
- # 0.5 for genuinely-entangled topologies (e.g. when slot0
1018
- # shares a simulator with the control) -- a "vote" stuck at
1019
- # 0.5 is not neutral in an RMS pool, it actively drags the
1020
- # result toward the center. The actual intent ("LHV serves
1021
- # only to act as a tie-breaker") is a conditional, not a
1022
- # weight: trust slot1/slot2 alone whenever they agree, and
1023
- # only consult slot0, then lhv, when they genuinely don't.
1024
- #
1025
- p0 = self.sim[hq[0][0]].prob(hq[0][1])
1026
- p1 = self.sim[hq[1][0]].prob(hq[1][1])
1027
- p2 = self.sim[hq[2][0]].prob(hq[2][1])
993
+ # Conditional tie-breaking cascade, NOT a fixed-weight pool
994
+ # -- same structure as before the crossbar was removed,
995
+ # with the (now-removed) crossbar replica's old role as
996
+ # "slot2" taken over by the classical LHV proxy: trust
997
+ # slot1 (hq[1], the real adjacent-patch replica) and the
998
+ # LHV together whenever they agree; fall back to slot0
999
+ # (hq[0], home) when they disagree and it has a decisive
1000
+ # opinion; consult the LHV alone, as an absolute last
1001
+ # resort, only when slot0 is also ambiguous.
1028
1002
  p_lhv = lhv.prob()
1029
1003
 
1030
- # Same fix as the 5-replica branch above: classify p1/p2 as
1031
- # decisively high / low / undecided rather than a bare
1004
+ # Same fix as the 4-replica branch above: classify p1/lhv
1005
+ # as decisively high / low / undecided rather than a bare
1032
1006
  # >=0.5 check.
1033
1007
  p1_high = p1 > (0.5 + self._epsilon)
1034
1008
  p1_low = p1 < (0.5 - self._epsilon)
1035
- p2_high = p2 > (0.5 + self._epsilon)
1036
- p2_low = p2 < (0.5 - self._epsilon)
1037
- end_caps_agree = (p1_high and p2_high) or (p1_low and p2_low)
1009
+ lhv_high = p_lhv > (0.5 + self._epsilon)
1010
+ lhv_low = p_lhv < (0.5 - self._epsilon)
1011
+ end_caps_agree = (p1_high and lhv_high) or (p1_low and lhv_low)
1038
1012
  slot0_disagrees_with_end_caps = (
1039
1013
  end_caps_agree
1040
1014
  and (abs(p0 - 0.5) > self._epsilon)
@@ -1055,8 +1029,8 @@ class QrackAceBackend:
1055
1029
  # itself default below.
1056
1030
  result = p0 >= 0.5
1057
1031
  elif end_caps_agree:
1058
- prms = math.sqrt((p1**2 + p2**2) / 2)
1059
- qrms = math.sqrt(((1 - p1) ** 2 + (1 - p2) ** 2) / 2)
1032
+ prms = math.sqrt((p1**2 + p_lhv**2) / 2)
1033
+ qrms = math.sqrt(((1 - p1) ** 2 + (1 - p_lhv) ** 2) / 2)
1060
1034
  eff_prob = (prms + (1 - qrms)) / 2
1061
1035
  result = (
1062
1036
  (random.random() < 0.5)
@@ -1064,9 +1038,9 @@ class QrackAceBackend:
1064
1038
  else (eff_prob >= 0.5)
1065
1039
  )
1066
1040
  elif abs(p0 - 0.5) > self._epsilon:
1067
- # Genuine deadlock between slot1/slot2; slot0 (a real,
1068
- # exactly-entangled qubit in this common topology) has
1069
- # a real opinion, so it breaks the tie.
1041
+ # Genuine deadlock between slot1 and the LHV; slot0 (a
1042
+ # real, exactly-entangled qubit in this common
1043
+ # topology) has a real opinion, so it breaks the tie.
1070
1044
  result = p0 >= 0.5
1071
1045
  else:
1072
1046
  # slot0 is itself ambiguous; only now does the LHV's
@@ -1077,9 +1051,9 @@ class QrackAceBackend:
1077
1051
  else (p_lhv >= 0.5)
1078
1052
  )
1079
1053
 
1080
- p = [p0, p1, p2]
1054
+ p = [p0, p1]
1081
1055
  syndrome = [1 - x for x in p] if result else list(p)
1082
- for q in range(3):
1056
+ for q in range(2):
1083
1057
  if syndrome[q] > (0.5 + self._epsilon):
1084
1058
  self.sim[hq[q][0]].x(hq[q][1])
1085
1059
  # The LHV proxy is never hard-collapsed via x(); it is only
@@ -1088,11 +1062,10 @@ class QrackAceBackend:
1088
1062
  # as a non-collapsing tie-breaker of last resort.
1089
1063
 
1090
1064
  if (not skip_rotation) and (not end_caps_agree):
1091
- a, i, w = [0, 0, 0], [0, 0, 0], [0, 0, 0]
1065
+ a, i, w = [0, 0], [0, 0], [0, 0]
1092
1066
  a[0], i[0], r0 = self._get_bloch_angles(hq[0])
1093
1067
  a[1], i[1], r1 = self._get_bloch_angles(hq[1])
1094
- a[2], i[2], r2 = self._get_bloch_angles(hq[2])
1095
- w = [1 - r0, 1 - r1, 1 - r2]
1068
+ w = [1 - r0, 1 - r1]
1096
1069
 
1097
1070
  w_total = sum(w)
1098
1071
  if w_total > self._epsilon:
@@ -1106,14 +1079,14 @@ class QrackAceBackend:
1106
1079
  # well-intentioned averaged target -- is a real
1107
1080
  # decoherence event on that relationship. slot0's
1108
1081
  # Bloch angles still inform the target above (so
1109
- # slot1/slot2 reconcile toward a value that accounts
1110
- # for what slot0 currently shows), but only slot1 and
1111
- # slot2 (the lossy shadow replicas, which have no
1112
- # comparable real entanglement to lose) are actually
1113
- # rotated.
1114
- for x in range(1, 3):
1115
- if [r0, r1, r2][x] <= self._rot_epsilon:
1116
- self._rotate_to_bloch(hq[x], a_target - a[x], i_target - i[x])
1082
+ # slot1 reconciles toward a value that accounts for
1083
+ # what slot0 currently shows), but only slot1 (the
1084
+ # lossy shadow replica, which has no comparable real
1085
+ # entanglement to lose) is actually rotated -- the
1086
+ # LHV proxy is never physically rotated either, since
1087
+ # it isn't a real qubit.
1088
+ if r1 <= self._rot_epsilon:
1089
+ self._rotate_to_bloch(hq[1], a_target - a[1], i_target - i[1])
1117
1090
 
1118
1091
  if phase:
1119
1092
  for q in qb:
@@ -1438,7 +1411,13 @@ class QrackAceBackend:
1438
1411
  # the single-replica CZ dual-check elsewhere.
1439
1412
  anc2 = None
1440
1413
  if self.is_error_detection and not self._in_gadget_capture and (len(hq1) > 1):
1441
- anc2 = self._detect_ancilla2_lq[hq1[0][0]]
1414
+ anc_sim = hq1[0][0]
1415
+ t_sims = {y[0] for y in hq2 if y[0] >= 0}
1416
+ for x in hq1:
1417
+ if x[0] in t_sims:
1418
+ anc_sim = x[0]
1419
+ break
1420
+ anc2 = self._detect_ancilla2_lq[anc_sim]
1442
1421
  self._in_gadget_capture = True
1443
1422
  # XOR on target
1444
1423
  self.cx(lq2, anc2)
@@ -1562,9 +1541,9 @@ class QrackAceBackend:
1562
1541
 
1563
1542
  qb1, _ = QrackAceBackend._get_qb_lhv_indices(hq1)
1564
1543
 
1565
- # Apply cross coupling on every qubit, including former-LHV boundary
1566
- # qubits, which now live as real qubits in the shared boundary sim.
1567
- # Also apply across target repetition.
1544
+ # Apply cross coupling on every real replica in hq1/h2 (the LHV
1545
+ # proxy is coupled separately, just below, via _cpauli_lhv -- it
1546
+ # is not one of hq's indices). Also apply across target repetition.
1568
1547
  for l2 in t2:
1569
1548
  h2 = self._unpack(l2)
1570
1549
  qb2, _ = QrackAceBackend._get_qb_lhv_indices(h2)
@@ -1910,12 +1889,14 @@ class QrackAceBackend:
1910
1889
 
1911
1890
  anc1, anc2 = None, None
1912
1891
  if self.is_error_detection and not self._in_gadget_capture and ((len(hq1) > 1) or (len(hq2) > 1)):
1913
- if len(hq2) == 1:
1914
- anc1 = self._detect_ancilla1_lq[hq1[0][0]]
1915
- anc2 = self._detect_ancilla2_lq[hq1[0][0]]
1916
- else:
1917
- anc1 = self._detect_ancilla1_lq[hq2[0][0]]
1918
- anc2 = self._detect_ancilla2_lq[hq2[0][0]]
1892
+ t_sims = {y[0] for y in hq2 if y[0] >= 0}
1893
+ anc_sim = hq1[0][0]
1894
+ for x in hq1:
1895
+ if x[0] in t_sims:
1896
+ anc_sim = x[0]
1897
+ break
1898
+ anc1 = self._detect_ancilla1_lq[anc_sim]
1899
+ anc2 = self._detect_ancilla2_lq[anc_sim]
1919
1900
  self._in_gadget_capture = True
1920
1901
  self.cx(lq1, anc1)
1921
1902
  self.cx(lq2, anc2)
@@ -2020,25 +2001,6 @@ class QrackAceBackend:
2020
2001
  self.mcx([c1, c2], t)
2021
2002
  return
2022
2003
 
2023
- if len(hqt) > 1:
2024
- self.h(t)
2025
- self.cx(c2, t)
2026
- self.adjt(t)
2027
- self.cx(c1, t)
2028
- self.t(t)
2029
- self.cx(c2, t)
2030
- self.adjt(t)
2031
- self.cx(c1, t)
2032
- self.t(t)
2033
- self.h(t)
2034
- self.t(c2)
2035
- self.cx(c1, c2)
2036
- self.t(c1)
2037
- self.adjt(c2)
2038
- self.cx(c1, c2)
2039
- return
2040
-
2041
-
2042
2004
  if not self._in_gadget_capture:
2043
2005
  self._correct(c1)
2044
2006
  self._correct(c2)
@@ -2066,9 +2028,24 @@ class QrackAceBackend:
2066
2028
  # bulk on this path (the len(hqt) > 1 case returns earlier,
2067
2029
  # above) and nothing else in this method uses that array anymore.
2068
2030
  anc_and = None
2069
- if self.is_error_detection and not self._in_gadget_capture and ((len(hq1) > 1) or (len(hq2) > 1)):
2031
+ if self.is_error_detection and not self._in_gadget_capture and ((len(hq1) > 1) or (len(hq2) > 1) or (len(hqt) > 1)):
2032
+ found = False
2033
+ anc_sim = hq1[0][0]
2034
+ t_sims = {y[0] for y in hqt if y[0] >= 0}
2035
+ for x in hq1:
2036
+ if (x[0] < 0) or (x[0] not in t_sims):
2037
+ continue
2038
+ anc_sim = x[0]
2039
+ for y in hq2:
2040
+ if (y[0] < 0) or (y[0] not in t_sims):
2041
+ continue
2042
+ if anc_sim == y[0]:
2043
+ found = True
2044
+ break
2045
+ if found:
2046
+ break
2070
2047
  self._in_gadget_capture = True
2071
- anc_and = self._detect_ancilla2_lq[hqt[0][0]]
2048
+ anc_and = self._detect_ancilla2_lq[anc_sim]
2072
2049
  self.cx(t, anc_and)
2073
2050
  # self.mcx() only supports a single control at the logical
2074
2051
  # level (a syntax-convenience wrapper, confirmed directly --
@@ -2084,7 +2061,6 @@ class QrackAceBackend:
2084
2061
  # skips setting up a second anc_and, and just performs the
2085
2062
  # ordinary native-witness-or-shadow Toffoli computation on
2086
2063
  # anc_and instead.
2087
- anc_sim = hqt[0][0]
2088
2064
  c1_idx = next((r[1] for r in hq1 if r[0] == anc_sim), None)
2089
2065
  c2_idx = next((r[1] for r in hq2 if r[0] == anc_sim), None)
2090
2066
  if (c1_idx is not None) and (c2_idx is not None):
@@ -2098,26 +2074,67 @@ class QrackAceBackend:
2098
2074
  qb2, _ = QrackAceBackend._get_qb_lhv_indices(hq2)
2099
2075
  qbt, _ = QrackAceBackend._get_qb_lhv_indices(hqt)
2100
2076
 
2077
+ witnesses = []
2078
+ needs_shadow = False
2101
2079
  for qt in qbt:
2102
- witness = None
2103
2080
  bt = hqt[qt]
2081
+ witness = None
2104
2082
  for q1 in qb1:
2105
2083
  b1 = hq1[q1]
2106
2084
  for q2 in qb2:
2107
2085
  b2 = hq2[q2]
2108
- for q2 in qb2:
2109
- b2 = hq2[q2]
2110
- if bt[0] == b1[0] and bt[0] == b2[0]:
2111
- witness = bt
2112
- break
2113
- if witness is not None:
2086
+ if bt[0] == b1[0] and bt[0] == b2[0]:
2087
+ witness = (b1, b2, bt)
2114
2088
  break
2115
2089
  if witness is not None:
2116
2090
  break
2117
- if witness:
2091
+ if witness is None:
2092
+ needs_shadow = True
2093
+ break
2094
+ witnesses.append(witness)
2095
+
2096
+ if needs_shadow:
2097
+ # Confirmed directly: whenever even one replica of t lacks a
2098
+ # native witness shared with c1 and c2, the per-replica
2099
+ # fallback to _ccx_shadow craters fidelity specifically when
2100
+ # either control is in genuine superposition -- 0.64-0.68
2101
+ # vs. 0.999+ for every other input tested, stable from 100 to
2102
+ # 2500 shots, so not shot noise. Diverting the WHOLE
2103
+ # operation (not just the unwitnessed replica) to the general
2104
+ # 6-CX decomposition instead: every step here is a genuine
2105
+ # logical cx()/h()/t()/adjt() call, which updates every
2106
+ # replica of t through the same _apply_coupling boundary-
2107
+ # crossing machinery already validated elsewhere in this
2108
+ # file, rather than the lower-level per-replica _cx_shadow
2109
+ # calls _ccx_shadow uses for just the unwitnessed ones.
2110
+ #
2111
+ # Wrapped in _in_gadget_capture, same as established earlier:
2112
+ # without it, each individual cx() here would independently
2113
+ # trigger its own XOR-invariant gadget setup, which corrupts
2114
+ # the precise T/Tdg phase relationships this decomposition
2115
+ # depends on (confirmed directly, the first time this
2116
+ # decomposition was used unconditionally -- corrupted even
2117
+ # c1/c2, which the per-replica path never touched at all).
2118
+ self._in_gadget_capture = True
2119
+ self.h(t)
2120
+ self.cx(c2, t)
2121
+ self.adjt(t)
2122
+ self.cx(c1, t)
2123
+ self.t(t)
2124
+ self.cx(c2, t)
2125
+ self.adjt(t)
2126
+ self.cx(c1, t)
2127
+ self.t(t)
2128
+ self.h(t)
2129
+ self.t(c2)
2130
+ self.cx(c1, c2)
2131
+ self.t(c1)
2132
+ self.adjt(c2)
2133
+ self.cx(c1, c2)
2134
+ self._in_gadget_capture = False
2135
+ else:
2136
+ for (b1, b2, bt) in witnesses:
2118
2137
  self.sim[b1[0]].mcx([b1[1], b2[1]], bt[1])
2119
- else:
2120
- self._ccx_shadow(b1, b2, bt, c1, c2, t)
2121
2138
 
2122
2139
  self._in_gadget_capture = True
2123
2140
 
@@ -2156,54 +2173,36 @@ class QrackAceBackend:
2156
2173
  return self.sim[b[0]].prob(b[1])
2157
2174
 
2158
2175
  self._correct(lq)
2159
- if len(hq) == 5:
2160
- # RMS
2176
+ if len(hq) == 4:
2177
+ # The 4 real replicas are already mutually consistent here
2178
+ # (the _correct() call above already ran the tie-breaking
2179
+ # cascade, using the LHV proxy as needed, and forced
2180
+ # agreement via x()). A plain, unweighted RMS over the
2181
+ # now-settled replicas is the correct, already-decided
2182
+ # answer -- there is no crossbar replica left to weight
2183
+ # specially any more.
2161
2184
  p = [
2162
2185
  self.sim[hq[0][0]].prob(hq[0][1]),
2163
2186
  self.sim[hq[1][0]].prob(hq[1][1]),
2164
2187
  self.sim[hq[2][0]].prob(hq[2][1]),
2165
2188
  self.sim[hq[3][0]].prob(hq[3][1]),
2166
- self.sim[hq[4][0]].prob(hq[4][1]),
2167
2189
  ]
2168
- # Balancing suggestion from Elara (the custom OpenAI GPT)
2169
- prms = math.sqrt((p[0] ** 2 + p[1] ** 2 + 3 * (p[2] ** 2) + p[3] ** 2 + p[4] ** 2) / 7)
2170
- qrms = math.sqrt(
2171
- (
2172
- (1 - p[0]) ** 2
2173
- + (1 - p[1]) ** 2
2174
- + 3 * ((1 - p[2]) ** 2)
2175
- + (1 - p[3]) ** 2
2176
- + (1 - p[4]) ** 2
2177
- )
2178
- / 7
2179
- )
2190
+ prms = math.sqrt(sum(x**2 for x in p) / 4)
2191
+ qrms = math.sqrt(sum((1 - x) ** 2 for x in p) / 4)
2180
2192
  else:
2181
- lhv = self._lhv.get(lq)
2182
- if lhv is None:
2183
- # RMS
2184
- p = [
2185
- self.sim[hq[0][0]].prob(hq[0][1]),
2186
- self.sim[hq[1][0]].prob(hq[1][1]),
2187
- self.sim[hq[2][0]].prob(hq[2][1]),
2188
- ]
2189
- # Balancing suggestion from Elara (the custom OpenAI GPT)
2190
- prms = math.sqrt((p[0] ** 2 + p[1] ** 2 + p[2] ** 2) / 3)
2191
- qrms = math.sqrt(((1 - p[0]) ** 2 + (1 - p[1]) ** 2 + (1 - p[2]) ** 2) / 3)
2192
- else:
2193
- p = [
2194
- self.sim[hq[0][0]].prob(hq[0][1]),
2195
- self.sim[hq[1][0]].prob(hq[1][1]),
2196
- self.sim[hq[2][0]].prob(hq[2][1]),
2197
- ]
2198
- # The three real replicas are already mutually consistent
2199
- # here (the _correct() call above already ran the
2200
- # conditional tie-breaking cascade and forced agreement via
2201
- # x()). Re-weighting in the LHV here would reintroduce the
2202
- # same center-dragging distortion the cascade was built to
2203
- # avoid -- a plain RMS over the now-settled replicas is the
2204
- # correct, already-decided answer.
2205
- prms = math.sqrt((p[0] ** 2 + p[1] ** 2 + p[2] ** 2) / 3)
2206
- qrms = math.sqrt(((1 - p[0]) ** 2 + (1 - p[1]) ** 2 + (1 - p[2]) ** 2) / 3)
2193
+ # The 2 real replicas are already mutually consistent here
2194
+ # (the _correct() call above already ran the conditional
2195
+ # tie-breaking cascade, using the LHV proxy as needed, and
2196
+ # forced agreement via x()). Re-weighting in the LHV here
2197
+ # would reintroduce the same center-dragging distortion the
2198
+ # cascade was built to avoid -- a plain RMS over the
2199
+ # now-settled pair is the correct, already-decided answer.
2200
+ p = [
2201
+ self.sim[hq[0][0]].prob(hq[0][1]),
2202
+ self.sim[hq[1][0]].prob(hq[1][1]),
2203
+ ]
2204
+ prms = math.sqrt((p[0] ** 2 + p[1] ** 2) / 2)
2205
+ qrms = math.sqrt(((1 - p[0]) ** 2 + (1 - p[1]) ** 2) / 2)
2207
2206
 
2208
2207
  return (prms + (1 - qrms)) / 2
2209
2208
 
@@ -3502,10 +3502,26 @@ class QrackSimulator:
3502
3502
  return out
3503
3503
 
3504
3504
  def lossy_out_to_file(self, f, p=6, b=4):
3505
+ """TurboQuant-based state out-to-file (for all simulator types)
3506
+
3507
+ Saves the simulator state to file, with TurboQuant-based lossy compression.
3508
+
3509
+ Args:
3510
+ f: Name of file
3511
+ p: base-2 exponent of block size
3512
+ b: compressed bits per block
3513
+ """
3505
3514
  Qrack.qrack_lib.lossy_out_to_file(self.sid, f.encode("utf-8"), min(p, self.num_qubits()), b)
3506
3515
  self._throw_if_error()
3507
3516
 
3508
3517
  def lossy_in_from_file(self, f):
3518
+ """TurboQuant-based state in-from-file (for all simulator types)
3519
+
3520
+ Loads the simulator state from file, with TurboQuant-based lossy compression.
3521
+
3522
+ Args:
3523
+ f: Name of file
3524
+ """
3509
3525
  Qrack.qrack_lib.lossy_in_from_file(self.sid, f.encode("utf-8"))
3510
3526
  self._throw_if_error()
3511
3527
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyqrack-cpu-complex128
3
- Version: 2.25.4
3
+ Version: 2.26.0
4
4
  Summary: pyqrack - Pure Python vm6502q/qrack Wrapper
5
5
  Home-page: https://github.com/vm6502q/pyqrack
6
6
  Author: Daniel Strano
@@ -7,7 +7,7 @@ from setuptools import setup
7
7
  from setuptools.command.build_py import build_py
8
8
 
9
9
 
10
- VERSION = "2.25.4"
10
+ VERSION = "2.26.0"
11
11
 
12
12
  # Read long description from README.
13
13
  README_PATH = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'README.md')