pyqrack-cpu-complex128 2.26.6__tar.gz → 2.27.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.26.6/pyqrack_cpu_complex128.egg-info → pyqrack_cpu_complex128-2.27.0}/PKG-INFO +1 -1
  2. {pyqrack_cpu_complex128-2.26.6 → pyqrack_cpu_complex128-2.27.0}/pyqrack/qrack_ace_backend.py +237 -53
  3. {pyqrack_cpu_complex128-2.26.6 → pyqrack_cpu_complex128-2.27.0/pyqrack_cpu_complex128.egg-info}/PKG-INFO +1 -1
  4. {pyqrack_cpu_complex128-2.26.6 → pyqrack_cpu_complex128-2.27.0}/setup.py +1 -1
  5. {pyqrack_cpu_complex128-2.26.6 → pyqrack_cpu_complex128-2.27.0}/LICENSE +0 -0
  6. {pyqrack_cpu_complex128-2.26.6 → pyqrack_cpu_complex128-2.27.0}/MANIFEST.in +0 -0
  7. {pyqrack_cpu_complex128-2.26.6 → pyqrack_cpu_complex128-2.27.0}/Makefile +0 -0
  8. {pyqrack_cpu_complex128-2.26.6 → pyqrack_cpu_complex128-2.27.0}/README.md +0 -0
  9. {pyqrack_cpu_complex128-2.26.6 → pyqrack_cpu_complex128-2.27.0}/pyproject.toml +0 -0
  10. {pyqrack_cpu_complex128-2.26.6 → pyqrack_cpu_complex128-2.27.0}/pyqrack/__init__.py +0 -0
  11. {pyqrack_cpu_complex128-2.26.6 → pyqrack_cpu_complex128-2.27.0}/pyqrack/neuron_activation_fn.py +0 -0
  12. {pyqrack_cpu_complex128-2.26.6 → pyqrack_cpu_complex128-2.27.0}/pyqrack/pauli.py +0 -0
  13. {pyqrack_cpu_complex128-2.26.6 → pyqrack_cpu_complex128-2.27.0}/pyqrack/qrack_circuit.py +0 -0
  14. {pyqrack_cpu_complex128-2.26.6 → pyqrack_cpu_complex128-2.27.0}/pyqrack/qrack_near_clifford_qec_backend.py +0 -0
  15. {pyqrack_cpu_complex128-2.26.6 → pyqrack_cpu_complex128-2.27.0}/pyqrack/qrack_neuron.py +0 -0
  16. {pyqrack_cpu_complex128-2.26.6 → pyqrack_cpu_complex128-2.27.0}/pyqrack/qrack_neuron_torch_layer.py +0 -0
  17. {pyqrack_cpu_complex128-2.26.6 → pyqrack_cpu_complex128-2.27.0}/pyqrack/qrack_simulator.py +0 -0
  18. {pyqrack_cpu_complex128-2.26.6 → pyqrack_cpu_complex128-2.27.0}/pyqrack/qrack_stabilizer.py +0 -0
  19. {pyqrack_cpu_complex128-2.26.6 → pyqrack_cpu_complex128-2.27.0}/pyqrack/qrack_system/__init__.py +0 -0
  20. {pyqrack_cpu_complex128-2.26.6 → pyqrack_cpu_complex128-2.27.0}/pyqrack/qrack_system/qrack_system.py +0 -0
  21. {pyqrack_cpu_complex128-2.26.6 → pyqrack_cpu_complex128-2.27.0}/pyqrack/quimb_circuit_type.py +0 -0
  22. {pyqrack_cpu_complex128-2.26.6 → pyqrack_cpu_complex128-2.27.0}/pyqrack/stats/__init__.py +0 -0
  23. {pyqrack_cpu_complex128-2.26.6 → pyqrack_cpu_complex128-2.27.0}/pyqrack/stats/load_quantized_data.py +0 -0
  24. {pyqrack_cpu_complex128-2.26.6 → pyqrack_cpu_complex128-2.27.0}/pyqrack/stats/quantize_by_range.py +0 -0
  25. {pyqrack_cpu_complex128-2.26.6 → pyqrack_cpu_complex128-2.27.0}/pyqrack_cpu_complex128.egg-info/SOURCES.txt +0 -0
  26. {pyqrack_cpu_complex128-2.26.6 → pyqrack_cpu_complex128-2.27.0}/pyqrack_cpu_complex128.egg-info/dependency_links.txt +0 -0
  27. {pyqrack_cpu_complex128-2.26.6 → pyqrack_cpu_complex128-2.27.0}/pyqrack_cpu_complex128.egg-info/not-zip-safe +0 -0
  28. {pyqrack_cpu_complex128-2.26.6 → pyqrack_cpu_complex128-2.27.0}/pyqrack_cpu_complex128.egg-info/requires.txt +0 -0
  29. {pyqrack_cpu_complex128-2.26.6 → pyqrack_cpu_complex128-2.27.0}/pyqrack_cpu_complex128.egg-info/top_level.txt +0 -0
  30. {pyqrack_cpu_complex128-2.26.6 → pyqrack_cpu_complex128-2.27.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyqrack-cpu-complex128
3
- Version: 2.26.6
3
+ Version: 2.27.0
4
4
  Summary: pyqrack - Pure Python vm6502q/qrack Wrapper
5
5
  Home-page: https://github.com/vm6502q/pyqrack
6
6
  Author: Daniel Strano
@@ -252,6 +252,7 @@ class QrackAceBackend:
252
252
  is_1d_chain=False,
253
253
  is_error_detection=True,
254
254
  is_boundary_repetition_code=False,
255
+ use_crossbars=True,
255
256
  to_clone=None,
256
257
  ):
257
258
  if to_clone:
@@ -263,6 +264,7 @@ class QrackAceBackend:
263
264
  is_1d_chain = to_clone.is_1d_chain
264
265
  is_error_detection = to_clone.is_error_detection
265
266
  is_boundary_repetition_code = to_clone.is_boundary_repetition_code
267
+ use_crossbars = to_clone.use_crossbars
266
268
  if qubit_count < 0:
267
269
  qubit_count = 0
268
270
  if long_range_columns < 0:
@@ -275,6 +277,19 @@ class QrackAceBackend:
275
277
  self.is_transpose = is_transpose
276
278
  self.is_torus = is_torus
277
279
  self.is_error_detection = is_error_detection
280
+ # Opt-in revival of the shared boundary "crossbar" QrackSimulator
281
+ # (see the note just below, at the qubit-allocation loop) that was
282
+ # previously removed outright for not scaling with patch count.
283
+ # Off by default -- same topology/behavior as before this option
284
+ # existed. On, it gives every boundary qubit one extra REAL replica
285
+ # in a single simulator shared by every boundary site in the whole
286
+ # grid, which is itself an un-scalable idea (that one simulator's
287
+ # qubit count grows with total boundary-qubit count, not with
288
+ # patch count) -- but for a small number of patches (e.g. 3-4,
289
+ # fitting on one GPU) it was measured to add real, genuine
290
+ # connectivity value on top of the classical LHV proxy, so it's
291
+ # kept available for exactly that regime rather than discarded.
292
+ self.use_crossbars = use_crossbars
278
293
  self.is_boundary_repetition_code = is_boundary_repetition_code
279
294
 
280
295
  if Qrack.fppow < 5:
@@ -334,19 +349,26 @@ class QrackAceBackend:
334
349
  self._is_row_long_range[-1] = False
335
350
  sim_count = col_patch_count * row_patch_count
336
351
 
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.
352
+ # The shared boundary "crossbar" QrackSimulator, self.use_crossbars
353
+ # (see the constructor): every boundary site (row- and/or column-
354
+ # boundary alike) gets one extra real replica in a single
355
+ # simulator shared by ALL boundary qubits, trusting that
356
+ # simulator's own greedy elision (set_sdrp) to automatically
357
+ # factor apart whatever boundary sites turn out to be separable,
358
+ # rather than hand-coding "crossbar intersection" logic. Measured
359
+ # to add real connectivity value, but the single shared simulator
360
+ # grows with total boundary-qubit count rather than patch count,
361
+ # so it doesn't scale -- off by default, and worth turning on only
362
+ # for a handful of patches. Every boundary qubit ALSO still gets
363
+ # its classical LHVQubit proxy (self._lhv, below) regardless, same
364
+ # as when this option is off: with the crossbar on, the proxy
365
+ # drops to being only the last-resort tie-breaker _correct() falls
366
+ # back to when even the crossbar replica is itself ambiguous; with
367
+ # it off, the proxy is promoted to cover the crossbar's old
368
+ # tie-breaking role directly.
369
+ boundary_sim_id = sim_count
370
+ boundary_count = 0
371
+
350
372
  self._qubits = []
351
373
  self._lhv = {}
352
374
  sim_counts = [0] * sim_count
@@ -362,6 +384,10 @@ class QrackAceBackend:
362
384
  qubit.append((t_sim_id, sim_counts[t_sim_id]))
363
385
  sim_counts[t_sim_id] += 1
364
386
 
387
+ if self.use_crossbars:
388
+ qubit.append((boundary_sim_id, boundary_count))
389
+ boundary_count += 1
390
+
365
391
  if (not to_clone) or (tot_qubits in to_clone._lhv):
366
392
  self._lhv[tot_qubits] = LHVQubit(
367
393
  to_clone=(to_clone._lhv[tot_qubits] if to_clone else None)
@@ -384,6 +410,18 @@ class QrackAceBackend:
384
410
  )
385
411
  tot_qubits += 1
386
412
 
413
+ # The crossbar's size is fixed by how many boundary sites exist.
414
+ # When there are none at all (e.g. a grid small enough, relative
415
+ # to long_range_rows/columns, to be fully connected with no QEC
416
+ # boundary), we must NOT allocate a 0-qubit QrackSimulator for it
417
+ # -- sim_count/sim_counts simply stay exactly as they were,
418
+ # exactly mirroring how every other per-simulator allocation below
419
+ # already guards for "nothing to allocate."
420
+ has_boundary = self.use_crossbars and (boundary_count > 0)
421
+ if has_boundary:
422
+ sim_counts.append(boundary_count)
423
+ sim_count += 1
424
+
387
425
  # Error-detection gadget (IBM-style detect-and-post-select, not
388
426
  # correction): one shared ancilla per simulator -- one per patch,
389
427
  # "a single ancilla per patch, overall" -- reused across every real
@@ -729,16 +767,19 @@ class QrackAceBackend:
729
767
 
730
768
  @staticmethod
731
769
  def _get_qb_lhv_indices(hq):
732
- # With the shared boundary "crossbar" QrackSimulator removed,
733
- # every entry of hq is again an ordinary (sim_id, idx) tuple into a
734
- # real physical replica: 1 for a bulk qubit, 2 for an edge-boundary
735
- # qubit (home + adjacent-patch), or 4 for a corner-boundary qubit
736
- # (home + adjacent-patch + 2 diagonal-patch). None of them are
737
- # virtual/LHV-backed slots any more -- the classical LHVQubit
738
- # proxy (self._lhv) is tracked entirely separately from hq, not as
739
- # one of its indices -- so qb is simply every index, in order. We
740
- # keep this helper's name and signature for minimal call-site
741
- # churn; "lhv" is always -1 (no index needs special-casing).
770
+ # Every entry of hq is an ordinary (sim_id, idx) tuple into a real
771
+ # physical replica: 1 for a bulk qubit; with use_crossbars=False
772
+ # (default), 2 for an edge-boundary qubit (home + adjacent-patch)
773
+ # or 4 for a corner-boundary qubit (home + adjacent-patch + 2
774
+ # diagonal-patch); with use_crossbars=True, 3 (home + adjacent-
775
+ # patch + shared crossbar) or 5 (home + adjacent-patch + shared
776
+ # crossbar + 2 diagonal-patch). None of them are virtual/LHV-
777
+ # backed slots -- the classical LHVQubit proxy (self._lhv) is
778
+ # tracked entirely separately from hq, not as one of its indices
779
+ # -- so qb is simply every index, in order, regardless of which
780
+ # of those counts applies. We keep this helper's name and
781
+ # signature for minimal call-site churn; "lhv" is always -1 (no
782
+ # index needs special-casing).
742
783
  qb = list(range(len(hq)))
743
784
  lhv = -1
744
785
 
@@ -945,6 +986,164 @@ class QrackAceBackend:
945
986
  # all -- skip the rotation rather than rotate toward an
946
987
  # arbitrary/undefined target derived from noise.
947
988
 
989
+ elif len(hq) == 5:
990
+ # Corner-boundary qubit, self.use_crossbars=True: 5 real
991
+ # replicas (home, adjacent-patch, the shared crossbar at
992
+ # slot 2, and the 2 diagonal-patch shadows). Structurally
993
+ # identical to the no-crossbar 4-replica case above -- the 4
994
+ # "end-cap" replicas [p0, p1, p3, p4] vote via their own RMS
995
+ # pool first; a genuine 2-2 split among them defers to the
996
+ # crossbar replica (hq[2], a REAL qubit, not a classical
997
+ # proxy); the classical LHV proxy is consulted only if the
998
+ # crossbar itself also turns out ambiguous.
999
+ p0 = self.sim[hq[0][0]].prob(hq[0][1])
1000
+ p1 = self.sim[hq[1][0]].prob(hq[1][1])
1001
+ p2 = self.sim[hq[2][0]].prob(hq[2][1])
1002
+ p3 = self.sim[hq[3][0]].prob(hq[3][1])
1003
+ p4 = self.sim[hq[4][0]].prob(hq[4][1])
1004
+ lhv = self._lhv.get(lq)
1005
+
1006
+ end_caps = [p0, p1, p3, p4]
1007
+ high_count = sum(1 for x in end_caps if x > (0.5 + self._epsilon))
1008
+ low_count = sum(1 for x in end_caps if x < (0.5 - self._epsilon))
1009
+ undecided_count = len(end_caps) - high_count - low_count
1010
+ end_caps_tied = (high_count == 2 and low_count == 2) or (undecided_count > 0)
1011
+
1012
+ if not end_caps_tied:
1013
+ prms = math.sqrt(sum(x**2 for x in end_caps) / 4)
1014
+ qrms = math.sqrt(sum((1 - x) ** 2 for x in end_caps) / 4)
1015
+ eff_prob = (prms + (1 - qrms)) / 2
1016
+ result = (
1017
+ (random.random() < 0.5)
1018
+ if abs(eff_prob - 0.5) <= self._epsilon
1019
+ else (eff_prob >= 0.5)
1020
+ )
1021
+ elif abs(p2 - 0.5) > self._epsilon:
1022
+ result = p2 >= 0.5
1023
+ elif lhv is not None:
1024
+ p_lhv = lhv.prob()
1025
+ result = (
1026
+ (random.random() < 0.5)
1027
+ if abs(p_lhv - 0.5) <= self._epsilon
1028
+ else (p_lhv >= 0.5)
1029
+ )
1030
+ else:
1031
+ result = random.random() < 0.5
1032
+
1033
+ p = [p0, p1, p2, p3, p4]
1034
+ syndrome = [1 - x for x in p] if result else list(p)
1035
+ for q in range(5):
1036
+ if syndrome[q] > (0.5 + self._epsilon):
1037
+ self.sim[hq[q][0]].x(hq[q][1])
1038
+
1039
+ if not skip_rotation:
1040
+ a, i, rs = [0] * 5, [0] * 5, [0] * 5
1041
+ for idx in range(5):
1042
+ a[idx], i[idx], rs[idx] = self._get_bloch_angles(hq[idx])
1043
+ w = [1 - r for r in rs]
1044
+
1045
+ w_total = sum(w)
1046
+ if w_total > self._epsilon:
1047
+ a_target = sum(wx * ax for wx, ax in zip(w, a)) / w_total
1048
+ i_target = sum(wx * ix for wx, ix in zip(w, i)) / w_total
1049
+ # Same separability gating as the 4-replica case
1050
+ # above (hq[0] excluded outright, hq[1:] only
1051
+ # rotated while still reasonably separable).
1052
+ for x in range(1, 5):
1053
+ if rs[x] <= self._rot_epsilon:
1054
+ self._rotate_to_bloch(hq[x], a_target - a[x], i_target - i[x])
1055
+
1056
+ elif len(hq) == 3:
1057
+ # Edge-boundary qubit, self.use_crossbars=True: 3 real
1058
+ # replicas (home, adjacent-patch, and the shared crossbar at
1059
+ # slot 2). Same conditional tie-breaking cascade as the
1060
+ # no-crossbar 2-replica case below, but with the crossbar
1061
+ # (hq[2], a REAL qubit) taking over the role the classical
1062
+ # LHV proxy plays there: trust slot1 (adjacent-patch) and
1063
+ # slot2 (crossbar) together whenever they agree; fall back
1064
+ # to slot0 (home) when they disagree and it has a decisive
1065
+ # opinion; consult the classical LHV proxy only as an
1066
+ # absolute last resort, if even slot0 is ambiguous.
1067
+ p0 = self.sim[hq[0][0]].prob(hq[0][1])
1068
+ p1 = self.sim[hq[1][0]].prob(hq[1][1])
1069
+ p2 = self.sim[hq[2][0]].prob(hq[2][1])
1070
+ lhv = self._lhv.get(lq)
1071
+
1072
+ p1_high = p1 > (0.5 + self._epsilon)
1073
+ p1_low = p1 < (0.5 - self._epsilon)
1074
+ p2_high = p2 > (0.5 + self._epsilon)
1075
+ p2_low = p2 < (0.5 - self._epsilon)
1076
+ end_caps_agree = (p1_high and p2_high) or (p1_low and p2_low)
1077
+ slot0_disagrees_with_end_caps = (
1078
+ end_caps_agree
1079
+ and (abs(p0 - 0.5) > self._epsilon)
1080
+ and ((p0 >= 0.5) != p1_high)
1081
+ )
1082
+ if slot0_disagrees_with_end_caps:
1083
+ # Same reasoning as the no-crossbar 2-replica case below:
1084
+ # the pair agreeing is not, by itself, reliable
1085
+ # corroboration if slot0 has a real, decisive, opposing
1086
+ # opinion -- that contradiction is the signal the pair is
1087
+ # stale, not corroborating.
1088
+ result = p0 >= 0.5
1089
+ elif end_caps_agree:
1090
+ prms = math.sqrt((p1**2 + p2**2) / 2)
1091
+ qrms = math.sqrt(((1 - p1) ** 2 + (1 - p2) ** 2) / 2)
1092
+ eff_prob = (prms + (1 - qrms)) / 2
1093
+ result = (
1094
+ (random.random() < 0.5)
1095
+ if abs(eff_prob - 0.5) <= self._epsilon
1096
+ else (eff_prob >= 0.5)
1097
+ )
1098
+ elif abs(p0 - 0.5) > self._epsilon:
1099
+ # Genuine deadlock between slot1/slot2; slot0 (a real,
1100
+ # exactly-entangled qubit in this common topology) has a
1101
+ # real opinion, so it breaks the tie.
1102
+ result = p0 >= 0.5
1103
+ elif lhv is not None:
1104
+ # slot0 is itself ambiguous, and so is the crossbar pair
1105
+ # above it; only now does the classical LHV's continuous,
1106
+ # non-collapsing proxy actually decide, as the last
1107
+ # resort.
1108
+ p_lhv = lhv.prob()
1109
+ result = (
1110
+ (random.random() < 0.5)
1111
+ if abs(p_lhv - 0.5) <= self._epsilon
1112
+ else (p_lhv >= 0.5)
1113
+ )
1114
+ else:
1115
+ result = random.random() < 0.5
1116
+
1117
+ p = [p0, p1, p2]
1118
+ syndrome = [1 - x for x in p] if result else list(p)
1119
+ for q in range(3):
1120
+ if syndrome[q] > (0.5 + self._epsilon):
1121
+ self.sim[hq[q][0]].x(hq[q][1])
1122
+ # The LHV proxy is never hard-collapsed via x(); it is only
1123
+ # ever updated by its own transversal gate evolution and
1124
+ # _cpauli_lhv, same as every other branch here.
1125
+
1126
+ if (not skip_rotation) and (not end_caps_agree):
1127
+ a, i, rs = [0, 0, 0], [0, 0, 0], [0, 0, 0]
1128
+ a[0], i[0], rs[0] = self._get_bloch_angles(hq[0])
1129
+ a[1], i[1], rs[1] = self._get_bloch_angles(hq[1])
1130
+ a[2], i[2], rs[2] = self._get_bloch_angles(hq[2])
1131
+ w = [1 - r for r in rs]
1132
+
1133
+ w_total = sum(w)
1134
+ if w_total > self._epsilon:
1135
+ a_target = sum(wx * ax for wx, ax in zip(w, a)) / w_total
1136
+ i_target = sum(wx * ix for wx, ix in zip(w, i)) / w_total
1137
+ # slot0 (hq[0]) is excluded from the rotation itself,
1138
+ # same reasoning as every other branch here; slot1
1139
+ # and slot2 (the crossbar) are both real, lossy
1140
+ # shadow-style replicas with no comparable
1141
+ # entanglement to protect, so both are eligible,
1142
+ # each gated on its own separability.
1143
+ for x in (1, 2):
1144
+ if rs[x] <= self._rot_epsilon:
1145
+ self._rotate_to_bloch(hq[x], a_target - a[x], i_target - i[x])
1146
+
948
1147
  else:
949
1148
  # Edge-boundary qubit: only 2 real replicas remain (home +
950
1149
  # adjacent-patch) now that the crossbar replica that used to
@@ -2112,36 +2311,21 @@ class QrackAceBackend:
2112
2311
  return self.sim[b[0]].prob(b[1])
2113
2312
 
2114
2313
  self._correct(lq)
2115
- if len(hq) == 4:
2116
- # The 4 real replicas are already mutually consistent here
2117
- # (the _correct() call above already ran the tie-breaking
2118
- # cascade, using the LHV proxy as needed, and forced
2119
- # agreement via x()). A plain, unweighted RMS over the
2120
- # now-settled replicas is the correct, already-decided
2121
- # answer -- there is no crossbar replica left to weight
2122
- # specially any more.
2123
- p = [
2124
- self.sim[hq[0][0]].prob(hq[0][1]),
2125
- self.sim[hq[1][0]].prob(hq[1][1]),
2126
- self.sim[hq[2][0]].prob(hq[2][1]),
2127
- self.sim[hq[3][0]].prob(hq[3][1]),
2128
- ]
2129
- prms = math.sqrt(sum(x**2 for x in p) / 4)
2130
- qrms = math.sqrt(sum((1 - x) ** 2 for x in p) / 4)
2131
- else:
2132
- # The 2 real replicas are already mutually consistent here
2133
- # (the _correct() call above already ran the conditional
2134
- # tie-breaking cascade, using the LHV proxy as needed, and
2135
- # forced agreement via x()). Re-weighting in the LHV here
2136
- # would reintroduce the same center-dragging distortion the
2137
- # cascade was built to avoid -- a plain RMS over the
2138
- # now-settled pair is the correct, already-decided answer.
2139
- p = [
2140
- self.sim[hq[0][0]].prob(hq[0][1]),
2141
- self.sim[hq[1][0]].prob(hq[1][1]),
2142
- ]
2143
- prms = math.sqrt((p[0] ** 2 + p[1] ** 2) / 2)
2144
- qrms = math.sqrt(((1 - p[0]) ** 2 + (1 - p[1]) ** 2) / 2)
2314
+ # The real replicas are already mutually consistent here (the
2315
+ # _correct() call above already ran the appropriate tie-breaking
2316
+ # cascade for however many replicas hq has -- 2, 3, 4, or 5,
2317
+ # depending on self.use_crossbars and whether this is an edge or
2318
+ # corner boundary qubit -- using the LHV proxy and/or the
2319
+ # crossbar replica as needed, and forced agreement via x()). A
2320
+ # plain, unweighted RMS over the now-settled replicas is the
2321
+ # correct, already-decided answer, regardless of replica count:
2322
+ # re-weighting any particular replica specially here would
2323
+ # reintroduce the same center-dragging distortion the cascade in
2324
+ # _correct() was built to avoid.
2325
+ p = [self.sim[b[0]].prob(b[1]) for b in hq]
2326
+ n = len(p)
2327
+ prms = math.sqrt(sum(x**2 for x in p) / n)
2328
+ qrms = math.sqrt(sum((1 - x) ** 2 for x in p) / n)
2145
2329
 
2146
2330
  return (prms + (1 - qrms)) / 2
2147
2331
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyqrack-cpu-complex128
3
- Version: 2.26.6
3
+ Version: 2.27.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.26.6"
10
+ VERSION = "2.27.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')