pyqrack-cpu 2.2.4__tar.gz → 2.3.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 (31) hide show
  1. {pyqrack_cpu-2.2.4 → pyqrack_cpu-2.3.0}/MANIFEST.in +1 -0
  2. {pyqrack_cpu-2.2.4/pyqrack_cpu.egg-info → pyqrack_cpu-2.3.0}/PKG-INFO +1 -1
  3. {pyqrack_cpu-2.2.4 → pyqrack_cpu-2.3.0}/pyqrack/qrack_ace_backend.py +181 -31
  4. pyqrack_cpu-2.3.0/pyqrack/scripts/optimize_bseq_correction.py +207 -0
  5. {pyqrack_cpu-2.2.4 → pyqrack_cpu-2.3.0/pyqrack_cpu.egg-info}/PKG-INFO +1 -1
  6. {pyqrack_cpu-2.2.4 → pyqrack_cpu-2.3.0}/pyqrack_cpu.egg-info/SOURCES.txt +1 -0
  7. {pyqrack_cpu-2.2.4 → pyqrack_cpu-2.3.0}/setup.py +1 -1
  8. {pyqrack_cpu-2.2.4 → pyqrack_cpu-2.3.0}/LICENSE +0 -0
  9. {pyqrack_cpu-2.2.4 → pyqrack_cpu-2.3.0}/Makefile +0 -0
  10. {pyqrack_cpu-2.2.4 → pyqrack_cpu-2.3.0}/README.md +0 -0
  11. {pyqrack_cpu-2.2.4 → pyqrack_cpu-2.3.0}/pyproject.toml +0 -0
  12. {pyqrack_cpu-2.2.4 → pyqrack_cpu-2.3.0}/pyqrack/__init__.py +0 -0
  13. {pyqrack_cpu-2.2.4 → pyqrack_cpu-2.3.0}/pyqrack/neuron_activation_fn.py +0 -0
  14. {pyqrack_cpu-2.2.4 → pyqrack_cpu-2.3.0}/pyqrack/pauli.py +0 -0
  15. {pyqrack_cpu-2.2.4 → pyqrack_cpu-2.3.0}/pyqrack/qrack_circuit.py +0 -0
  16. {pyqrack_cpu-2.2.4 → pyqrack_cpu-2.3.0}/pyqrack/qrack_near_clifford_qec_backend.py +0 -0
  17. {pyqrack_cpu-2.2.4 → pyqrack_cpu-2.3.0}/pyqrack/qrack_neuron.py +0 -0
  18. {pyqrack_cpu-2.2.4 → pyqrack_cpu-2.3.0}/pyqrack/qrack_neuron_torch_layer.py +0 -0
  19. {pyqrack_cpu-2.2.4 → pyqrack_cpu-2.3.0}/pyqrack/qrack_simulator.py +0 -0
  20. {pyqrack_cpu-2.2.4 → pyqrack_cpu-2.3.0}/pyqrack/qrack_stabilizer.py +0 -0
  21. {pyqrack_cpu-2.2.4 → pyqrack_cpu-2.3.0}/pyqrack/qrack_system/__init__.py +0 -0
  22. {pyqrack_cpu-2.2.4 → pyqrack_cpu-2.3.0}/pyqrack/qrack_system/qrack_system.py +0 -0
  23. {pyqrack_cpu-2.2.4 → pyqrack_cpu-2.3.0}/pyqrack/quimb_circuit_type.py +0 -0
  24. {pyqrack_cpu-2.2.4 → pyqrack_cpu-2.3.0}/pyqrack/stats/__init__.py +0 -0
  25. {pyqrack_cpu-2.2.4 → pyqrack_cpu-2.3.0}/pyqrack/stats/load_quantized_data.py +0 -0
  26. {pyqrack_cpu-2.2.4 → pyqrack_cpu-2.3.0}/pyqrack/stats/quantize_by_range.py +0 -0
  27. {pyqrack_cpu-2.2.4 → pyqrack_cpu-2.3.0}/pyqrack_cpu.egg-info/dependency_links.txt +0 -0
  28. {pyqrack_cpu-2.2.4 → pyqrack_cpu-2.3.0}/pyqrack_cpu.egg-info/not-zip-safe +0 -0
  29. {pyqrack_cpu-2.2.4 → pyqrack_cpu-2.3.0}/pyqrack_cpu.egg-info/requires.txt +0 -0
  30. {pyqrack_cpu-2.2.4 → pyqrack_cpu-2.3.0}/pyqrack_cpu.egg-info/top_level.txt +0 -0
  31. {pyqrack_cpu-2.2.4 → pyqrack_cpu-2.3.0}/setup.cfg +0 -0
@@ -1,3 +1,4 @@
1
1
  include pyqrack/qrack_system/qrack_lib/*
2
2
  include pyqrack/qrack_system/qrack_cl_precompile/*
3
+ include pyqrack/scripts/*
3
4
  include Makefile
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyqrack-cpu
3
- Version: 2.2.4
3
+ Version: 2.3.0
4
4
  Summary: pyqrack - Pure Python vm6502q/qrack Wrapper
5
5
  Home-page: https://github.com/vm6502q/pyqrack
6
6
  Author: Daniel Strano
@@ -28,6 +28,39 @@ try:
28
28
  except ImportError:
29
29
  _IS_QISKIT_AER_AVAILABLE = False
30
30
 
31
+ _IS_TORCH_AVAILABLE = True
32
+ try:
33
+ import torch
34
+ import torch.nn as nn
35
+ except ImportError:
36
+ _IS_TORCH_AVAILABLE = False
37
+
38
+ _BSEQ_CORRECTOR_MODEL = None
39
+ _BSEQ_CORRECTOR_LOAD_ATTEMPTED = False
40
+
41
+
42
+ def _get_bseq_corrector():
43
+ """Lazily load the trained BSEQ (Bell/CHSH) boundary corrector model.
44
+
45
+ Returns None if torch isn't available or the model file isn't found,
46
+ in which case callers fall back to the existing non-learned cascade.
47
+ Memoized at module scope: loaded at most once per process.
48
+ """
49
+ global _BSEQ_CORRECTOR_MODEL, _BSEQ_CORRECTOR_LOAD_ATTEMPTED
50
+ if _BSEQ_CORRECTOR_LOAD_ATTEMPTED:
51
+ return _BSEQ_CORRECTOR_MODEL
52
+ _BSEQ_CORRECTOR_LOAD_ATTEMPTED = True
53
+ if not _IS_TORCH_AVAILABLE:
54
+ return None
55
+ model_path = os.path.join(os.path.dirname(__file__), "bseq_corrector.jit.pt")
56
+ try:
57
+ model = torch.jit.load(model_path)
58
+ model.eval()
59
+ _BSEQ_CORRECTOR_MODEL = model
60
+ except Exception:
61
+ _BSEQ_CORRECTOR_MODEL = None
62
+ return _BSEQ_CORRECTOR_MODEL
63
+
31
64
 
32
65
  # Initial stub and concept produced through conversation with Elara
33
66
  # (the custom OpenAI GPT)
@@ -295,6 +328,10 @@ class QrackAceBackend:
295
328
 
296
329
  self._qubits = []
297
330
  self._lhv = {}
331
+ self._ancilla = {}
332
+ self._ancilla_paired = set(to_clone._ancilla_paired) if to_clone else set()
333
+ self._bell_partner = dict(to_clone._bell_partner) if to_clone else {}
334
+ self._last_y_rotation = dict(to_clone._last_y_rotation) if to_clone else {}
298
335
  sim_counts = [0] * sim_count
299
336
  sim_id = 0
300
337
  tot_qubits = 0
@@ -311,6 +348,16 @@ class QrackAceBackend:
311
348
  qubit.append((boundary_sim_id, boundary_count))
312
349
  boundary_count += 1
313
350
 
351
+ # Reserve a second boundary-sim qubit as the ancilla,
352
+ # right alongside slot2. It starts fresh (|0>) and is
353
+ # Bell-paired with slot2 lazily, the first time slot2
354
+ # is actually touched by a real coupling gate (see
355
+ # _apply_coupling), so it taps slot2's true evolving
356
+ # state rather than a disconnected pair prepared at
357
+ # construction time.
358
+ self._ancilla[tot_qubits] = (boundary_sim_id, boundary_count)
359
+ boundary_count += 1
360
+
314
361
  self._lhv[tot_qubits] = LHVQubit(
315
362
  to_clone=(to_clone._lhv[tot_qubits] if to_clone else None)
316
363
  )
@@ -362,7 +409,7 @@ class QrackAceBackend:
362
409
  )
363
410
  )
364
411
 
365
- # You can still call "set_sdrp," after the constructor.
412
+ # You can still "set_sdrp" later, after the constructor.
366
413
  # if "QRACK_QUNIT_SEPARABILITY_THRESHOLD" not in os.environ:
367
414
  # # (1 - 1 / sqrt(2)) / 4 (but empirically tuned)
368
415
  # self.sim[i].set_sdrp(0.073223304703363119)
@@ -372,6 +419,10 @@ class QrackAceBackend:
372
419
  def clone(self):
373
420
  return QrackAceBackend(to_clone=self)
374
421
 
422
+ def set_sdrp(self, sdrp):
423
+ for sim in self.sim:
424
+ sim.set_sdrp(sdrp)
425
+
375
426
  def measure_shots_consensus(self, q, s, n_instances=3, threshold=0.1):
376
427
  # Consensus measurement across n_instances independent clones.
377
428
  #
@@ -615,6 +666,47 @@ class QrackAceBackend:
615
666
 
616
667
  sim.mtrx([m00, m01, m10, m11])
617
668
 
669
+ # Trainable BSEQ correction: a genuine controlled-U(theta,phi,lambda)
670
+ # gate (QrackSimulator.mcu), with the ancilla as control and the
671
+ # boundary qubit (slot2) as target -- both live in the same boundary
672
+ # simulator, so this is a real, coherent two-qubit unitary, not a
673
+ # classical shadow approximation. Applied directly to the quantum
674
+ # state with no probability read or collapsed first, so it composes
675
+ # correctly even while the Bell-pair partner is still genuinely
676
+ # superposed. These 3 angles are the only trainable parameters;
677
+ # fit them empirically against the measured BSEQ S statistic (see
678
+ # the companion training/optimization script), not hand-derived.
679
+ _BSEQ_THETA = 0.5524356880065896
680
+ _BSEQ_PHI = 0.5611771084538236
681
+ _BSEQ_LAMBDA = -0.3706652126771467
682
+
683
+ def _apply_bseq_correction(self, lq):
684
+ """Apply the trained controlled-U BSEQ correction, if applicable.
685
+
686
+ Fires every time this boundary qubit's state is about to be read
687
+ out (prob()/_correct()/m()/force_m() all funnel through here, so
688
+ this covers every simulator-API readout for the qubit). A no-op
689
+ whenever there's no ancilla for this site, or it hasn't been
690
+ Bell-paired with slot2 yet (nothing to condition on).
691
+ """
692
+ anc = self._ancilla.get(lq)
693
+ if anc is None or lq not in self._ancilla_paired:
694
+ return
695
+ hq = self._unpack(lq)
696
+ if len(hq) < 3:
697
+ return
698
+ anc_sim, anc_idx = anc
699
+ slot2_sim, slot2_idx = hq[2]
700
+ if anc_sim != slot2_sim:
701
+ return
702
+ self.sim[anc_sim].mcu(
703
+ [anc_idx],
704
+ slot2_idx,
705
+ QrackAceBackend._BSEQ_THETA,
706
+ QrackAceBackend._BSEQ_PHI,
707
+ QrackAceBackend._BSEQ_LAMBDA,
708
+ )
709
+
618
710
  def _correct(self, lq, phase=False, skip_rotation=False):
619
711
  hq = self._unpack(lq)
620
712
 
@@ -702,35 +794,65 @@ class QrackAceBackend:
702
794
  if syndrome[q] > (0.5 + self._epsilon):
703
795
  self.sim[hq[q][0]].x(hq[q][1])
704
796
  else:
705
- # Weighted 4-source vote: slot0, slot1, slot2 (real qubits)
706
- # plus lhv (continuous, non-collapsing proxy). Weights sum
707
- # to an odd total (QrackAceBackend._LHV_VOTE_WEIGHTS), so
708
- # the underlying hard-vote tally is always tie-free, while
709
- # the RMS formula itself generalizes the existing unweighted
710
- # one (and reduces to it exactly when all weights are equal).
711
- p = [
712
- self.sim[hq[0][0]].prob(hq[0][1]),
713
- self.sim[hq[1][0]].prob(hq[1][1]),
714
- self.sim[hq[2][0]].prob(hq[2][1]),
715
- lhv.prob(),
716
- ]
717
- w = QrackAceBackend._LHV_VOTE_WEIGHTS
718
- sw = sum(w)
719
- prms = math.sqrt(sum(wi * pi**2 for wi, pi in zip(w, p)) / sw)
720
- qrms = math.sqrt(sum(wi * (1 - pi) ** 2 for wi, pi in zip(w, p)) / sw)
721
- eff_prob = (prms + (1 - qrms)) / 2
722
- result = (
723
- (random.random() < 0.5)
724
- if abs(eff_prob - 0.5) <= self._epsilon
725
- else (eff_prob >= 0.5)
726
- )
797
+ # Conditional tie-breaking cascade, NOT a fixed-weight pool.
798
+ # A fixed weight on slot0/lhv pulls every decision toward
799
+ # 0.5 even when slot1 and slot2 already agree confidently,
800
+ # because slot0 and lhv are legitimately, permanently near
801
+ # 0.5 for genuinely-entangled topologies (e.g. when slot0
802
+ # shares a simulator with the control) -- a "vote" stuck at
803
+ # 0.5 is not neutral in an RMS pool, it actively drags the
804
+ # result toward the center. The actual intent ("LHV serves
805
+ # only to act as a tie-breaker") is a conditional, not a
806
+ # weight: trust slot1/slot2 alone whenever they agree, and
807
+ # only consult slot0, then lhv, when they genuinely don't.
808
+ #
809
+ # The BSEQ correction (a real, coherent controlled-U gate
810
+ # on slot2, conditioned on its ancilla) is applied FIRST,
811
+ # directly to the quantum state -- before any probability
812
+ # is read. This composes correctly even while this qubit's
813
+ # Bell-pair partner is still genuinely superposed, unlike
814
+ # every classical-decision approach that was tried and
815
+ # rejected here (all of them were provably forced into an
816
+ # uninformative coin flip in exactly that case).
817
+ self._apply_bseq_correction(lq)
818
+
819
+ p0 = self.sim[hq[0][0]].prob(hq[0][1])
820
+ p1 = self.sim[hq[1][0]].prob(hq[1][1])
821
+ p2 = self.sim[hq[2][0]].prob(hq[2][1])
822
+ p_lhv = lhv.prob()
823
+
824
+ if (p1 >= 0.5) == (p2 >= 0.5):
825
+ prms = math.sqrt((p1**2 + p2**2) / 2)
826
+ qrms = math.sqrt(((1 - p1) ** 2 + (1 - p2) ** 2) / 2)
827
+ eff_prob = (prms + (1 - qrms)) / 2
828
+ result = (
829
+ (random.random() < 0.5)
830
+ if abs(eff_prob - 0.5) <= self._epsilon
831
+ else (eff_prob >= 0.5)
832
+ )
833
+ elif abs(p0 - 0.5) > self._epsilon:
834
+ # Genuine deadlock between slot1/slot2; slot0 (a real,
835
+ # exactly-entangled qubit in this common topology) has
836
+ # a real opinion, so it breaks the tie.
837
+ result = p0 >= 0.5
838
+ else:
839
+ # slot0 is itself ambiguous; only now does the LHV's
840
+ # continuous, non-collapsing proxy actually decide.
841
+ result = (
842
+ (random.random() < 0.5)
843
+ if abs(p_lhv - 0.5) <= self._epsilon
844
+ else (p_lhv >= 0.5)
845
+ )
846
+
847
+ p = [p0, p1, p2]
727
848
  syndrome = [1 - x for x in p] if result else list(p)
728
849
  for q in range(3):
729
850
  if syndrome[q] > (0.5 + self._epsilon):
730
851
  self.sim[hq[q][0]].x(hq[q][1])
731
- # The LHV proxy is never hard-collapsed via x(); only ever
732
- # updated by continuous rotation, preserving the property
733
- # that makes it useful as a non-collapsing stabilizing term.
852
+ # The LHV proxy is never hard-collapsed via x(); it is only
853
+ # ever updated by its own transversal gate evolution and
854
+ # _cpauli_lhv, preserving the property that makes it usable
855
+ # as a non-collapsing tie-breaker of last resort.
734
856
 
735
857
  if not skip_rotation:
736
858
  a, i = [0, 0, 0], [0, 0, 0]
@@ -784,6 +906,9 @@ class QrackAceBackend:
784
906
  # Correction deferred to next 2-qubit gate (_cpauli calls _correct)
785
907
 
786
908
  def r(self, p, th, lq):
909
+ if p == Pauli.PauliY:
910
+ self._last_y_rotation[lq] = self._last_y_rotation.get(lq, 0.0) + th
911
+
787
912
  hq = self._unpack(lq)
788
913
  if len(hq) < 2:
789
914
  b = hq[0]
@@ -1016,6 +1141,27 @@ class QrackAceBackend:
1016
1141
  # qubits, which now live as real qubits in the shared boundary sim.
1017
1142
  self._apply_coupling(pauli, anti, qb1, hq1, qb2, hq2, lq1_lr)
1018
1143
 
1144
+ if lq2 in self._ancilla and lq2 not in self._ancilla_paired and len(hq2) >= 3:
1145
+ anc_sim, anc_idx = self._ancilla[lq2]
1146
+ slot2_sim, slot2_idx = hq2[2]
1147
+ if anc_sim == slot2_sim:
1148
+ # slot2 as CONTROL, ancilla as target: a CX never modifies
1149
+ # its control qubit's own marginal state, so this taps
1150
+ # slot2's real, evolving state non-invasively -- the
1151
+ # ancilla becomes perfectly correlated with slot2 without
1152
+ # disturbing it (unlike the reverse direction, which would
1153
+ # mix/dephase slot2 itself every time pairing occurred).
1154
+ self.sim[anc_sim].mcx([slot2_idx], anc_idx)
1155
+ else:
1156
+ # Different simulators (shouldn't normally happen, since
1157
+ # ancilla and slot2 are allocated in the same boundary sim,
1158
+ # but guard defensively): fall back to a direct two-qubit
1159
+ # gate is unavailable cross-sim here, so this path is not
1160
+ # expected to be exercised in practice.
1161
+ pass
1162
+ self._ancilla_paired.add(lq2)
1163
+ self._bell_partner[lq2] = lq1
1164
+
1019
1165
  if lq2 in self._lhv:
1020
1166
  ctrl_prob = self.sim[hq1[0][0]].prob(hq1[0][1])
1021
1167
  _cpauli_lhv(ctrl_prob, self._lhv[lq2], pauli, anti)
@@ -1148,12 +1294,16 @@ class QrackAceBackend:
1148
1294
  self.sim[hq[0][0]].prob(hq[0][1]),
1149
1295
  self.sim[hq[1][0]].prob(hq[1][1]),
1150
1296
  self.sim[hq[2][0]].prob(hq[2][1]),
1151
- lhv.prob(),
1152
1297
  ]
1153
- w = QrackAceBackend._LHV_VOTE_WEIGHTS
1154
- sw = sum(w)
1155
- prms = math.sqrt(sum(wi * pi**2 for wi, pi in zip(w, p)) / sw)
1156
- qrms = math.sqrt(sum(wi * (1 - pi) ** 2 for wi, pi in zip(w, p)) / sw)
1298
+ # The three real replicas are already mutually consistent
1299
+ # here (the _correct() call above already ran the
1300
+ # conditional tie-breaking cascade and forced agreement via
1301
+ # x()). Re-weighting in the LHV here would reintroduce the
1302
+ # same center-dragging distortion the cascade was built to
1303
+ # avoid -- a plain RMS over the now-settled replicas is the
1304
+ # correct, already-decided answer.
1305
+ prms = math.sqrt((p[0] ** 2 + p[1] ** 2 + p[2] ** 2) / 3)
1306
+ qrms = math.sqrt(((1 - p[0]) ** 2 + (1 - p[1]) ** 2 + (1 - p[2]) ** 2) / 3)
1157
1307
 
1158
1308
  return (prms + (1 - qrms)) / 2
1159
1309
 
@@ -0,0 +1,207 @@
1
+ # Empirical optimizer for QrackAceBackend's BSEQ (Bell/CHSH) ancilla
2
+ # correction gate.
3
+ #
4
+ # The correction is a single, genuine controlled-U(theta, phi, lambda)
5
+ # gate (QrackSimulator.mcu), with each boundary qubit's ancilla as
6
+ # control and the boundary qubit itself as target. It is applied
7
+ # directly to the live quantum state -- before any probability is read
8
+ # -- every time the boundary qubit's state is corrected (prob() / m() /
9
+ # force_m() all funnel through this), so it composes correctly even
10
+ # while the qubit's Bell-pair partner is still genuinely superposed.
11
+ #
12
+ # The 3 angles (QrackAceBackend._BSEQ_THETA/_PHI/_LAMBDA) are the only
13
+ # trainable parameters. They are fit here by direct, gradient-free
14
+ # search against the empirically measured CHSH S statistic (or its 4
15
+ # components individually), averaged over repeated Bell-pair trials to
16
+ # average down the substantial per-construction measurement variance
17
+ # inherent to this architecture (each circuit construction makes its
18
+ # own one-time classical collapse choice early in the elision pipeline;
19
+ # averaging over MANY independent constructions is what makes the mean
20
+ # S a meaningful, optimizable target -- a single fixed gate cannot
21
+ # reduce per-shot variance, since that variance is set by an earlier,
22
+ # already-classical decision the gate cannot retroactively undo).
23
+ #
24
+ # Usage:
25
+ # python3 optimize_bseq_correction.py [--iterations N] [--time-budget SECONDS]
26
+ #
27
+ # Writes the found angles directly into pyqrack's installed
28
+ # qrack_ace_backend.py (QrackAceBackend._BSEQ_THETA/_PHI/_LAMBDA), so
29
+ # every subsequently-constructed QrackAceBackend in the running
30
+ # process (and, if you choose to persist the file, future processes)
31
+ # uses the fitted correction automatically -- no separate model file,
32
+ # no extra inference dependency. The correction is 3 floats; baking
33
+ # them in as class attributes is already the most efficient possible
34
+ # packaging, and needs no JIT/torch artifact at inference time.
35
+
36
+ import argparse
37
+ import math
38
+ import random
39
+ import statistics
40
+ import time
41
+
42
+ from pyqrack import QrackAceBackend, Pauli
43
+ import pyqrack.qrack_ace_backend as _ace_mod
44
+
45
+ PI = math.pi
46
+
47
+ # Standard CHSH angle set.
48
+ THETA_A, THETA_AP = 0.0, PI / 2
49
+ THETA_B, THETA_BP = PI / 4, -PI / 4
50
+
51
+
52
+ def _apply_measurement_basis(sim, q, theta):
53
+ sim.r(Pauli.PauliY, theta, q)
54
+
55
+
56
+ def _measure_expectation(sim, shots):
57
+ results = sim.measure_shots([0, 1], shots)
58
+ total = 0
59
+ for res in results:
60
+ a = (res >> 0) & 1
61
+ b = (res >> 1) & 1
62
+ total += 1 if a == b else -1
63
+ return total / shots
64
+
65
+
66
+ def _expectation(theta1, theta2, shots, width=16, long_range_columns=1):
67
+ """One fresh Bell-pair-with-ancilla construction, measured at a given
68
+ basis-angle pair. This is exactly the bseq.py pattern: h(0), cx(0,1),
69
+ then a PauliY rotation on each half before measuring."""
70
+ s = QrackAceBackend(width, long_range_columns=long_range_columns)
71
+ s.h(0)
72
+ s.mcx([0], 1)
73
+ _apply_measurement_basis(s, 0, theta1)
74
+ _apply_measurement_basis(s, 1, theta2)
75
+ return _measure_expectation(s, shots)
76
+
77
+
78
+ def compute_chsh_components(shots, **kwargs):
79
+ """Return the 4 CHSH correlator terms (E(a,b), E(a,b'), E(a',b), E(a',b'))."""
80
+ return (
81
+ _expectation(THETA_A, THETA_B, shots, **kwargs),
82
+ _expectation(THETA_A, THETA_BP, shots, **kwargs),
83
+ _expectation(THETA_AP, THETA_B, shots, **kwargs),
84
+ _expectation(THETA_AP, THETA_BP, shots, **kwargs),
85
+ )
86
+
87
+
88
+ def compute_S(shots, **kwargs):
89
+ e_ab, e_abp, e_apb, e_apbp = compute_chsh_components(shots, **kwargs)
90
+ return e_ab + e_abp + e_apb - e_apbp
91
+
92
+
93
+ def set_correction(theta, phi, lam):
94
+ _ace_mod.QrackAceBackend._BSEQ_THETA = theta
95
+ _ace_mod.QrackAceBackend._BSEQ_PHI = phi
96
+ _ace_mod.QrackAceBackend._BSEQ_LAMBDA = lam
97
+
98
+
99
+ def mean_reward(theta, phi, lam, n_repeats, shots, **kwargs):
100
+ """The actual optimization target: mean S across many independent
101
+ Bell-pair-with-ancilla constructions ("an RCS data set for Bell-pair
102
+ perturbations of the ancillae"), not a single noisy reading."""
103
+ set_correction(theta, phi, lam)
104
+ vals = [compute_S(shots, **kwargs) for _ in range(n_repeats)]
105
+ return statistics.mean(vals)
106
+
107
+
108
+ def optimize(
109
+ n_repeats=4,
110
+ shots=64,
111
+ time_budget=200.0,
112
+ max_iterations=None,
113
+ seed=None,
114
+ initial_step=PI / 4,
115
+ step_decay=0.8,
116
+ decay_every=10,
117
+ ):
118
+ """Gradient-free random-walk search over (theta, phi, lambda),
119
+ maximizing mean_reward. Measurement sampling is not differentiable,
120
+ so this uses repeated empirical evaluation rather than backprop --
121
+ appropriate given the actual cost here is 3 scalars, not a network.
122
+ """
123
+ if seed is not None:
124
+ random.seed(seed)
125
+
126
+ best_params = (0.0, 0.0, 0.0)
127
+ best_score = mean_reward(*best_params, n_repeats, shots)
128
+ print(f"start: mean_S={best_score:.4f}")
129
+
130
+ t_start = time.time()
131
+ step = initial_step
132
+ iteration = 0
133
+ while True:
134
+ if max_iterations is not None and iteration >= max_iterations:
135
+ break
136
+ if time_budget is not None and (time.time() - t_start) >= time_budget:
137
+ break
138
+
139
+ candidate = tuple(p + random.uniform(-step, step) for p in best_params)
140
+ score = mean_reward(*candidate, n_repeats, shots)
141
+ if score > best_score:
142
+ best_score = score
143
+ best_params = candidate
144
+ print(
145
+ f"iter {iteration}: improved -> "
146
+ f"params={[round(p, 4) for p in candidate]} mean_S={score:.4f}"
147
+ )
148
+
149
+ iteration += 1
150
+ if iteration % decay_every == 0:
151
+ step *= step_decay
152
+
153
+ print()
154
+ print(f"FINAL params: {[round(p, 6) for p in best_params]}")
155
+ print(f"FINAL mean_S: {best_score:.4f} (classical bound: 2.0, Tsirelson: {2*math.sqrt(2):.4f})")
156
+ print(f"total iterations: {iteration}")
157
+ return best_params, best_score
158
+
159
+
160
+ def write_params_to_backend_file(theta, phi, lam):
161
+ """Patch the installed pyqrack/qrack_ace_backend.py in place, so the
162
+ fitted correction persists for future processes too, without
163
+ requiring a separate model-loading step at import time."""
164
+ import os
165
+ import re
166
+
167
+ path = os.path.join(os.path.dirname(_ace_mod.__file__), "qrack_ace_backend.py")
168
+ with open(path, "r") as f:
169
+ src = f.read()
170
+
171
+ src = re.sub(r"_BSEQ_THETA\s*=\s*[-0-9.eE]+", f"_BSEQ_THETA = {theta!r}", src)
172
+ src = re.sub(r"_BSEQ_PHI\s*=\s*[-0-9.eE]+", f"_BSEQ_PHI = {phi!r}", src)
173
+ src = re.sub(r"_BSEQ_LAMBDA\s*=\s*[-0-9.eE]+", f"_BSEQ_LAMBDA = {lam!r}", src)
174
+
175
+ with open(path, "w") as f:
176
+ f.write(src)
177
+ print(f"Wrote fitted angles into {path}")
178
+
179
+
180
+ def main():
181
+ parser = argparse.ArgumentParser(description=__doc__)
182
+ parser.add_argument("--iterations", type=int, default=None)
183
+ parser.add_argument("--time-budget", type=float, default=200.0)
184
+ parser.add_argument("--repeats", type=int, default=4)
185
+ parser.add_argument("--shots", type=int, default=64)
186
+ parser.add_argument("--seed", type=int, default=None)
187
+ parser.add_argument(
188
+ "--persist",
189
+ action="store_true",
190
+ help="Write the fitted angles into the installed backend file.",
191
+ )
192
+ args = parser.parse_args()
193
+
194
+ params, score = optimize(
195
+ n_repeats=args.repeats,
196
+ shots=args.shots,
197
+ time_budget=args.time_budget,
198
+ max_iterations=args.iterations,
199
+ seed=args.seed,
200
+ )
201
+
202
+ if args.persist:
203
+ write_params_to_backend_file(*params)
204
+
205
+
206
+ if __name__ == "__main__":
207
+ main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyqrack-cpu
3
- Version: 2.2.4
3
+ Version: 2.3.0
4
4
  Summary: pyqrack - Pure Python vm6502q/qrack Wrapper
5
5
  Home-page: https://github.com/vm6502q/pyqrack
6
6
  Author: Daniel Strano
@@ -17,6 +17,7 @@ pyqrack/qrack_stabilizer.py
17
17
  pyqrack/quimb_circuit_type.py
18
18
  pyqrack/qrack_system/__init__.py
19
19
  pyqrack/qrack_system/qrack_system.py
20
+ pyqrack/scripts/optimize_bseq_correction.py
20
21
  pyqrack/stats/__init__.py
21
22
  pyqrack/stats/load_quantized_data.py
22
23
  pyqrack/stats/quantize_by_range.py
@@ -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.2.4"
10
+ VERSION = "2.3.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')
File without changes
File without changes
File without changes
File without changes
File without changes