pyqrack-cuda-complex128 2.2.3__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.3 → pyqrack_cuda_complex128-2.3.0}/MANIFEST.in +1 -0
- {pyqrack_cuda_complex128-2.2.3/pyqrack_cuda_complex128.egg-info → pyqrack_cuda_complex128-2.3.0}/PKG-INFO +1 -1
- {pyqrack_cuda_complex128-2.2.3 → pyqrack_cuda_complex128-2.3.0}/pyqrack/qrack_ace_backend.py +416 -199
- pyqrack_cuda_complex128-2.3.0/pyqrack/scripts/optimize_bseq_correction.py +207 -0
- {pyqrack_cuda_complex128-2.2.3 → pyqrack_cuda_complex128-2.3.0/pyqrack_cuda_complex128.egg-info}/PKG-INFO +1 -1
- {pyqrack_cuda_complex128-2.2.3 → pyqrack_cuda_complex128-2.3.0}/pyqrack_cuda_complex128.egg-info/SOURCES.txt +1 -0
- {pyqrack_cuda_complex128-2.2.3 → pyqrack_cuda_complex128-2.3.0}/setup.py +1 -1
- {pyqrack_cuda_complex128-2.2.3 → pyqrack_cuda_complex128-2.3.0}/LICENSE +0 -0
- {pyqrack_cuda_complex128-2.2.3 → pyqrack_cuda_complex128-2.3.0}/Makefile +0 -0
- {pyqrack_cuda_complex128-2.2.3 → pyqrack_cuda_complex128-2.3.0}/README.md +0 -0
- {pyqrack_cuda_complex128-2.2.3 → pyqrack_cuda_complex128-2.3.0}/pyproject.toml +0 -0
- {pyqrack_cuda_complex128-2.2.3 → pyqrack_cuda_complex128-2.3.0}/pyqrack/__init__.py +0 -0
- {pyqrack_cuda_complex128-2.2.3 → pyqrack_cuda_complex128-2.3.0}/pyqrack/neuron_activation_fn.py +0 -0
- {pyqrack_cuda_complex128-2.2.3 → pyqrack_cuda_complex128-2.3.0}/pyqrack/pauli.py +0 -0
- {pyqrack_cuda_complex128-2.2.3 → pyqrack_cuda_complex128-2.3.0}/pyqrack/qrack_circuit.py +0 -0
- {pyqrack_cuda_complex128-2.2.3 → pyqrack_cuda_complex128-2.3.0}/pyqrack/qrack_near_clifford_qec_backend.py +0 -0
- {pyqrack_cuda_complex128-2.2.3 → pyqrack_cuda_complex128-2.3.0}/pyqrack/qrack_neuron.py +0 -0
- {pyqrack_cuda_complex128-2.2.3 → pyqrack_cuda_complex128-2.3.0}/pyqrack/qrack_neuron_torch_layer.py +0 -0
- {pyqrack_cuda_complex128-2.2.3 → pyqrack_cuda_complex128-2.3.0}/pyqrack/qrack_simulator.py +0 -0
- {pyqrack_cuda_complex128-2.2.3 → pyqrack_cuda_complex128-2.3.0}/pyqrack/qrack_stabilizer.py +0 -0
- {pyqrack_cuda_complex128-2.2.3 → pyqrack_cuda_complex128-2.3.0}/pyqrack/qrack_system/__init__.py +0 -0
- {pyqrack_cuda_complex128-2.2.3 → pyqrack_cuda_complex128-2.3.0}/pyqrack/qrack_system/qrack_system.py +0 -0
- {pyqrack_cuda_complex128-2.2.3 → pyqrack_cuda_complex128-2.3.0}/pyqrack/quimb_circuit_type.py +0 -0
- {pyqrack_cuda_complex128-2.2.3 → pyqrack_cuda_complex128-2.3.0}/pyqrack/stats/__init__.py +0 -0
- {pyqrack_cuda_complex128-2.2.3 → pyqrack_cuda_complex128-2.3.0}/pyqrack/stats/load_quantized_data.py +0 -0
- {pyqrack_cuda_complex128-2.2.3 → pyqrack_cuda_complex128-2.3.0}/pyqrack/stats/quantize_by_range.py +0 -0
- {pyqrack_cuda_complex128-2.2.3 → pyqrack_cuda_complex128-2.3.0}/pyqrack_cuda_complex128.egg-info/dependency_links.txt +0 -0
- {pyqrack_cuda_complex128-2.2.3 → pyqrack_cuda_complex128-2.3.0}/pyqrack_cuda_complex128.egg-info/not-zip-safe +0 -0
- {pyqrack_cuda_complex128-2.2.3 → pyqrack_cuda_complex128-2.3.0}/pyqrack_cuda_complex128.egg-info/requires.txt +0 -0
- {pyqrack_cuda_complex128-2.2.3 → pyqrack_cuda_complex128-2.3.0}/pyqrack_cuda_complex128.egg-info/top_level.txt +0 -0
- {pyqrack_cuda_complex128-2.2.3 → pyqrack_cuda_complex128-2.3.0}/setup.cfg +0 -0
{pyqrack_cuda_complex128-2.2.3 → 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)
|
|
@@ -191,7 +224,6 @@ def _cpauli_lhv(prob, targ, axis, anti, theta=math.pi):
|
|
|
191
224
|
elif axis == Pauli.PauliZ:
|
|
192
225
|
targ.rz(effective_theta)
|
|
193
226
|
|
|
194
|
-
|
|
195
227
|
class QrackAceBackend:
|
|
196
228
|
"""A back end for elided quantum error correction
|
|
197
229
|
|
|
@@ -212,6 +244,12 @@ class QrackAceBackend:
|
|
|
212
244
|
is_transpose(bool): Rows are long if False, columns are long if True
|
|
213
245
|
"""
|
|
214
246
|
|
|
247
|
+
# Sweepable vote weights for the 4-source boundary correction pool:
|
|
248
|
+
# [slot0, slot1, slot2, lhv]. Must sum to an odd number so the
|
|
249
|
+
# underlying hard-vote tally (independent of the continuous RMS
|
|
250
|
+
# formula) is always tie-free.
|
|
251
|
+
_LHV_VOTE_WEIGHTS = [2, 1, 1, 1]
|
|
252
|
+
|
|
215
253
|
def __init__(
|
|
216
254
|
self,
|
|
217
255
|
qubit_count=1,
|
|
@@ -276,7 +314,24 @@ class QrackAceBackend:
|
|
|
276
314
|
self._is_row_long_range[-1] = False
|
|
277
315
|
sim_count = col_patch_count * row_patch_count
|
|
278
316
|
|
|
317
|
+
# Boundary qubits no longer carry a private classical LHV proxy.
|
|
318
|
+
# Instead, every boundary site (row- and/or column-boundary alike)
|
|
319
|
+
# gets a real qubit in one single, shared "crossbar" QrackSimulator.
|
|
320
|
+
# That simulator's own greedy elision (set_sdrp) is trusted to
|
|
321
|
+
# automatically factor apart whatever boundary sites turn out to be
|
|
322
|
+
# separable (e.g. disjoint rails, rail intersections), exactly the
|
|
323
|
+
# same way it already factors apart unentangled subspaces within
|
|
324
|
+
# any other single QrackSimulator instance. We don't need to special
|
|
325
|
+
# -case "crossbar intersections" by hand; the elision does it for us.
|
|
326
|
+
boundary_sim_id = sim_count
|
|
327
|
+
boundary_count = 0
|
|
328
|
+
|
|
279
329
|
self._qubits = []
|
|
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 {}
|
|
280
335
|
sim_counts = [0] * sim_count
|
|
281
336
|
sim_id = 0
|
|
282
337
|
tot_qubits = 0
|
|
@@ -290,8 +345,21 @@ class QrackAceBackend:
|
|
|
290
345
|
qubit.append((t_sim_id, sim_counts[t_sim_id]))
|
|
291
346
|
sim_counts[t_sim_id] += 1
|
|
292
347
|
|
|
293
|
-
qubit.append(
|
|
294
|
-
|
|
348
|
+
qubit.append((boundary_sim_id, boundary_count))
|
|
349
|
+
boundary_count += 1
|
|
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
|
+
|
|
361
|
+
self._lhv[tot_qubits] = LHVQubit(
|
|
362
|
+
to_clone=(to_clone._lhv[tot_qubits] if to_clone else None)
|
|
295
363
|
)
|
|
296
364
|
|
|
297
365
|
if (not c) and (not r):
|
|
@@ -309,8 +377,23 @@ class QrackAceBackend:
|
|
|
309
377
|
self._qubits.append(qubit)
|
|
310
378
|
tot_qubits += 1
|
|
311
379
|
|
|
380
|
+
# The crossbar's size is fixed by how many boundary sites exist.
|
|
381
|
+
# When there are none (e.g. a grid small enough, relative to
|
|
382
|
+
# long_range_rows/columns, that the whole thing is "fully
|
|
383
|
+
# connected" with no QEC boundary at all), we must NOT allocate a
|
|
384
|
+
# 0-qubit QrackSimulator for the crossbar: a 0-qubit QrackSimulator
|
|
385
|
+
# can be constructed, but calling .clone() on one crashes the
|
|
386
|
+
# native Qrack core (segfault), and clone() is called routinely by
|
|
387
|
+
# measure_shots()/clone(). So the boundary sim is only created when
|
|
388
|
+
# boundary_count > 0, exactly mirroring how the original LHV-based
|
|
389
|
+
# code never instantiated anything for the boundary case when
|
|
390
|
+
# there were no boundary sites.
|
|
391
|
+
has_boundary = boundary_count > 0
|
|
392
|
+
if has_boundary:
|
|
393
|
+
sim_counts.append(boundary_count)
|
|
394
|
+
|
|
312
395
|
self.sim = []
|
|
313
|
-
for i in range(sim_count):
|
|
396
|
+
for i in range(sim_count + (1 if has_boundary else 0)):
|
|
314
397
|
self.sim.append(
|
|
315
398
|
to_clone.sim[i].clone()
|
|
316
399
|
if to_clone
|
|
@@ -326,11 +409,13 @@ class QrackAceBackend:
|
|
|
326
409
|
)
|
|
327
410
|
)
|
|
328
411
|
|
|
329
|
-
# You can still
|
|
412
|
+
# You can still "set_sdrp" later, after the constructor.
|
|
330
413
|
# if "QRACK_QUNIT_SEPARABILITY_THRESHOLD" not in os.environ:
|
|
331
414
|
# # (1 - 1 / sqrt(2)) / 4 (but empirically tuned)
|
|
332
415
|
# self.sim[i].set_sdrp(0.073223304703363119)
|
|
333
416
|
|
|
417
|
+
self._boundary_sim_id = boundary_sim_id if has_boundary else None
|
|
418
|
+
|
|
334
419
|
def clone(self):
|
|
335
420
|
return QrackAceBackend(to_clone=self)
|
|
336
421
|
|
|
@@ -405,6 +490,24 @@ class QrackAceBackend:
|
|
|
405
490
|
p1 = self.sim[q1[0]].prob(q1[1]) if isinstance(q1, tuple) else q1.prob()
|
|
406
491
|
p2 = self.sim[q2[0]].prob(q2[1]) if isinstance(q2, tuple) else q2.prob()
|
|
407
492
|
|
|
493
|
+
# When p1 and p2 are within floating-point noise of each other
|
|
494
|
+
# (self._epsilon), they carry no real information about which
|
|
495
|
+
# qubit is "more likely 1" -- e.g. a fresh target right after
|
|
496
|
+
# _cx_shadow's H() always reads ~0.5, indistinguishably from a
|
|
497
|
+
# genuinely-mixed control. This shadow mechanism can only return
|
|
498
|
+
# ONE definite classical pick per call, so resolving every such
|
|
499
|
+
# near-tie to a FIXED qubit (as a plain "<" or "<=" comparison
|
|
500
|
+
# would, once p1/p2 are within epsilon of each other) reintroduces
|
|
501
|
+
# a systematic bias -- it just moves the bias from "favors q1" to
|
|
502
|
+
# "always favors q2" instead of removing it. The aggregate
|
|
503
|
+
# statistics this shadow is meant to approximate (e.g. a real CX
|
|
504
|
+
# from a maximally-mixed control onto a fresh target makes BOTH
|
|
505
|
+
# qubits individually read prob=0.5, perfectly correlated) are
|
|
506
|
+
# only reproduced, across many circuit instances, if a genuine
|
|
507
|
+
# tie is broken at random rather than by a fixed rule.
|
|
508
|
+
if abs(p1 - p2) <= self._epsilon:
|
|
509
|
+
return (p1, q2) if random.random() < 0.5 else (p2, q1)
|
|
510
|
+
|
|
408
511
|
if p1 < p2:
|
|
409
512
|
return p2, q1
|
|
410
513
|
|
|
@@ -412,7 +515,16 @@ class QrackAceBackend:
|
|
|
412
515
|
|
|
413
516
|
def _cz_shadow(self, q1, q2):
|
|
414
517
|
prob_max, t = self._ct_pair_prob(q1, q2)
|
|
415
|
-
|
|
518
|
+
# NOTE: this must be ">=", not ">". H() applied to any qubit in a
|
|
519
|
+
# definite computational-basis state (e.g. a fresh boundary/ancilla
|
|
520
|
+
# qubit, as in _cx_shadow's H-sandwich) lands at EXACTLY prob=0.5,
|
|
521
|
+
# deterministically -- this is the ordinary case, not a rare
|
|
522
|
+
# floating-point tie. A strict "> 0.5" therefore silently no-ops
|
|
523
|
+
# the shadow gate every time it targets a fresh qubit, which
|
|
524
|
+
# breaks entanglement transfer for the extremely common case of a
|
|
525
|
+
# CX/CY/CZ from a maximally-mixed-looking control onto a fresh
|
|
526
|
+
# target (e.g. H(0); cx(0,1) for a Bell pair).
|
|
527
|
+
if prob_max >= (0.5 - self._epsilon):
|
|
416
528
|
if isinstance(t, tuple):
|
|
417
529
|
self.sim[t[0]].z(t[1])
|
|
418
530
|
else:
|
|
@@ -472,35 +584,23 @@ class QrackAceBackend:
|
|
|
472
584
|
|
|
473
585
|
@staticmethod
|
|
474
586
|
def _get_qb_lhv_indices(hq):
|
|
475
|
-
|
|
587
|
+
# Historically, index 2 (when present) pointed at a private
|
|
588
|
+
# classical LHVQubit proxy and had to be special-cased everywhere.
|
|
589
|
+
# It is now an ordinary (sim_id, idx) tuple into the shared
|
|
590
|
+
# boundary "crossbar" QrackSimulator, so it is just one more
|
|
591
|
+
# coupling target like every other index. We keep this helper's
|
|
592
|
+
# name and signature for minimal call-site churn; "lhv" is now
|
|
593
|
+
# always -1 (no index needs special-casing any more).
|
|
476
594
|
if len(hq) < 2:
|
|
477
595
|
qb = [0]
|
|
478
|
-
lhv = -1
|
|
479
596
|
elif len(hq) < 4:
|
|
480
|
-
qb = [0, 1]
|
|
481
|
-
lhv = 2
|
|
597
|
+
qb = [0, 1, 2]
|
|
482
598
|
else:
|
|
483
|
-
qb = [0, 1, 3, 4]
|
|
484
|
-
|
|
599
|
+
qb = [0, 1, 2, 3, 4]
|
|
600
|
+
lhv = -1
|
|
485
601
|
|
|
486
602
|
return qb, lhv
|
|
487
603
|
|
|
488
|
-
@staticmethod
|
|
489
|
-
def _get_lhv_bloch_angles(sim):
|
|
490
|
-
# Z axis
|
|
491
|
-
z = sim.bloch[2]
|
|
492
|
-
|
|
493
|
-
# X axis
|
|
494
|
-
x = sim.bloch[0]
|
|
495
|
-
|
|
496
|
-
# Y axis
|
|
497
|
-
y = sim.bloch[1]
|
|
498
|
-
|
|
499
|
-
inclination = math.atan2(math.sqrt(x**2 + y**2), z)
|
|
500
|
-
azimuth = math.atan2(y, x)
|
|
501
|
-
|
|
502
|
-
return azimuth, inclination
|
|
503
|
-
|
|
504
604
|
def _get_bloch_angles(self, hq):
|
|
505
605
|
sim = self.sim[hq[0]].clone()
|
|
506
606
|
q = hq[1]
|
|
@@ -543,9 +643,17 @@ class QrackAceBackend:
|
|
|
543
643
|
|
|
544
644
|
sim.mtrx([m00, m01, m10, m11], q)
|
|
545
645
|
|
|
646
|
+
@staticmethod
|
|
647
|
+
def _get_lhv_bloch_angles(sim):
|
|
648
|
+
z = sim.bloch[2]
|
|
649
|
+
x = sim.bloch[0]
|
|
650
|
+
y = sim.bloch[1]
|
|
651
|
+
inclination = math.atan2(math.sqrt(x**2 + y**2), z)
|
|
652
|
+
azimuth = math.atan2(y, x)
|
|
653
|
+
return azimuth, inclination
|
|
654
|
+
|
|
546
655
|
@staticmethod
|
|
547
656
|
def _rotate_lhv_to_bloch(sim, delta_azimuth, delta_inclination):
|
|
548
|
-
# Apply rotation as "Azimuth, Inclination" (AI)
|
|
549
657
|
cosA = math.cos(delta_azimuth)
|
|
550
658
|
sinA = math.sin(delta_azimuth)
|
|
551
659
|
cosI = math.cos(delta_inclination / 2)
|
|
@@ -558,27 +666,68 @@ class QrackAceBackend:
|
|
|
558
666
|
|
|
559
667
|
sim.mtrx([m00, m01, m10, m11])
|
|
560
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
|
+
|
|
561
710
|
def _correct(self, lq, phase=False, skip_rotation=False):
|
|
562
711
|
hq = self._unpack(lq)
|
|
563
712
|
|
|
564
713
|
if len(hq) == 1:
|
|
565
714
|
return
|
|
566
715
|
|
|
567
|
-
qb,
|
|
716
|
+
qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
|
|
568
717
|
|
|
569
718
|
if phase:
|
|
570
719
|
for q in qb:
|
|
571
720
|
b = hq[q]
|
|
572
721
|
self.sim[b[0]].h(b[1])
|
|
573
|
-
|
|
574
|
-
|
|
722
|
+
if lq in self._lhv:
|
|
723
|
+
self._lhv[lq].h()
|
|
575
724
|
|
|
576
725
|
if len(hq) == 5:
|
|
577
726
|
# RMS
|
|
578
727
|
p = [
|
|
579
728
|
self.sim[hq[0][0]].prob(hq[0][1]),
|
|
580
729
|
self.sim[hq[1][0]].prob(hq[1][1]),
|
|
581
|
-
hq[2].prob(),
|
|
730
|
+
self.sim[hq[2][0]].prob(hq[2][1]),
|
|
582
731
|
self.sim[hq[3][0]].prob(hq[3][1]),
|
|
583
732
|
self.sim[hq[4][0]].prob(hq[4][1]),
|
|
584
733
|
]
|
|
@@ -598,7 +747,7 @@ class QrackAceBackend:
|
|
|
598
747
|
result = (
|
|
599
748
|
(random.random() < 0.5)
|
|
600
749
|
if abs(eff_prob - 0.5) <= self._epsilon
|
|
601
|
-
else (eff_prob
|
|
750
|
+
else (eff_prob >= 0.5)
|
|
602
751
|
)
|
|
603
752
|
syndrome = (
|
|
604
753
|
[1 - p[0], 1 - p[1], 1 - p[2], 1 - p[3], 1 - p[4]]
|
|
@@ -607,91 +756,121 @@ class QrackAceBackend:
|
|
|
607
756
|
)
|
|
608
757
|
for q in range(5):
|
|
609
758
|
if syndrome[q] > (0.5 + self._epsilon):
|
|
610
|
-
|
|
611
|
-
hq[q].x()
|
|
612
|
-
else:
|
|
613
|
-
self.sim[hq[q][0]].x(hq[q][1])
|
|
759
|
+
self.sim[hq[q][0]].x(hq[q][1])
|
|
614
760
|
|
|
615
761
|
if not skip_rotation:
|
|
616
762
|
a, i = [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]
|
|
617
763
|
a[0], i[0] = self._get_bloch_angles(hq[0])
|
|
618
764
|
a[1], i[1] = self._get_bloch_angles(hq[1])
|
|
619
|
-
a[2], i[2] =
|
|
765
|
+
a[2], i[2] = self._get_bloch_angles(hq[2])
|
|
620
766
|
a[3], i[3] = self._get_bloch_angles(hq[3])
|
|
621
767
|
a[4], i[4] = self._get_bloch_angles(hq[4])
|
|
622
768
|
|
|
623
|
-
a_target =
|
|
624
|
-
i_target =
|
|
769
|
+
a_target = sum(a) / 5
|
|
770
|
+
i_target = sum(i) / 5
|
|
625
771
|
for x in range(5):
|
|
626
|
-
|
|
627
|
-
continue
|
|
628
|
-
a_target += a[x]
|
|
629
|
-
i_target += i[x]
|
|
630
|
-
|
|
631
|
-
a_target /= 5
|
|
632
|
-
i_target /= 5
|
|
633
|
-
for x in range(5):
|
|
634
|
-
if x == 2:
|
|
635
|
-
QrackAceBackend._rotate_lhv_to_bloch(
|
|
636
|
-
hq[x], a_target - a[x], i_target - i[x]
|
|
637
|
-
)
|
|
638
|
-
else:
|
|
639
|
-
self._rotate_to_bloch(hq[x], a_target - a[x], i_target - i[x])
|
|
772
|
+
self._rotate_to_bloch(hq[x], a_target - a[x], i_target - i[x])
|
|
640
773
|
|
|
641
774
|
else:
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
(
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
else:
|
|
775
|
+
lhv = self._lhv.get(lq)
|
|
776
|
+
if lhv is None:
|
|
777
|
+
# RMS
|
|
778
|
+
p = [
|
|
779
|
+
self.sim[hq[0][0]].prob(hq[0][1]),
|
|
780
|
+
self.sim[hq[1][0]].prob(hq[1][1]),
|
|
781
|
+
self.sim[hq[2][0]].prob(hq[2][1]),
|
|
782
|
+
]
|
|
783
|
+
# Balancing suggestion from Elara (the custom OpenAI GPT)
|
|
784
|
+
prms = math.sqrt((p[0] ** 2 + p[1] ** 2 + p[2] ** 2) / 3)
|
|
785
|
+
qrms = math.sqrt(((1 - p[0]) ** 2 + (1 - p[1]) ** 2 + (1 - p[2]) ** 2) / 3)
|
|
786
|
+
eff_prob = (prms + (1 - qrms)) / 2
|
|
787
|
+
result = (
|
|
788
|
+
(random.random() < 0.5)
|
|
789
|
+
if abs(eff_prob - 0.5) <= self._epsilon
|
|
790
|
+
else (eff_prob >= 0.5)
|
|
791
|
+
)
|
|
792
|
+
syndrome = [1 - p[0], 1 - p[1], 1 - p[2]] if result else [p[0], p[1], p[2]]
|
|
793
|
+
for q in range(3):
|
|
794
|
+
if syndrome[q] > (0.5 + self._epsilon):
|
|
663
795
|
self.sim[hq[q][0]].x(hq[q][1])
|
|
796
|
+
else:
|
|
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]
|
|
848
|
+
syndrome = [1 - x for x in p] if result else list(p)
|
|
849
|
+
for q in range(3):
|
|
850
|
+
if syndrome[q] > (0.5 + self._epsilon):
|
|
851
|
+
self.sim[hq[q][0]].x(hq[q][1])
|
|
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.
|
|
664
856
|
|
|
665
857
|
if not skip_rotation:
|
|
666
858
|
a, i = [0, 0, 0], [0, 0, 0]
|
|
667
859
|
a[0], i[0] = self._get_bloch_angles(hq[0])
|
|
668
860
|
a[1], i[1] = self._get_bloch_angles(hq[1])
|
|
669
|
-
a[2], i[2] =
|
|
670
|
-
|
|
671
|
-
a_target = 0
|
|
672
|
-
i_target = 0
|
|
673
|
-
for x in range(3):
|
|
674
|
-
if x == 2:
|
|
675
|
-
continue
|
|
676
|
-
a_target += a[x]
|
|
677
|
-
i_target += i[x]
|
|
861
|
+
a[2], i[2] = self._get_bloch_angles(hq[2])
|
|
678
862
|
|
|
679
|
-
a_target
|
|
680
|
-
i_target
|
|
863
|
+
a_target = sum(a) / 3
|
|
864
|
+
i_target = sum(i) / 3
|
|
681
865
|
for x in range(3):
|
|
682
|
-
|
|
683
|
-
QrackAceBackend._rotate_lhv_to_bloch(
|
|
684
|
-
hq[x], a_target - a[x], i_target - i[x]
|
|
685
|
-
)
|
|
686
|
-
else:
|
|
687
|
-
self._rotate_to_bloch(hq[x], a_target - a[x], i_target - i[x])
|
|
866
|
+
self._rotate_to_bloch(hq[x], a_target - a[x], i_target - i[x])
|
|
688
867
|
|
|
689
868
|
if phase:
|
|
690
869
|
for q in qb:
|
|
691
870
|
b = hq[q]
|
|
692
871
|
self.sim[b[0]].h(b[1])
|
|
693
|
-
|
|
694
|
-
|
|
872
|
+
if lq in self._lhv:
|
|
873
|
+
self._lhv[lq].h()
|
|
695
874
|
|
|
696
875
|
def apply_magnetic_bias(self, q, b):
|
|
697
876
|
if b == 0:
|
|
@@ -699,22 +878,13 @@ class QrackAceBackend:
|
|
|
699
878
|
b = math.exp(b)
|
|
700
879
|
for x in q:
|
|
701
880
|
hq = self._unpack(x)
|
|
702
|
-
for
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
math.atan(math.tan(i) * b) - i,
|
|
710
|
-
)
|
|
711
|
-
else:
|
|
712
|
-
a, i = self._get_bloch_angles(h)
|
|
713
|
-
self._rotate_to_bloch(
|
|
714
|
-
h,
|
|
715
|
-
math.atan(math.tan(a) * b) - a,
|
|
716
|
-
math.atan(math.tan(i) * b) - i,
|
|
717
|
-
)
|
|
881
|
+
for h in hq:
|
|
882
|
+
a, i = self._get_bloch_angles(h)
|
|
883
|
+
self._rotate_to_bloch(
|
|
884
|
+
h,
|
|
885
|
+
math.atan(math.tan(a) * b) - a,
|
|
886
|
+
math.atan(math.tan(i) * b) - i,
|
|
887
|
+
)
|
|
718
888
|
|
|
719
889
|
def u(self, lq, th, ph, lm):
|
|
720
890
|
hq = self._unpack(lq)
|
|
@@ -723,37 +893,42 @@ class QrackAceBackend:
|
|
|
723
893
|
self.sim[b[0]].u(b[1], th, ph, lm)
|
|
724
894
|
return
|
|
725
895
|
|
|
726
|
-
qb,
|
|
896
|
+
qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
|
|
727
897
|
|
|
728
898
|
for q in qb:
|
|
729
899
|
b = hq[q]
|
|
730
900
|
self.sim[b[0]].u(b[1], th, ph, lm)
|
|
731
901
|
|
|
732
|
-
|
|
733
|
-
|
|
902
|
+
lhv = self._lhv.get(lq)
|
|
903
|
+
if lhv is not None:
|
|
904
|
+
lhv.u(th, ph, lm)
|
|
734
905
|
|
|
735
906
|
# Correction deferred to next 2-qubit gate (_cpauli calls _correct)
|
|
736
907
|
|
|
737
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
|
+
|
|
738
912
|
hq = self._unpack(lq)
|
|
739
913
|
if len(hq) < 2:
|
|
740
914
|
b = hq[0]
|
|
741
915
|
self.sim[b[0]].r(p, th, b[1])
|
|
742
916
|
return
|
|
743
917
|
|
|
744
|
-
qb,
|
|
918
|
+
qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
|
|
745
919
|
|
|
746
920
|
for q in qb:
|
|
747
921
|
b = hq[q]
|
|
748
922
|
self.sim[b[0]].r(p, th, b[1])
|
|
749
923
|
|
|
750
|
-
|
|
751
|
-
if
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
924
|
+
lhv = self._lhv.get(lq)
|
|
925
|
+
if lhv is not None:
|
|
926
|
+
if p == Pauli.PauliX:
|
|
927
|
+
lhv.rx(th)
|
|
928
|
+
elif p == Pauli.PauliY:
|
|
929
|
+
lhv.ry(th)
|
|
930
|
+
elif p == Pauli.PauliZ:
|
|
931
|
+
lhv.rz(th)
|
|
757
932
|
|
|
758
933
|
# Correction deferred to next 2-qubit gate (_cpauli calls _correct)
|
|
759
934
|
|
|
@@ -766,14 +941,15 @@ class QrackAceBackend:
|
|
|
766
941
|
|
|
767
942
|
self._correct(lq)
|
|
768
943
|
|
|
769
|
-
qb,
|
|
944
|
+
qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
|
|
770
945
|
|
|
771
946
|
for q in qb:
|
|
772
947
|
b = hq[q]
|
|
773
948
|
self.sim[b[0]].h(b[1])
|
|
774
949
|
|
|
775
|
-
|
|
776
|
-
|
|
950
|
+
lhv = self._lhv.get(lq)
|
|
951
|
+
if lhv is not None:
|
|
952
|
+
lhv.h()
|
|
777
953
|
|
|
778
954
|
# Correction deferred to next 2-qubit gate (_cpauli calls _correct)
|
|
779
955
|
|
|
@@ -784,14 +960,15 @@ class QrackAceBackend:
|
|
|
784
960
|
self.sim[b[0]].s(b[1])
|
|
785
961
|
return
|
|
786
962
|
|
|
787
|
-
qb,
|
|
963
|
+
qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
|
|
788
964
|
|
|
789
965
|
for q in qb:
|
|
790
966
|
b = hq[q]
|
|
791
967
|
self.sim[b[0]].s(b[1])
|
|
792
968
|
|
|
793
|
-
|
|
794
|
-
|
|
969
|
+
lhv = self._lhv.get(lq)
|
|
970
|
+
if lhv is not None:
|
|
971
|
+
lhv.s()
|
|
795
972
|
|
|
796
973
|
def adjs(self, lq):
|
|
797
974
|
hq = self._unpack(lq)
|
|
@@ -800,14 +977,15 @@ class QrackAceBackend:
|
|
|
800
977
|
self.sim[b[0]].adjs(b[1])
|
|
801
978
|
return
|
|
802
979
|
|
|
803
|
-
qb,
|
|
980
|
+
qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
|
|
804
981
|
|
|
805
982
|
for q in qb:
|
|
806
983
|
b = hq[q]
|
|
807
984
|
self.sim[b[0]].adjs(b[1])
|
|
808
985
|
|
|
809
|
-
|
|
810
|
-
|
|
986
|
+
lhv = self._lhv.get(lq)
|
|
987
|
+
if lhv is not None:
|
|
988
|
+
lhv.adjs()
|
|
811
989
|
|
|
812
990
|
def x(self, lq):
|
|
813
991
|
hq = self._unpack(lq)
|
|
@@ -816,14 +994,15 @@ class QrackAceBackend:
|
|
|
816
994
|
self.sim[b[0]].x(b[1])
|
|
817
995
|
return
|
|
818
996
|
|
|
819
|
-
qb,
|
|
997
|
+
qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
|
|
820
998
|
|
|
821
999
|
for q in qb:
|
|
822
1000
|
b = hq[q]
|
|
823
1001
|
self.sim[b[0]].x(b[1])
|
|
824
1002
|
|
|
825
|
-
|
|
826
|
-
|
|
1003
|
+
lhv = self._lhv.get(lq)
|
|
1004
|
+
if lhv is not None:
|
|
1005
|
+
lhv.x()
|
|
827
1006
|
|
|
828
1007
|
def y(self, lq):
|
|
829
1008
|
hq = self._unpack(lq)
|
|
@@ -832,14 +1011,15 @@ class QrackAceBackend:
|
|
|
832
1011
|
self.sim[b[0]].y(b[1])
|
|
833
1012
|
return
|
|
834
1013
|
|
|
835
|
-
qb,
|
|
1014
|
+
qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
|
|
836
1015
|
|
|
837
1016
|
for q in qb:
|
|
838
1017
|
b = hq[q]
|
|
839
1018
|
self.sim[b[0]].y(b[1])
|
|
840
1019
|
|
|
841
|
-
|
|
842
|
-
|
|
1020
|
+
lhv = self._lhv.get(lq)
|
|
1021
|
+
if lhv is not None:
|
|
1022
|
+
lhv.y()
|
|
843
1023
|
|
|
844
1024
|
def z(self, lq):
|
|
845
1025
|
hq = self._unpack(lq)
|
|
@@ -848,14 +1028,15 @@ class QrackAceBackend:
|
|
|
848
1028
|
self.sim[b[0]].z(b[1])
|
|
849
1029
|
return
|
|
850
1030
|
|
|
851
|
-
qb,
|
|
1031
|
+
qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
|
|
852
1032
|
|
|
853
1033
|
for q in qb:
|
|
854
1034
|
b = hq[q]
|
|
855
1035
|
self.sim[b[0]].z(b[1])
|
|
856
1036
|
|
|
857
|
-
|
|
858
|
-
|
|
1037
|
+
lhv = self._lhv.get(lq)
|
|
1038
|
+
if lhv is not None:
|
|
1039
|
+
lhv.z()
|
|
859
1040
|
|
|
860
1041
|
def t(self, lq):
|
|
861
1042
|
hq = self._unpack(lq)
|
|
@@ -864,14 +1045,15 @@ class QrackAceBackend:
|
|
|
864
1045
|
self.sim[b[0]].t(b[1])
|
|
865
1046
|
return
|
|
866
1047
|
|
|
867
|
-
qb,
|
|
1048
|
+
qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
|
|
868
1049
|
|
|
869
1050
|
for q in qb:
|
|
870
1051
|
b = hq[q]
|
|
871
1052
|
self.sim[b[0]].t(b[1])
|
|
872
1053
|
|
|
873
|
-
|
|
874
|
-
|
|
1054
|
+
lhv = self._lhv.get(lq)
|
|
1055
|
+
if lhv is not None:
|
|
1056
|
+
lhv.t()
|
|
875
1057
|
|
|
876
1058
|
def adjt(self, lq):
|
|
877
1059
|
hq = self._unpack(lq)
|
|
@@ -880,14 +1062,15 @@ class QrackAceBackend:
|
|
|
880
1062
|
self.sim[b[0]].adjt(b[1])
|
|
881
1063
|
return
|
|
882
1064
|
|
|
883
|
-
qb,
|
|
1065
|
+
qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
|
|
884
1066
|
|
|
885
1067
|
for q in qb:
|
|
886
1068
|
b = hq[q]
|
|
887
1069
|
self.sim[b[0]].adjt(b[1])
|
|
888
1070
|
|
|
889
|
-
|
|
890
|
-
|
|
1071
|
+
lhv = self._lhv.get(lq)
|
|
1072
|
+
if lhv is not None:
|
|
1073
|
+
lhv.adjt()
|
|
891
1074
|
|
|
892
1075
|
def _get_gate(self, pauli, anti, sim_id):
|
|
893
1076
|
gate = None
|
|
@@ -927,15 +1110,11 @@ class QrackAceBackend:
|
|
|
927
1110
|
|
|
928
1111
|
return connected, boundary
|
|
929
1112
|
|
|
930
|
-
def _apply_coupling(self, pauli, anti, qb1,
|
|
1113
|
+
def _apply_coupling(self, pauli, anti, qb1, hq1, qb2, hq2, lq1_lr):
|
|
931
1114
|
for q1 in qb1:
|
|
932
|
-
if q1 == lhv1:
|
|
933
|
-
continue
|
|
934
1115
|
b1 = hq1[q1]
|
|
935
1116
|
gate_fn, shadow_fn = self._get_gate(pauli, anti, b1[0])
|
|
936
1117
|
for q2 in qb2:
|
|
937
|
-
if q2 == lhv2:
|
|
938
|
-
continue
|
|
939
1118
|
b2 = hq2[q2]
|
|
940
1119
|
if b1[0] == b2[0]:
|
|
941
1120
|
gate_fn([b1[1]], b2[1])
|
|
@@ -956,18 +1135,36 @@ class QrackAceBackend:
|
|
|
956
1135
|
|
|
957
1136
|
self._correct(lq1)
|
|
958
1137
|
|
|
959
|
-
qb1,
|
|
960
|
-
qb2,
|
|
961
|
-
# Apply cross coupling on
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
1138
|
+
qb1, _ = QrackAceBackend._get_qb_lhv_indices(hq1)
|
|
1139
|
+
qb2, _ = QrackAceBackend._get_qb_lhv_indices(hq2)
|
|
1140
|
+
# Apply cross coupling on every qubit, including former-LHV boundary
|
|
1141
|
+
# qubits, which now live as real qubits in the shared boundary sim.
|
|
1142
|
+
self._apply_coupling(pauli, anti, qb1, hq1, qb2, hq2, lq1_lr)
|
|
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
|
+
|
|
1165
|
+
if lq2 in self._lhv:
|
|
1166
|
+
ctrl_prob = self.sim[hq1[0][0]].prob(hq1[0][1])
|
|
1167
|
+
_cpauli_lhv(ctrl_prob, self._lhv[lq2], pauli, anti)
|
|
971
1168
|
|
|
972
1169
|
self._correct(lq1, True)
|
|
973
1170
|
if pauli != Pauli.PauliZ:
|
|
@@ -1064,7 +1261,7 @@ class QrackAceBackend:
|
|
|
1064
1261
|
p = [
|
|
1065
1262
|
self.sim[hq[0][0]].prob(hq[0][1]),
|
|
1066
1263
|
self.sim[hq[1][0]].prob(hq[1][1]),
|
|
1067
|
-
hq[2].prob(),
|
|
1264
|
+
self.sim[hq[2][0]].prob(hq[2][1]),
|
|
1068
1265
|
self.sim[hq[3][0]].prob(hq[3][1]),
|
|
1069
1266
|
self.sim[hq[4][0]].prob(hq[4][1]),
|
|
1070
1267
|
]
|
|
@@ -1081,15 +1278,32 @@ class QrackAceBackend:
|
|
|
1081
1278
|
/ 7
|
|
1082
1279
|
)
|
|
1083
1280
|
else:
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1281
|
+
lhv = self._lhv.get(lq)
|
|
1282
|
+
if lhv is None:
|
|
1283
|
+
# RMS
|
|
1284
|
+
p = [
|
|
1285
|
+
self.sim[hq[0][0]].prob(hq[0][1]),
|
|
1286
|
+
self.sim[hq[1][0]].prob(hq[1][1]),
|
|
1287
|
+
self.sim[hq[2][0]].prob(hq[2][1]),
|
|
1288
|
+
]
|
|
1289
|
+
# Balancing suggestion from Elara (the custom OpenAI GPT)
|
|
1290
|
+
prms = math.sqrt((p[0] ** 2 + p[1] ** 2 + p[2] ** 2) / 3)
|
|
1291
|
+
qrms = math.sqrt(((1 - p[0]) ** 2 + (1 - p[1]) ** 2 + (1 - p[2]) ** 2) / 3)
|
|
1292
|
+
else:
|
|
1293
|
+
p = [
|
|
1294
|
+
self.sim[hq[0][0]].prob(hq[0][1]),
|
|
1295
|
+
self.sim[hq[1][0]].prob(hq[1][1]),
|
|
1296
|
+
self.sim[hq[2][0]].prob(hq[2][1]),
|
|
1297
|
+
]
|
|
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)
|
|
1093
1307
|
|
|
1094
1308
|
return (prms + (1 - qrms)) / 2
|
|
1095
1309
|
|
|
@@ -1100,9 +1314,9 @@ class QrackAceBackend:
|
|
|
1100
1314
|
return self.sim[b[0]].m(b[1])
|
|
1101
1315
|
|
|
1102
1316
|
p = self.prob(lq)
|
|
1103
|
-
result = ((p + self._epsilon) >= 1
|
|
1317
|
+
result = ((p + self._epsilon) >= 1) or (random.random() < p)
|
|
1104
1318
|
|
|
1105
|
-
qb,
|
|
1319
|
+
qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
|
|
1106
1320
|
|
|
1107
1321
|
for q in qb:
|
|
1108
1322
|
b = hq[q]
|
|
@@ -1113,10 +1327,11 @@ class QrackAceBackend:
|
|
|
1113
1327
|
else:
|
|
1114
1328
|
self.sim[b[0]].force_m(b[1], result)
|
|
1115
1329
|
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1330
|
+
lhv = self._lhv.get(lq)
|
|
1331
|
+
if lhv is not None:
|
|
1332
|
+
lhv.reset()
|
|
1333
|
+
if result:
|
|
1334
|
+
lhv.x()
|
|
1120
1335
|
|
|
1121
1336
|
return result
|
|
1122
1337
|
|
|
@@ -1128,7 +1343,7 @@ class QrackAceBackend:
|
|
|
1128
1343
|
|
|
1129
1344
|
self._correct(lq)
|
|
1130
1345
|
|
|
1131
|
-
qb,
|
|
1346
|
+
qb, _ = QrackAceBackend._get_qb_lhv_indices(hq)
|
|
1132
1347
|
|
|
1133
1348
|
for q in qb:
|
|
1134
1349
|
b = hq[q]
|
|
@@ -1139,10 +1354,11 @@ class QrackAceBackend:
|
|
|
1139
1354
|
else:
|
|
1140
1355
|
self.sim[b[0]].force_m(b[1], result)
|
|
1141
1356
|
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1357
|
+
lhv = self._lhv.get(lq)
|
|
1358
|
+
if lhv is not None:
|
|
1359
|
+
lhv.reset()
|
|
1360
|
+
if result:
|
|
1361
|
+
lhv.x()
|
|
1146
1362
|
|
|
1147
1363
|
return result
|
|
1148
1364
|
|
|
@@ -1509,25 +1725,22 @@ class QrackAceBackend:
|
|
|
1509
1725
|
return self._coupling_map
|
|
1510
1726
|
|
|
1511
1727
|
coupling_map = set()
|
|
1512
|
-
|
|
1513
|
-
# i.e. the column count); self._col_length is the number of rows.
|
|
1514
|
-
num_rows, num_cols = self._col_length, self._row_length
|
|
1728
|
+
rows, cols = self._row_length, self._col_length
|
|
1515
1729
|
|
|
1516
|
-
#
|
|
1730
|
+
# Map each column index to its full list of logical qubit indices
|
|
1517
1731
|
def logical_index(row, col):
|
|
1518
|
-
return row *
|
|
1732
|
+
return row * cols + col
|
|
1519
1733
|
|
|
1520
|
-
for
|
|
1521
|
-
|
|
1522
|
-
for
|
|
1523
|
-
|
|
1734
|
+
for col in range(cols):
|
|
1735
|
+
connected_cols, _ = self._get_connected(col, False)
|
|
1736
|
+
for row in range(rows):
|
|
1737
|
+
connected_rows, _ = self._get_connected(row, False)
|
|
1524
1738
|
a = logical_index(row, col)
|
|
1525
|
-
for
|
|
1526
|
-
for
|
|
1739
|
+
for c in connected_cols:
|
|
1740
|
+
for r in connected_rows:
|
|
1527
1741
|
b = logical_index(r, c)
|
|
1528
1742
|
if a != b:
|
|
1529
1743
|
coupling_map.add((a, b))
|
|
1530
|
-
coupling_map.add((b, a))
|
|
1531
1744
|
|
|
1532
1745
|
self._coupling_map = sorted(coupling_map)
|
|
1533
1746
|
|
|
@@ -1541,11 +1754,9 @@ class QrackAceBackend:
|
|
|
1541
1754
|
)
|
|
1542
1755
|
noise_model = NoiseModel()
|
|
1543
1756
|
|
|
1757
|
+
# Single-qubit depolarizing only on boundary qubits
|
|
1544
1758
|
boundary_qubits = set()
|
|
1545
1759
|
for a, b in self.get_logical_coupling_map():
|
|
1546
|
-
if a > b:
|
|
1547
|
-
#Skip duplicates
|
|
1548
|
-
continue
|
|
1549
1760
|
col_a = a % self._row_length
|
|
1550
1761
|
col_b = b % self._row_length
|
|
1551
1762
|
is_long_a = self._is_col_long_range[col_a]
|
|
@@ -1556,6 +1767,12 @@ class QrackAceBackend:
|
|
|
1556
1767
|
if not is_long_b:
|
|
1557
1768
|
boundary_qubits.add(b)
|
|
1558
1769
|
|
|
1770
|
+
for q in boundary_qubits:
|
|
1771
|
+
for gate in ["u", "u1", "u2", "u3", "h", "x", "y", "z",
|
|
1772
|
+
"s", "sdg", "t", "tdg", "rx", "ry", "rz"]:
|
|
1773
|
+
noise_model.add_quantum_error(
|
|
1774
|
+
depolarizing_error(x, 1), gate, [q])
|
|
1775
|
+
|
|
1559
1776
|
# Two-qubit depolarizing on boundary-crossing and boundary-adjacent gates
|
|
1560
1777
|
for a, b in self.get_logical_coupling_map():
|
|
1561
1778
|
col_a = a % self._row_length
|
|
@@ -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.3 → 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.3 → 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.3 → pyqrack_cuda_complex128-2.3.0}/pyqrack/qrack_system/__init__.py
RENAMED
|
File without changes
|
{pyqrack_cuda_complex128-2.2.3 → pyqrack_cuda_complex128-2.3.0}/pyqrack/qrack_system/qrack_system.py
RENAMED
|
File without changes
|
{pyqrack_cuda_complex128-2.2.3 → pyqrack_cuda_complex128-2.3.0}/pyqrack/quimb_circuit_type.py
RENAMED
|
File without changes
|
|
File without changes
|
{pyqrack_cuda_complex128-2.2.3 → pyqrack_cuda_complex128-2.3.0}/pyqrack/stats/load_quantized_data.py
RENAMED
|
File without changes
|
{pyqrack_cuda_complex128-2.2.3 → 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
|