qec 0.2.7__py3-none-any.whl → 0.3.0__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/code_instances/saved_codes/1.json +30 -0
- qec/code_instances/saved_codes/steane.json +34 -0
- qec/code_instances/saved_codes/test.json +1 -0
- {qec-0.2.7.dist-info → qec-0.3.0.dist-info}/METADATA +1 -1
- qec-0.3.0.dist-info/RECORD +9 -0
- qec/code_constructions/__init__.py +0 -3
- qec/code_constructions/css_code.py +0 -773
- qec/code_constructions/hgp_code.py +0 -308
- qec/code_constructions/stabilizer_code.py +0 -591
- qec/code_instances/__init__.py +0 -1
- qec/code_instances/five_qubit_code.py +0 -67
- qec/codetables_de/__init__.py +0 -1
- qec/codetables_de/codetables_de.py +0 -93
- qec/utils/__init__.py +0 -0
- qec/utils/binary_pauli_utils.py +0 -403
- qec/utils/codetables_de_utils.py +0 -274
- qec/utils/sparse_binary_utils.py +0 -64
- qec-0.2.7.dist-info/RECORD +0 -18
- {qec-0.2.7.dist-info → qec-0.3.0.dist-info}/LICENSE +0 -0
- {qec-0.2.7.dist-info → qec-0.3.0.dist-info}/WHEEL +0 -0
- {qec-0.2.7.dist-info → qec-0.3.0.dist-info}/top_level.txt +0 -0
@@ -1,591 +0,0 @@
|
|
1
|
-
from qec.utils.sparse_binary_utils import convert_to_binary_scipy_sparse
|
2
|
-
from qec.utils.binary_pauli_utils import (
|
3
|
-
symplectic_product,
|
4
|
-
check_binary_pauli_matrices_commute,
|
5
|
-
pauli_str_to_binary_pcm,
|
6
|
-
binary_pcm_to_pauli_str,
|
7
|
-
binary_pauli_hamming_weight,
|
8
|
-
)
|
9
|
-
|
10
|
-
import numpy as np
|
11
|
-
import scipy.sparse
|
12
|
-
from tqdm import tqdm
|
13
|
-
from ldpc import BpOsdDecoder
|
14
|
-
import ldpc.mod2
|
15
|
-
import time
|
16
|
-
from typing import Tuple, Optional, Union, Sequence
|
17
|
-
import logging
|
18
|
-
|
19
|
-
logging.basicConfig(level=logging.DEBUG)
|
20
|
-
|
21
|
-
|
22
|
-
class StabilizerCode(object):
|
23
|
-
"""
|
24
|
-
A quantum stabilizer code, which defines and manipulates stabilizer generators,
|
25
|
-
computes logical operators, and stores parameters such as the number of physical qubits
|
26
|
-
and the number of logical qubits.
|
27
|
-
|
28
|
-
Parameters
|
29
|
-
----------
|
30
|
-
stabilizers : np.typing.ArrayLike or scipy.sparse.spmatrix or list
|
31
|
-
Either a binary parity check matrix (with an even number of columns),
|
32
|
-
or a list of Pauli strings that specify the stabilizers of the code.
|
33
|
-
name : str, optional
|
34
|
-
A name for the code. Defaults to "stabilizer code".
|
35
|
-
|
36
|
-
Attributes
|
37
|
-
----------
|
38
|
-
name : str
|
39
|
-
The name of the code.
|
40
|
-
stabilizer_matrix : scipy.sparse.spmatrix
|
41
|
-
The binary parity check matrix representation of the stabilizers.
|
42
|
-
phyical_qubit_count : int
|
43
|
-
The number of physical qubits in the code.
|
44
|
-
logical_qubit_count : int
|
45
|
-
The number of logical qubits in the code.
|
46
|
-
code_distance : int
|
47
|
-
(Not computed by default) The distance of the code, if known or computed.
|
48
|
-
logical_operator_basis : scipy.sparse.spmatrix or None
|
49
|
-
A basis for the logical operators of the code.
|
50
|
-
"""
|
51
|
-
|
52
|
-
def __init__(
|
53
|
-
self,
|
54
|
-
stabilizers: Union[np.ndarray, scipy.sparse.spmatrix, list],
|
55
|
-
name: str = None,
|
56
|
-
):
|
57
|
-
"""
|
58
|
-
Construct a StabilizerCode instance from either a parity check matrix or a list of
|
59
|
-
Pauli stabilizers.
|
60
|
-
|
61
|
-
Parameters
|
62
|
-
----------
|
63
|
-
stabilizers : np.typing.ArrayLike or scipy.sparse.spmatrix or list
|
64
|
-
Either a binary parity check matrix (with an even number of columns),
|
65
|
-
or a list of Pauli strings that specify the stabilizers of the code.
|
66
|
-
name : str, optional
|
67
|
-
A name for the code. If None, it defaults to "stabilizer code".
|
68
|
-
|
69
|
-
Raises
|
70
|
-
------
|
71
|
-
TypeError
|
72
|
-
If `stabilizers` is not an array-like, sparse matrix, or list of Pauli strings.
|
73
|
-
ValueError
|
74
|
-
If the parity check matrix does not have an even number of columns,
|
75
|
-
or the stabilizers do not mutually commute.
|
76
|
-
"""
|
77
|
-
self.name = name if name else "stabilizer code"
|
78
|
-
|
79
|
-
self.stabilizer_matrix = None
|
80
|
-
self.physical_qubit_count = None
|
81
|
-
self.logical_qubit_count = None
|
82
|
-
self.code_distance = None
|
83
|
-
self.logical_operator_basis = None
|
84
|
-
|
85
|
-
if isinstance(stabilizers, list):
|
86
|
-
stabilizers = np.array(stabilizers)
|
87
|
-
|
88
|
-
if not isinstance(stabilizers, (np.ndarray, scipy.sparse.spmatrix)):
|
89
|
-
raise TypeError(
|
90
|
-
"Please provide either a parity check matrix or a list of Pauli stabilizers."
|
91
|
-
)
|
92
|
-
|
93
|
-
if isinstance(stabilizers, np.ndarray) and stabilizers.dtype.kind in {"U", "S"}:
|
94
|
-
self.stabilizer_matrix = pauli_str_to_binary_pcm(stabilizers)
|
95
|
-
else:
|
96
|
-
if stabilizers.shape[1] % 2 == 0:
|
97
|
-
self.stabilizer_matrix = convert_to_binary_scipy_sparse(stabilizers)
|
98
|
-
else:
|
99
|
-
raise ValueError(
|
100
|
-
"The parity check matrix must have an even number of columns."
|
101
|
-
)
|
102
|
-
|
103
|
-
self.physical_qubit_count = self.stabilizer_matrix.shape[1] // 2
|
104
|
-
|
105
|
-
# Check that stabilizers commute
|
106
|
-
if not self.check_stabilizers_commute():
|
107
|
-
raise ValueError("The stabilizers do not commute.")
|
108
|
-
|
109
|
-
# Compute the number of logical qubits
|
110
|
-
self.logical_qubit_count = self.physical_qubit_count - ldpc.mod2.rank(
|
111
|
-
self.stabilizer_matrix, method="dense"
|
112
|
-
)
|
113
|
-
|
114
|
-
# Compute a basis for the logical operators of the code
|
115
|
-
self.logical_operator_basis = self.compute_logical_basis()
|
116
|
-
|
117
|
-
@property
|
118
|
-
def pauli_stabilizers(self):
|
119
|
-
"""
|
120
|
-
Get or set the stabilizers in Pauli string format.
|
121
|
-
|
122
|
-
Returns
|
123
|
-
-------
|
124
|
-
np.ndarray
|
125
|
-
An array of Pauli strings representing the stabilizers.
|
126
|
-
"""
|
127
|
-
return binary_pcm_to_pauli_str(self.stabilizer_matrix)
|
128
|
-
|
129
|
-
@pauli_stabilizers.setter
|
130
|
-
def pauli_stabilizers(self, pauli_stabilizers: np.ndarray):
|
131
|
-
"""
|
132
|
-
Set the stabilizers using Pauli strings.
|
133
|
-
|
134
|
-
Parameters
|
135
|
-
----------
|
136
|
-
pauli_stabilizers : np.ndarray
|
137
|
-
An array of Pauli strings representing the stabilizers.
|
138
|
-
|
139
|
-
Raises
|
140
|
-
------
|
141
|
-
AssertionError
|
142
|
-
If the newly set stabilizers do not commute.
|
143
|
-
"""
|
144
|
-
self.stabilizer_matrix = pauli_str_to_binary_pcm(pauli_stabilizers)
|
145
|
-
if not self.check_stabilizers_commute():
|
146
|
-
raise ValueError("The stabilizers do not commute.")
|
147
|
-
|
148
|
-
def check_stabilizers_commute(self) -> bool:
|
149
|
-
"""
|
150
|
-
Check whether the current set of stabilizers mutually commute.
|
151
|
-
|
152
|
-
Returns
|
153
|
-
-------
|
154
|
-
bool
|
155
|
-
True if all stabilizers commute, otherwise False.
|
156
|
-
"""
|
157
|
-
return check_binary_pauli_matrices_commute(
|
158
|
-
self.stabilizer_matrix, self.stabilizer_matrix
|
159
|
-
)
|
160
|
-
|
161
|
-
def compute_logical_basis(self) -> scipy.sparse.spmatrix:
|
162
|
-
"""
|
163
|
-
Compute a basis for the logical operators of the code by extending the parity check
|
164
|
-
matrix. The resulting basis operators are stored in `self.logicals`.
|
165
|
-
|
166
|
-
Returns
|
167
|
-
-------
|
168
|
-
scipy.sparse.spmatrix
|
169
|
-
A basis for the logical operators in binary representation.
|
170
|
-
|
171
|
-
Notes
|
172
|
-
-----
|
173
|
-
This method uses the kernel of the parity check matrix to find operators that
|
174
|
-
commute with all stabilizers, and then identifies a subset that spans the space
|
175
|
-
of logical operators.
|
176
|
-
"""
|
177
|
-
kernel_h = ldpc.mod2.kernel(self.stabilizer_matrix)
|
178
|
-
|
179
|
-
# Sort the rows of the kernel by weight
|
180
|
-
row_weights = kernel_h.getnnz(axis=1)
|
181
|
-
sorted_rows = np.argsort(row_weights)
|
182
|
-
kernel_h = kernel_h[sorted_rows, :]
|
183
|
-
|
184
|
-
swapped_kernel = scipy.sparse.hstack(
|
185
|
-
[
|
186
|
-
kernel_h[:, self.physical_qubit_count :],
|
187
|
-
kernel_h[:, : self.physical_qubit_count],
|
188
|
-
]
|
189
|
-
)
|
190
|
-
|
191
|
-
logical_stack = scipy.sparse.vstack([self.stabilizer_matrix, swapped_kernel])
|
192
|
-
p_rows = ldpc.mod2.pivot_rows(logical_stack)
|
193
|
-
|
194
|
-
self.logical_operator_basis = logical_stack[
|
195
|
-
p_rows[self.stabilizer_matrix.shape[0] :]
|
196
|
-
]
|
197
|
-
|
198
|
-
if self.logical_operator_basis.nnz == 0:
|
199
|
-
self.code_distance = np.inf
|
200
|
-
return self.logical_operator_basis
|
201
|
-
|
202
|
-
basis_minimum_hamming_weight = np.min(
|
203
|
-
binary_pauli_hamming_weight(self.logical_operator_basis).flatten()
|
204
|
-
)
|
205
|
-
|
206
|
-
# Update distance based on the minimum hamming weight of the logical operators in this basis
|
207
|
-
if self.code_distance is None:
|
208
|
-
self.code_distance = basis_minimum_hamming_weight
|
209
|
-
elif basis_minimum_hamming_weight < self.code_distance:
|
210
|
-
self.code_distance = basis_minimum_hamming_weight
|
211
|
-
else:
|
212
|
-
pass
|
213
|
-
|
214
|
-
return logical_stack[p_rows[self.stabilizer_matrix.shape[0] :]]
|
215
|
-
|
216
|
-
def check_valid_logical_basis(self) -> bool:
|
217
|
-
"""
|
218
|
-
Validate that the stored logical operators form a proper logical basis for the code.
|
219
|
-
|
220
|
-
Checks that they commute with the stabilizers, pairwise anti-commute (in the symplectic
|
221
|
-
sense), and have full rank.
|
222
|
-
|
223
|
-
Returns
|
224
|
-
-------
|
225
|
-
bool
|
226
|
-
True if the logical operators form a valid basis, otherwise False.
|
227
|
-
"""
|
228
|
-
try:
|
229
|
-
assert check_binary_pauli_matrices_commute(
|
230
|
-
self.stabilizer_matrix, self.logical_operator_basis
|
231
|
-
), "Logical operators do not commute with stabilizers."
|
232
|
-
|
233
|
-
logical_product = symplectic_product(
|
234
|
-
self.logical_operator_basis, self.logical_operator_basis
|
235
|
-
)
|
236
|
-
logical_product.eliminate_zeros()
|
237
|
-
assert (
|
238
|
-
logical_product.nnz != 0
|
239
|
-
), "The logical operators do not anti-commute with one another."
|
240
|
-
|
241
|
-
assert (
|
242
|
-
ldpc.mod2.rank(self.logical_operator_basis, method="dense")
|
243
|
-
== 2 * self.logical_qubit_count
|
244
|
-
), "The logical operators do not form a basis for the code."
|
245
|
-
|
246
|
-
assert (
|
247
|
-
self.logical_operator_basis.shape[0] == 2 * self.logical_qubit_count
|
248
|
-
), "The logical operators are not linearly independent."
|
249
|
-
|
250
|
-
except AssertionError as e:
|
251
|
-
logging.error(e)
|
252
|
-
return False
|
253
|
-
|
254
|
-
return True
|
255
|
-
|
256
|
-
def compute_exact_code_distance(
|
257
|
-
self, timeout: float = 0.5
|
258
|
-
) -> Tuple[Optional[int], float]:
|
259
|
-
"""
|
260
|
-
Compute the distance of the code by searching through linear combinations of
|
261
|
-
logical operators and stabilizers, returning a tuple of the minimal Hamming weight
|
262
|
-
found and the fraction of logical operators considered before timing out.
|
263
|
-
|
264
|
-
Parameters
|
265
|
-
----------
|
266
|
-
timeout : float, optional
|
267
|
-
The time limit (in seconds) for the exhaustive search. Default is 0.5 seconds. To obtain the exact distance, set to `np.inf`.
|
268
|
-
|
269
|
-
Returns
|
270
|
-
-------
|
271
|
-
Tuple[Optional[int], float]
|
272
|
-
A tuple containing:
|
273
|
-
- The best-known distance of the code as an integer (or `None` if no distance was found).
|
274
|
-
- The fraction of logical combinations considered before the search ended.
|
275
|
-
|
276
|
-
Notes
|
277
|
-
-----
|
278
|
-
- We compute the row span of both the stabilizers and the logical operators.
|
279
|
-
- For every logical operator in the logical span, we add (mod 2) each stabilizer
|
280
|
-
in the stabilizer span to form candidate logical operators.
|
281
|
-
- We compute the Hamming weight of each candidate operator (i.e. how many qubits
|
282
|
-
are acted upon by the operator).
|
283
|
-
- We track the minimal Hamming weight encountered. If `timeout` is exceeded,
|
284
|
-
we immediately return the best distance found so far.
|
285
|
-
|
286
|
-
Examples
|
287
|
-
--------
|
288
|
-
>>> code = StabilizerCode(["XZZX", "ZZXX"])
|
289
|
-
>>> dist, fraction = code.compute_exact_code_distance(timeout=1.0)
|
290
|
-
>>> print(dist, fraction)
|
291
|
-
"""
|
292
|
-
start_time = time.time()
|
293
|
-
|
294
|
-
stabilizer_span = ldpc.mod2.row_span(self.stabilizer_matrix)[1:]
|
295
|
-
logical_span = ldpc.mod2.row_span(self.logical_operator_basis)[1:]
|
296
|
-
|
297
|
-
if self.code_distance is None:
|
298
|
-
distance = np.inf
|
299
|
-
else:
|
300
|
-
distance = self.code_distance
|
301
|
-
|
302
|
-
logicals_considered = 0
|
303
|
-
total_logical_operators = stabilizer_span.shape[0] * logical_span.shape[0]
|
304
|
-
|
305
|
-
for logical in logical_span:
|
306
|
-
if time.time() - start_time > timeout:
|
307
|
-
break
|
308
|
-
for stabilizer in stabilizer_span:
|
309
|
-
if time.time() - start_time > timeout:
|
310
|
-
break
|
311
|
-
candidate_logical = logical + stabilizer
|
312
|
-
candidate_logical.data %= 2
|
313
|
-
|
314
|
-
hamming_weight = binary_pauli_hamming_weight(candidate_logical)[0]
|
315
|
-
if hamming_weight < distance:
|
316
|
-
distance = hamming_weight
|
317
|
-
logicals_considered += 1
|
318
|
-
|
319
|
-
self.code_distance = distance
|
320
|
-
fraction_considered = logicals_considered / total_logical_operators
|
321
|
-
|
322
|
-
return (
|
323
|
-
(int(distance), fraction_considered)
|
324
|
-
if distance != np.inf
|
325
|
-
else (None, fraction_considered)
|
326
|
-
)
|
327
|
-
|
328
|
-
def get_code_parameters(self) -> tuple:
|
329
|
-
"""
|
330
|
-
Return the parameters of the code as a tuple: (n, k, d).
|
331
|
-
|
332
|
-
Returns
|
333
|
-
-------
|
334
|
-
tuple
|
335
|
-
A tuple of integers representing the number of physical qubits, logical qubits,
|
336
|
-
and the distance of the code.
|
337
|
-
"""
|
338
|
-
return self.physical_qubit_count, self.logical_qubit_count, self.code_distance
|
339
|
-
|
340
|
-
def save_code(self, save_dense: bool = False):
|
341
|
-
"""
|
342
|
-
Save the stabilizer code to disk.
|
343
|
-
|
344
|
-
Parameters
|
345
|
-
----------
|
346
|
-
save_dense : bool, optional
|
347
|
-
If True, saves the parity check matrix as a dense format.
|
348
|
-
Otherwise, saves the parity check matrix as a sparse format.
|
349
|
-
"""
|
350
|
-
pass
|
351
|
-
|
352
|
-
def load_code(self):
|
353
|
-
"""
|
354
|
-
Load the stabilizer code from a saved file.
|
355
|
-
"""
|
356
|
-
pass
|
357
|
-
|
358
|
-
def __repr__(self):
|
359
|
-
"""
|
360
|
-
Return an unambiguous string representation of the StabilizerCode instance.
|
361
|
-
|
362
|
-
Returns
|
363
|
-
-------
|
364
|
-
str
|
365
|
-
An unambiguous representation for debugging and development.
|
366
|
-
"""
|
367
|
-
return f"Name: {self.name}, Class: Stabilizer Code"
|
368
|
-
|
369
|
-
def __str__(self):
|
370
|
-
"""
|
371
|
-
Return a string describing the stabilizer code, including its parameters.
|
372
|
-
|
373
|
-
Returns
|
374
|
-
-------
|
375
|
-
str
|
376
|
-
A human-readable string with the name, n, k, and d parameters of the code.
|
377
|
-
"""
|
378
|
-
return f"< Stabilizer Code, Name: {self.name}, Parameters: [[{self.physical_qubit_count}, {self.logical_qubit_count}, {self.code_distance}]] >"
|
379
|
-
|
380
|
-
def estimate_min_distance(
|
381
|
-
self,
|
382
|
-
timeout_seconds: float = 0.25,
|
383
|
-
p: float = 0.25,
|
384
|
-
reduce_logical_basis: bool = False,
|
385
|
-
decoder: Optional[BpOsdDecoder] = None,
|
386
|
-
) -> int:
|
387
|
-
"""
|
388
|
-
Estimate the minimum distance of the stabilizer code using a BP+OSD decoder-based search.
|
389
|
-
|
390
|
-
Parameters
|
391
|
-
----------
|
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.
|
400
|
-
|
401
|
-
Returns
|
402
|
-
-------
|
403
|
-
int
|
404
|
-
Best estimate of code distance found within time limit
|
405
|
-
"""
|
406
|
-
if self.logical_operator_basis is None:
|
407
|
-
self.logical_operator_basis = self.compute_logical_basis()
|
408
|
-
|
409
|
-
# Initial setup of decoder and parameters
|
410
|
-
bp_osd, stack, full_rank_stabilizer_matrix, min_distance, max_distance = (
|
411
|
-
self._setup_distance_estimation_decoder(decoder)
|
412
|
-
)
|
413
|
-
|
414
|
-
# Initialize storage for candidate logicals and tracking
|
415
|
-
candidate_logicals = []
|
416
|
-
weight_one_syndromes_searched = 0
|
417
|
-
|
418
|
-
# Main search loop
|
419
|
-
start_time = time.time()
|
420
|
-
with tqdm(total=timeout_seconds, desc="Estimating distance") as pbar:
|
421
|
-
while time.time() - start_time < timeout_seconds:
|
422
|
-
# Update progress bar
|
423
|
-
elapsed = time.time() - start_time
|
424
|
-
pbar.update(elapsed - pbar.n)
|
425
|
-
|
426
|
-
# Initialize an empty dummy syndrome
|
427
|
-
dummy_syndrome = np.zeros(stack.shape[0], dtype=np.uint8)
|
428
|
-
|
429
|
-
if weight_one_syndromes_searched < self.logical_operator_basis.shape[0]:
|
430
|
-
# Try each logical operator individually first
|
431
|
-
dummy_syndrome[
|
432
|
-
full_rank_stabilizer_matrix.shape[0]
|
433
|
-
+ weight_one_syndromes_searched
|
434
|
-
] = 1
|
435
|
-
weight_one_syndromes_searched += 1
|
436
|
-
else:
|
437
|
-
# Randomly pick a combination of logical rows
|
438
|
-
while True:
|
439
|
-
random_mask = np.random.choice(
|
440
|
-
[0, 1],
|
441
|
-
size=self.logical_operator_basis.shape[0],
|
442
|
-
p=[1 - p, p],
|
443
|
-
)
|
444
|
-
if np.any(random_mask):
|
445
|
-
break
|
446
|
-
for idx, bit in enumerate(random_mask):
|
447
|
-
if bit == 1:
|
448
|
-
dummy_syndrome[self.stabilizer_matrix.shape[0] + idx] = 1
|
449
|
-
|
450
|
-
candidate = bp_osd.decode(dummy_syndrome)
|
451
|
-
w = np.count_nonzero(
|
452
|
-
candidate[: self.physical_qubit_count]
|
453
|
-
| candidate[self.physical_qubit_count :]
|
454
|
-
)
|
455
|
-
|
456
|
-
if w < min_distance:
|
457
|
-
min_distance = w
|
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
|
468
|
-
if (
|
469
|
-
len(candidate_logicals) >= self.logical_qubit_count
|
470
|
-
and reduce_logical_basis
|
471
|
-
):
|
472
|
-
self._reduce_logical_operator_basis(candidate_logicals)
|
473
|
-
(
|
474
|
-
bp_osd,
|
475
|
-
stack,
|
476
|
-
full_rank_stabilizer_matrix,
|
477
|
-
min_distance,
|
478
|
-
max_distance,
|
479
|
-
) = self._setup_distance_estimation_decoder(decoder)
|
480
|
-
candidate_logicals = []
|
481
|
-
weight_one_syndromes_searched = 0
|
482
|
-
|
483
|
-
pbar.set_description(
|
484
|
-
f"Estimating distance: min-weight found <= {min_distance}, "
|
485
|
-
f"basis weights: {self.logical_basis_weights()}"
|
486
|
-
)
|
487
|
-
|
488
|
-
# Final basis reduction if needed
|
489
|
-
if reduce_logical_basis and candidate_logicals:
|
490
|
-
self._reduce_logical_operator_basis(candidate_logicals)
|
491
|
-
|
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:]]
|
581
|
-
|
582
|
-
def logical_basis_weights(self):
|
583
|
-
"""
|
584
|
-
Return the Hamming weights of the logical operators in the current basis.
|
585
|
-
|
586
|
-
Returns
|
587
|
-
-------
|
588
|
-
np.ndarray
|
589
|
-
An array of integers representing the Hamming weights of the logical operators.
|
590
|
-
"""
|
591
|
-
return binary_pauli_hamming_weight(self.logical_operator_basis).flatten()
|
qec/code_instances/__init__.py
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
from .five_qubit_code import FiveQubitCode
|
@@ -1,67 +0,0 @@
|
|
1
|
-
from qec.code_constructions import StabilizerCode
|
2
|
-
|
3
|
-
|
4
|
-
class FiveQubitCode(StabilizerCode):
|
5
|
-
"""
|
6
|
-
Five-Qubit Quantum Error-Correcting Code.
|
7
|
-
|
8
|
-
The `FiveQubitCode` class implements the [[5, 1, 3]] quantum stabilizer code,
|
9
|
-
which is the smallest possible quantum error-correcting code capable of
|
10
|
-
correcting an arbitrary single-qubit error. This code encodes one logical
|
11
|
-
qubit into five physical qubits and has a distance of three, allowing it
|
12
|
-
to detect up to two errors and correct one.
|
13
|
-
|
14
|
-
Parameters
|
15
|
-
----------
|
16
|
-
None
|
17
|
-
|
18
|
-
Attributes
|
19
|
-
----------
|
20
|
-
code_distance : int
|
21
|
-
The distance of the quantum code. For the five-qubit code, this is set to 3.
|
22
|
-
|
23
|
-
Inherits
|
24
|
-
--------
|
25
|
-
StabilizerCode
|
26
|
-
The base class providing functionalities for stabilizer-based quantum
|
27
|
-
error-correcting codes, including initialization, distance computation,
|
28
|
-
and parameter retrieval.
|
29
|
-
|
30
|
-
Examples
|
31
|
-
--------
|
32
|
-
>>> five_qubit = FiveQubitCode()
|
33
|
-
>>> five_qubit.phyiscal_qubit_count
|
34
|
-
5
|
35
|
-
>>> five_qubit.logical_qubit_count
|
36
|
-
1
|
37
|
-
>>> five_qubit.code_distance
|
38
|
-
3
|
39
|
-
"""
|
40
|
-
|
41
|
-
def __init__(self):
|
42
|
-
"""
|
43
|
-
Initialize the Five-Qubit Code with predefined Pauli stabilizers.
|
44
|
-
|
45
|
-
The constructor sets up the stabilizer generators for the [[5, 1, 3]]
|
46
|
-
quantum code using their corresponding Pauli strings. It then calls the
|
47
|
-
superclass initializer to establish the stabilizer matrix and other
|
48
|
-
essential parameters.
|
49
|
-
|
50
|
-
Parameters
|
51
|
-
----------
|
52
|
-
None
|
53
|
-
|
54
|
-
Raises
|
55
|
-
------
|
56
|
-
ValueError
|
57
|
-
If the provided stabilizer generators do not satisfy the necessary
|
58
|
-
commutation relations required for a valid stabilizer code.
|
59
|
-
"""
|
60
|
-
# Define the Pauli stabilizer generators for the five-qubit code
|
61
|
-
pauli_stabilizers = [["XZZXI"], ["IXZZX"], ["XIXZZ"], ["ZXIXZ"]]
|
62
|
-
|
63
|
-
# Initialize the StabilizerCode with the defined stabilizers and a custom name
|
64
|
-
super().__init__(stabilizers=pauli_stabilizers, name="5-Qubit Code")
|
65
|
-
|
66
|
-
# Set the distance attribute specific to the five-qubit code
|
67
|
-
self.code_distance = 3
|
qec/codetables_de/__init__.py
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
from .codetables_de import CodeTablesDE
|