qec 0.0.11__py3-none-any.whl → 0.2.1__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- qec/__init__.py +0 -0
- qec/quantum_codes/__init__.py +2 -0
- qec/quantum_codes/codetables_de.py +93 -0
- qec/quantum_codes/five_qubit_code.py +67 -0
- qec/stabilizer_code/__init__.py +1 -0
- qec/stabilizer_code/css_code.py +609 -0
- qec/stabilizer_code/stabilizer_code.py +591 -0
- qec/utils/__init__.py +0 -0
- qec/utils/binary_pauli_utils.py +403 -0
- qec/utils/codetables_de_utils.py +274 -0
- qec/utils/sparse_binary_utils.py +64 -0
- qec-0.2.1.dist-info/LICENSE +21 -0
- qec-0.2.1.dist-info/METADATA +82 -0
- qec-0.2.1.dist-info/RECORD +16 -0
- {qec-0.0.11.dist-info → qec-0.2.1.dist-info}/WHEEL +1 -1
- qec/css.py +0 -164
- qec/hgp.py +0 -75
- qec/lifted_hgp.py +0 -79
- qec/protograph.py +0 -150
- qec/quantum_codes.py +0 -185
- qec/stab.py +0 -119
- qec/xzzx_codes.py +0 -333
- qec-0.0.11.dist-info/METADATA +0 -18
- qec-0.0.11.dist-info/RECORD +0 -11
- {qec-0.0.11.dist-info → qec-0.2.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,403 @@
|
|
1
|
+
import numpy as np
|
2
|
+
import scipy
|
3
|
+
import scipy.sparse
|
4
|
+
from qec.utils.sparse_binary_utils import convert_to_binary_scipy_sparse
|
5
|
+
|
6
|
+
|
7
|
+
def GF4_to_binary(GF4_matrix: np.typing.ArrayLike) -> scipy.sparse.csr_matrix:
|
8
|
+
"""
|
9
|
+
Convert a matrix over GF4 (elements {0,1,2,3}) to a binary sparse matrix in CSR format.
|
10
|
+
|
11
|
+
Each entry (row i, column j) is mapped as follows:
|
12
|
+
- 0 => no 1's (row has [0, 0])
|
13
|
+
- 1 => one 1 in column 2*j ([1, 0])
|
14
|
+
- 2 => two 1's in columns 2*j and 2*j + 1 ([1, 1])
|
15
|
+
- 3 => one 1 in column 2*j + 1 ([0, 1])
|
16
|
+
|
17
|
+
Parameters
|
18
|
+
----------
|
19
|
+
GF4_matrix : ArrayLike
|
20
|
+
Input matrix of shape (M, N) containing only elements from {0, 1, 2, 3}.
|
21
|
+
Can be a dense array-like or any SciPy sparse matrix format.
|
22
|
+
|
23
|
+
Returns
|
24
|
+
-------
|
25
|
+
scipy.sparse.csr_matrix
|
26
|
+
Binary sparse matrix in CSR format, of shape (M, 2*N).
|
27
|
+
|
28
|
+
Raises
|
29
|
+
------
|
30
|
+
ValueError
|
31
|
+
If the input matrix has elements outside {0, 1, 2, 3}.
|
32
|
+
|
33
|
+
Examples
|
34
|
+
--------
|
35
|
+
>>> import numpy as np
|
36
|
+
>>> from scipy.sparse import scipy.sparse.csr_matrix
|
37
|
+
>>> mat = np.array([[0, 1],
|
38
|
+
... [2, 3]])
|
39
|
+
>>> GF4_to_binary(mat).toarray()
|
40
|
+
array([[0, 1, 1, 0],
|
41
|
+
[1, 1, 0, 1]], dtype=uint8)
|
42
|
+
"""
|
43
|
+
if scipy.sparse.issparse(GF4_matrix):
|
44
|
+
mat_coo = GF4_matrix.tocoo(copy=False)
|
45
|
+
|
46
|
+
if not np.all(np.isin(mat_coo.data, [1, 2, 3])):
|
47
|
+
raise ValueError(
|
48
|
+
"Input matrix must contain only elements from GF4: {0, 1, 2, 3}"
|
49
|
+
)
|
50
|
+
|
51
|
+
row_ids = []
|
52
|
+
col_ids = []
|
53
|
+
rows, cols = mat_coo.shape
|
54
|
+
|
55
|
+
for r, c, val in zip(mat_coo.row, mat_coo.col, mat_coo.data):
|
56
|
+
if val == 1:
|
57
|
+
row_ids.append(r)
|
58
|
+
col_ids.append(c)
|
59
|
+
elif val == 2:
|
60
|
+
row_ids.extend([r, r])
|
61
|
+
col_ids.extend([c, cols + c])
|
62
|
+
elif val == 3:
|
63
|
+
row_ids.append(r)
|
64
|
+
col_ids.append(cols + c)
|
65
|
+
|
66
|
+
data = np.ones(len(row_ids), dtype=np.uint8)
|
67
|
+
return scipy.sparse.csr_matrix(
|
68
|
+
(data, (row_ids, col_ids)), shape=(rows, 2 * cols)
|
69
|
+
)
|
70
|
+
|
71
|
+
GF4_matrix = np.asanyarray(GF4_matrix, dtype=int)
|
72
|
+
if not np.all(np.isin(GF4_matrix, [0, 1, 2, 3])):
|
73
|
+
raise ValueError(
|
74
|
+
"Input matrix must contain only elements from GF4: {0, 1, 2, 3}"
|
75
|
+
)
|
76
|
+
|
77
|
+
row_ids = []
|
78
|
+
col_ids = []
|
79
|
+
rows, cols = GF4_matrix.shape
|
80
|
+
|
81
|
+
for i in range(rows):
|
82
|
+
for j in range(cols):
|
83
|
+
val = GF4_matrix[i, j]
|
84
|
+
if val == 1:
|
85
|
+
row_ids.append(i)
|
86
|
+
col_ids.append(j)
|
87
|
+
elif val == 2:
|
88
|
+
row_ids.extend([i, i])
|
89
|
+
col_ids.extend([j, j + cols])
|
90
|
+
elif val == 3:
|
91
|
+
row_ids.append(i)
|
92
|
+
col_ids.append(j + cols)
|
93
|
+
|
94
|
+
data = np.ones(len(row_ids), dtype=np.uint8)
|
95
|
+
return scipy.sparse.csr_matrix((data, (row_ids, col_ids)), shape=(rows, 2 * cols))
|
96
|
+
|
97
|
+
|
98
|
+
def pauli_str_to_binary_pcm(
|
99
|
+
pauli_strings: np.typing.ArrayLike,
|
100
|
+
) -> scipy.sparse.csr_matrix:
|
101
|
+
"""
|
102
|
+
Convert an (M x 1) array of Pauli strings, where each string has length N, corresponding to the number of physical qubits, into a binary parity-check matrix (PCM) with dimensions (M x 2*N).
|
103
|
+
|
104
|
+
The mapping for each qubit j in the string is:
|
105
|
+
- 'I' => (0|0)
|
106
|
+
- 'X' => (1|0)
|
107
|
+
- 'Z' => (0|1)
|
108
|
+
- 'Y' => (1|1)
|
109
|
+
where the first element (a), in (a|b) is at column j and the second element (b) is at column j + N.
|
110
|
+
|
111
|
+
Parameters
|
112
|
+
----------
|
113
|
+
pauli_strings : ArrayLike
|
114
|
+
Array of shape (M, 1), where each element is a string of Pauli operators
|
115
|
+
('I', 'X', 'Y', 'Z'). Can be dense or any SciPy sparse matrix format with
|
116
|
+
an object/string dtype.
|
117
|
+
|
118
|
+
Returns
|
119
|
+
-------
|
120
|
+
scipy.sparse.csr_matrix
|
121
|
+
Binary parity-check matrix of shape (M, 2*N) in CSR format, where M is the number of stabilisers and
|
122
|
+
N is the number of physical qubits.
|
123
|
+
Raises
|
124
|
+
------
|
125
|
+
ValueError
|
126
|
+
If any character in the Pauli strings is not one of {'I', 'X', 'Y', 'Z'}.
|
127
|
+
|
128
|
+
Examples
|
129
|
+
--------
|
130
|
+
>>> import numpy as np
|
131
|
+
>>> paulis = np.array([["XIZ"], ["YYI"]], dtype=object)
|
132
|
+
>>> pcm = pauli_str_to_binary_pcm(paulis)
|
133
|
+
>>> pcm.toarray()
|
134
|
+
array([[1, 0, 0, 0, 0, 1],
|
135
|
+
[1, 1, 0, 1, 1, 0]], dtype=uint8)
|
136
|
+
"""
|
137
|
+
|
138
|
+
if scipy.sparse.issparse(pauli_strings):
|
139
|
+
if pauli_strings.dtype == object:
|
140
|
+
mat_coo = pauli_strings.tocoo(copy=False)
|
141
|
+
dense = np.full(pauli_strings.shape, "I", dtype=str)
|
142
|
+
for r, c, val in zip(mat_coo.row, mat_coo.col, mat_coo.data):
|
143
|
+
dense[r, c] = val
|
144
|
+
pauli_strings = dense
|
145
|
+
else:
|
146
|
+
pauli_strings = pauli_strings.toarray()
|
147
|
+
|
148
|
+
pauli_strings = np.asanyarray(pauli_strings, dtype=str)
|
149
|
+
|
150
|
+
if pauli_strings.size == 0:
|
151
|
+
return scipy.sparse.csr_matrix((0, 0))
|
152
|
+
|
153
|
+
row_ids = []
|
154
|
+
col_ids = []
|
155
|
+
|
156
|
+
m_stabilisers = pauli_strings.shape[0]
|
157
|
+
n_qubits = len(pauli_strings[0, 0])
|
158
|
+
|
159
|
+
for i, string in enumerate(pauli_strings):
|
160
|
+
if len(string[0]) != n_qubits:
|
161
|
+
raise ValueError("The Pauli strings do not have equal length.")
|
162
|
+
for j, char in enumerate(string[0]):
|
163
|
+
if char == "I":
|
164
|
+
continue
|
165
|
+
elif char == "X":
|
166
|
+
row_ids.append(i)
|
167
|
+
col_ids.append(j)
|
168
|
+
elif char == "Z":
|
169
|
+
row_ids.append(i)
|
170
|
+
col_ids.append(j + n_qubits)
|
171
|
+
elif char == "Y":
|
172
|
+
row_ids.extend([i, i])
|
173
|
+
col_ids.extend([j, j + n_qubits])
|
174
|
+
else:
|
175
|
+
raise ValueError(f"Invalid Pauli character '{char}' encountered.")
|
176
|
+
|
177
|
+
data = np.ones(len(row_ids), dtype=np.uint8)
|
178
|
+
|
179
|
+
return scipy.sparse.csr_matrix(
|
180
|
+
(data, (row_ids, col_ids)), shape=(m_stabilisers, 2 * n_qubits), dtype=np.uint8
|
181
|
+
)
|
182
|
+
|
183
|
+
|
184
|
+
def binary_pcm_to_pauli_str(binary_pcm: np.typing.ArrayLike) -> np.ndarray:
|
185
|
+
"""
|
186
|
+
Convert a binary (M x 2*N) PCM corresponding to M stabilisers acting on N physical qubits,
|
187
|
+
back into an array (M x 1) of Pauli strings that have length N.
|
188
|
+
|
189
|
+
For each qubit j, columns (j | j + N) of the PCM encode:
|
190
|
+
- (0|0) => 'I'
|
191
|
+
- (1|0) => 'X'
|
192
|
+
- (0|1) => 'Z'
|
193
|
+
- (1|1) => 'Y'
|
194
|
+
|
195
|
+
Parameters
|
196
|
+
----------
|
197
|
+
binary_pcm : ArrayLike
|
198
|
+
Binary matrix of shape (M, 2*N), in dense or any SciPy sparse matrix format.
|
199
|
+
|
200
|
+
Returns
|
201
|
+
-------
|
202
|
+
np.ndarray
|
203
|
+
Array of shape (M, 1), where each element is a string of Pauli operators with length N.
|
204
|
+
|
205
|
+
Examples
|
206
|
+
--------
|
207
|
+
>>> import numpy as np
|
208
|
+
>>> from scipy.sparse import scipy.sparse.csr_matrix
|
209
|
+
>>> pcm = np.array([[1, 0, 0, 0, 0, 1],
|
210
|
+
... [1, 1, 0, 1, 1, 0]], dtype=np.uint8)
|
211
|
+
>>> pauli_str_to_return = binary_pcm_to_pauli_str(pcm)
|
212
|
+
>>> pauli_str_to_return
|
213
|
+
array([['XIZ'],
|
214
|
+
['YYI']], dtype='<U3')
|
215
|
+
"""
|
216
|
+
if scipy.sparse.issparse(binary_pcm):
|
217
|
+
binary_pcm = binary_pcm.toarray()
|
218
|
+
|
219
|
+
binary_pcm = np.asanyarray(binary_pcm, dtype=int)
|
220
|
+
n_rows, n_cols = binary_pcm.shape
|
221
|
+
n_qubits = n_cols // 2
|
222
|
+
pauli_strings = [""] * n_rows
|
223
|
+
|
224
|
+
for i in range(n_rows):
|
225
|
+
row = binary_pcm[i]
|
226
|
+
x_bits = row[:n_qubits]
|
227
|
+
z_bits = row[n_qubits:]
|
228
|
+
for x_bit, z_bit in zip(x_bits, z_bits):
|
229
|
+
if x_bit == 0 and z_bit == 0:
|
230
|
+
pauli_strings[i] += "I"
|
231
|
+
elif x_bit == 1 and z_bit == 0:
|
232
|
+
pauli_strings[i] += "X"
|
233
|
+
elif x_bit == 0 and z_bit == 1:
|
234
|
+
pauli_strings[i] += "Z"
|
235
|
+
else:
|
236
|
+
pauli_strings[i] += "Y"
|
237
|
+
|
238
|
+
return np.array(pauli_strings, dtype=str).reshape(-1, 1)
|
239
|
+
|
240
|
+
|
241
|
+
def symplectic_product(
|
242
|
+
a: np.typing.ArrayLike, b: np.typing.ArrayLike
|
243
|
+
) -> scipy.sparse.csr_matrix:
|
244
|
+
"""
|
245
|
+
Compute the symplectic product of two binary matrices in CSR format.
|
246
|
+
|
247
|
+
The input matrices (A,B) are first converted to binary sparse format (modulo 2)
|
248
|
+
and then partitioned into `x` and `z` components, where x and z have the same shape:
|
249
|
+
|
250
|
+
A = (a_x|a_z)
|
251
|
+
B = (b_x|b_z)
|
252
|
+
|
253
|
+
Then the symplectic product is computed as: (a_x * b_z^T + a_z * b_x^T) mod 2.
|
254
|
+
|
255
|
+
Parameters
|
256
|
+
----------
|
257
|
+
a : array_like
|
258
|
+
A 2D array-like object with shape (M, 2N), which will be converted to
|
259
|
+
a binary sparse matrix (mod 2).
|
260
|
+
b : array_like
|
261
|
+
A 2D array-like object with shape (M, 2N), which will be converted to
|
262
|
+
a binary sparse matrix (mod 2). Must have the same shape as `a`.
|
263
|
+
|
264
|
+
Returns
|
265
|
+
-------
|
266
|
+
scipy.sparse.csr_matrix
|
267
|
+
The symplectic product of the two input matrices, stored in CSR format.
|
268
|
+
|
269
|
+
Raises
|
270
|
+
------
|
271
|
+
AssertionError
|
272
|
+
If the shapes of `a` and `b` do not match.
|
273
|
+
AssertionError
|
274
|
+
If the number of columns of `a` (and `b`) is not even.
|
275
|
+
|
276
|
+
Notes
|
277
|
+
-----
|
278
|
+
This function is particularly useful for calculating commutation between Pauli operators,
|
279
|
+
where a result of 0 indicates commuting operators, and 1 indicates anti-commuting operators.
|
280
|
+
|
281
|
+
Examples
|
282
|
+
--------
|
283
|
+
>>> import numpy as np
|
284
|
+
>>> from qec.utils.sparse_binary_utils import convert_to_binary_scipy_sparse
|
285
|
+
>>> a_data = np.array([[1, 0, 0, 1],
|
286
|
+
... [0, 1, 1, 0],
|
287
|
+
... [1, 1, 0, 0]], dtype=int)
|
288
|
+
>>> b_data = np.array([[0, 1, 1, 0],
|
289
|
+
... [1, 0, 0, 1],
|
290
|
+
... [0, 1, 1, 0]], dtype=int)
|
291
|
+
>>> # Compute symplectic product
|
292
|
+
>>> sp = symplectic_product(a_data, b_data)
|
293
|
+
>>> sp.toarray()
|
294
|
+
array([[0, 0, 0],
|
295
|
+
[0, 0, 0],
|
296
|
+
[1, 1, 1]], dtype=int8)
|
297
|
+
"""
|
298
|
+
|
299
|
+
a = convert_to_binary_scipy_sparse(a)
|
300
|
+
b = convert_to_binary_scipy_sparse(b)
|
301
|
+
|
302
|
+
assert (
|
303
|
+
a.shape[1] == b.shape[1]
|
304
|
+
), "Input matrices must have the same number of columns."
|
305
|
+
assert a.shape[1] % 2 == 0, "Input matrices must have an even number of columns."
|
306
|
+
|
307
|
+
n = a.shape[1] // 2
|
308
|
+
|
309
|
+
ax = a[:, :n]
|
310
|
+
az = a[:, n:]
|
311
|
+
bx = b[:, :n]
|
312
|
+
bz = b[:, n:]
|
313
|
+
|
314
|
+
sp = ax @ bz.T + az @ bx.T
|
315
|
+
sp.data %= 2
|
316
|
+
|
317
|
+
return sp
|
318
|
+
|
319
|
+
|
320
|
+
def check_binary_pauli_matrices_commute(
|
321
|
+
mat1: scipy.sparse.spmatrix, mat2: scipy.sparse.spmatrix
|
322
|
+
) -> bool:
|
323
|
+
"""
|
324
|
+
Check if two binary Pauli matrices commute.
|
325
|
+
"""
|
326
|
+
symplectic_product_result = symplectic_product(mat1, mat2)
|
327
|
+
symplectic_product_result.eliminate_zeros()
|
328
|
+
return not np.any(symplectic_product_result.data)
|
329
|
+
|
330
|
+
|
331
|
+
def binary_pauli_hamming_weight(
|
332
|
+
mat: scipy.sparse.spmatrix,
|
333
|
+
) -> np.ndarray:
|
334
|
+
"""
|
335
|
+
Compute the row-wise Hamming weight of a binary Pauli matrix.
|
336
|
+
|
337
|
+
A binary Pauli matrix has 2*n columns, where the first n columns encode
|
338
|
+
the X part and the second n columns encode the Z part. The Hamming weight
|
339
|
+
for each row is the number of qubits that are acted upon by a non-identity
|
340
|
+
Pauli operator (X, Y, or Z). In other words, for each row, we count the
|
341
|
+
number of columns where either the X part or the Z part has a 1.
|
342
|
+
|
343
|
+
Parameters
|
344
|
+
----------
|
345
|
+
mat : scipy.sparse.spmatrix
|
346
|
+
A binary Pauli matrix with an even number of columns (2*n). Each entry
|
347
|
+
must be 0 or 1, indicating whether the row has an X or Z component
|
348
|
+
for the corresponding qubit.
|
349
|
+
|
350
|
+
Returns
|
351
|
+
-------
|
352
|
+
np.ndarray
|
353
|
+
A 1D NumPy array of length `mat.shape[0]`, where the i-th entry is
|
354
|
+
the Hamming weight of the i-th row in `mat`.
|
355
|
+
|
356
|
+
Raises
|
357
|
+
------
|
358
|
+
AssertionError
|
359
|
+
If the matrix does not have an even number of columns.
|
360
|
+
|
361
|
+
Notes
|
362
|
+
-----
|
363
|
+
Internally, this function:
|
364
|
+
1. Splits the matrix into the X and Z parts.
|
365
|
+
2. Computes an elementwise OR of the X and Z parts.
|
366
|
+
3. Counts the non-zero entries per row (i.e., columns where the row has a 1).
|
367
|
+
|
368
|
+
Because the bitwise OR operator `|` is not directly supported for CSR
|
369
|
+
matrices, we achieve the OR operation by adding the two sparse matrices
|
370
|
+
and capping the sum at 1. Any entries with a value >= 1 in the sum
|
371
|
+
are set to 1, which corresponds to OR semantics for binary data.
|
372
|
+
|
373
|
+
Examples
|
374
|
+
--------
|
375
|
+
>>> import numpy as np
|
376
|
+
>>> from scipy.sparse import csr_matrix
|
377
|
+
>>> # Create a 2-row matrix, each row having 6 columns (for n=3 qubits).
|
378
|
+
>>> # Row 0: columns [0,2] are set -> X on qubits 0 and 2.
|
379
|
+
>>> # Row 1: columns [3,4,5] are set -> Z on qubit 1, Y on qubit 2.
|
380
|
+
>>> mat_data = np.array([[1,0,1,0,0,0],
|
381
|
+
... [0,0,0,1,1,1]], dtype=np.uint8)
|
382
|
+
>>> mat_sparse = csr_matrix(mat_data)
|
383
|
+
>>> binary_pauli_hamming_weight(mat_sparse)
|
384
|
+
array([2, 2], dtype=int32)
|
385
|
+
"""
|
386
|
+
assert mat.shape[1] % 2 == 0, "Input matrix must have an even number of columns."
|
387
|
+
|
388
|
+
# Determine the number of qubits from the total columns.
|
389
|
+
n = mat.shape[1] // 2
|
390
|
+
|
391
|
+
# Partition the matrix into X and Z parts.
|
392
|
+
x_part = mat[:, :n]
|
393
|
+
z_part = mat[:, n:]
|
394
|
+
|
395
|
+
# We want a bitwise OR. Since CSR matrices do not support a direct OR,
|
396
|
+
# we add and then cap at 1: (x_part + z_part >= 1) -> 1
|
397
|
+
xz_or = x_part.copy()
|
398
|
+
xz_or += z_part
|
399
|
+
# Clip values greater than 1 to 1.
|
400
|
+
xz_or.data[xz_or.data > 1] = 1
|
401
|
+
|
402
|
+
# The row-wise Hamming weight is the number of non-zero columns in each row.
|
403
|
+
return xz_or.getnnz(axis=1)
|
@@ -0,0 +1,274 @@
|
|
1
|
+
import logging
|
2
|
+
|
3
|
+
# Suppress debug and info messages from urllib3 and requests libraries
|
4
|
+
logging.getLogger("urllib3").setLevel(logging.WARNING)
|
5
|
+
logging.getLogger("requests").setLevel(logging.WARNING)
|
6
|
+
|
7
|
+
from scipy.sparse import csr_matrix
|
8
|
+
|
9
|
+
import requests
|
10
|
+
from bs4 import BeautifulSoup
|
11
|
+
import json
|
12
|
+
|
13
|
+
|
14
|
+
def get_codetables_de_matrix(q, n, k, output_json_path=None, write_to_file=False):
|
15
|
+
"""
|
16
|
+
Retrieve quantum code data from Markus Grassl's codetables.de website.
|
17
|
+
|
18
|
+
This function queries the URL:
|
19
|
+
``https://codetables.de/QECC/QECC.php?q={q}&n={n}&k={k}``,
|
20
|
+
attempting to fetch data for a quantum code with the specified parameters
|
21
|
+
over GF(q). The HTML response is parsed to extract:
|
22
|
+
|
23
|
+
- The lower bound (``d_lower``) and upper bound (``d_upper``) on the code distance.
|
24
|
+
- The stabilizer matrix (as lines within a ``<pre>`` block).
|
25
|
+
|
26
|
+
The stabilizer matrix is then converted into a list of rows, each containing
|
27
|
+
the column indices of any '1' entries (the ``pcm``). The result is returned
|
28
|
+
as a dictionary, and optionally written to a JSON file.
|
29
|
+
|
30
|
+
Parameters
|
31
|
+
----------
|
32
|
+
q : int
|
33
|
+
The field size (e.g. 2, 4, etc.).
|
34
|
+
n : int
|
35
|
+
The length of the code (number of physical qubits).
|
36
|
+
k : int
|
37
|
+
The dimension of the code (number of logical qubits).
|
38
|
+
output_json_path : str or None, optional
|
39
|
+
File path to which the resulting dictionary will be written if
|
40
|
+
``write_to_file`` is set to True. If None and ``write_to_file`` is True,
|
41
|
+
raises a ValueError.
|
42
|
+
write_to_file : bool, optional
|
43
|
+
Whether to write the resulting dictionary to a JSON file.
|
44
|
+
|
45
|
+
Returns
|
46
|
+
-------
|
47
|
+
dict
|
48
|
+
A dictionary with the fields:
|
49
|
+
``{"n", "k", "d_upper", "d_lower", "url", "pcm"}``.
|
50
|
+
|
51
|
+
- ``pcm`` is a list of lists, where each inner list contains the column
|
52
|
+
indices of '1's for that row of the stabilizer matrix.
|
53
|
+
- ``url`` is the codetables.de URL used for the query.
|
54
|
+
- ``d_upper`` and ``d_lower`` are the distance bounds, if found.
|
55
|
+
|
56
|
+
Raises
|
57
|
+
------
|
58
|
+
ValueError
|
59
|
+
If the server response is not 200 OK, or if no valid stabilizer matrix
|
60
|
+
lines could be found in the HTML (i.e., no code data for those parameters).
|
61
|
+
Also raised if ``write_to_file`` is True and ``output_json_path`` is None.
|
62
|
+
|
63
|
+
Notes
|
64
|
+
-----
|
65
|
+
- Data is sourced from `codetables.de <https://codetables.de>`__,
|
66
|
+
maintained by Markus Grassl.
|
67
|
+
- The function does not return an actual matrix but rather a convenient
|
68
|
+
representation of it (the ``pcm``). Use ``pcm_to_csr_matrix`` or another
|
69
|
+
helper to convert it into a numerical/sparse form.
|
70
|
+
"""
|
71
|
+
url = f"https://codetables.de/QECC/QECC.php?q={q}&n={n}&k={k}"
|
72
|
+
resp = requests.get(url)
|
73
|
+
if resp.status_code != 200:
|
74
|
+
raise ValueError(
|
75
|
+
f"Failed to retrieve data (status code: {resp.status_code}). URL was: {url}"
|
76
|
+
)
|
77
|
+
|
78
|
+
soup = BeautifulSoup(resp.text, "html.parser")
|
79
|
+
|
80
|
+
# 1) Extract lower and upper distance bounds from <table> elements
|
81
|
+
lower_bound = None
|
82
|
+
upper_bound = None
|
83
|
+
tables = soup.find_all("table")
|
84
|
+
for table in tables:
|
85
|
+
rows = table.find_all("tr")
|
86
|
+
for row in rows:
|
87
|
+
cells = row.find_all("td")
|
88
|
+
if len(cells) == 2:
|
89
|
+
heading = cells[0].get_text(strip=True).lower()
|
90
|
+
value = cells[1].get_text(strip=True)
|
91
|
+
if "lower bound" in heading:
|
92
|
+
lower_bound = value
|
93
|
+
elif "upper bound" in heading:
|
94
|
+
upper_bound = value
|
95
|
+
|
96
|
+
# 2) Extract the stabilizer matrix lines from <pre> tags
|
97
|
+
matrix_lines = []
|
98
|
+
for tag in soup.find_all("pre"):
|
99
|
+
text = tag.get_text()
|
100
|
+
if "stabilizer matrix" in text.lower():
|
101
|
+
lines = text.splitlines()
|
102
|
+
capture = False
|
103
|
+
for line in lines:
|
104
|
+
if "stabilizer matrix" in line.lower():
|
105
|
+
capture = True
|
106
|
+
continue
|
107
|
+
if capture:
|
108
|
+
# Stop at 'last modified:' or if the line is empty
|
109
|
+
if "last modified:" in line.lower():
|
110
|
+
break
|
111
|
+
if line.strip() != "":
|
112
|
+
matrix_lines.append(line.strip())
|
113
|
+
|
114
|
+
if not matrix_lines:
|
115
|
+
raise ValueError(f"No valid stabilizer matrix found at {url}")
|
116
|
+
|
117
|
+
# 3) Convert lines -> list of column-index lists
|
118
|
+
pcm_list = []
|
119
|
+
for line in matrix_lines:
|
120
|
+
line = line.strip().strip("[]").replace("|", " ")
|
121
|
+
elements = line.split()
|
122
|
+
row_cols = [i for i, val in enumerate(elements) if val == "1"]
|
123
|
+
pcm_list.append(row_cols)
|
124
|
+
|
125
|
+
if not pcm_list:
|
126
|
+
raise ValueError(f"No valid rows containing '1' found at {url}")
|
127
|
+
|
128
|
+
# 4) Build final dictionary
|
129
|
+
result_dict = {
|
130
|
+
"n": n,
|
131
|
+
"k": k,
|
132
|
+
"d_upper": upper_bound,
|
133
|
+
"d_lower": lower_bound,
|
134
|
+
"url": url,
|
135
|
+
"pcm": pcm_list,
|
136
|
+
}
|
137
|
+
|
138
|
+
# 5) Optionally write to JSON file
|
139
|
+
if write_to_file:
|
140
|
+
if output_json_path is None:
|
141
|
+
raise ValueError("output_json_path must be provided if write_to_file=True.")
|
142
|
+
with open(output_json_path, "w") as out_file:
|
143
|
+
json.dump(result_dict, out_file, indent=2)
|
144
|
+
|
145
|
+
return result_dict
|
146
|
+
|
147
|
+
|
148
|
+
def pcm_to_csr_matrix(pcm, num_cols=None):
|
149
|
+
"""
|
150
|
+
Convert a "pcm" to a SciPy CSR matrix.
|
151
|
+
|
152
|
+
Each inner list of ``pcm`` is interpreted as the column indices in which
|
153
|
+
row `i` has a value of 1. The resulting CSR matrix will thus have as many
|
154
|
+
rows as ``len(pcm)``. The number of columns can either be:
|
155
|
+
|
156
|
+
- Inferred automatically (``num_cols=None``) by taking 1 + max(column index).
|
157
|
+
- Specified by the user. If a column index is >= num_cols, a ValueError is raised.
|
158
|
+
|
159
|
+
Parameters
|
160
|
+
----------
|
161
|
+
pcm : list of lists of int
|
162
|
+
Each element ``pcm[i]`` is a list of column indices where row i has '1'.
|
163
|
+
num_cols : int or None, optional
|
164
|
+
The desired number of columns (width of the matrix).
|
165
|
+
If None, the width is auto-detected from the maximum column index.
|
166
|
+
|
167
|
+
Returns
|
168
|
+
-------
|
169
|
+
csr_matrix
|
170
|
+
A sparse matrix of shape ``(len(pcm), num_cols)``.
|
171
|
+
|
172
|
+
Raises
|
173
|
+
------
|
174
|
+
ValueError
|
175
|
+
If any column index exceeds the specified ``num_cols``.
|
176
|
+
Also raised if no rows or invalid columns exist.
|
177
|
+
|
178
|
+
See Also
|
179
|
+
--------
|
180
|
+
get_codetables_de_matrix : Returns a dictionary with ``pcm`` field from codetables.de.
|
181
|
+
|
182
|
+
Notes
|
183
|
+
-----
|
184
|
+
Data is typically retrieved from `codetables.de <https://codetables.de>`__
|
185
|
+
and fed into this function to produce a numerical/sparse representation.
|
186
|
+
"""
|
187
|
+
if not pcm:
|
188
|
+
# No rows at all => shape (0, num_cols) or (0, 0) if num_cols is None
|
189
|
+
if num_cols is None:
|
190
|
+
return csr_matrix((0, 0), dtype=int)
|
191
|
+
else:
|
192
|
+
return csr_matrix((0, num_cols), dtype=int)
|
193
|
+
|
194
|
+
row_indices = []
|
195
|
+
col_indices = []
|
196
|
+
data = []
|
197
|
+
|
198
|
+
max_col_found = -1
|
199
|
+
|
200
|
+
# Collect row/col for each '1' entry
|
201
|
+
for row_idx, col_list in enumerate(pcm):
|
202
|
+
for c in col_list:
|
203
|
+
row_indices.append(row_idx)
|
204
|
+
col_indices.append(c)
|
205
|
+
data.append(1)
|
206
|
+
if c > max_col_found:
|
207
|
+
max_col_found = c
|
208
|
+
|
209
|
+
num_rows = len(pcm)
|
210
|
+
|
211
|
+
# Auto-detect columns if not specified
|
212
|
+
if num_cols is None:
|
213
|
+
num_cols = max_col_found + 1
|
214
|
+
else:
|
215
|
+
# If the user specified num_cols, ensure the data fits
|
216
|
+
if max_col_found >= num_cols:
|
217
|
+
raise ValueError(
|
218
|
+
f"Column index {max_col_found} is out of range for a matrix of width {num_cols}."
|
219
|
+
)
|
220
|
+
|
221
|
+
return csr_matrix(
|
222
|
+
(data, (row_indices, col_indices)), shape=(num_rows, num_cols), dtype=int
|
223
|
+
)
|
224
|
+
|
225
|
+
|
226
|
+
def load_codetables_de_matrix_from_json(json_data):
|
227
|
+
"""
|
228
|
+
Construct a CSR matrix from a codetables.de JSON/dict output.
|
229
|
+
|
230
|
+
This function takes either a dictionary (as returned by
|
231
|
+
``get_codetables_de_matrix``) or a JSON string that decodes to the same
|
232
|
+
structure, and converts the ``pcm`` field into a SciPy CSR matrix.
|
233
|
+
|
234
|
+
Parameters
|
235
|
+
----------
|
236
|
+
json_data : dict or str
|
237
|
+
Must contain at least the following keys:
|
238
|
+
``{"n", "k", "d_upper", "d_lower", "url", "pcm"}``.
|
239
|
+
- ``pcm`` is a list of lists of column indices.
|
240
|
+
|
241
|
+
Returns
|
242
|
+
-------
|
243
|
+
csr_matrix
|
244
|
+
The stabilizer matrix in CSR format.
|
245
|
+
dict
|
246
|
+
The original dictionary that was passed in (or parsed from JSON).
|
247
|
+
|
248
|
+
Raises
|
249
|
+
------
|
250
|
+
ValueError
|
251
|
+
If ``json_data`` is not a dict, if it cannot be parsed into one,
|
252
|
+
or if required keys are missing.
|
253
|
+
|
254
|
+
Notes
|
255
|
+
-----
|
256
|
+
- Data is assumed to come from Markus Grassl's `codetables.de <https://codetables.de>`__.
|
257
|
+
- This utility is helpful when the data is stored or transmitted in JSON form
|
258
|
+
but needs to be loaded back into a matrix representation for further processing.
|
259
|
+
"""
|
260
|
+
if isinstance(json_data, str):
|
261
|
+
json_data = json.loads(json_data)
|
262
|
+
|
263
|
+
if not isinstance(json_data, dict):
|
264
|
+
raise ValueError(
|
265
|
+
"json_data must be a dict or a JSON string that decodes to a dict."
|
266
|
+
)
|
267
|
+
|
268
|
+
required_keys = {"n", "k", "d_upper", "d_lower", "url", "pcm"}
|
269
|
+
if not required_keys.issubset(json_data.keys()):
|
270
|
+
raise ValueError(f"JSON data missing required keys: {required_keys}")
|
271
|
+
|
272
|
+
pcm = json_data["pcm"]
|
273
|
+
sparse_matrix = pcm_to_csr_matrix(pcm)
|
274
|
+
return sparse_matrix, json_data
|