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,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
|
qec/utils/binary_pauli_utils.py
DELETED
@@ -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)
|