pyqrack-complex128 2.29.4__tar.gz → 2.30.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_complex128-2.29.4 → pyqrack_complex128-2.30.0}/Makefile +1 -1
- {pyqrack_complex128-2.29.4/pyqrack_complex128.egg-info → pyqrack_complex128-2.30.0}/PKG-INFO +1 -1
- {pyqrack_complex128-2.29.4 → pyqrack_complex128-2.30.0}/pyqrack/__init__.py +1 -0
- {pyqrack_complex128-2.29.4 → pyqrack_complex128-2.30.0}/pyqrack/qrack_ace_backend.py +51 -4
- pyqrack_complex128-2.30.0/pyqrack/qrack_bridge.py +266 -0
- {pyqrack_complex128-2.29.4 → pyqrack_complex128-2.30.0}/pyqrack/qrack_simulator.py +3 -3
- {pyqrack_complex128-2.29.4 → pyqrack_complex128-2.30.0/pyqrack_complex128.egg-info}/PKG-INFO +1 -1
- {pyqrack_complex128-2.29.4 → pyqrack_complex128-2.30.0}/pyqrack_complex128.egg-info/SOURCES.txt +1 -0
- {pyqrack_complex128-2.29.4 → pyqrack_complex128-2.30.0}/setup.py +1 -1
- {pyqrack_complex128-2.29.4 → pyqrack_complex128-2.30.0}/LICENSE +0 -0
- {pyqrack_complex128-2.29.4 → pyqrack_complex128-2.30.0}/MANIFEST.in +0 -0
- {pyqrack_complex128-2.29.4 → pyqrack_complex128-2.30.0}/README.md +0 -0
- {pyqrack_complex128-2.29.4 → pyqrack_complex128-2.30.0}/pyproject.toml +0 -0
- {pyqrack_complex128-2.29.4 → pyqrack_complex128-2.30.0}/pyqrack/neuron_activation_fn.py +0 -0
- {pyqrack_complex128-2.29.4 → pyqrack_complex128-2.30.0}/pyqrack/pauli.py +0 -0
- {pyqrack_complex128-2.29.4 → pyqrack_complex128-2.30.0}/pyqrack/qrack_ace_mps_backend.py +0 -0
- {pyqrack_complex128-2.29.4 → pyqrack_complex128-2.30.0}/pyqrack/qrack_circuit.py +0 -0
- {pyqrack_complex128-2.29.4 → pyqrack_complex128-2.30.0}/pyqrack/qrack_near_clifford_qec_backend.py +0 -0
- {pyqrack_complex128-2.29.4 → pyqrack_complex128-2.30.0}/pyqrack/qrack_neuron.py +0 -0
- {pyqrack_complex128-2.29.4 → pyqrack_complex128-2.30.0}/pyqrack/qrack_neuron_torch_layer.py +0 -0
- {pyqrack_complex128-2.29.4 → pyqrack_complex128-2.30.0}/pyqrack/qrack_stabilizer.py +0 -0
- {pyqrack_complex128-2.29.4 → pyqrack_complex128-2.30.0}/pyqrack/qrack_system/__init__.py +0 -0
- {pyqrack_complex128-2.29.4 → pyqrack_complex128-2.30.0}/pyqrack/qrack_system/qrack_system.py +0 -0
- {pyqrack_complex128-2.29.4 → pyqrack_complex128-2.30.0}/pyqrack/quimb_circuit_type.py +0 -0
- {pyqrack_complex128-2.29.4 → pyqrack_complex128-2.30.0}/pyqrack/stats/__init__.py +0 -0
- {pyqrack_complex128-2.29.4 → pyqrack_complex128-2.30.0}/pyqrack/stats/load_quantized_data.py +0 -0
- {pyqrack_complex128-2.29.4 → pyqrack_complex128-2.30.0}/pyqrack/stats/quantize_by_range.py +0 -0
- {pyqrack_complex128-2.29.4 → pyqrack_complex128-2.30.0}/pyqrack_complex128.egg-info/dependency_links.txt +0 -0
- {pyqrack_complex128-2.29.4 → pyqrack_complex128-2.30.0}/pyqrack_complex128.egg-info/not-zip-safe +0 -0
- {pyqrack_complex128-2.29.4 → pyqrack_complex128-2.30.0}/pyqrack_complex128.egg-info/requires.txt +0 -0
- {pyqrack_complex128-2.29.4 → pyqrack_complex128-2.30.0}/pyqrack_complex128.egg-info/top_level.txt +0 -0
- {pyqrack_complex128-2.29.4 → pyqrack_complex128-2.30.0}/setup.cfg +0 -0
|
@@ -30,7 +30,7 @@ build-deps:
|
|
|
30
30
|
rm -rf pyqrack/qrack_system/qrack_cl_precompile
|
|
31
31
|
ifneq ($(OS),Windows_NT)
|
|
32
32
|
ifeq ($(QRACK_PRESENT),)
|
|
33
|
-
git clone https://github.com/unitaryfund/qrack.git; cd qrack; git checkout
|
|
33
|
+
git clone https://github.com/unitaryfund/qrack.git; cd qrack; git checkout 98b474070d3b4ee373d8645ee413f26f4b81c2b4; cd ..
|
|
34
34
|
endif
|
|
35
35
|
mkdir -p qrack/build
|
|
36
36
|
ifeq ($(UNAME_S),Linux)
|
|
@@ -9,6 +9,7 @@ from .quimb_circuit_type import QuimbCircuitType
|
|
|
9
9
|
from .qrack_ace_backend import QrackAceBackend
|
|
10
10
|
from .qrack_ace_mps_backend import QrackAceMPSBackend, QrackMPSPatch
|
|
11
11
|
from .qrack_near_clifford_qec_backend import QrackNearCliffordQecBackend
|
|
12
|
+
from .qrack_bridge import QrackBridge
|
|
12
13
|
from .qrack_circuit import QrackCircuit
|
|
13
14
|
from .qrack_neuron import QrackNeuron
|
|
14
15
|
from .qrack_neuron_torch_layer import (
|
|
@@ -429,6 +429,7 @@ class QrackAceBackend:
|
|
|
429
429
|
# exactly mirroring how every other per-simulator allocation below
|
|
430
430
|
# already guards for "nothing to allocate."
|
|
431
431
|
has_boundary = self.use_crossbars and (boundary_count > 0)
|
|
432
|
+
self._crossbar_sim = boundary_sim_id if has_boundary else None
|
|
432
433
|
if has_boundary:
|
|
433
434
|
sim_counts.append(boundary_count)
|
|
434
435
|
sim_count += 1
|
|
@@ -1603,6 +1604,54 @@ class QrackAceBackend:
|
|
|
1603
1604
|
|
|
1604
1605
|
return connected, boundary
|
|
1605
1606
|
|
|
1607
|
+
def _update_lineage(self, lq1, hq1, lq2, hq2):
|
|
1608
|
+
# Lineage-tracking authority: a replica stays "clean" only while every
|
|
1609
|
+
# coupling it has taken part in was a REAL gate with a clean replica of
|
|
1610
|
+
# the partner. The authority for each participant becomes the clean
|
|
1611
|
+
# replicas that just met a clean partner replica in a shared simulator.
|
|
1612
|
+
# A replica with an unbroken real-gate history holds the qubit's full
|
|
1613
|
+
# coherent state, including entanglement accumulated across several
|
|
1614
|
+
# couplings -- e.g. the shared crossbar simulator, which holds a replica
|
|
1615
|
+
# of every boundary qubit, keeps a seam-to-seam subcircuit fully
|
|
1616
|
+
# coherent across every patch boundary. Only if no clean pair shares a
|
|
1617
|
+
# simulator does it fall back to the plain fresh-copy rule.
|
|
1618
|
+
if (not self.is_boundary_repetition_code) or self._in_gadget_capture or (lq1 is None) or (lq2 is None):
|
|
1619
|
+
return
|
|
1620
|
+
if (len(hq1) < 2) and (len(hq2) < 2):
|
|
1621
|
+
return
|
|
1622
|
+
|
|
1623
|
+
def clean(lq, hq):
|
|
1624
|
+
a = self._authority.get(lq) if len(hq) > 1 else None
|
|
1625
|
+
return set(range(len(hq))) if a is None else set(a)
|
|
1626
|
+
|
|
1627
|
+
c1, c2 = clean(lq1, hq1), clean(lq2, hq2)
|
|
1628
|
+
new1 = {i for i in c1 if any(hq1[i][0] == hq2[j][0] for j in c2)}
|
|
1629
|
+
new2 = {j for j in c2 if any(hq1[i][0] == hq2[j][0] for i in c1)}
|
|
1630
|
+
if not new1:
|
|
1631
|
+
sims2 = {b[0] for b in hq2}
|
|
1632
|
+
new1 = {i for i, b in enumerate(hq1) if b[0] in sims2}
|
|
1633
|
+
sims1 = {b[0] for b in hq1}
|
|
1634
|
+
new2 = {j for j, b in enumerate(hq2) if b[0] in sims1}
|
|
1635
|
+
# When both participants are boundary qubits, their crossbar replicas
|
|
1636
|
+
# (one shared simulator for every boundary qubit in the grid) are the
|
|
1637
|
+
# only copies guaranteed to see the WHOLE seam-to-seam history; copies
|
|
1638
|
+
# in a patch simulator can be left holding a pairwise correlation that
|
|
1639
|
+
# a later coupling elsewhere has made stale. So a clean crossbar pair
|
|
1640
|
+
# becomes the sole authority.
|
|
1641
|
+
cb = self._crossbar_sim
|
|
1642
|
+
if (cb is not None) and (len(hq1) > 1) and (len(hq2) > 1):
|
|
1643
|
+
i_cb = [i for i in new1 if hq1[i][0] == cb]
|
|
1644
|
+
j_cb = [j for j in new2 if hq2[j][0] == cb]
|
|
1645
|
+
if i_cb and j_cb:
|
|
1646
|
+
new1, new2 = set(i_cb), set(j_cb)
|
|
1647
|
+
for lq, hq, new in ((lq1, hq1, new1), (lq2, hq2, new2)):
|
|
1648
|
+
if len(hq) < 2 or not new:
|
|
1649
|
+
continue
|
|
1650
|
+
if len(new) == len(hq):
|
|
1651
|
+
self._authority.pop(lq, None)
|
|
1652
|
+
else:
|
|
1653
|
+
self._authority[lq] = new
|
|
1654
|
+
|
|
1606
1655
|
def _set_authority(self, lq, hq, partner_hq):
|
|
1607
1656
|
# Record which replica slot(s) of boundary qubit lq just received a
|
|
1608
1657
|
# REAL coupling gate (they share a simulator with the partner). See
|
|
@@ -1658,8 +1707,7 @@ class QrackAceBackend:
|
|
|
1658
1707
|
gate_fn, shadow_fn = self._get_gate(pauli, anti, sim_id)
|
|
1659
1708
|
_, idx2 = hq2[i]
|
|
1660
1709
|
gate_fn([idx1], idx2)
|
|
1661
|
-
self.
|
|
1662
|
-
self._set_authority(lq2, hq2, hq1)
|
|
1710
|
+
self._update_lineage(lq1, hq1, lq2, hq2)
|
|
1663
1711
|
return
|
|
1664
1712
|
|
|
1665
1713
|
# Logical-level error-detection gadget: an earlier version of this
|
|
@@ -1761,8 +1809,7 @@ class QrackAceBackend:
|
|
|
1761
1809
|
shadow_fn(b1, b2)
|
|
1762
1810
|
shadow_targets.append(b2)
|
|
1763
1811
|
|
|
1764
|
-
self.
|
|
1765
|
-
self._set_authority(lq2, hq2, hq1)
|
|
1812
|
+
self._update_lineage(lq1, hq1, lq2, hq2)
|
|
1766
1813
|
|
|
1767
1814
|
if not self._in_gadget_capture:
|
|
1768
1815
|
if pauli != Pauli.PauliZ:
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
# Bridged patching on QrackSimulator patches, recompressed through out_ket()/in_ket().
|
|
2
|
+
#
|
|
3
|
+
# By (Anthropic) Claude and Dan Strano
|
|
4
|
+
#
|
|
5
|
+
# Two-patch split: patch A = qubits [0, n // 2), patch B = the rest (first patch rounded down).
|
|
6
|
+
#
|
|
7
|
+
# Representation. Each patch is ONE QrackSimulator holding its data qubits (low indices) plus exactly
|
|
8
|
+
# b bond qubits (high indices), allocated and released as the bridge grows and shrinks. Bond j of
|
|
9
|
+
# patch A is paired with bond j of patch B. The represented state is both patch states contracted on
|
|
10
|
+
# the live bond pairs with the unnormalized virtual Bell projection sum_k <k|<k|:
|
|
11
|
+
#
|
|
12
|
+
# |Psi> ~ sum_k <k|psi_A> (x) <k|psi_B> (k over the 2^b bond basis states)
|
|
13
|
+
#
|
|
14
|
+
# i.e. with each patch's ket read as a (2^n_patch x 2^b) matrix M_A, M_B: Psi = M_A M_B^T.
|
|
15
|
+
# Rank chi = 2^b. Every in-patch gate is applied natively, once, to all chi branches at once.
|
|
16
|
+
#
|
|
17
|
+
# Cross-cut gates by gate teleportation through a fresh bond pair (exact, gates only):
|
|
18
|
+
# controlled-U from c (one patch) to t (the other):
|
|
19
|
+
# control patch: CNOT c -> bond_b (copy the control into the bond)
|
|
20
|
+
# target patch: H bond_b ; controlled-U bond_b -> t
|
|
21
|
+
# Contracting on (bond_b, bond_b) enforces equality, which is exactly controlled-U(c -> t).
|
|
22
|
+
# SWAP is 3 CNOTs; iSWAP is (S x S), CZ, SWAP; iSWAP^dagger is SWAP, CZ, (S^dag x S^dag).
|
|
23
|
+
#
|
|
24
|
+
# Recompression after each bridged two-qubit gate: out_ket() both patches, thin QR of each
|
|
25
|
+
# (2^n_patch x chi) matrix, SVD of the chi x chi core, keep the largest singular values (exact when
|
|
26
|
+
# only zeros are dropped; above max_bonds, greedy or Born-rule group selection), and in_ket() back.
|
|
27
|
+
# The singular values are absorbed into patch A, so patch B's bond columns stay orthonormal (B is
|
|
28
|
+
# isometric), which makes patch A's own marginal over its data qubits the exact marginal.
|
|
29
|
+
# Nothing larger than the kets themselves and a chi x chi core is ever formed: no density matrix,
|
|
30
|
+
# no (2^n x 2^n) object of any kind.
|
|
31
|
+
#
|
|
32
|
+
# Readout (exact for the stored state): sample patch A's data natively with its own measure_shots
|
|
33
|
+
# (exact because B is isometric); for each distinct outcome a, patch B's conditional state is
|
|
34
|
+
# M_B @ M_A[a, :], sampled in numpy from the exported B ket.
|
|
35
|
+
#
|
|
36
|
+
# fidelity_estimate multiplies the kept Schmidt weight (greedy) or sum_g p_g^2 (born) per cut.
|
|
37
|
+
|
|
38
|
+
import ctypes
|
|
39
|
+
import math
|
|
40
|
+
|
|
41
|
+
import numpy as np
|
|
42
|
+
from .qrack_simulator import QrackSimulator
|
|
43
|
+
from .qrack_system import Qrack
|
|
44
|
+
|
|
45
|
+
# Ket I/O straight into/out of numpy buffers (PyQrack's out_ket()/in_ket() round-trip through Python
|
|
46
|
+
# lists of complex, which dominated runtime). Same library calls, OutKet/InKet, without the copies.
|
|
47
|
+
_REAL = np.float32 if Qrack.fppow < 6 else np.float64
|
|
48
|
+
_CREAL = ctypes.c_float if Qrack.fppow < 6 else ctypes.c_double
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _out_ket(sim):
|
|
52
|
+
buf = np.empty(2 << sim.num_qubits(), dtype=_REAL)
|
|
53
|
+
Qrack.qrack_lib.OutKet(sim.sid, buf.ctypes.data_as(ctypes.POINTER(_CREAL)))
|
|
54
|
+
sim._throw_if_error()
|
|
55
|
+
return buf.view(np.complex64 if _REAL is np.float32 else np.complex128).astype(complex)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _in_ket(sim, v):
|
|
59
|
+
buf = np.ascontiguousarray(np.asarray(v, dtype=complex).view(np.float64).astype(_REAL))
|
|
60
|
+
Qrack.qrack_lib.InKet(sim.sid, buf.ctypes.data_as(ctypes.POINTER(_CREAL)))
|
|
61
|
+
sim._throw_if_error()
|
|
62
|
+
|
|
63
|
+
_X = [0, 1, 1, 0]
|
|
64
|
+
_Y = [0, -1j, 1j, 0]
|
|
65
|
+
_Z = [1, 0, 0, -1]
|
|
66
|
+
_TOL = 1e-6 # relative singular-value floor treated as exactly zero (Qrack is single precision)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class QrackBridge:
|
|
70
|
+
def __init__(self, qubit_count, max_bonds=6, seed=None, truncation="greedy", _clone=None):
|
|
71
|
+
if _clone is not None:
|
|
72
|
+
src = _clone
|
|
73
|
+
self.n, self.qa, self.qb, self.side = src.n, src.qa, src.qb, src.side
|
|
74
|
+
self.max_bonds, self.truncation = src.max_bonds, src.truncation
|
|
75
|
+
self.sim = [s.clone() for s in src.sim]
|
|
76
|
+
self.b = src.b
|
|
77
|
+
self.fidelity_estimate, self.truncations, self.peak_bonds = src.fidelity_estimate, src.truncations, src.peak_bonds
|
|
78
|
+
self._defer = False
|
|
79
|
+
self.rng = np.random.default_rng(int(src.rng.integers(1 << 62)))
|
|
80
|
+
return
|
|
81
|
+
if truncation not in ("greedy", "born"):
|
|
82
|
+
raise ValueError("truncation must be 'greedy' or 'born'")
|
|
83
|
+
self.n = qubit_count
|
|
84
|
+
self.qa = list(range(qubit_count // 2))
|
|
85
|
+
self.qb = list(range(qubit_count // 2, qubit_count))
|
|
86
|
+
self.side = {q: (0, i) for i, q in enumerate(self.qa)}
|
|
87
|
+
self.side.update({q: (1, i) for i, q in enumerate(self.qb)})
|
|
88
|
+
self.max_bonds = max_bonds
|
|
89
|
+
self.truncation = truncation
|
|
90
|
+
self.sim = [QrackSimulator(len(self.qa)), QrackSimulator(len(self.qb))]
|
|
91
|
+
self.b = 0
|
|
92
|
+
self.fidelity_estimate = 1.0
|
|
93
|
+
self.truncations = 0
|
|
94
|
+
self.peak_bonds = 0
|
|
95
|
+
self._defer = False
|
|
96
|
+
self.rng = np.random.default_rng(seed)
|
|
97
|
+
|
|
98
|
+
def clone(self):
|
|
99
|
+
return QrackBridge(None, _clone=self)
|
|
100
|
+
|
|
101
|
+
def _nd(self, p):
|
|
102
|
+
return len(self.qa) if p == 0 else len(self.qb)
|
|
103
|
+
|
|
104
|
+
def _loc(self, q):
|
|
105
|
+
p, i = self.side[q]
|
|
106
|
+
return self.sim[p], p, i
|
|
107
|
+
|
|
108
|
+
# --- local gates ---------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
def u(self, q, th, ph, lm):
|
|
111
|
+
s, _, i = self._loc(q)
|
|
112
|
+
s.u(i, th, ph, lm)
|
|
113
|
+
|
|
114
|
+
def x(self, q):
|
|
115
|
+
s, _, i = self._loc(q)
|
|
116
|
+
s.x(i)
|
|
117
|
+
|
|
118
|
+
def h(self, q):
|
|
119
|
+
s, _, i = self._loc(q)
|
|
120
|
+
s.h(i)
|
|
121
|
+
|
|
122
|
+
def s(self, q):
|
|
123
|
+
s, _, i = self._loc(q)
|
|
124
|
+
s.s(i)
|
|
125
|
+
|
|
126
|
+
def adjs(self, q):
|
|
127
|
+
s, _, i = self._loc(q)
|
|
128
|
+
s.adjs(i)
|
|
129
|
+
|
|
130
|
+
# --- controlled gates, local or bridged ----------------------------------
|
|
131
|
+
|
|
132
|
+
def _controlled(self, c, t, mtrx, anti):
|
|
133
|
+
sc, pc, ic = self._loc(c)
|
|
134
|
+
st, pt, it = self._loc(t)
|
|
135
|
+
if pc == pt:
|
|
136
|
+
(sc.macmtrx if anti else sc.mcmtrx)([ic], mtrx, it)
|
|
137
|
+
return
|
|
138
|
+
kc = self._nd(pc) + self.b
|
|
139
|
+
kt = self._nd(pt) + self.b
|
|
140
|
+
sc.allocate_qubit(kc)
|
|
141
|
+
st.allocate_qubit(kt)
|
|
142
|
+
(sc.macx if anti else sc.mcx)([ic], kc)
|
|
143
|
+
st.h(kt)
|
|
144
|
+
st.mcmtrx([kt], mtrx, it)
|
|
145
|
+
self.b += 1
|
|
146
|
+
self.peak_bonds = max(self.peak_bonds, self.b)
|
|
147
|
+
# Inside a SWAP-type gate, recompress exactly between its CNOTs; truncate once at its end.
|
|
148
|
+
self._compress(exact_only=self._defer)
|
|
149
|
+
|
|
150
|
+
def mcx(self, c, t): self._controlled(c[0], t, _X, False)
|
|
151
|
+
def mcy(self, c, t): self._controlled(c[0], t, _Y, False)
|
|
152
|
+
def mcz(self, c, t): self._controlled(c[0], t, _Z, False)
|
|
153
|
+
def macx(self, c, t): self._controlled(c[0], t, _X, True)
|
|
154
|
+
def macy(self, c, t): self._controlled(c[0], t, _Y, True)
|
|
155
|
+
def macz(self, c, t): self._controlled(c[0], t, _Z, True)
|
|
156
|
+
|
|
157
|
+
def cx(self, a, b): self.mcx([a], b)
|
|
158
|
+
def cy(self, a, b): self.mcy([a], b)
|
|
159
|
+
def cz(self, a, b): self.mcz([a], b)
|
|
160
|
+
|
|
161
|
+
def _composite(self, steps):
|
|
162
|
+
outer = self._defer
|
|
163
|
+
self._defer = True
|
|
164
|
+
for f in steps:
|
|
165
|
+
f()
|
|
166
|
+
self._defer = outer
|
|
167
|
+
if not outer:
|
|
168
|
+
self._compress()
|
|
169
|
+
|
|
170
|
+
def swap(self, a, b):
|
|
171
|
+
if self.side[a][0] == self.side[b][0]:
|
|
172
|
+
s, _, i = self._loc(a)
|
|
173
|
+
s.swap(i, self.side[b][1])
|
|
174
|
+
return
|
|
175
|
+
self._composite([lambda: self.mcx([a], b), lambda: self.mcx([b], a), lambda: self.mcx([a], b)])
|
|
176
|
+
|
|
177
|
+
def iswap(self, a, b):
|
|
178
|
+
if self.side[a][0] == self.side[b][0]:
|
|
179
|
+
s, _, i = self._loc(a)
|
|
180
|
+
s.iswap(i, self.side[b][1])
|
|
181
|
+
return
|
|
182
|
+
self._composite([lambda: self.s(a), lambda: self.s(b), lambda: self.mcz([a], b), lambda: self.swap(a, b)])
|
|
183
|
+
|
|
184
|
+
def adjiswap(self, a, b):
|
|
185
|
+
if self.side[a][0] == self.side[b][0]:
|
|
186
|
+
s, _, i = self._loc(a)
|
|
187
|
+
s.adjiswap(i, self.side[b][1])
|
|
188
|
+
return
|
|
189
|
+
self._composite([lambda: self.swap(a, b), lambda: self.mcz([a], b), lambda: self.adjs(a), lambda: self.adjs(b)])
|
|
190
|
+
|
|
191
|
+
# --- recompression through the kets --------------------------------------
|
|
192
|
+
|
|
193
|
+
def _matrices(self):
|
|
194
|
+
"""Each patch ket as a (2^n_patch x 2^b) matrix over the live bonds (dead slots are |0>)."""
|
|
195
|
+
out = []
|
|
196
|
+
for p in (0, 1):
|
|
197
|
+
nd = self._nd(p)
|
|
198
|
+
v = _out_ket(self.sim[p])
|
|
199
|
+
out.append(v.reshape(1 << self.b, 1 << nd).T) # (data, bond): bond = high qubits
|
|
200
|
+
return out
|
|
201
|
+
|
|
202
|
+
def _store(self, p, mat, b_new):
|
|
203
|
+
nd = self._nd(p)
|
|
204
|
+
full = np.zeros((1 << b_new, 1 << nd), dtype=complex)
|
|
205
|
+
full[: mat.shape[1], :] = mat.T
|
|
206
|
+
v = full.reshape(-1)
|
|
207
|
+
v = v / np.linalg.norm(v)
|
|
208
|
+
self.sim[p] = QrackSimulator(nd + b_new)
|
|
209
|
+
_in_ket(self.sim[p], v)
|
|
210
|
+
|
|
211
|
+
def _compress(self, exact_only=False):
|
|
212
|
+
if self.b == 0:
|
|
213
|
+
return
|
|
214
|
+
ma, mb = self._matrices()
|
|
215
|
+
qa_, ra = np.linalg.qr(ma)
|
|
216
|
+
qb_, rb = np.linalg.qr(mb)
|
|
217
|
+
u, sv, vh = np.linalg.svd(ra @ rb.T)
|
|
218
|
+
if sv[0] <= 0:
|
|
219
|
+
return
|
|
220
|
+
live = int(np.sum(sv > _TOL * sv[0]))
|
|
221
|
+
b_exact = int(math.ceil(math.log2(max(live, 1))))
|
|
222
|
+
b_new = b_exact if exact_only else min(b_exact, self.max_bonds)
|
|
223
|
+
keep = min(live, 1 << b_new)
|
|
224
|
+
w = sv ** 2
|
|
225
|
+
if keep < live:
|
|
226
|
+
self.truncations += 1
|
|
227
|
+
if self.truncation == "greedy":
|
|
228
|
+
idx = np.arange(keep)
|
|
229
|
+
self.fidelity_estimate *= float(np.sum(w[:keep]) / np.sum(w[:live]))
|
|
230
|
+
else:
|
|
231
|
+
groups = [np.arange(i, min(i + keep, live)) for i in range(0, live, keep)]
|
|
232
|
+
pg = np.array([np.sum(w[g]) for g in groups]) / np.sum(w[:live])
|
|
233
|
+
idx = groups[int(self.rng.choice(len(groups), p=pg))]
|
|
234
|
+
self.fidelity_estimate *= float(np.sum(pg ** 2))
|
|
235
|
+
else:
|
|
236
|
+
idx = np.arange(keep)
|
|
237
|
+
new_a = (qa_ @ u[:, idx]) * sv[idx][None, :] # singular values absorbed into patch A
|
|
238
|
+
new_b = qb_ @ vh[idx, :].T # orthonormal columns: patch B isometric
|
|
239
|
+
self._store(0, new_a, b_new)
|
|
240
|
+
self._store(1, new_b, b_new)
|
|
241
|
+
self.b = b_new
|
|
242
|
+
|
|
243
|
+
# --- readout -----------------------------------------------------------------
|
|
244
|
+
|
|
245
|
+
def measure_shots(self, qubits, shots):
|
|
246
|
+
self._compress(exact_only=True) # canonical: patch B isometric
|
|
247
|
+
na, nb = len(self.qa), len(self.qb)
|
|
248
|
+
a_draws = self.sim[0].measure_shots(list(range(na)), shots) # exact marginal of patch A
|
|
249
|
+
ma, mb = self._matrices()
|
|
250
|
+
out = []
|
|
251
|
+
for a, cnt in zip(*np.unique(np.asarray(a_draws, dtype=np.int64), return_counts=True)):
|
|
252
|
+
amp = mb @ ma[a, :]
|
|
253
|
+
pb = np.abs(amp) ** 2
|
|
254
|
+
pb /= pb.sum()
|
|
255
|
+
for bidx in self.rng.choice(len(pb), size=int(cnt), p=pb):
|
|
256
|
+
full = int(a) | (int(bidx) << na)
|
|
257
|
+
out.append(sum(((full >> q) & 1) << j for j, q in enumerate(qubits)))
|
|
258
|
+
self.rng.shuffle(out)
|
|
259
|
+
return out
|
|
260
|
+
|
|
261
|
+
def out_ket(self):
|
|
262
|
+
"""Dense contracted state (2^n), little-endian over logical qubits; for testing."""
|
|
263
|
+
ma, mb = self._matrices()
|
|
264
|
+
psi = (ma @ mb.T) # [a, b]
|
|
265
|
+
v = psi.T.reshape(-1) # index = a + 2^na * b
|
|
266
|
+
return v / np.linalg.norm(v)
|
|
@@ -452,7 +452,7 @@ class QrackSimulator:
|
|
|
452
452
|
Qrack.qrack_lib.Exp(
|
|
453
453
|
self.sid,
|
|
454
454
|
len(b),
|
|
455
|
-
QrackSimulator.
|
|
455
|
+
QrackSimulator._int_byref(b),
|
|
456
456
|
ctypes.c_double(ph),
|
|
457
457
|
QrackSimulator._ulonglong_byref(q),
|
|
458
458
|
)
|
|
@@ -971,7 +971,7 @@ class QrackSimulator:
|
|
|
971
971
|
Qrack.qrack_lib.MCExp(
|
|
972
972
|
self.sid,
|
|
973
973
|
len(b),
|
|
974
|
-
QrackSimulator.
|
|
974
|
+
QrackSimulator._int_byref(b),
|
|
975
975
|
ctypes.c_double(ph),
|
|
976
976
|
len(cs),
|
|
977
977
|
QrackSimulator._ulonglong_byref(cs),
|
|
@@ -3117,7 +3117,7 @@ class QrackSimulator:
|
|
|
3117
3117
|
if len(b) != len(q):
|
|
3118
3118
|
raise RuntimeError("Lengths of list parameters are mismatched.")
|
|
3119
3119
|
result = Qrack.qrack_lib.JointEnsembleProbability(
|
|
3120
|
-
self.sid, len(b), QrackSimulator.
|
|
3120
|
+
self.sid, len(b), QrackSimulator._int_byref(b), q
|
|
3121
3121
|
)
|
|
3122
3122
|
self._throw_if_error()
|
|
3123
3123
|
return result
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyqrack_complex128-2.29.4 → pyqrack_complex128-2.30.0}/pyqrack/qrack_near_clifford_qec_backend.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyqrack_complex128-2.29.4 → pyqrack_complex128-2.30.0}/pyqrack/qrack_system/qrack_system.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyqrack_complex128-2.29.4 → pyqrack_complex128-2.30.0}/pyqrack/stats/load_quantized_data.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyqrack_complex128-2.29.4 → pyqrack_complex128-2.30.0}/pyqrack_complex128.egg-info/not-zip-safe
RENAMED
|
File without changes
|
{pyqrack_complex128-2.29.4 → pyqrack_complex128-2.30.0}/pyqrack_complex128.egg-info/requires.txt
RENAMED
|
File without changes
|
{pyqrack_complex128-2.29.4 → pyqrack_complex128-2.30.0}/pyqrack_complex128.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|