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.
@@ -1,93 +0,0 @@
1
- from qec.code_constructions import StabilizerCode
2
- from qec.utils.codetables_de_utils import get_codetables_de_matrix, pcm_to_csr_matrix
3
-
4
-
5
- class CodeTablesDE(StabilizerCode):
6
- """
7
- A code object built from data obtained from Markus Grassl's codetables.de website (with `q=4`).
8
-
9
- This class inherits from `StabilizerCode` and initialises the code
10
- by querying the codetables.de website for the specified parameters `(n, k)`,
11
- constructing the stabilizer (PCM) matrix, and passing it up to the
12
- `StabilizerCode` parent class.
13
-
14
- Parameters
15
- ----------
16
- physical_qubit_count : int
17
- Length of the code (number of physical qubits).
18
- logical_qubit_count : int
19
- Dimension of the code (number of logical qubits).
20
-
21
- Attributes
22
- ----------
23
- name : str
24
- Name assigned to this code instance. Defaults to "CodeTablesDE".
25
- url : str
26
- The URL from which this code's data was retrieved.
27
- code_distance : int
28
- The code's minimum distance. This is updated if the reported upper bound
29
- from the codetables.de website is smaller than the base class default.
30
-
31
- See Also
32
- --------
33
- StabilizerCode : Parent class providing stabilizer code functionality.
34
- get_codetables_de_matrix : Function that queries codetables.de to retrieve code data.
35
- pcm_to_csr_matrix : Function that converts a PCM-like list of column indices into a CSR matrix.
36
-
37
- Notes
38
- -----
39
- - The data is retrieved from:
40
- https://codetables.de
41
- maintained by Markus Grassl.
42
- """
43
-
44
- def __init__(self, physical_qubit_count: int, logical_qubit_count: int):
45
- """
46
- Initialise a code from Markus Grassl's codetables.de website with `q=4`, `n`, and `k`.
47
-
48
- This method queries the codetables.de database for a stabilizer matrix
49
- describing a code with parameters (q=4, n, k). The matrix is then
50
- converted to a CSR (Compressed Sparse Row) format and passed to
51
- the parent `StabilizerCode` class.
52
-
53
- Parameters
54
- ----------
55
- n : int
56
- Length of the code (number of physical qubits).
57
- k : int
58
- Dimension of the code (number of logical qubits).
59
-
60
- Notes
61
- -----
62
- - `d_upper` from the query result is used to potentially update `self.code_distance`
63
- if it is smaller than the default distance assigned by `StabilizerCode`.
64
- - Since this code is defined over GF(4), `q` is hardcoded as 4.
65
- - Data is retrieved from Markus Grassl's website (https://codetables.de).
66
-
67
- Raises
68
- ------
69
- ValueError
70
- If no valid matrix data can be retrieved from codetables.de, or
71
- if the site indicates that such a code does not exist.
72
- """
73
- # Retrieve code data from codetables.de
74
- ct_dict = get_codetables_de_matrix(
75
- q=4, n=physical_qubit_count, k=logical_qubit_count
76
- )
77
-
78
- # Construct the stabilizer matrix in CSR format
79
- # The matrix is 2*n columns wide, as is typical for GF(4) stabilizers.
80
- stabilizer_matrix = pcm_to_csr_matrix(
81
- ct_dict["pcm"], num_cols=2 * int(ct_dict["n"])
82
- )
83
-
84
- # Initialise the parent class with this stabilizer matrix
85
- super().__init__(stabilizers=stabilizer_matrix)
86
-
87
- # Name of this code and the URL from which we retrieved it
88
- self.name = "CodeTablesDE"
89
- self.url = ct_dict["url"]
90
-
91
- # Update distance if the reported upper bound is smaller than the default
92
- if int(ct_dict["d_upper"]) < self.code_distance:
93
- self.code_distance = int(ct_dict["d_upper"])
qec/utils/__init__.py DELETED
File without changes
@@ -1,403 +0,0 @@
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)