pyqrack-cpu-complex128 2.27.0__tar.gz → 2.28.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_cpu_complex128-2.27.0 → pyqrack_cpu_complex128-2.28.0}/Makefile +1 -1
- {pyqrack_cpu_complex128-2.27.0/pyqrack_cpu_complex128.egg-info → pyqrack_cpu_complex128-2.28.0}/PKG-INFO +1 -1
- {pyqrack_cpu_complex128-2.27.0 → pyqrack_cpu_complex128-2.28.0}/pyqrack/__init__.py +1 -0
- {pyqrack_cpu_complex128-2.27.0 → pyqrack_cpu_complex128-2.28.0}/pyqrack/qrack_ace_backend.py +21 -12
- pyqrack_cpu_complex128-2.28.0/pyqrack/qrack_ace_mps_backend.py +392 -0
- {pyqrack_cpu_complex128-2.27.0 → pyqrack_cpu_complex128-2.28.0/pyqrack_cpu_complex128.egg-info}/PKG-INFO +1 -1
- {pyqrack_cpu_complex128-2.27.0 → pyqrack_cpu_complex128-2.28.0}/pyqrack_cpu_complex128.egg-info/SOURCES.txt +1 -0
- {pyqrack_cpu_complex128-2.27.0 → pyqrack_cpu_complex128-2.28.0}/setup.py +1 -1
- {pyqrack_cpu_complex128-2.27.0 → pyqrack_cpu_complex128-2.28.0}/LICENSE +0 -0
- {pyqrack_cpu_complex128-2.27.0 → pyqrack_cpu_complex128-2.28.0}/MANIFEST.in +0 -0
- {pyqrack_cpu_complex128-2.27.0 → pyqrack_cpu_complex128-2.28.0}/README.md +0 -0
- {pyqrack_cpu_complex128-2.27.0 → pyqrack_cpu_complex128-2.28.0}/pyproject.toml +0 -0
- {pyqrack_cpu_complex128-2.27.0 → pyqrack_cpu_complex128-2.28.0}/pyqrack/neuron_activation_fn.py +0 -0
- {pyqrack_cpu_complex128-2.27.0 → pyqrack_cpu_complex128-2.28.0}/pyqrack/pauli.py +0 -0
- {pyqrack_cpu_complex128-2.27.0 → pyqrack_cpu_complex128-2.28.0}/pyqrack/qrack_circuit.py +0 -0
- {pyqrack_cpu_complex128-2.27.0 → pyqrack_cpu_complex128-2.28.0}/pyqrack/qrack_near_clifford_qec_backend.py +0 -0
- {pyqrack_cpu_complex128-2.27.0 → pyqrack_cpu_complex128-2.28.0}/pyqrack/qrack_neuron.py +0 -0
- {pyqrack_cpu_complex128-2.27.0 → pyqrack_cpu_complex128-2.28.0}/pyqrack/qrack_neuron_torch_layer.py +0 -0
- {pyqrack_cpu_complex128-2.27.0 → pyqrack_cpu_complex128-2.28.0}/pyqrack/qrack_simulator.py +0 -0
- {pyqrack_cpu_complex128-2.27.0 → pyqrack_cpu_complex128-2.28.0}/pyqrack/qrack_stabilizer.py +0 -0
- {pyqrack_cpu_complex128-2.27.0 → pyqrack_cpu_complex128-2.28.0}/pyqrack/qrack_system/__init__.py +0 -0
- {pyqrack_cpu_complex128-2.27.0 → pyqrack_cpu_complex128-2.28.0}/pyqrack/qrack_system/qrack_system.py +0 -0
- {pyqrack_cpu_complex128-2.27.0 → pyqrack_cpu_complex128-2.28.0}/pyqrack/quimb_circuit_type.py +0 -0
- {pyqrack_cpu_complex128-2.27.0 → pyqrack_cpu_complex128-2.28.0}/pyqrack/stats/__init__.py +0 -0
- {pyqrack_cpu_complex128-2.27.0 → pyqrack_cpu_complex128-2.28.0}/pyqrack/stats/load_quantized_data.py +0 -0
- {pyqrack_cpu_complex128-2.27.0 → pyqrack_cpu_complex128-2.28.0}/pyqrack/stats/quantize_by_range.py +0 -0
- {pyqrack_cpu_complex128-2.27.0 → pyqrack_cpu_complex128-2.28.0}/pyqrack_cpu_complex128.egg-info/dependency_links.txt +0 -0
- {pyqrack_cpu_complex128-2.27.0 → pyqrack_cpu_complex128-2.28.0}/pyqrack_cpu_complex128.egg-info/not-zip-safe +0 -0
- {pyqrack_cpu_complex128-2.27.0 → pyqrack_cpu_complex128-2.28.0}/pyqrack_cpu_complex128.egg-info/requires.txt +0 -0
- {pyqrack_cpu_complex128-2.27.0 → pyqrack_cpu_complex128-2.28.0}/pyqrack_cpu_complex128.egg-info/top_level.txt +0 -0
- {pyqrack_cpu_complex128-2.27.0 → pyqrack_cpu_complex128-2.28.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 5c5a54c0d44b6039e57de770f57a37b6e8caeafa; cd ..
|
|
34
34
|
endif
|
|
35
35
|
mkdir -p qrack/build
|
|
36
36
|
ifeq ($(UNAME_S),Linux)
|
|
@@ -7,6 +7,7 @@ from .pauli import Pauli
|
|
|
7
7
|
from .neuron_activation_fn import NeuronActivationFn
|
|
8
8
|
from .quimb_circuit_type import QuimbCircuitType
|
|
9
9
|
from .qrack_ace_backend import QrackAceBackend
|
|
10
|
+
from .qrack_ace_mps_backend import QrackAceMPSBackend, QrackMPSPatch
|
|
10
11
|
from .qrack_near_clifford_qec_backend import QrackNearCliffordQecBackend
|
|
11
12
|
from .qrack_circuit import QrackCircuit
|
|
12
13
|
from .qrack_neuron import QrackNeuron
|
{pyqrack_cpu_complex128-2.27.0 → pyqrack_cpu_complex128-2.28.0}/pyqrack/qrack_ace_backend.py
RENAMED
|
@@ -498,25 +498,34 @@ class QrackAceBackend:
|
|
|
498
498
|
# "Golden value"
|
|
499
499
|
self._sdrp = 0.0
|
|
500
500
|
|
|
501
|
+
sim_kwargs = dict(
|
|
502
|
+
is_schmidt_decompose_multi=is_schmidt_decompose_multi,
|
|
503
|
+
is_stabilizer_hybrid=is_stabilizer_hybrid,
|
|
504
|
+
is_binary_decision_tree=is_binary_decision_tree,
|
|
505
|
+
is_gpu=is_gpu,
|
|
506
|
+
is_host_pointer=is_host_pointer,
|
|
507
|
+
is_near_clifford_tableau_writer=is_near_clifford_tableau_writer,
|
|
508
|
+
noise=noise,
|
|
509
|
+
)
|
|
501
510
|
self.sim = []
|
|
502
511
|
for i in range(sim_count):
|
|
503
512
|
self.sim.append(
|
|
504
513
|
to_clone.sim[i].clone()
|
|
505
514
|
if to_clone
|
|
506
|
-
else
|
|
507
|
-
sim_counts[i],
|
|
508
|
-
is_schmidt_decompose_multi=is_schmidt_decompose_multi,
|
|
509
|
-
is_stabilizer_hybrid=is_stabilizer_hybrid,
|
|
510
|
-
is_binary_decision_tree=is_binary_decision_tree,
|
|
511
|
-
is_gpu=is_gpu,
|
|
512
|
-
is_host_pointer=is_host_pointer,
|
|
513
|
-
is_near_clifford_tableau_writer=is_near_clifford_tableau_writer,
|
|
514
|
-
noise=noise,
|
|
515
|
-
)
|
|
515
|
+
else self._new_patch_sim(sim_counts[i], sim_kwargs)
|
|
516
516
|
)
|
|
517
517
|
|
|
518
|
+
def _new_patch_sim(self, qubit_count, sim_kwargs):
|
|
519
|
+
# Factory for one patch's "just-in-time state machine." Nothing
|
|
520
|
+
# else in this class depends on the patch being a QrackSimulator:
|
|
521
|
+
# any object exposing the same gate/prob/m/force_m/clone interface
|
|
522
|
+
# can be substituted by overriding this (see QrackAceMPSBackend).
|
|
523
|
+
return QrackSimulator(qubit_count, **sim_kwargs)
|
|
524
|
+
|
|
518
525
|
def clone(self):
|
|
519
|
-
|
|
526
|
+
# type(self), not QrackAceBackend, so subclasses survive cloning
|
|
527
|
+
# (measure_shots() and run_qiskit_circuit() clone constantly).
|
|
528
|
+
return type(self)(to_clone=self)
|
|
520
529
|
|
|
521
530
|
def set_sdrp(self, sdrp):
|
|
522
531
|
sdrp = min(1, sdrp)
|
|
@@ -2707,7 +2716,7 @@ class QrackAceBackend:
|
|
|
2707
2716
|
self._classical_memory = 0
|
|
2708
2717
|
self._classical_register = 0
|
|
2709
2718
|
else:
|
|
2710
|
-
self._sim =
|
|
2719
|
+
self._sim = type(preamble_sim)(to_clone=preamble_sim)
|
|
2711
2720
|
self._classical_memory = preamble_memory
|
|
2712
2721
|
self._classical_register = preamble_register
|
|
2713
2722
|
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
# (C) Daniel Strano and the Qrack contributors 2017-2025. All rights reserved.
|
|
2
|
+
#
|
|
3
|
+
# Use of this source code is governed by an MIT-style license that can be
|
|
4
|
+
# found in the LICENSE file or at https://opensource.org/licenses/MIT.
|
|
5
|
+
#
|
|
6
|
+
# Produced with input from (Anthropic) Claude
|
|
7
|
+
#
|
|
8
|
+
# QrackAceBackend over matrix-product-state patches.
|
|
9
|
+
#
|
|
10
|
+
# QrackAceBackend's elision scheme (replicated boundary qubits, shadow
|
|
11
|
+
# couplers, _correct() reconciliation, LHV tie-breakers, error-detection
|
|
12
|
+
# gadgets) never inspects HOW a patch stores its state: it only drives each
|
|
13
|
+
# patch through a small "just-in-time state machine" interface -- single-
|
|
14
|
+
# qubit gates, (anti-)controlled Paulis, swap, prob(), m(), force_m(),
|
|
15
|
+
# clone(). QrackMPSPatch implements exactly that interface on an MPS, so
|
|
16
|
+
# every line of ACE's own logic runs unchanged on top of it.
|
|
17
|
+
#
|
|
18
|
+
# What MPS buys: a patch's cost is set by its entanglement (bond dimension
|
|
19
|
+
# chi), not by 2^(patch width), so patches far wider than a state vector
|
|
20
|
+
# could hold become possible when in-patch entanglement allows -- and, with
|
|
21
|
+
# max_bond/cutoff, a second, tunable approximation layer (truncation INSIDE
|
|
22
|
+
# a patch) composes with ACE's first one (elision BETWEEN patches).
|
|
23
|
+
#
|
|
24
|
+
# Implementation notes:
|
|
25
|
+
# * Plain numpy MPS, site tensors A[s] of shape (Dl, 2, Dr), with an
|
|
26
|
+
# explicitly tracked orthogonality center. (A quimb-backed prototype
|
|
27
|
+
# was ~180x slower inside ACE, almost entirely from re-deriving the
|
|
28
|
+
# orthogonality center on every prob() call -- and ACE calls prob()
|
|
29
|
+
# constantly. Tracking it explicitly makes prob() O(chi^2) plus the
|
|
30
|
+
# cost of moving the center, usually zero or one QR step.)
|
|
31
|
+
# * Logical qubits map to MPS sites through a permutation, so swap() is a
|
|
32
|
+
# free, exact relabeling -- the same reason ACE's own native swap is
|
|
33
|
+
# exact -- and non-adjacent 2-qubit gates are routed by adjacent SWAPs
|
|
34
|
+
# that are LEFT in place (relabeled), not undone.
|
|
35
|
+
# * Truncation: max_bond caps chi; cutoff is a per-split budget on
|
|
36
|
+
# discarded probability weight (sum of discarded s^2 / total s^2).
|
|
37
|
+
# set_sdrp() sets cutoff, as the MPS analog of Qrack's SDRP.
|
|
38
|
+
|
|
39
|
+
import math
|
|
40
|
+
import random
|
|
41
|
+
|
|
42
|
+
_IS_NUMPY_AVAILABLE = True
|
|
43
|
+
try:
|
|
44
|
+
import numpy as np
|
|
45
|
+
except:
|
|
46
|
+
_IS_NUMPY_AVAILABLE = False
|
|
47
|
+
|
|
48
|
+
from .pauli import Pauli
|
|
49
|
+
from .qrack_ace_backend import QrackAceBackend
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
_I2 = np.eye(2, dtype=complex)
|
|
53
|
+
_X = np.array([[0, 1], [1, 0]], dtype=complex)
|
|
54
|
+
_Y = np.array([[0, -1j], [1j, 0]], dtype=complex)
|
|
55
|
+
_Z = np.array([[1, 0], [0, -1]], dtype=complex)
|
|
56
|
+
_H = np.array([[1, 1], [1, -1]], dtype=complex) / math.sqrt(2)
|
|
57
|
+
_S = np.diag([1, 1j]).astype(complex)
|
|
58
|
+
_SDG = _S.conj().T
|
|
59
|
+
_T = np.diag([1, np.exp(1j * math.pi / 4)]).astype(complex)
|
|
60
|
+
_TDG = _T.conj().T
|
|
61
|
+
_SX = 0.5 * np.array([[1 + 1j, 1 - 1j], [1 - 1j, 1 + 1j]], dtype=complex)
|
|
62
|
+
_SXDG = _SX.conj().T
|
|
63
|
+
_P0 = np.diag([1, 0]).astype(complex)
|
|
64
|
+
_P1 = np.diag([0, 1]).astype(complex)
|
|
65
|
+
_SWAP = np.array(
|
|
66
|
+
[[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1]], dtype=complex
|
|
67
|
+
)
|
|
68
|
+
_PAULI_MTRX = {Pauli.PauliX: _X, Pauli.PauliY: _Y, Pauli.PauliZ: _Z}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _controlled(u, anti=False):
|
|
72
|
+
# 4x4 in the (control, target) basis, control = first (most significant).
|
|
73
|
+
g = np.eye(4, dtype=complex)
|
|
74
|
+
if anti:
|
|
75
|
+
g[0:2, 0:2] = u
|
|
76
|
+
else:
|
|
77
|
+
g[2:4, 2:4] = u
|
|
78
|
+
return g
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
_CX = _controlled(_X)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class QrackMPSPatch:
|
|
85
|
+
"""One ACE patch held as an MPS, exposing the subset of the
|
|
86
|
+
QrackSimulator interface that QrackAceBackend calls on its patches.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
qubit_count: patch width.
|
|
90
|
+
max_bond: bond-dimension cap (None = no cap).
|
|
91
|
+
cutoff: per-split discarded-weight budget (0 = exact up to
|
|
92
|
+
numerical zeros).
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
_ZERO_TOL = 1e-14
|
|
96
|
+
|
|
97
|
+
def __init__(self, qubit_count, max_bond=None, cutoff=0.0, _state=None):
|
|
98
|
+
if not _IS_NUMPY_AVAILABLE:
|
|
99
|
+
raise RuntimeError(
|
|
100
|
+
"Before instantiating QrackAceMPSPatch, you must install numpy!"
|
|
101
|
+
)
|
|
102
|
+
self.n = qubit_count
|
|
103
|
+
self.max_bond = max_bond
|
|
104
|
+
self.cutoff = float(cutoff)
|
|
105
|
+
self.truncation_error = 0.0 # accumulated discarded weight
|
|
106
|
+
if _state is not None:
|
|
107
|
+
self.A, self.site_of, self.q_at, self.center = _state
|
|
108
|
+
else:
|
|
109
|
+
t = np.zeros((1, 2, 1), dtype=complex)
|
|
110
|
+
t[0, 0, 0] = 1.0
|
|
111
|
+
self.A = [t.copy() for _ in range(max(1, qubit_count))]
|
|
112
|
+
self.site_of = list(range(qubit_count))
|
|
113
|
+
self.q_at = list(range(qubit_count))
|
|
114
|
+
self.center = 0
|
|
115
|
+
|
|
116
|
+
# --- bookkeeping -------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
def clone(self):
|
|
119
|
+
c = QrackMPSPatch(
|
|
120
|
+
self.n, self.max_bond, self.cutoff,
|
|
121
|
+
_state=([a.copy() for a in self.A], list(self.site_of), list(self.q_at), self.center),
|
|
122
|
+
)
|
|
123
|
+
c.truncation_error = self.truncation_error
|
|
124
|
+
return c
|
|
125
|
+
|
|
126
|
+
def set_sdrp(self, sdrp):
|
|
127
|
+
self.cutoff = max(0.0, float(sdrp))
|
|
128
|
+
|
|
129
|
+
def set_device(self, device_id):
|
|
130
|
+
# MPS patches run on the host; accepted for interface compatibility.
|
|
131
|
+
pass
|
|
132
|
+
|
|
133
|
+
def num_qubits(self):
|
|
134
|
+
return self.n
|
|
135
|
+
|
|
136
|
+
def max_bond_dim(self):
|
|
137
|
+
return max(a.shape[2] for a in self.A)
|
|
138
|
+
|
|
139
|
+
# --- canonical-form machinery -------------------------------------------
|
|
140
|
+
|
|
141
|
+
def _move_center(self, s):
|
|
142
|
+
A = self.A
|
|
143
|
+
while self.center < s:
|
|
144
|
+
c = self.center
|
|
145
|
+
dl, d, dr = A[c].shape
|
|
146
|
+
q, r = np.linalg.qr(A[c].reshape(dl * d, dr))
|
|
147
|
+
A[c] = q.reshape(dl, d, q.shape[1])
|
|
148
|
+
A[c + 1] = np.tensordot(r, A[c + 1], axes=(1, 0))
|
|
149
|
+
self.center += 1
|
|
150
|
+
while self.center > s:
|
|
151
|
+
c = self.center
|
|
152
|
+
dl, d, dr = A[c].shape
|
|
153
|
+
q, r = np.linalg.qr(A[c].reshape(dl, d * dr).T)
|
|
154
|
+
A[c] = q.T.reshape(q.shape[1], d, dr)
|
|
155
|
+
A[c - 1] = np.tensordot(A[c - 1], r.T, axes=(2, 0))
|
|
156
|
+
self.center -= 1
|
|
157
|
+
|
|
158
|
+
def _apply_site_1q(self, mat, s, unitary=True):
|
|
159
|
+
if not unitary:
|
|
160
|
+
# Non-unitary (projector): only safe at the orthogonality center.
|
|
161
|
+
self._move_center(s)
|
|
162
|
+
self.A[s] = np.einsum("ab,lbr->lar", mat, self.A[s])
|
|
163
|
+
|
|
164
|
+
def _apply_adjacent_2q(self, g4, s):
|
|
165
|
+
# g4 acts on (site s, site s+1), site s = most significant.
|
|
166
|
+
self._move_center(s)
|
|
167
|
+
a, b = self.A[s], self.A[s + 1]
|
|
168
|
+
dl, dr = a.shape[0], b.shape[2]
|
|
169
|
+
theta = np.tensordot(a, b, axes=(2, 0)) # (dl,2,2,dr)
|
|
170
|
+
theta = np.einsum("ijkl,aklb->aijb", g4.reshape(2, 2, 2, 2), theta)
|
|
171
|
+
u, sv, vh = np.linalg.svd(theta.reshape(dl * 2, 2 * dr), full_matrices=False)
|
|
172
|
+
w = sv ** 2
|
|
173
|
+
total = float(np.sum(w))
|
|
174
|
+
keep = len(sv)
|
|
175
|
+
# Drop numerical zeros always; drop more only within the budget.
|
|
176
|
+
tail = np.cumsum(w[::-1])[::-1] # tail[k] = sum_{j>=k} w[j]
|
|
177
|
+
budget = max(self.cutoff * total, self._ZERO_TOL * total)
|
|
178
|
+
while keep > 1 and tail[keep - 1] <= budget:
|
|
179
|
+
keep -= 1
|
|
180
|
+
if self.max_bond is not None and keep > self.max_bond:
|
|
181
|
+
keep = self.max_bond
|
|
182
|
+
discarded = float(np.sum(w[keep:]))
|
|
183
|
+
if discarded > 0 and total > 0:
|
|
184
|
+
self.truncation_error += discarded / total
|
|
185
|
+
u, sv, vh = u[:, :keep], sv[:keep], vh[:keep, :]
|
|
186
|
+
if discarded > 0:
|
|
187
|
+
# Restore the pre-truncation norm (the center carries it all).
|
|
188
|
+
sv = sv * math.sqrt(total / float(np.sum(sv ** 2)))
|
|
189
|
+
self.A[s] = u.reshape(dl, 2, keep)
|
|
190
|
+
self.A[s + 1] = (sv[:, None] * vh).reshape(keep, 2, dr)
|
|
191
|
+
self.center = s + 1
|
|
192
|
+
|
|
193
|
+
def _swap_sites(self, s):
|
|
194
|
+
# Physically exchange sites s, s+1 and relabel.
|
|
195
|
+
self._apply_adjacent_2q(_SWAP, s)
|
|
196
|
+
qa, qb = self.q_at[s], self.q_at[s + 1]
|
|
197
|
+
self.q_at[s], self.q_at[s + 1] = qb, qa
|
|
198
|
+
self.site_of[qa], self.site_of[qb] = s + 1, s
|
|
199
|
+
|
|
200
|
+
# --- gate application --------------------------------------------------
|
|
201
|
+
|
|
202
|
+
def _g1(self, mat, q, unitary=True):
|
|
203
|
+
self._apply_site_1q(mat, self.site_of[q], unitary)
|
|
204
|
+
|
|
205
|
+
def _g2(self, g4, q1, q2):
|
|
206
|
+
# g4 acts on (q1, q2), q1 = most significant.
|
|
207
|
+
if q1 == q2:
|
|
208
|
+
raise ValueError("Two-qubit gate on identical qubits.")
|
|
209
|
+
# Route q2 next to q1 with adjacent swaps, left in place.
|
|
210
|
+
while abs(self.site_of[q1] - self.site_of[q2]) > 1:
|
|
211
|
+
s1, s2 = self.site_of[q1], self.site_of[q2]
|
|
212
|
+
if s2 > s1:
|
|
213
|
+
self._swap_sites(s2 - 1)
|
|
214
|
+
else:
|
|
215
|
+
self._swap_sites(s2)
|
|
216
|
+
s1, s2 = self.site_of[q1], self.site_of[q2]
|
|
217
|
+
if s1 < s2:
|
|
218
|
+
self._apply_adjacent_2q(g4, s1)
|
|
219
|
+
else:
|
|
220
|
+
self._apply_adjacent_2q(_SWAP @ g4 @ _SWAP, s2)
|
|
221
|
+
|
|
222
|
+
def mtrx(self, m, q):
|
|
223
|
+
self._g1(np.array([[m[0], m[1]], [m[2], m[3]]], dtype=complex), q)
|
|
224
|
+
|
|
225
|
+
def u(self, q, th, ph, la):
|
|
226
|
+
c, s = math.cos(th / 2), math.sin(th / 2)
|
|
227
|
+
self._g1(np.array(
|
|
228
|
+
[[c, -np.exp(1j * la) * s],
|
|
229
|
+
[np.exp(1j * ph) * s, np.exp(1j * (ph + la)) * c]], dtype=complex), q)
|
|
230
|
+
|
|
231
|
+
def r(self, b, ph, q):
|
|
232
|
+
p = _PAULI_MTRX.get(b)
|
|
233
|
+
if p is None:
|
|
234
|
+
return # PauliI: global phase only
|
|
235
|
+
self._g1(math.cos(ph / 2) * _I2 - 1j * math.sin(ph / 2) * p, q)
|
|
236
|
+
|
|
237
|
+
def h(self, q): self._g1(_H, q)
|
|
238
|
+
def x(self, q): self._g1(_X, q)
|
|
239
|
+
def y(self, q): self._g1(_Y, q)
|
|
240
|
+
def z(self, q): self._g1(_Z, q)
|
|
241
|
+
def s(self, q): self._g1(_S, q)
|
|
242
|
+
def adjs(self, q): self._g1(_SDG, q)
|
|
243
|
+
def t(self, q): self._g1(_T, q)
|
|
244
|
+
def adjt(self, q): self._g1(_TDG, q)
|
|
245
|
+
def sx(self, q): self._g1(_SX, q)
|
|
246
|
+
def adjsx(self, q): self._g1(_SXDG, q)
|
|
247
|
+
|
|
248
|
+
def _toffoli(self, c1, c2, t):
|
|
249
|
+
self.h(t)
|
|
250
|
+
self._g2(_CX, c2, t); self.adjt(t)
|
|
251
|
+
self._g2(_CX, c1, t); self.t(t)
|
|
252
|
+
self._g2(_CX, c2, t); self.adjt(t)
|
|
253
|
+
self._g2(_CX, c1, t); self.t(t)
|
|
254
|
+
self.h(t)
|
|
255
|
+
self.t(c2)
|
|
256
|
+
self._g2(_CX, c1, c2); self.t(c1); self.adjt(c2)
|
|
257
|
+
self._g2(_CX, c1, c2)
|
|
258
|
+
|
|
259
|
+
def _mc_pauli(self, c, q, u, anti):
|
|
260
|
+
c = list(c)
|
|
261
|
+
if len(c) == 0:
|
|
262
|
+
self._g1(u, q)
|
|
263
|
+
elif len(c) == 1:
|
|
264
|
+
self._g2(_controlled(u, anti), c[0], q)
|
|
265
|
+
elif len(c) == 2:
|
|
266
|
+
# Exact 1-/2-qubit Toffoli decomposition, conjugated into
|
|
267
|
+
# X, Y, or Z on the target.
|
|
268
|
+
if anti:
|
|
269
|
+
for x in c:
|
|
270
|
+
self.x(x)
|
|
271
|
+
pre, post = None, None
|
|
272
|
+
if u is _Y:
|
|
273
|
+
pre, post = _SDG, _S
|
|
274
|
+
elif u is _Z:
|
|
275
|
+
pre, post = _H, _H
|
|
276
|
+
if pre is not None:
|
|
277
|
+
self._g1(pre, q)
|
|
278
|
+
self._toffoli(c[0], c[1], q)
|
|
279
|
+
if post is not None:
|
|
280
|
+
self._g1(post, q)
|
|
281
|
+
if anti:
|
|
282
|
+
for x in c:
|
|
283
|
+
self.x(x)
|
|
284
|
+
else:
|
|
285
|
+
raise NotImplementedError("QrackMPSPatch supports at most 2 controls per gate.")
|
|
286
|
+
|
|
287
|
+
def mcx(self, c, q): self._mc_pauli(c, q, _X, False)
|
|
288
|
+
def mcy(self, c, q): self._mc_pauli(c, q, _Y, False)
|
|
289
|
+
def mcz(self, c, q): self._mc_pauli(c, q, _Z, False)
|
|
290
|
+
def macx(self, c, q): self._mc_pauli(c, q, _X, True)
|
|
291
|
+
def macy(self, c, q): self._mc_pauli(c, q, _Y, True)
|
|
292
|
+
def macz(self, c, q): self._mc_pauli(c, q, _Z, True)
|
|
293
|
+
|
|
294
|
+
def swap(self, q1, q2):
|
|
295
|
+
# Exact and free: relabel which site each logical qubit lives on.
|
|
296
|
+
if q1 == q2:
|
|
297
|
+
return
|
|
298
|
+
s1, s2 = self.site_of[q1], self.site_of[q2]
|
|
299
|
+
self.site_of[q1], self.site_of[q2] = s2, s1
|
|
300
|
+
self.q_at[s1], self.q_at[s2] = q2, q1
|
|
301
|
+
|
|
302
|
+
def cswap(self, c, q1, q2):
|
|
303
|
+
c = list(c)
|
|
304
|
+
if q1 == q2:
|
|
305
|
+
return
|
|
306
|
+
if len(c) != 1:
|
|
307
|
+
raise NotImplementedError("QrackMPSPatch.cswap() supports exactly 1 control.")
|
|
308
|
+
self._g2(_CX, q2, q1)
|
|
309
|
+
self._toffoli(c[0], q1, q2)
|
|
310
|
+
self._g2(_CX, q2, q1)
|
|
311
|
+
|
|
312
|
+
# --- readout -----------------------------------------------------------
|
|
313
|
+
|
|
314
|
+
def prob(self, q):
|
|
315
|
+
s = self.site_of[q]
|
|
316
|
+
self._move_center(s)
|
|
317
|
+
a = self.A[s]
|
|
318
|
+
total = float(np.sum(np.abs(a) ** 2))
|
|
319
|
+
if total <= 0:
|
|
320
|
+
return 0.0
|
|
321
|
+
one = float(np.sum(np.abs(a[:, 1, :]) ** 2))
|
|
322
|
+
return min(1.0, max(0.0, one / total))
|
|
323
|
+
|
|
324
|
+
def force_m(self, q, r):
|
|
325
|
+
s = self.site_of[q]
|
|
326
|
+
self._move_center(s)
|
|
327
|
+
a = self.A[s].copy()
|
|
328
|
+
a[:, 0 if r else 1, :] = 0
|
|
329
|
+
nrm = math.sqrt(float(np.sum(np.abs(a) ** 2)))
|
|
330
|
+
if nrm > 0:
|
|
331
|
+
self.A[s] = a / nrm
|
|
332
|
+
return bool(r)
|
|
333
|
+
|
|
334
|
+
def m(self, q):
|
|
335
|
+
return self.force_m(q, random.random() < self.prob(q))
|
|
336
|
+
|
|
337
|
+
def m_all(self):
|
|
338
|
+
result = 0
|
|
339
|
+
for q in range(self.n):
|
|
340
|
+
if self.m(q):
|
|
341
|
+
result |= 1 << q
|
|
342
|
+
return result
|
|
343
|
+
|
|
344
|
+
# --- dense readout (testing only; 2^n) ----------------------------------
|
|
345
|
+
|
|
346
|
+
def out_ket(self):
|
|
347
|
+
v = self.A[0]
|
|
348
|
+
for a in self.A[1:]:
|
|
349
|
+
v = np.tensordot(v, a, axes=(v.ndim - 1, 0))
|
|
350
|
+
v = v.reshape([2] * self.n) # axis k = site k
|
|
351
|
+
# Reorder axes into Qrack's little-endian logical order: the
|
|
352
|
+
# C-order flatten treats axis 0 as most significant, so axis 0
|
|
353
|
+
# must be logical qubit n-1.
|
|
354
|
+
v = np.transpose(v, [self.site_of[q] for q in range(self.n - 1, -1, -1)])
|
|
355
|
+
return v.reshape(-1)
|
|
356
|
+
|
|
357
|
+
def out_probs(self):
|
|
358
|
+
return np.abs(self.out_ket()) ** 2
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
class QrackAceMPSBackend(QrackAceBackend):
|
|
362
|
+
"""QrackAceBackend whose patches are MPS (QrackMPSPatch) instead of
|
|
363
|
+
QrackSimulator. All elision, shadow-coupling, reconciliation and
|
|
364
|
+
error-detection logic is inherited unchanged.
|
|
365
|
+
|
|
366
|
+
Extra args:
|
|
367
|
+
max_bond: per-patch MPS bond-dimension cap (None = no cap).
|
|
368
|
+
mps_cutoff: per-split discarded-weight budget (0 = exact).
|
|
369
|
+
QrackSimulator-only construction options (is_gpu, is_stabilizer_hybrid,
|
|
370
|
+
...) are accepted and ignored.
|
|
371
|
+
"""
|
|
372
|
+
|
|
373
|
+
def __init__(self, *args, max_bond=None, mps_cutoff=0.0, to_clone=None, **kwargs):
|
|
374
|
+
if not _IS_NUMPY_AVAILABLE:
|
|
375
|
+
raise RuntimeError(
|
|
376
|
+
"Before instantiating QrackAceMPSBackend, you must install numpy!"
|
|
377
|
+
)
|
|
378
|
+
if to_clone is not None:
|
|
379
|
+
max_bond = to_clone.max_bond
|
|
380
|
+
mps_cutoff = to_clone.mps_cutoff
|
|
381
|
+
self.max_bond = max_bond
|
|
382
|
+
self.mps_cutoff = mps_cutoff
|
|
383
|
+
super().__init__(*args, to_clone=to_clone, **kwargs)
|
|
384
|
+
|
|
385
|
+
def _new_patch_sim(self, qubit_count, sim_kwargs):
|
|
386
|
+
return QrackMPSPatch(qubit_count, max_bond=self.max_bond, cutoff=self.mps_cutoff)
|
|
387
|
+
|
|
388
|
+
def max_bond_dims(self):
|
|
389
|
+
return [s.max_bond_dim() for s in self.sim]
|
|
390
|
+
|
|
391
|
+
def truncation_errors(self):
|
|
392
|
+
return [s.truncation_error for s in self.sim]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyqrack_cpu_complex128-2.27.0 → pyqrack_cpu_complex128-2.28.0}/pyqrack/neuron_activation_fn.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyqrack_cpu_complex128-2.27.0 → pyqrack_cpu_complex128-2.28.0}/pyqrack/qrack_neuron_torch_layer.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyqrack_cpu_complex128-2.27.0 → pyqrack_cpu_complex128-2.28.0}/pyqrack/qrack_system/__init__.py
RENAMED
|
File without changes
|
{pyqrack_cpu_complex128-2.27.0 → pyqrack_cpu_complex128-2.28.0}/pyqrack/qrack_system/qrack_system.py
RENAMED
|
File without changes
|
{pyqrack_cpu_complex128-2.27.0 → pyqrack_cpu_complex128-2.28.0}/pyqrack/quimb_circuit_type.py
RENAMED
|
File without changes
|
|
File without changes
|
{pyqrack_cpu_complex128-2.27.0 → pyqrack_cpu_complex128-2.28.0}/pyqrack/stats/load_quantized_data.py
RENAMED
|
File without changes
|
{pyqrack_cpu_complex128-2.27.0 → pyqrack_cpu_complex128-2.28.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
|