qec 0.2.0__py3-none-any.whl → 0.2.1__py3-none-any.whl
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.
- qec/stabilizer_code/css_code.py +605 -2
- qec/stabilizer_code/stabilizer_code.py +125 -143
- qec/utils/binary_pauli_utils.py +3 -1
- qec/utils/codetables_de_utils.py +2 -0
- {qec-0.2.0.dist-info → qec-0.2.1.dist-info}/METADATA +1 -1
- qec-0.2.1.dist-info/RECORD +16 -0
- qec-0.2.0.dist-info/RECORD +0 -16
- {qec-0.2.0.dist-info → qec-0.2.1.dist-info}/LICENSE +0 -0
- {qec-0.2.0.dist-info → qec-0.2.1.dist-info}/WHEEL +0 -0
- {qec-0.2.0.dist-info → qec-0.2.1.dist-info}/top_level.txt +0 -0
qec/stabilizer_code/css_code.py
CHANGED
@@ -1,6 +1,609 @@
|
|
1
1
|
from qec.stabilizer_code import StabilizerCode
|
2
|
+
from qec.utils.sparse_binary_utils import convert_to_binary_scipy_sparse
|
3
|
+
|
4
|
+
# Added / ammended from old code
|
5
|
+
from typing import Union, Tuple
|
6
|
+
import numpy as np
|
7
|
+
import ldpc.mod2
|
8
|
+
import scipy
|
9
|
+
from ldpc import BpOsdDecoder
|
10
|
+
from tqdm import tqdm
|
11
|
+
import time
|
12
|
+
import logging
|
13
|
+
from typing import Optional
|
14
|
+
|
15
|
+
logging.basicConfig(level=logging.DEBUG)
|
2
16
|
|
3
17
|
|
4
18
|
class CSSCode(StabilizerCode):
|
5
|
-
|
6
|
-
|
19
|
+
"""
|
20
|
+
A class for generating and manipulating Calderbank-Shor-Steane (CSS) quantum error-correcting codes.
|
21
|
+
|
22
|
+
Prameters
|
23
|
+
---------
|
24
|
+
x_stabilizer_matrix (hx): Union[np.ndarray, scipy.sparse.spmatrix]
|
25
|
+
The X-check matrix.
|
26
|
+
z_stabilizer_matrix (hz): Union[np.ndarray, scipy.sparse.spmatrix]
|
27
|
+
The Z-check matrix.
|
28
|
+
name: str, optional
|
29
|
+
A name for this CSS code. Defaults to "CSS code".
|
30
|
+
|
31
|
+
Attributes
|
32
|
+
----------
|
33
|
+
x_stabilizer_matrix (hx): Union[np.ndarray, scipy.sparse.spmatrix]
|
34
|
+
The X-check matrix.
|
35
|
+
z_stabilizer_matrix (hz): Union[np.ndarray, scipy.sparse.spmatrix]
|
36
|
+
The Z-check matrix.
|
37
|
+
name (str):
|
38
|
+
A name for this CSS code.
|
39
|
+
physical_qubit_count (N): int
|
40
|
+
The number of physical qubits in the code.
|
41
|
+
logical_qubit_count (K): int
|
42
|
+
The number of logical qubits in the code. Dimension of the code.
|
43
|
+
code_distance (d): int
|
44
|
+
(Not computed by default) Minimum distance of the code.
|
45
|
+
x_logical_operator_basis (lx): (Union[np.ndarray, scipy.sparse.spmatrix]
|
46
|
+
Logical X operator basis.
|
47
|
+
z_logical_operator_basis (lz): (Union[np.ndarray, scipy.sparse.spmatrix]
|
48
|
+
Logical Z operator basis.
|
49
|
+
"""
|
50
|
+
|
51
|
+
def __init__(
|
52
|
+
self,
|
53
|
+
x_stabilizer_matrix: Union[np.ndarray, scipy.sparse.spmatrix],
|
54
|
+
z_stabilizer_matrix: Union[np.ndarray, scipy.sparse.spmatrix],
|
55
|
+
name: str = None,
|
56
|
+
):
|
57
|
+
"""
|
58
|
+
Initialise a new instance of the CSSCode class.
|
59
|
+
|
60
|
+
Parameters
|
61
|
+
----------
|
62
|
+
x_stabilizer_matrix (hx): Union[np.ndarray, scipy.sparse.spmatrix]
|
63
|
+
The X-check matrix.
|
64
|
+
z_stabilizer_matrix (hz): Union[np.ndarray, scipy.sparse.spmatrix]
|
65
|
+
The Z-check matrix.
|
66
|
+
name: str, optional
|
67
|
+
A name for this CSS code. Defaults to "CSS code".
|
68
|
+
"""
|
69
|
+
|
70
|
+
# Assign a default name if none is provided
|
71
|
+
if name is None:
|
72
|
+
self.name = "CSS code"
|
73
|
+
else:
|
74
|
+
self.name = name
|
75
|
+
|
76
|
+
self.x_logical_operator_basis = None
|
77
|
+
self.z_logical_operator_basis = None
|
78
|
+
|
79
|
+
# Check if the input matrices are NumPy arrays or SciPy sparse matrices
|
80
|
+
if not isinstance(x_stabilizer_matrix, (np.ndarray, scipy.sparse.spmatrix)):
|
81
|
+
raise TypeError(
|
82
|
+
"Please provide x and z stabilizer matrices as either a numpy array or a scipy sparse matrix."
|
83
|
+
)
|
84
|
+
|
85
|
+
# Convert matrices to sparse representation and set them as class attributes (replaced the old code "convert_to_sparse")
|
86
|
+
self.x_stabilizer_matrix = convert_to_binary_scipy_sparse(x_stabilizer_matrix)
|
87
|
+
self.z_stabilizer_matrix = convert_to_binary_scipy_sparse(z_stabilizer_matrix)
|
88
|
+
|
89
|
+
# Calculate the number of physical qubits from the matrix dimension
|
90
|
+
self.physical_qubit_count = self.x_stabilizer_matrix.shape[1]
|
91
|
+
|
92
|
+
# Validate the number of qubits for both matrices
|
93
|
+
try:
|
94
|
+
assert self.physical_qubit_count == self.z_stabilizer_matrix.shape[1]
|
95
|
+
except AssertionError:
|
96
|
+
raise ValueError(
|
97
|
+
f"Input matrices x_stabilizer_matrix and z_stabilizer_matrix must have the same number of columns.\
|
98
|
+
Current column count, x_stabilizer_matrix: {x_stabilizer_matrix.shape[1]}; z_stabilizer_matrix: {z_stabilizer_matrix.shape[1]}"
|
99
|
+
)
|
100
|
+
|
101
|
+
# Validate if the input matrices commute
|
102
|
+
try:
|
103
|
+
assert not np.any(
|
104
|
+
(self.x_stabilizer_matrix @ self.z_stabilizer_matrix.T).data % 2
|
105
|
+
)
|
106
|
+
except AssertionError:
|
107
|
+
raise ValueError(
|
108
|
+
"Input matrices hx and hz do not commute. I.e. they do not satisfy\
|
109
|
+
the requirement that hx@hz.T = 0."
|
110
|
+
)
|
111
|
+
|
112
|
+
# Compute a basis of the logical operators
|
113
|
+
self.compute_logical_basis()
|
114
|
+
|
115
|
+
def compute_logical_basis(self):
|
116
|
+
"""
|
117
|
+
Compute the logical operator basis for the given CSS code.
|
118
|
+
|
119
|
+
Returns
|
120
|
+
-------
|
121
|
+
Tuple[scipy.sparse.spmatrix, scipy.sparse.spmatrix]
|
122
|
+
Logical X and Z operator bases (lx, lz).
|
123
|
+
|
124
|
+
Notes
|
125
|
+
-----
|
126
|
+
This method uses the kernel of the X and Z stabilizer matrices to find operators that commute with all the stabilizers,
|
127
|
+
and then identifies the subsets of which are not themselves linear combinations of the stabilizers.
|
128
|
+
"""
|
129
|
+
|
130
|
+
# Compute the kernel of hx and hz matrices
|
131
|
+
|
132
|
+
# Z logicals
|
133
|
+
|
134
|
+
# Compute the kernel of hx
|
135
|
+
ker_hx = ldpc.mod2.kernel(self.x_stabilizer_matrix) # kernel of X-stabilisers
|
136
|
+
# Sort the rows of ker_hx by weight
|
137
|
+
row_weights = ker_hx.getnnz(axis=1)
|
138
|
+
sorted_rows = np.argsort(row_weights)
|
139
|
+
ker_hx = ker_hx[sorted_rows, :]
|
140
|
+
# Z logicals are elements of ker_hx (that commute with all the X-stabilisers) that are not linear combinations of Z-stabilisers
|
141
|
+
logical_stack = scipy.sparse.vstack([self.z_stabilizer_matrix, ker_hx]).tocsr()
|
142
|
+
self.rank_hz = ldpc.mod2.rank(self.z_stabilizer_matrix)
|
143
|
+
# The first self.rank_hz pivot_rows of logical_stack are the Z-stabilisers. The remaining pivot_rows are the Z logicals
|
144
|
+
pivots = ldpc.mod2.pivot_rows(logical_stack)
|
145
|
+
self.z_logical_operator_basis = logical_stack[pivots[self.rank_hz :], :]
|
146
|
+
|
147
|
+
# X logicals
|
148
|
+
|
149
|
+
# Compute the kernel of hz
|
150
|
+
ker_hz = ldpc.mod2.kernel(self.z_stabilizer_matrix)
|
151
|
+
# Sort the rows of ker_hz by weight
|
152
|
+
row_weights = ker_hz.getnnz(axis=1)
|
153
|
+
sorted_rows = np.argsort(row_weights)
|
154
|
+
ker_hz = ker_hz[sorted_rows, :]
|
155
|
+
# X logicals are elements of ker_hz (that commute with all the Z-stabilisers) that are not linear combinations of X-stabilisers
|
156
|
+
logical_stack = scipy.sparse.vstack([self.x_stabilizer_matrix, ker_hz]).tocsr()
|
157
|
+
self.rank_hx = ldpc.mod2.rank(self.x_stabilizer_matrix)
|
158
|
+
# The first self.rank_hx pivot_rows of logical_stack are the X-stabilisers. The remaining pivot_rows are the X logicals
|
159
|
+
pivots = ldpc.mod2.pivot_rows(logical_stack)
|
160
|
+
self.x_logical_operator_basis = logical_stack[pivots[self.rank_hx :], :]
|
161
|
+
|
162
|
+
# set the dimension of the code (i.e. the number of logical qubits)
|
163
|
+
self.logical_qubit_count = self.x_logical_operator_basis.shape[0]
|
164
|
+
|
165
|
+
# find the minimum weight logical operators
|
166
|
+
self.x_code_distance = self.physical_qubit_count
|
167
|
+
self.z_code_distance = self.physical_qubit_count
|
168
|
+
|
169
|
+
for i in range(self.logical_qubit_count):
|
170
|
+
if self.x_logical_operator_basis[i].nnz < self.x_code_distance:
|
171
|
+
self.x_code_distance = self.x_logical_operator_basis[i].nnz
|
172
|
+
if self.z_logical_operator_basis[i].nnz < self.z_code_distance:
|
173
|
+
self.z_code_distance = self.z_logical_operator_basis[i].nnz
|
174
|
+
self.code_distance = np.min([self.x_code_distance, self.z_code_distance])
|
175
|
+
|
176
|
+
# FIXME: How does this differ from rank_hx and rank_hz descibed above (ldpc.mod2.rank())?
|
177
|
+
# compute the hx and hz rank
|
178
|
+
self.rank_hx = self.physical_qubit_count - ker_hx.shape[0]
|
179
|
+
self.rank_hz = self.physical_qubit_count - ker_hz.shape[0]
|
180
|
+
|
181
|
+
return (self.x_logical_operator_basis, self.z_logical_operator_basis)
|
182
|
+
|
183
|
+
# TODO: Add a function to save the logical operator basis to a file
|
184
|
+
|
185
|
+
def check_valid_logical_xz_basis(self) -> bool:
|
186
|
+
"""
|
187
|
+
Validate that the stored logical operators form a proper logical basis for the code.
|
188
|
+
|
189
|
+
Checks that they commute with the stabilizers, pairwise anti-commute, and have full rank.
|
190
|
+
|
191
|
+
Returns
|
192
|
+
-------
|
193
|
+
bool
|
194
|
+
True if the logical operators form a valid basis, otherwise False.
|
195
|
+
"""
|
196
|
+
|
197
|
+
# If logical bases are not computed yet, compute them
|
198
|
+
if (
|
199
|
+
self.x_logical_operator_basis is None
|
200
|
+
or self.z_logical_operator_basis is None
|
201
|
+
):
|
202
|
+
self.x_logical_operator_basis, self.z_logical_operator_basis = (
|
203
|
+
self.compute_logical_basis(
|
204
|
+
self.x_stabilizer_matrix, self.z_stabilizer_matrix
|
205
|
+
)
|
206
|
+
)
|
207
|
+
self.logical_qubit_count = self.x_logical_operator_basis.shape[0]
|
208
|
+
|
209
|
+
try:
|
210
|
+
# Test dimension
|
211
|
+
assert (
|
212
|
+
self.logical_qubit_count
|
213
|
+
== self.z_logical_operator_basis.shape[0]
|
214
|
+
== self.x_logical_operator_basis.shape[0]
|
215
|
+
), "Logical operator basis dimensions do not match."
|
216
|
+
|
217
|
+
# Check logical basis linearly independent (i.e. full rank)
|
218
|
+
assert (
|
219
|
+
ldpc.mod2.rank(self.x_logical_operator_basis)
|
220
|
+
== self.logical_qubit_count
|
221
|
+
), "X logical operator basis is not full rank, and hence not linearly independent."
|
222
|
+
assert (
|
223
|
+
ldpc.mod2.rank(self.z_logical_operator_basis)
|
224
|
+
== self.logical_qubit_count
|
225
|
+
), "Z logical operator basis is not full rank, and hence not linearly independent."
|
226
|
+
|
227
|
+
# Perform various tests to validate the logical bases
|
228
|
+
|
229
|
+
# Check that the logical operators commute with the stabilizers
|
230
|
+
try:
|
231
|
+
assert not np.any(
|
232
|
+
(self.x_logical_operator_basis @ self.z_stabilizer_matrix.T).data
|
233
|
+
% 2
|
234
|
+
), "X logical operators do not commute with Z stabilizers."
|
235
|
+
except AssertionError as e:
|
236
|
+
logging.error(e)
|
237
|
+
return False
|
238
|
+
|
239
|
+
try:
|
240
|
+
assert not np.any(
|
241
|
+
(self.z_logical_operator_basis @ self.x_stabilizer_matrix.T).data
|
242
|
+
% 2
|
243
|
+
), "Z logical operators do not commute with X stabilizers."
|
244
|
+
except AssertionError as e:
|
245
|
+
logging.error(e)
|
246
|
+
return False
|
247
|
+
|
248
|
+
# Check that the logical operators anticommute with each other (by checking that the rank of the product is full rank)
|
249
|
+
test = self.x_logical_operator_basis @ self.z_logical_operator_basis.T
|
250
|
+
test.data = test.data % 2
|
251
|
+
assert (
|
252
|
+
ldpc.mod2.rank(test) == self.logical_qubit_count
|
253
|
+
), "Logical operators do not pairwise anticommute."
|
254
|
+
|
255
|
+
test = self.z_logical_operator_basis @ self.x_logical_operator_basis.T
|
256
|
+
test.data = test.data % 2
|
257
|
+
assert (
|
258
|
+
ldpc.mod2.rank(test) == self.logical_qubit_count
|
259
|
+
), "Logical operators do not pairwise anticommute."
|
260
|
+
|
261
|
+
# TODO: Check that the logical operators are not themselves stabilizers?
|
262
|
+
|
263
|
+
except AssertionError as e:
|
264
|
+
logging.error(e)
|
265
|
+
return False
|
266
|
+
|
267
|
+
return True
|
268
|
+
|
269
|
+
def compute_exact_code_distance(
|
270
|
+
self, timeout: float = 0.5
|
271
|
+
) -> Tuple[Optional[int], Optional[int], float]:
|
272
|
+
"""
|
273
|
+
Compute the exact distance of the CSS code by searching through linear combinations
|
274
|
+
of logical operators and stabilisers, ensuring balanced progress between X and Z searches.
|
275
|
+
|
276
|
+
Parameters
|
277
|
+
----------
|
278
|
+
timeout : float, optional
|
279
|
+
The time limit (in seconds) for the exhaustive search. Default is 0.5 seconds.
|
280
|
+
To obtain the exact distance, set to `np.inf`.
|
281
|
+
|
282
|
+
Returns
|
283
|
+
-------
|
284
|
+
Tuple[Optional[int], Optional[int], float]
|
285
|
+
A tuple containing:
|
286
|
+
- The best-known X distance of the code (or None if no X distance was found)
|
287
|
+
- The best-known Z distance of the code (or None if no Z distance was found)
|
288
|
+
- The fraction of total combinations considered before timeout
|
289
|
+
|
290
|
+
Notes
|
291
|
+
-----
|
292
|
+
- Searches X and Z combinations in an interleaved manner to ensure balanced progress
|
293
|
+
- For each type (X/Z):
|
294
|
+
- We compute the row span of both stabilisers and logical operators
|
295
|
+
- For every logical operator in the logical span, we add (mod 2) each stabiliser
|
296
|
+
- We compute the Hamming weight of each candidate operator
|
297
|
+
- We track the minimal Hamming weight encountered
|
298
|
+
"""
|
299
|
+
start_time = time.time()
|
300
|
+
|
301
|
+
# Get stabiliser spans
|
302
|
+
x_stabiliser_span = ldpc.mod2.row_span(self.x_stabilizer_matrix)[1:]
|
303
|
+
z_stabiliser_span = ldpc.mod2.row_span(self.z_stabilizer_matrix)[1:]
|
304
|
+
|
305
|
+
# Get logical spans
|
306
|
+
x_logical_span = ldpc.mod2.row_span(self.x_logical_operator_basis)[1:]
|
307
|
+
z_logical_span = ldpc.mod2.row_span(self.z_logical_operator_basis)[1:]
|
308
|
+
|
309
|
+
# Initialize distances
|
310
|
+
if self.x_code_distance is None:
|
311
|
+
x_code_distance = np.inf
|
312
|
+
else:
|
313
|
+
x_code_distance = self.x_code_distance
|
314
|
+
|
315
|
+
if self.z_code_distance is None:
|
316
|
+
z_code_distance = np.inf
|
317
|
+
else:
|
318
|
+
z_code_distance = self.z_code_distance
|
319
|
+
|
320
|
+
# Prepare iterators for both X and Z combinations
|
321
|
+
x_combinations = (
|
322
|
+
(x_l, x_s) for x_l in x_logical_span for x_s in x_stabiliser_span
|
323
|
+
)
|
324
|
+
z_combinations = (
|
325
|
+
(z_l, z_s) for z_l in z_logical_span for z_s in z_stabiliser_span
|
326
|
+
)
|
327
|
+
|
328
|
+
total_x_combinations = x_stabiliser_span.shape[0] * x_logical_span.shape[0]
|
329
|
+
total_z_combinations = z_stabiliser_span.shape[0] * z_logical_span.shape[0]
|
330
|
+
total_combinations = total_x_combinations + total_z_combinations
|
331
|
+
combinations_considered = 0
|
332
|
+
|
333
|
+
# Create iterables that we can exhaust
|
334
|
+
x_iter = iter(x_combinations)
|
335
|
+
z_iter = iter(z_combinations)
|
336
|
+
x_exhausted = False
|
337
|
+
z_exhausted = False
|
338
|
+
|
339
|
+
while not (x_exhausted and z_exhausted):
|
340
|
+
if time.time() - start_time > timeout:
|
341
|
+
break
|
342
|
+
|
343
|
+
# Try X combination if not exhausted
|
344
|
+
if not x_exhausted:
|
345
|
+
try:
|
346
|
+
x_logical, x_stabiliser = next(x_iter)
|
347
|
+
candidate_x = x_logical + x_stabiliser
|
348
|
+
candidate_x.data %= 2
|
349
|
+
x_weight = candidate_x.getnnz()
|
350
|
+
if x_weight < x_code_distance:
|
351
|
+
x_code_distance = x_weight
|
352
|
+
combinations_considered += 1
|
353
|
+
except StopIteration:
|
354
|
+
x_exhausted = True
|
355
|
+
|
356
|
+
# Try Z combination if not exhausted
|
357
|
+
if not z_exhausted:
|
358
|
+
try:
|
359
|
+
z_logical, z_stabiliser = next(z_iter)
|
360
|
+
candidate_z = z_logical + z_stabiliser
|
361
|
+
candidate_z.data %= 2
|
362
|
+
z_weight = candidate_z.getnnz()
|
363
|
+
if z_weight < z_code_distance:
|
364
|
+
z_code_distance = z_weight
|
365
|
+
combinations_considered += 1
|
366
|
+
except StopIteration:
|
367
|
+
z_exhausted = True
|
368
|
+
|
369
|
+
# Update code distances
|
370
|
+
self.x_code_distance = x_code_distance if x_code_distance != np.inf else None
|
371
|
+
self.z_code_distance = z_code_distance if z_code_distance != np.inf else None
|
372
|
+
self.code_distance = (
|
373
|
+
min(x_code_distance, z_code_distance)
|
374
|
+
if x_code_distance != np.inf and z_code_distance != np.inf
|
375
|
+
else None
|
376
|
+
)
|
377
|
+
|
378
|
+
# Calculate fraction of combinations considered
|
379
|
+
fraction_considered = combinations_considered / total_combinations
|
380
|
+
|
381
|
+
return (
|
382
|
+
int(x_code_distance) if x_code_distance != np.inf else None,
|
383
|
+
int(z_code_distance) if z_code_distance != np.inf else None,
|
384
|
+
fraction_considered,
|
385
|
+
)
|
386
|
+
|
387
|
+
def estimate_min_distance(
|
388
|
+
self,
|
389
|
+
timeout_seconds: float = 0.25,
|
390
|
+
p: float = 0.25,
|
391
|
+
reduce_logical_basis: bool = False,
|
392
|
+
decoder: Optional[BpOsdDecoder] = None,
|
393
|
+
) -> int:
|
394
|
+
"""
|
395
|
+
Estimate the minimum distance of the CSS code using a BP+OSD decoder-based search.
|
396
|
+
|
397
|
+
Parameters
|
398
|
+
----------
|
399
|
+
timeout_seconds : float, optional
|
400
|
+
Time limit in seconds for the search. Default: 0.25
|
401
|
+
p : float, optional
|
402
|
+
Probability for including each logical operator in trial combinations. Default: 0.25
|
403
|
+
reduce_logical_basis : bool, optional
|
404
|
+
Whether to attempt reducing the logical operator basis. Default: False
|
405
|
+
decoder : Optional[BpOsdDecoder], optional
|
406
|
+
Pre-configured BP+OSD decoder. If None, initializes with default settings.
|
407
|
+
|
408
|
+
Returns
|
409
|
+
-------
|
410
|
+
int
|
411
|
+
Best estimate of code distance found within time limit.
|
412
|
+
"""
|
413
|
+
start_time = time.time()
|
414
|
+
|
415
|
+
# Ensure logical operator bases are computed
|
416
|
+
if (
|
417
|
+
self.x_logical_operator_basis is None
|
418
|
+
or self.z_logical_operator_basis is None
|
419
|
+
):
|
420
|
+
self.compute_logical_basis()
|
421
|
+
|
422
|
+
# Setup decoders for X and Z logical operators
|
423
|
+
bp_osd_x, x_stack, _, x_min_distance, x_max_distance = (
|
424
|
+
self._setup_distance_estimation_decoder(
|
425
|
+
self.x_stabilizer_matrix, self.x_logical_operator_basis, decoder
|
426
|
+
)
|
427
|
+
)
|
428
|
+
bp_osd_z, z_stack, _, z_min_distance, z_max_distance = (
|
429
|
+
self._setup_distance_estimation_decoder(
|
430
|
+
self.z_stabilizer_matrix, self.z_logical_operator_basis, decoder
|
431
|
+
)
|
432
|
+
)
|
433
|
+
|
434
|
+
candidate_logicals_x = []
|
435
|
+
candidate_logicals_z = []
|
436
|
+
|
437
|
+
# Search loop
|
438
|
+
with tqdm(total=timeout_seconds, desc="Estimating distance") as pbar:
|
439
|
+
while time.time() - start_time < timeout_seconds:
|
440
|
+
elapsed = time.time() - start_time
|
441
|
+
pbar.update(elapsed - pbar.n)
|
442
|
+
|
443
|
+
# Generate random logical combinations for X
|
444
|
+
dummy_syndrome_x = (
|
445
|
+
self._generate_random_logical_combination_for_distance_estimation(
|
446
|
+
x_stack, p, self.x_stabilizer_matrix.shape[0]
|
447
|
+
)
|
448
|
+
)
|
449
|
+
candidate_x = bp_osd_x.decode(dummy_syndrome_x)
|
450
|
+
x_weight = np.count_nonzero(candidate_x)
|
451
|
+
|
452
|
+
if x_weight < x_min_distance:
|
453
|
+
x_min_distance = x_weight
|
454
|
+
|
455
|
+
if x_weight < x_max_distance and reduce_logical_basis:
|
456
|
+
candidate_logicals_x.append(candidate_x)
|
457
|
+
|
458
|
+
# Generate random logical combinations for Z
|
459
|
+
dummy_syndrome_z = (
|
460
|
+
self._generate_random_logical_combination_for_distance_estimation(
|
461
|
+
z_stack, p, self.z_stabilizer_matrix.shape[0]
|
462
|
+
)
|
463
|
+
)
|
464
|
+
candidate_z = bp_osd_z.decode(dummy_syndrome_z)
|
465
|
+
z_weight = np.count_nonzero(candidate_z)
|
466
|
+
|
467
|
+
if z_weight < z_min_distance:
|
468
|
+
z_min_distance = z_weight
|
469
|
+
|
470
|
+
if z_weight < z_max_distance and reduce_logical_basis:
|
471
|
+
candidate_logicals_z.append(candidate_z)
|
472
|
+
|
473
|
+
# Update progress bar description
|
474
|
+
pbar.set_description(
|
475
|
+
f"Estimating distance: dx <= {x_min_distance}, dz <= {z_min_distance}"
|
476
|
+
)
|
477
|
+
|
478
|
+
# Update distances and reduce logical bases if applicable
|
479
|
+
self.x_code_distance = x_min_distance
|
480
|
+
self.z_code_distance = z_min_distance
|
481
|
+
self.code_distance = min(x_min_distance, z_min_distance)
|
482
|
+
|
483
|
+
if reduce_logical_basis:
|
484
|
+
self._reduce_logical_operator_basis(
|
485
|
+
candidate_logicals_x, candidate_logicals_z
|
486
|
+
)
|
487
|
+
|
488
|
+
return self.code_distance
|
489
|
+
|
490
|
+
def _setup_distance_estimation_decoder(
|
491
|
+
self, stabilizer_matrix, logical_operator_basis, decoder=None
|
492
|
+
) -> Tuple[BpOsdDecoder, scipy.sparse.spmatrix, scipy.sparse.spmatrix, int, int]:
|
493
|
+
"""
|
494
|
+
Helper function to set up the BP+OSD decoder for distance estimation.
|
495
|
+
|
496
|
+
Parameters
|
497
|
+
----------
|
498
|
+
stabilizer_matrix : scipy.sparse.spmatrix
|
499
|
+
Stabilizer matrix of the code.
|
500
|
+
logical_operator_basis : scipy.sparse.spmatrix
|
501
|
+
Logical operator basis of the code.
|
502
|
+
decoder : Optional[BpOsdDecoder], optional
|
503
|
+
Pre-configured decoder. If None, initializes with default settings.
|
504
|
+
|
505
|
+
Returns
|
506
|
+
-------
|
507
|
+
Tuple[BpOsdDecoder, scipy.sparse.spmatrix, scipy.sparse.spmatrix, int, int]
|
508
|
+
Decoder, stacked matrix, stabilizer matrix, minimum distance, and maximum distance.
|
509
|
+
"""
|
510
|
+
# Remove redundant rows from stabilizer matrix
|
511
|
+
p_rows = ldpc.mod2.pivot_rows(stabilizer_matrix)
|
512
|
+
full_rank_stabilizer_matrix = stabilizer_matrix[p_rows]
|
513
|
+
|
514
|
+
# Build a stacked matrix of stabilizers and logicals
|
515
|
+
stack = scipy.sparse.vstack(
|
516
|
+
[full_rank_stabilizer_matrix, logical_operator_basis]
|
517
|
+
).tocsr()
|
518
|
+
|
519
|
+
# Initial distance estimate from current logicals
|
520
|
+
min_distance = np.min(logical_operator_basis.getnnz(axis=1))
|
521
|
+
max_distance = np.max(logical_operator_basis.getnnz(axis=1))
|
522
|
+
|
523
|
+
# Set up BP+OSD decoder if not provided
|
524
|
+
if decoder is None:
|
525
|
+
decoder = BpOsdDecoder(
|
526
|
+
stack,
|
527
|
+
error_rate=0.1,
|
528
|
+
max_iter=10,
|
529
|
+
bp_method="ms",
|
530
|
+
schedule="parallel",
|
531
|
+
ms_scaling_factor=1.0,
|
532
|
+
osd_method="osd_0",
|
533
|
+
osd_order=0,
|
534
|
+
)
|
535
|
+
|
536
|
+
return decoder, stack, full_rank_stabilizer_matrix, min_distance, max_distance
|
537
|
+
|
538
|
+
def _generate_random_logical_combination_for_distance_estimation(
|
539
|
+
self, stack: scipy.sparse.spmatrix, p: float, stabilizer_count: int
|
540
|
+
) -> np.ndarray:
|
541
|
+
"""
|
542
|
+
Generate a random logical combination for the BP+OSD decoder.
|
543
|
+
|
544
|
+
Parameters
|
545
|
+
----------
|
546
|
+
stack : scipy.sparse.spmatrix
|
547
|
+
The stacked stabilizer and logical operator matrix.
|
548
|
+
p : float
|
549
|
+
Probability for including each logical operator in the combination.
|
550
|
+
stabilizer_count : int
|
551
|
+
Number of stabilizer rows in the stacked matrix.
|
552
|
+
|
553
|
+
Returns
|
554
|
+
-------
|
555
|
+
np.ndarray
|
556
|
+
Randomly generated syndrome vector.
|
557
|
+
"""
|
558
|
+
random_mask = np.random.choice([0, 1], size=stack.shape[0], p=[1 - p, p])
|
559
|
+
random_mask[:stabilizer_count] = (
|
560
|
+
0 # Ensure no stabilizer-only rows are selected
|
561
|
+
)
|
562
|
+
|
563
|
+
while not np.any(random_mask):
|
564
|
+
random_mask = np.random.choice([0, 1], size=stack.shape[0], p=[1 - p, p])
|
565
|
+
random_mask[:stabilizer_count] = 0
|
566
|
+
|
567
|
+
dummy_syndrome = np.zeros(stack.shape[0], dtype=np.uint8)
|
568
|
+
dummy_syndrome[np.nonzero(random_mask)[0]] = 1
|
569
|
+
|
570
|
+
return dummy_syndrome
|
571
|
+
|
572
|
+
def fix_logical_operators(self, fix_logical: str = "X"):
|
573
|
+
if not isinstance(fix_logical, str):
|
574
|
+
raise TypeError("fix_logical parameter must be a string")
|
575
|
+
|
576
|
+
if fix_logical.lower() == "x":
|
577
|
+
temp = self.z_logical_operator_basis @ self.x_logical_operator_basis.T
|
578
|
+
temp.data = temp.data % 2
|
579
|
+
temp = ldpc.mod2.inverse(temp)
|
580
|
+
self.z_logical_operator_basis = temp @ self.z_logical_operator_basis
|
581
|
+
self.z_logical_operator_basis.data = self.z_logical_operator_basis.data % 2
|
582
|
+
|
583
|
+
elif fix_logical.lower() == "z":
|
584
|
+
temp = self.x_logical_operator_basis @ self.z_logical_operator_basis.T
|
585
|
+
temp.data = temp.data % 2
|
586
|
+
temp = ldpc.mod2.inverse(temp)
|
587
|
+
self.x_logical_operator_basis = temp @ self.x_logical_operator_basis
|
588
|
+
self.x_logical_operator_basis.data = self.x_logical_operator_basis.data % 2
|
589
|
+
else:
|
590
|
+
raise ValueError("Invalid fix_logical parameter")
|
591
|
+
|
592
|
+
@property
|
593
|
+
def logical_operator_weights(self) -> Tuple[np.ndarray, np.ndarray]:
|
594
|
+
x_weights = []
|
595
|
+
z_weights = []
|
596
|
+
for i in range(self.logical_qubit_count):
|
597
|
+
x_weights.append(self.x_logical_operator_basis[i].nnz)
|
598
|
+
z_weights.append(self.z_logical_operator_basis[i].nnz)
|
599
|
+
|
600
|
+
return (np.array(x_weights), np.array(z_weights))
|
601
|
+
|
602
|
+
def __str__(self):
|
603
|
+
"""
|
604
|
+
Return a string representation of the CSSCode object.
|
605
|
+
|
606
|
+
Returns:
|
607
|
+
str: String representation of the CSS code.
|
608
|
+
"""
|
609
|
+
return f"{self.name} Code: [[N={self.physical_qubit_count}, K={self.logical_qubit_count}, dx<={self.x_code_distance}, dz<={self.z_code_distance}]]"
|
@@ -45,7 +45,7 @@ class StabilizerCode(object):
|
|
45
45
|
The number of logical qubits in the code.
|
46
46
|
code_distance : int
|
47
47
|
(Not computed by default) The distance of the code, if known or computed.
|
48
|
-
|
48
|
+
logical_operator_basis : scipy.sparse.spmatrix or None
|
49
49
|
A basis for the logical operators of the code.
|
50
50
|
"""
|
51
51
|
|
@@ -177,7 +177,7 @@ class StabilizerCode(object):
|
|
177
177
|
kernel_h = ldpc.mod2.kernel(self.stabilizer_matrix)
|
178
178
|
|
179
179
|
# Sort the rows of the kernel by weight
|
180
|
-
row_weights =
|
180
|
+
row_weights = kernel_h.getnnz(axis=1)
|
181
181
|
sorted_rows = np.argsort(row_weights)
|
182
182
|
kernel_h = kernel_h[sorted_rows, :]
|
183
183
|
|
@@ -377,166 +377,64 @@ class StabilizerCode(object):
|
|
377
377
|
"""
|
378
378
|
return f"< Stabilizer Code, Name: {self.name}, Parameters: [[{self.physical_qubit_count}, {self.logical_qubit_count}, {self.code_distance}]] >"
|
379
379
|
|
380
|
-
def reduce_logical_operator_basis(
|
381
|
-
self,
|
382
|
-
candidate_logicals: Union[Sequence, np.ndarray, scipy.sparse.spmatrix] = [],
|
383
|
-
):
|
384
|
-
"""
|
385
|
-
Reduce the logical operator basis to include lower-weight logicals.
|
386
|
-
|
387
|
-
Parameters
|
388
|
-
----------
|
389
|
-
candidate_logicals : Union[Sequence, np.ndarray, scipy.sparse.spmatrix], optional
|
390
|
-
A list or array of candidate logical operators to be considered for reducing the basis.
|
391
|
-
Defaults to an empty list.
|
392
|
-
"""
|
393
|
-
if len(candidate_logicals) != 0:
|
394
|
-
# Convert candidates to a sparse matrix if they aren't already
|
395
|
-
if not isinstance(candidate_logicals, scipy.sparse.spmatrix):
|
396
|
-
candidate_logicals = scipy.sparse.csr_matrix(
|
397
|
-
scipy.sparse.csr_matrix(candidate_logicals)
|
398
|
-
)
|
399
|
-
|
400
|
-
# Stack the candidate logicals with the existing logicals
|
401
|
-
temp1 = scipy.sparse.vstack(
|
402
|
-
[candidate_logicals, self.logical_operator_basis]
|
403
|
-
).tocsr()
|
404
|
-
|
405
|
-
# Compute the Hamming weight over GF4 (number of qubits with non-identity operators)
|
406
|
-
# Split into X and Z parts
|
407
|
-
row_weights = binary_pauli_hamming_weight(temp1).flatten()
|
408
|
-
|
409
|
-
# Sort the rows by Hamming weight (ascending)
|
410
|
-
sorted_rows = np.argsort(row_weights)
|
411
|
-
temp1 = temp1[sorted_rows, :]
|
412
|
-
|
413
|
-
# Add the stabilizer matrix to the top of the stack
|
414
|
-
temp1 = scipy.sparse.vstack([self.stabilizer_matrix, temp1])
|
415
|
-
|
416
|
-
# Calculate the rank of the stabilizer matrix (todo: find way of removing this step)
|
417
|
-
stabilizer_rank = ldpc.mod2.rank(self.stabilizer_matrix)
|
418
|
-
|
419
|
-
# Perform row reduction to find a new logical basis
|
420
|
-
p_rows = ldpc.mod2.pivot_rows(temp1)
|
421
|
-
self.logical_operator_basis = temp1[p_rows[stabilizer_rank:]]
|
422
|
-
|
423
380
|
def estimate_min_distance(
|
424
381
|
self,
|
425
382
|
timeout_seconds: float = 0.25,
|
426
383
|
p: float = 0.25,
|
427
|
-
max_iter: int = 10,
|
428
|
-
error_rate: float = 0.1,
|
429
|
-
bp_method: str = "ms",
|
430
|
-
schedule: str = "parallel",
|
431
|
-
ms_scaling_factor: float = 1.0,
|
432
|
-
osd_method: str = "osd_0",
|
433
|
-
osd_order: int = 0,
|
434
384
|
reduce_logical_basis: bool = False,
|
385
|
+
decoder: Optional[BpOsdDecoder] = None,
|
435
386
|
) -> int:
|
436
387
|
"""
|
437
388
|
Estimate the minimum distance of the stabilizer code using a BP+OSD decoder-based search.
|
438
389
|
|
439
390
|
Parameters
|
440
391
|
----------
|
441
|
-
timeout_seconds : float
|
442
|
-
|
443
|
-
p : float
|
444
|
-
Probability
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
Crossover probability for the BP+OSD decoder.
|
450
|
-
bp_method : str, optional
|
451
|
-
Belief Propagation method (e.g., "ms" for min-sum).
|
452
|
-
schedule : str, optional
|
453
|
-
Update schedule for BP (e.g., "parallel").
|
454
|
-
ms_scaling_factor : float, optional
|
455
|
-
Scaling factor for min-sum updates.
|
456
|
-
osd_method : str, optional
|
457
|
-
Order-statistic decoding method (e.g., "osd_0").
|
458
|
-
osd_order : int, optional
|
459
|
-
OSD order.
|
460
|
-
reduce_logical_basis : bool, optional
|
461
|
-
If True, attempts to reduce the logical operator basis to include lower-weight operators.
|
392
|
+
timeout_seconds : float
|
393
|
+
Time limit in seconds for the search. Default: 0.25
|
394
|
+
p : float
|
395
|
+
Probability for including each logical operator in trial combinations. Default: 0.25
|
396
|
+
reduce_logical_basis : bool
|
397
|
+
Whether to attempt reducing logical operator basis. Default: False
|
398
|
+
decoder : Optional[BpOsdDecoder]
|
399
|
+
Pre-configured BP+OSD decoder. If None, initialises with default settings.
|
462
400
|
|
463
401
|
Returns
|
464
402
|
-------
|
465
403
|
int
|
466
|
-
|
404
|
+
Best estimate of code distance found within time limit
|
467
405
|
"""
|
468
406
|
if self.logical_operator_basis is None:
|
469
407
|
self.logical_operator_basis = self.compute_logical_basis()
|
470
408
|
|
471
|
-
|
472
|
-
# # Remove redundnant rows from stabilizer matrix
|
473
|
-
p_rows = ldpc.mod2.pivot_rows(self.stabilizer_matrix)
|
474
|
-
full_rank_stabilizer_matrix = self.stabilizer_matrix[p_rows]
|
475
|
-
# full_rank_stabilizer_matrix = self.stabilizer_matrix
|
476
|
-
|
477
|
-
# Build a stacked matrix of stabilizers and logicals
|
478
|
-
stack = scipy.sparse.vstack(
|
479
|
-
[full_rank_stabilizer_matrix, self.logical_operator_basis]
|
480
|
-
).tocsr()
|
481
|
-
|
482
|
-
# Initial distance estimate from the current logicals
|
483
|
-
|
484
|
-
min_distance = np.min(
|
485
|
-
binary_pauli_hamming_weight(self.logical_operator_basis)
|
486
|
-
)
|
487
|
-
|
488
|
-
max_distance = np.max(self.logical_basis_weights())
|
489
|
-
|
490
|
-
# Set up BP+OSD decoder
|
491
|
-
bp_osd = BpOsdDecoder(
|
492
|
-
stack,
|
493
|
-
error_rate=error_rate,
|
494
|
-
max_iter=max_iter,
|
495
|
-
bp_method=bp_method,
|
496
|
-
schedule=schedule,
|
497
|
-
ms_scaling_factor=ms_scaling_factor,
|
498
|
-
osd_method=osd_method,
|
499
|
-
osd_order=osd_order,
|
500
|
-
)
|
501
|
-
|
502
|
-
return (
|
503
|
-
bp_osd,
|
504
|
-
stack,
|
505
|
-
full_rank_stabilizer_matrix,
|
506
|
-
min_distance,
|
507
|
-
max_distance,
|
508
|
-
)
|
509
|
-
|
510
|
-
# setup the decoder
|
409
|
+
# Initial setup of decoder and parameters
|
511
410
|
bp_osd, stack, full_rank_stabilizer_matrix, min_distance, max_distance = (
|
512
|
-
|
411
|
+
self._setup_distance_estimation_decoder(decoder)
|
513
412
|
)
|
514
413
|
|
515
|
-
#
|
414
|
+
# Initialize storage for candidate logicals and tracking
|
516
415
|
candidate_logicals = []
|
416
|
+
weight_one_syndromes_searched = 0
|
517
417
|
|
518
|
-
#
|
418
|
+
# Main search loop
|
519
419
|
start_time = time.time()
|
520
420
|
with tqdm(total=timeout_seconds, desc="Estimating distance") as pbar:
|
521
|
-
weight_one_syndromes_searched = 0
|
522
421
|
while time.time() - start_time < timeout_seconds:
|
422
|
+
# Update progress bar
|
523
423
|
elapsed = time.time() - start_time
|
524
|
-
# Update progress bar based on elapsed time
|
525
424
|
pbar.update(elapsed - pbar.n)
|
526
425
|
|
527
426
|
# Initialize an empty dummy syndrome
|
528
427
|
dummy_syndrome = np.zeros(stack.shape[0], dtype=np.uint8)
|
529
428
|
|
530
429
|
if weight_one_syndromes_searched < self.logical_operator_basis.shape[0]:
|
430
|
+
# Try each logical operator individually first
|
531
431
|
dummy_syndrome[
|
532
432
|
full_rank_stabilizer_matrix.shape[0]
|
533
433
|
+ weight_one_syndromes_searched
|
534
|
-
] = 1
|
434
|
+
] = 1
|
535
435
|
weight_one_syndromes_searched += 1
|
536
|
-
|
537
436
|
else:
|
538
437
|
# Randomly pick a combination of logical rows
|
539
|
-
# (with probability p, set the corresponding row in the syndrome to 1)
|
540
438
|
while True:
|
541
439
|
random_mask = np.random.choice(
|
542
440
|
[0, 1],
|
@@ -550,7 +448,6 @@ class StabilizerCode(object):
|
|
550
448
|
dummy_syndrome[self.stabilizer_matrix.shape[0] + idx] = 1
|
551
449
|
|
552
450
|
candidate = bp_osd.decode(dummy_syndrome)
|
553
|
-
|
554
451
|
w = np.count_nonzero(
|
555
452
|
candidate[: self.physical_qubit_count]
|
556
453
|
| candidate[self.physical_qubit_count :]
|
@@ -558,44 +455,129 @@ class StabilizerCode(object):
|
|
558
455
|
|
559
456
|
if w < min_distance:
|
560
457
|
min_distance = w
|
561
|
-
if w < max_distance:
|
562
|
-
|
563
|
-
|
564
|
-
[
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
# 3) If requested, reduce the logical operator basis to include lower-weight operators
|
458
|
+
if w < max_distance and reduce_logical_basis:
|
459
|
+
lc = np.hstack(
|
460
|
+
[
|
461
|
+
candidate[self.physical_qubit_count :],
|
462
|
+
candidate[: self.physical_qubit_count],
|
463
|
+
]
|
464
|
+
)
|
465
|
+
candidate_logicals.append(lc)
|
466
|
+
|
467
|
+
# Reduce logical operator basis if we have enough candidates
|
572
468
|
if (
|
573
469
|
len(candidate_logicals) >= self.logical_qubit_count
|
574
470
|
and reduce_logical_basis
|
575
471
|
):
|
576
|
-
self.
|
472
|
+
self._reduce_logical_operator_basis(candidate_logicals)
|
577
473
|
(
|
578
474
|
bp_osd,
|
579
475
|
stack,
|
580
476
|
full_rank_stabilizer_matrix,
|
581
477
|
min_distance,
|
582
478
|
max_distance,
|
583
|
-
) =
|
479
|
+
) = self._setup_distance_estimation_decoder(decoder)
|
584
480
|
candidate_logicals = []
|
585
481
|
weight_one_syndromes_searched = 0
|
586
482
|
|
587
483
|
pbar.set_description(
|
588
|
-
f"Estimating distance: min-weight found <= {min_distance},
|
484
|
+
f"Estimating distance: min-weight found <= {min_distance}, "
|
485
|
+
f"basis weights: {self.logical_basis_weights()}"
|
589
486
|
)
|
590
487
|
|
591
|
-
|
592
|
-
|
593
|
-
candidate_logicals
|
594
|
-
weight_one_syndromes_searched = 0
|
595
|
-
max_distance = np.max(self.logical_basis_weights())
|
488
|
+
# Final basis reduction if needed
|
489
|
+
if reduce_logical_basis and candidate_logicals:
|
490
|
+
self._reduce_logical_operator_basis(candidate_logicals)
|
596
491
|
|
597
|
-
# Update and return the estimated distance
|
598
492
|
self.code_distance = min_distance
|
493
|
+
return min_distance
|
494
|
+
|
495
|
+
def _setup_distance_estimation_decoder(
|
496
|
+
self, decoder: Optional[BpOsdDecoder] = None
|
497
|
+
) -> Tuple[BpOsdDecoder, scipy.sparse.spmatrix, scipy.sparse.spmatrix, int, int]:
|
498
|
+
"""
|
499
|
+
Set up decoder and initial parameters.
|
500
|
+
|
501
|
+
Parameters
|
502
|
+
----------
|
503
|
+
decoder : Optional[BpOsdDecoder]
|
504
|
+
Pre-configured decoder. If None, initialises with default settings.
|
505
|
+
|
506
|
+
Returns
|
507
|
+
-------
|
508
|
+
Tuple[BpOsdDecoder, scipy.sparse.spmatrix, scipy.sparse.spmatrix, int, int]
|
509
|
+
Returns (decoder, stack matrix, full rank stabilizer matrix, min distance, max distance)
|
510
|
+
"""
|
511
|
+
# Remove redundant rows from stabilizer matrix
|
512
|
+
p_rows = ldpc.mod2.pivot_rows(self.stabilizer_matrix)
|
513
|
+
full_rank_stabilizer_matrix = self.stabilizer_matrix[p_rows]
|
514
|
+
|
515
|
+
# Build a stacked matrix of stabilizers and logicals
|
516
|
+
stack = scipy.sparse.vstack(
|
517
|
+
[full_rank_stabilizer_matrix, self.logical_operator_basis]
|
518
|
+
).tocsr()
|
519
|
+
|
520
|
+
# Initial distance estimate from current logicals
|
521
|
+
min_distance = np.min(binary_pauli_hamming_weight(self.logical_operator_basis))
|
522
|
+
max_distance = np.max(self.logical_basis_weights())
|
523
|
+
|
524
|
+
# Set up BP+OSD decoder if not provided
|
525
|
+
if decoder is None:
|
526
|
+
decoder = BpOsdDecoder(
|
527
|
+
stack,
|
528
|
+
error_rate=0.1,
|
529
|
+
max_iter=10,
|
530
|
+
bp_method="ms",
|
531
|
+
schedule="parallel",
|
532
|
+
ms_scaling_factor=1.0,
|
533
|
+
osd_method="osd_0",
|
534
|
+
osd_order=0,
|
535
|
+
)
|
536
|
+
|
537
|
+
return decoder, stack, full_rank_stabilizer_matrix, min_distance, max_distance
|
538
|
+
|
539
|
+
def _reduce_logical_operator_basis(
|
540
|
+
self,
|
541
|
+
candidate_logicals: Union[Sequence, np.ndarray, scipy.sparse.spmatrix] = [],
|
542
|
+
):
|
543
|
+
"""
|
544
|
+
Reduce the logical operator basis to include lower-weight logicals.
|
545
|
+
|
546
|
+
Parameters
|
547
|
+
----------
|
548
|
+
candidate_logicals : Union[Sequence, np.ndarray, scipy.sparse.spmatrix], optional
|
549
|
+
A list or array of candidate logical operators to be considered for reducing the basis.
|
550
|
+
Defaults to an empty list.
|
551
|
+
"""
|
552
|
+
if len(candidate_logicals) != 0:
|
553
|
+
# Convert candidates to a sparse matrix if they aren't already
|
554
|
+
if not isinstance(candidate_logicals, scipy.sparse.spmatrix):
|
555
|
+
candidate_logicals = scipy.sparse.csr_matrix(
|
556
|
+
scipy.sparse.csr_matrix(candidate_logicals)
|
557
|
+
)
|
558
|
+
|
559
|
+
# Stack the candidate logicals with the existing logicals
|
560
|
+
temp1 = scipy.sparse.vstack(
|
561
|
+
[candidate_logicals, self.logical_operator_basis]
|
562
|
+
).tocsr()
|
563
|
+
|
564
|
+
# Compute the Hamming weight over GF4 (number of qubits with non-identity operators)
|
565
|
+
# Split into X and Z parts
|
566
|
+
row_weights = binary_pauli_hamming_weight(temp1).flatten()
|
567
|
+
|
568
|
+
# Sort the rows by Hamming weight (ascending)
|
569
|
+
sorted_rows = np.argsort(row_weights)
|
570
|
+
temp1 = temp1[sorted_rows, :]
|
571
|
+
|
572
|
+
# Add the stabilizer matrix to the top of the stack
|
573
|
+
temp1 = scipy.sparse.vstack([self.stabilizer_matrix, temp1])
|
574
|
+
|
575
|
+
# Calculate the rank of the stabilizer matrix (todo: find way of removing this step)
|
576
|
+
stabilizer_rank = ldpc.mod2.rank(self.stabilizer_matrix)
|
577
|
+
|
578
|
+
# Perform row reduction to find a new logical basis
|
579
|
+
p_rows = ldpc.mod2.pivot_rows(temp1)
|
580
|
+
self.logical_operator_basis = temp1[p_rows[stabilizer_rank:]]
|
599
581
|
|
600
582
|
def logical_basis_weights(self):
|
601
583
|
"""
|
qec/utils/binary_pauli_utils.py
CHANGED
@@ -299,7 +299,9 @@ def symplectic_product(
|
|
299
299
|
a = convert_to_binary_scipy_sparse(a)
|
300
300
|
b = convert_to_binary_scipy_sparse(b)
|
301
301
|
|
302
|
-
assert (
|
302
|
+
assert (
|
303
|
+
a.shape[1] == b.shape[1]
|
304
|
+
), "Input matrices must have the same number of columns."
|
303
305
|
assert a.shape[1] % 2 == 0, "Input matrices must have an even number of columns."
|
304
306
|
|
305
307
|
n = a.shape[1] // 2
|
qec/utils/codetables_de_utils.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
import logging
|
2
|
+
|
2
3
|
# Suppress debug and info messages from urllib3 and requests libraries
|
3
4
|
logging.getLogger("urllib3").setLevel(logging.WARNING)
|
4
5
|
logging.getLogger("requests").setLevel(logging.WARNING)
|
@@ -9,6 +10,7 @@ import requests
|
|
9
10
|
from bs4 import BeautifulSoup
|
10
11
|
import json
|
11
12
|
|
13
|
+
|
12
14
|
def get_codetables_de_matrix(q, n, k, output_json_path=None, write_to_file=False):
|
13
15
|
"""
|
14
16
|
Retrieve quantum code data from Markus Grassl's codetables.de website.
|
@@ -0,0 +1,16 @@
|
|
1
|
+
qec/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
qec/quantum_codes/__init__.py,sha256=DQ1ztrq-vBpTyoehaMWOhals46tRj553Jmkq68bDk-E,117
|
3
|
+
qec/quantum_codes/codetables_de.py,sha256=loBDBOK2cbDJ5moKmIx2MXg6e30XEPrEYau19bbDgac,3623
|
4
|
+
qec/quantum_codes/five_qubit_code.py,sha256=0zrGLyIpfyKwYG7uL00yMcM5PdhQGF17_MiI2qTMhOk,2190
|
5
|
+
qec/stabilizer_code/__init__.py,sha256=L5UMjHBlvfQBhkNlEZYSkyaHvNOcDHjc3oxYibMYHRk,63
|
6
|
+
qec/stabilizer_code/css_code.py,sha256=JhNiBHqfwu4OgMVUsXl6yJ4L5KNW4Dn2Sf0beBdAl2s,24763
|
7
|
+
qec/stabilizer_code/stabilizer_code.py,sha256=I5u8JKZu88ioC4E2nBJ-00xCmnL8nU6kdAvwYOfmNRk,22138
|
8
|
+
qec/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
|
+
qec/utils/binary_pauli_utils.py,sha256=BSlngYDdRICu0aVu4u_m0bvLicohORyGxfk5eRER7TQ,13245
|
10
|
+
qec/utils/codetables_de_utils.py,sha256=S1wcVGJkkASQQ5s71QAsYBmpyE-3xTb6UsvgMfQtuiw,9469
|
11
|
+
qec/utils/sparse_binary_utils.py,sha256=Y9xfGKzOGFiVTyhb6iF6N7-5oMY6Ah9oLrnv8HhSBHA,1965
|
12
|
+
qec-0.2.1.dist-info/LICENSE,sha256=1b_xwNz1znYBfEaCL6pN2gNBAn8pQIjDRs_UhDp1EJI,1066
|
13
|
+
qec-0.2.1.dist-info/METADATA,sha256=AbWaMM6fYb65-0lUw6qWuywZigdtHseO-6QAbNZK0QM,2367
|
14
|
+
qec-0.2.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
15
|
+
qec-0.2.1.dist-info/top_level.txt,sha256=d8l_7pJ5u9uWdviNp0FUK-j8VPZqywkDek7qa4NDank,4
|
16
|
+
qec-0.2.1.dist-info/RECORD,,
|
qec-0.2.0.dist-info/RECORD
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
qec/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
qec/quantum_codes/__init__.py,sha256=DQ1ztrq-vBpTyoehaMWOhals46tRj553Jmkq68bDk-E,117
|
3
|
-
qec/quantum_codes/codetables_de.py,sha256=loBDBOK2cbDJ5moKmIx2MXg6e30XEPrEYau19bbDgac,3623
|
4
|
-
qec/quantum_codes/five_qubit_code.py,sha256=0zrGLyIpfyKwYG7uL00yMcM5PdhQGF17_MiI2qTMhOk,2190
|
5
|
-
qec/stabilizer_code/__init__.py,sha256=L5UMjHBlvfQBhkNlEZYSkyaHvNOcDHjc3oxYibMYHRk,63
|
6
|
-
qec/stabilizer_code/css_code.py,sha256=8BotcCuWrbnxnbZ1ZIJDI1jgr6-ohq-haPolc59TcWw,127
|
7
|
-
qec/stabilizer_code/stabilizer_code.py,sha256=_3oQwq2UNkPmP2R2qcsKTzYO4CLDvQdaiGxsN4_4r0I,22804
|
8
|
-
qec/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
|
-
qec/utils/binary_pauli_utils.py,sha256=FKxOMyEgUfSL1DF--8GUf4Nl6ytbK8Slyw7x2evhAac,13231
|
10
|
-
qec/utils/codetables_de_utils.py,sha256=soCf3u2v-C5EYYMiL8Ta4H6UF8KhRCEkjxLd6qBJai4,9467
|
11
|
-
qec/utils/sparse_binary_utils.py,sha256=Y9xfGKzOGFiVTyhb6iF6N7-5oMY6Ah9oLrnv8HhSBHA,1965
|
12
|
-
qec-0.2.0.dist-info/LICENSE,sha256=1b_xwNz1znYBfEaCL6pN2gNBAn8pQIjDRs_UhDp1EJI,1066
|
13
|
-
qec-0.2.0.dist-info/METADATA,sha256=DisbbTcVUey4dp5WelBc4aZeFcUkkwpsxRzMd44QncU,2367
|
14
|
-
qec-0.2.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
15
|
-
qec-0.2.0.dist-info/top_level.txt,sha256=d8l_7pJ5u9uWdviNp0FUK-j8VPZqywkDek7qa4NDank,4
|
16
|
-
qec-0.2.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|