qec 0.2.0__py3-none-any.whl → 0.2.2__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_constructions/__init__.py +4 -0
- qec/code_constructions/css_code.py +773 -0
- qec/code_constructions/hgp_code.py +264 -0
- qec/{stabilizer_code → code_constructions}/stabilizer_code.py +125 -143
- qec/code_instances/__init__.py +1 -0
- qec/{quantum_codes → code_instances}/five_qubit_code.py +1 -1
- qec/codetables_de/__init__.py +1 -0
- qec/{quantum_codes → codetables_de}/codetables_de.py +1 -1
- qec/utils/binary_pauli_utils.py +3 -1
- qec/utils/codetables_de_utils.py +2 -0
- qec-0.2.2.dist-info/METADATA +146 -0
- qec-0.2.2.dist-info/RECORD +18 -0
- qec/quantum_codes/__init__.py +0 -2
- qec/stabilizer_code/__init__.py +0 -1
- qec/stabilizer_code/css_code.py +0 -6
- qec-0.2.0.dist-info/METADATA +0 -82
- qec-0.2.0.dist-info/RECORD +0 -16
- {qec-0.2.0.dist-info → qec-0.2.2.dist-info}/LICENSE +0 -0
- {qec-0.2.0.dist-info → qec-0.2.2.dist-info}/WHEEL +0 -0
- {qec-0.2.0.dist-info → qec-0.2.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,264 @@
|
|
1
|
+
import numpy as np
|
2
|
+
import scipy
|
3
|
+
from typing import Union, Tuple
|
4
|
+
import ldpc.mod2
|
5
|
+
import time
|
6
|
+
|
7
|
+
from qec.code_constructions import CSSCode
|
8
|
+
from qec.utils.sparse_binary_utils import convert_to_binary_scipy_sparse
|
9
|
+
|
10
|
+
class HypergraphProductCode(CSSCode):
|
11
|
+
"""
|
12
|
+
Implements a Hypergraph Product (HGP) code - derived from two classical linear binary codes.
|
13
|
+
|
14
|
+
Parameters
|
15
|
+
----------
|
16
|
+
seed_matrix_1 :
|
17
|
+
A classical linear binary code used as a "seed" in the HGP construction method.
|
18
|
+
seed_matrix_2 :
|
19
|
+
A classical linear binary code used as a "seed" in the HGP construction method.
|
20
|
+
name : str, default = None
|
21
|
+
The name of the code. If None, the name is set to: "Hypergraph product code"
|
22
|
+
|
23
|
+
Attributes
|
24
|
+
----------
|
25
|
+
seed_matrix_1 : scipy.sparse.spmatrix
|
26
|
+
The input seed_matrix_1 stored as a scipy sparse matrix.
|
27
|
+
seed_matrix_2 : scipy.sparse.spmatrix
|
28
|
+
The input seed_matrix_2 stored as a scipy sparse matrix.
|
29
|
+
_n1 : int
|
30
|
+
Number of columns in seed_matrix_1
|
31
|
+
_n2 : int
|
32
|
+
Number of columns in seed_matrix_2
|
33
|
+
_m1 : int
|
34
|
+
Number of rows in seed_matrix_1 (the number of columns of it's transpose)
|
35
|
+
_m2 : int
|
36
|
+
Number of rows in seed_matrix_2 (the number of columns of it's transpose)
|
37
|
+
|
38
|
+
Notes
|
39
|
+
-----
|
40
|
+
|
41
|
+
The X and Z stabilizer matrices are given by [1]_:
|
42
|
+
|
43
|
+
.. math::
|
44
|
+
|
45
|
+
\begin{align}
|
46
|
+
H_{X} &= \begin{pmatrix}
|
47
|
+
H_{1}\otimes I_{n_{2}} & \,\,I_{r_{1}}\otimes H_{2}^{T}
|
48
|
+
\end{pmatrix}\tag*{(1)}\\
|
49
|
+
H_{Z} &= \begin{pmatrix}
|
50
|
+
I_{n_{1}}\otimes H_{2} & \,\,H_{1}^{T}\otimes I_{r_{2}}
|
51
|
+
\end{pmatrix}~, \tag*{(2)}
|
52
|
+
\end{align}
|
53
|
+
|
54
|
+
where :math:`H_1` and :math:`H_2` correspond to the parity check matrix of the first and second "seed" codes.
|
55
|
+
|
56
|
+
|
57
|
+
.. [1] J.-P. Tillich and G. Zemor, “Quantum LDPC Codes With Positive Rate and Minimum Distance Proportional to the Square Root of the Blocklength”, IEEE Transactions on Information Theory 60, 1193 (2014)
|
58
|
+
"""
|
59
|
+
|
60
|
+
def __init__(
|
61
|
+
self,
|
62
|
+
seed_matrix_1 : Union[np.ndarray, scipy.sparse.spmatrix],
|
63
|
+
seed_matrix_2 : Union[np.ndarray, scipy.sparse.spmatrix],
|
64
|
+
name : str = None
|
65
|
+
):
|
66
|
+
|
67
|
+
self.name = name if name else "Hypergraph product code"
|
68
|
+
|
69
|
+
if not all(isinstance(seed_m, (np.ndarray, scipy.sparse.spmatrix)) for seed_m in (seed_matrix_1, seed_matrix_2)):
|
70
|
+
raise TypeError("The seed matrices must be either numpy arrays or scipy sparse matrices.")
|
71
|
+
|
72
|
+
self.seed_matrix_1 = convert_to_binary_scipy_sparse(seed_matrix_1)
|
73
|
+
self.seed_matrix_2 = convert_to_binary_scipy_sparse(seed_matrix_2)
|
74
|
+
|
75
|
+
# maybe move the below to a private _construct_stabilizer_matrices function?
|
76
|
+
# --------------------------------------------------------------------------
|
77
|
+
self._n1 = seed_matrix_1.shape[1]
|
78
|
+
self._n2 = seed_matrix_2.shape[1]
|
79
|
+
|
80
|
+
self._m1 = seed_matrix_1.shape[0]
|
81
|
+
self._m2 = seed_matrix_2.shape[0]
|
82
|
+
|
83
|
+
x_left = scipy.sparse.kron(seed_matrix_1, scipy.sparse.eye(self._n2))
|
84
|
+
x_right = scipy.sparse.kron(scipy.sparse.eye(self._m1), seed_matrix_2.T)
|
85
|
+
self.x_stabilizer_matrix = scipy.sparse.hstack([x_left, x_right])
|
86
|
+
|
87
|
+
z_left = scipy.sparse.kron(scipy.sparse.eye(self._n1), seed_matrix_2)
|
88
|
+
z_right = scipy.sparse.kron(seed_matrix_1.T, scipy.sparse.eye(self._m2))
|
89
|
+
self.z_stabilizer_matrix = scipy.sparse.hstack([z_left, z_right])
|
90
|
+
# --------------------------------------------------------------------------
|
91
|
+
|
92
|
+
super().__init__(self.x_stabilizer_matrix, self.z_stabilizer_matrix, self.name)
|
93
|
+
|
94
|
+
|
95
|
+
def compute_exact_code_distance(self) -> int:
|
96
|
+
"""
|
97
|
+
Computes the exact code distance of the HGP code.
|
98
|
+
|
99
|
+
Returns
|
100
|
+
-------
|
101
|
+
int
|
102
|
+
The distance of the code.
|
103
|
+
|
104
|
+
Notes
|
105
|
+
-----
|
106
|
+
The distance of a HGP code is given as:
|
107
|
+
|
108
|
+
.. math::
|
109
|
+
|
110
|
+
\min(d_1, d_2, d_1^T, d_2^T)
|
111
|
+
|
112
|
+
corresponding to the distance of the seed codes and the distance of their transposes.
|
113
|
+
"""
|
114
|
+
|
115
|
+
rank_seed_m1 = ldpc.mod2.rank(self.seed_matrix_1)
|
116
|
+
rank_seed_m2 = ldpc.mod2.rank(self.seed_matrix_2)
|
117
|
+
|
118
|
+
if self.seed_matrix_1.shape[1] != rank_seed_m1:
|
119
|
+
self.d1 = ldpc.mod2.compute_exact_code_distance(self.seed_matrix_1)
|
120
|
+
else:
|
121
|
+
self.d1 = np.inf
|
122
|
+
|
123
|
+
if self.seed_matrix_2.shape[1] != rank_seed_m2:
|
124
|
+
self.d2 = ldpc.mod2.compute_exact_code_distance(self.seed_matrix_2)
|
125
|
+
else:
|
126
|
+
self.d2 = np.inf
|
127
|
+
|
128
|
+
# note: rank(A) = rank(A^T):
|
129
|
+
if self.seed_matrix_1.shape[0] != rank_seed_m1:
|
130
|
+
self.d1T = ldpc.mod2.compute_exact_code_distance(self.seed_matrix_1.T)
|
131
|
+
else:
|
132
|
+
self.d1T = np.inf
|
133
|
+
|
134
|
+
if self.seed_matrix_2.shape[0] != rank_seed_m2:
|
135
|
+
self.d2T = ldpc.mod2.compute_exact_code_distance(self.seed_matrix_2.T)
|
136
|
+
else:
|
137
|
+
self.d2T = np.inf
|
138
|
+
|
139
|
+
self.x_code_distance = min(self.d1T, self.d2)
|
140
|
+
self.z_code_distance = min(self.d1, self.d2T)
|
141
|
+
self.code_distance = min(self.x_code_distance, self.z_code_distance)
|
142
|
+
|
143
|
+
return self.code_distance
|
144
|
+
|
145
|
+
|
146
|
+
def estimate_min_distance(self, timeout_seconds: float = 0.025) -> int:
|
147
|
+
"""
|
148
|
+
Estimate the minimum X and Z distance of the HGP code. Parameters
|
149
|
+
----------
|
150
|
+
timeout_seconds : float, optional
|
151
|
+
Time limit in seconds for the full search. Default: 0.25
|
152
|
+
|
153
|
+
Returns
|
154
|
+
-------
|
155
|
+
int
|
156
|
+
Best estimate of the (overall) code distance found within time limit.
|
157
|
+
|
158
|
+
"""
|
159
|
+
|
160
|
+
rank_seed_m1 = ldpc.mod2.rank(self.seed_matrix_1)
|
161
|
+
rank_seed_m2 = ldpc.mod2.rank(self.seed_matrix_2)
|
162
|
+
|
163
|
+
|
164
|
+
d1_timeout_seconds = timeout_seconds/4
|
165
|
+
if self.seed_matrix_1.shape[1] != rank_seed_m1:
|
166
|
+
d1_start_time = time.time()
|
167
|
+
d1_min_estimate, _, _ = ldpc.mod2.estimate_code_distance(self.seed_matrix_1, d1_timeout_seconds, 0)
|
168
|
+
d1_run_time = time.time() - d1_start_time
|
169
|
+
else:
|
170
|
+
d1_min_estimate = np.inf
|
171
|
+
d1_run_time = 0
|
172
|
+
|
173
|
+
d1T_timeout_seconds = (d1_timeout_seconds * 4 - d1_run_time)/3 if d1_run_time <= d1_timeout_seconds else timeout_seconds/4
|
174
|
+
if self.seed_matrix_1.shape[0] != rank_seed_m1:
|
175
|
+
d1T_start_time = time.time()
|
176
|
+
d1T_min_estimate, _, _ = ldpc.mod2.estimate_code_distance(self.seed_matrix_1.T, d1T_timeout_seconds, 0)
|
177
|
+
d1T_run_time = time.time() - d1T_start_time
|
178
|
+
else:
|
179
|
+
d1T_min_estimate = np.inf
|
180
|
+
d1T_run_time = 0
|
181
|
+
|
182
|
+
d2_timeout_seconds = (d1T_timeout_seconds * 3 - d1T_run_time)/2 if d1T_run_time <= d1T_timeout_seconds else timeout_seconds/4
|
183
|
+
if self.seed_matrix_2.shape[1] != rank_seed_m2:
|
184
|
+
d2_start_time = time.time()
|
185
|
+
d2_min_estimate, _, _ = ldpc.mod2.estimate_code_distance(self.seed_matrix_2, d2_timeout_seconds, 0)
|
186
|
+
d2_run_time = time.time() - d2_start_time
|
187
|
+
else:
|
188
|
+
d2_min_estimate = np.inf
|
189
|
+
d2_run_time = 0
|
190
|
+
|
191
|
+
|
192
|
+
d2T_timeout_seconds = (d2_timeout_seconds * 2 - d2_run_time) if d2_run_time <= d2_timeout_seconds else timeout_seconds/4
|
193
|
+
if self.seed_matrix_2.shape[0] != rank_seed_m2:
|
194
|
+
d2T_min_estimate, _, _ = ldpc.mod2.estimate_code_distance(self.seed_matrix_2.T, d2T_timeout_seconds, 0)
|
195
|
+
else:
|
196
|
+
d2T_min_estimate = np.inf
|
197
|
+
|
198
|
+
self.x_code_distance = min(d1T_min_estimate, d2_min_estimate)
|
199
|
+
self.z_code_distance = min(d1_min_estimate, d2T_min_estimate)
|
200
|
+
self.code_distance = min(self.x_code_distance, self.z_code_distance)
|
201
|
+
|
202
|
+
|
203
|
+
return self.code_distance
|
204
|
+
|
205
|
+
|
206
|
+
|
207
|
+
def compute_logical_basis(self) -> Tuple[scipy.sparse.spmatrix, scipy.sparse.spmatrix]:
|
208
|
+
"""
|
209
|
+
Compute the logical operator basis for the given HGP code.
|
210
|
+
|
211
|
+
Returns
|
212
|
+
-------
|
213
|
+
Tuple[scipy.sparse.spmatrix, scipy.sparse.spmatrix]
|
214
|
+
Logical X and Z operator bases (lx, lz).
|
215
|
+
"""
|
216
|
+
|
217
|
+
ker_h1 = ldpc.mod2.kernel(self.seed_matrix_1)
|
218
|
+
ker_h2 = ldpc.mod2.kernel(self.seed_matrix_2)
|
219
|
+
ker_h1T = ldpc.mod2.kernel(self.seed_matrix_1.T)
|
220
|
+
ker_h2T = ldpc.mod2.kernel(self.seed_matrix_2.T)
|
221
|
+
|
222
|
+
row_comp_h1 = ldpc.mod2.row_complement_basis(self.seed_matrix_1)
|
223
|
+
row_comp_h2 = ldpc.mod2.row_complement_basis(self.seed_matrix_2)
|
224
|
+
row_comp_h1T = ldpc.mod2.row_complement_basis(self.seed_matrix_1.T)
|
225
|
+
row_comp_h2T = ldpc.mod2.row_complement_basis(self.seed_matrix_2.T)
|
226
|
+
|
227
|
+
temp = scipy.sparse.kron(ker_h1, row_comp_h2)
|
228
|
+
lz1 = scipy.sparse.hstack([temp, scipy.sparse.csr_matrix((temp.shape[0], self._m1*self._m2), dtype=np.uint8)])
|
229
|
+
|
230
|
+
temp = scipy.sparse.kron(row_comp_h1T, ker_h2T)
|
231
|
+
lz2 = scipy.sparse.hstack([scipy.sparse.csr_matrix((temp.shape[0], self._n1*self._n2), dtype=np.uint8), temp])
|
232
|
+
|
233
|
+
self.z_logical_operator_basis = scipy.sparse.vstack([lz1, lz2], dtype=np.uint8)
|
234
|
+
|
235
|
+
|
236
|
+
temp = scipy.sparse.kron(row_comp_h1, ker_h2)
|
237
|
+
lx1 = scipy.sparse.hstack([temp, scipy.sparse.csr_matrix((temp.shape[0], self._m1*self._m2), dtype=np.uint8)])
|
238
|
+
|
239
|
+
temp = scipy.sparse.kron(ker_h1T, row_comp_h2T)
|
240
|
+
lx2 = scipy.sparse.hstack([scipy.sparse.csr_matrix((temp.shape[0], self._n1*self._n2), dtype=np.uint8), temp])
|
241
|
+
|
242
|
+
self.x_logical_operator_basis= scipy.sparse.vstack([lx1, lx2], dtype = np.uint8)
|
243
|
+
|
244
|
+
# Follows the way it is done in CSSCode -> move it into __init__?
|
245
|
+
#----------------------------------------------------------------
|
246
|
+
self.logical_qubit_count = self.x_logical_operator_basis.shape[0]
|
247
|
+
#----------------------------------------------------------------
|
248
|
+
|
249
|
+
return (self.x_logical_operator_basis, self.z_logical_operator_basis)
|
250
|
+
|
251
|
+
|
252
|
+
def __str__(self):
|
253
|
+
"""
|
254
|
+
String representation of the HGP code. Includes the name and [[n, k, d]] properties of the code.
|
255
|
+
|
256
|
+
Returns
|
257
|
+
-------
|
258
|
+
str
|
259
|
+
String representation of the HGP code.
|
260
|
+
"""
|
261
|
+
|
262
|
+
return f"{self.name} Hypergraphproduct Code: [[N={self.physical_qubit_count}, K={self.logical_qubit_count}, dx={self.x_code_distance}, dz={self.z_code_distance}]]"
|
263
|
+
|
264
|
+
|
@@ -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
|
"""
|
@@ -0,0 +1 @@
|
|
1
|
+
from .five_qubit_code import FiveQubitCode
|
@@ -0,0 +1 @@
|
|
1
|
+
from .codetables_de import CodeTablesDE
|
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.
|