pyqrack-cuda-complex128 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.
- {pyqrack_cuda_complex128-2.2.4 → pyqrack_cuda_complex128-2.3.0}/MANIFEST.in +1 -0
- {pyqrack_cuda_complex128-2.2.4/pyqrack_cuda_complex128.egg-info → pyqrack_cuda_complex128-2.3.0}/PKG-INFO +1 -1
- {pyqrack_cuda_complex128-2.2.4 → pyqrack_cuda_complex128-2.3.0}/pyqrack/qrack_ace_backend.py +181 -31
- pyqrack_cuda_complex128-2.3.0/pyqrack/scripts/optimize_bseq_correction.py +207 -0
- {pyqrack_cuda_complex128-2.2.4 → pyqrack_cuda_complex128-2.3.0/pyqrack_cuda_complex128.egg-info}/PKG-INFO +1 -1
- {pyqrack_cuda_complex128-2.2.4 → pyqrack_cuda_complex128-2.3.0}/pyqrack_cuda_complex128.egg-info/SOURCES.txt +1 -0
- {pyqrack_cuda_complex128-2.2.4 → pyqrack_cuda_complex128-2.3.0}/setup.py +1 -1
- {pyqrack_cuda_complex128-2.2.4 → pyqrack_cuda_complex128-2.3.0}/LICENSE +0 -0
- {pyqrack_cuda_complex128-2.2.4 → pyqrack_cuda_complex128-2.3.0}/Makefile +0 -0
- {pyqrack_cuda_complex128-2.2.4 → pyqrack_cuda_complex128-2.3.0}/README.md +0 -0
- {pyqrack_cuda_complex128-2.2.4 → pyqrack_cuda_complex128-2.3.0}/pyproject.toml +0 -0
- {pyqrack_cuda_complex128-2.2.4 → pyqrack_cuda_complex128-2.3.0}/pyqrack/__init__.py +0 -0
- {pyqrack_cuda_complex128-2.2.4 → pyqrack_cuda_complex128-2.3.0}/pyqrack/neuron_activation_fn.py +0 -0
- {pyqrack_cuda_complex128-2.2.4 → pyqrack_cuda_complex128-2.3.0}/pyqrack/pauli.py +0 -0
- {pyqrack_cuda_complex128-2.2.4 → pyqrack_cuda_complex128-2.3.0}/pyqrack/qrack_circuit.py +0 -0
- {pyqrack_cuda_complex128-2.2.4 → pyqrack_cuda_complex128-2.3.0}/pyqrack/qrack_near_clifford_qec_backend.py +0 -0
- {pyqrack_cuda_complex128-2.2.4 → pyqrack_cuda_complex128-2.3.0}/pyqrack/qrack_neuron.py +0 -0
- {pyqrack_cuda_complex128-2.2.4 → pyqrack_cuda_complex128-2.3.0}/pyqrack/qrack_neuron_torch_layer.py +0 -0
- {pyqrack_cuda_complex128-2.2.4 → pyqrack_cuda_complex128-2.3.0}/pyqrack/qrack_simulator.py +0 -0
- {pyqrack_cuda_complex128-2.2.4 → pyqrack_cuda_complex128-2.3.0}/pyqrack/qrack_stabilizer.py +0 -0
- {pyqrack_cuda_complex128-2.2.4 → pyqrack_cuda_complex128-2.3.0}/pyqrack/qrack_system/__init__.py +0 -0
- {pyqrack_cuda_complex128-2.2.4 → pyqrack_cuda_complex128-2.3.0}/pyqrack/qrack_system/qrack_system.py +0 -0
- {pyqrack_cuda_complex128-2.2.4 → pyqrack_cuda_complex128-2.3.0}/pyqrack/quimb_circuit_type.py +0 -0
- {pyqrack_cuda_complex128-2.2.4 → pyqrack_cuda_complex128-2.3.0}/pyqrack/stats/__init__.py +0 -0
- {pyqrack_cuda_complex128-2.2.4 → pyqrack_cuda_complex128-2.3.0}/pyqrack/stats/load_quantized_data.py +0 -0
- {pyqrack_cuda_complex128-2.2.4 → pyqrack_cuda_complex128-2.3.0}/pyqrack/stats/quantize_by_range.py +0 -0
- {pyqrack_cuda_complex128-2.2.4 → pyqrack_cuda_complex128-2.3.0}/pyqrack_cuda_complex128.egg-info/dependency_links.txt +0 -0
- {pyqrack_cuda_complex128-2.2.4 → pyqrack_cuda_complex128-2.3.0}/pyqrack_cuda_complex128.egg-info/not-zip-safe +0 -0
- {pyqrack_cuda_complex128-2.2.4 → pyqrack_cuda_complex128-2.3.0}/pyqrack_cuda_complex128.egg-info/requires.txt +0 -0
- {pyqrack_cuda_complex128-2.2.4 → pyqrack_cuda_complex128-2.3.0}/pyqrack_cuda_complex128.egg-info/top_level.txt +0 -0
- {pyqrack_cuda_complex128-2.2.4 → pyqrack_cuda_complex128-2.3.0}/setup.cfg +0 -0
{pyqrack_cuda_complex128-2.2.4 → pyqrack_cuda_complex128-2.3.0}/pyqrack/qrack_ace_backend.py
RENAMED
|
@@ -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
|
|
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
|
-
#
|
|
706
|
-
#
|
|
707
|
-
#
|
|
708
|
-
#
|
|
709
|
-
#
|
|
710
|
-
#
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
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
|
|
732
|
-
# updated by
|
|
733
|
-
#
|
|
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
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
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()
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyqrack_cuda_complex128-2.2.4 → pyqrack_cuda_complex128-2.3.0}/pyqrack/neuron_activation_fn.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyqrack_cuda_complex128-2.2.4 → pyqrack_cuda_complex128-2.3.0}/pyqrack/qrack_neuron_torch_layer.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyqrack_cuda_complex128-2.2.4 → pyqrack_cuda_complex128-2.3.0}/pyqrack/qrack_system/__init__.py
RENAMED
|
File without changes
|
{pyqrack_cuda_complex128-2.2.4 → pyqrack_cuda_complex128-2.3.0}/pyqrack/qrack_system/qrack_system.py
RENAMED
|
File without changes
|
{pyqrack_cuda_complex128-2.2.4 → pyqrack_cuda_complex128-2.3.0}/pyqrack/quimb_circuit_type.py
RENAMED
|
File without changes
|
|
File without changes
|
{pyqrack_cuda_complex128-2.2.4 → pyqrack_cuda_complex128-2.3.0}/pyqrack/stats/load_quantized_data.py
RENAMED
|
File without changes
|
{pyqrack_cuda_complex128-2.2.4 → pyqrack_cuda_complex128-2.3.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
|