qec 0.3.0__py3-none-any.whl → 0.3.3__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/__init__.py +2 -0
- qec/code_constructions/__init__.py +17 -0
- qec/code_constructions/css_code.py +959 -0
- qec/code_constructions/hgp_code.py +344 -0
- qec/code_constructions/periodic_surface_xzzx.py +100 -0
- qec/code_constructions/rotated_xzzx.py +122 -0
- qec/code_constructions/stabilizer_code.py +654 -0
- qec/code_constructions/surface_code.py +66 -0
- qec/code_constructions/toric_code.py +56 -0
- qec/code_instances/__init__.py +5 -0
- qec/code_instances/five_qubit_code.py +67 -0
- qec/code_instances/saved_codes/1.json +22 -22
- qec/code_instances/saved_codes/2.json +30 -0
- qec/code_instances/saved_codes/3.json +30 -0
- qec/code_instances/saved_codes/4.json +30 -0
- qec/codetables_de/__init__.py +3 -0
- qec/codetables_de/codetables_de.py +97 -0
- qec/utils/__init__.py +6 -0
- qec/utils/binary_pauli_utils.py +404 -0
- qec/utils/codetables_de_utils.py +272 -0
- qec/utils/load_code_util.py +142 -0
- qec/utils/sparse_binary_utils.py +80 -0
- {qec-0.3.0.dist-info → qec-0.3.3.dist-info}/METADATA +56 -25
- qec-0.3.3.dist-info/RECORD +27 -0
- {qec-0.3.0.dist-info → qec-0.3.3.dist-info}/WHEEL +1 -1
- qec/code_instances/saved_codes/steane.json +0 -34
- qec/code_instances/saved_codes/test.json +0 -1
- qec-0.3.0.dist-info/RECORD +0 -9
- {qec-0.3.0.dist-info → qec-0.3.3.dist-info/licenses}/LICENSE +0 -0
- {qec-0.3.0.dist-info → qec-0.3.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,344 @@
|
|
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 (
|
9
|
+
convert_to_binary_scipy_sparse,
|
10
|
+
binary_csr_matrix_to_dict,
|
11
|
+
)
|
12
|
+
|
13
|
+
|
14
|
+
class HypergraphProductCode(CSSCode):
|
15
|
+
r"""
|
16
|
+
Implements a Hypergraph Product (HGP) code - derived from two classical linear binary codes.
|
17
|
+
|
18
|
+
Parameters
|
19
|
+
----------
|
20
|
+
seed_matrix_1 :
|
21
|
+
A classical linear binary code used as a "seed" in the HGP construction method.
|
22
|
+
seed_matrix_2 :
|
23
|
+
A classical linear binary code used as a "seed" in the HGP construction method.
|
24
|
+
name : str, default = None
|
25
|
+
The name of the code. If None, the name is set to: "Hypergraph product code"
|
26
|
+
|
27
|
+
Attributes
|
28
|
+
----------
|
29
|
+
seed_matrix_1 : scipy.sparse.spmatrix
|
30
|
+
The input seed_matrix_1 stored as a scipy sparse matrix.
|
31
|
+
seed_matrix_2 : scipy.sparse.spmatrix
|
32
|
+
The input seed_matrix_2 stored as a scipy sparse matrix.
|
33
|
+
_n1 : int
|
34
|
+
Number of columns in seed_matrix_1
|
35
|
+
_n2 : int
|
36
|
+
Number of columns in seed_matrix_2
|
37
|
+
_m1 : int
|
38
|
+
Number of rows in seed_matrix_1 (the number of columns of it's transpose)
|
39
|
+
_m2 : int
|
40
|
+
Number of rows in seed_matrix_2 (the number of columns of it's transpose)
|
41
|
+
|
42
|
+
Notes
|
43
|
+
-----
|
44
|
+
|
45
|
+
The X and Z stabilizer matrices are given by [1]_:
|
46
|
+
|
47
|
+
.. math::
|
48
|
+
|
49
|
+
\begin{align}
|
50
|
+
H_{X} &= \begin{pmatrix}
|
51
|
+
H_{1}\otimes I_{n_{2}} & \,\,I_{r_{1}}\otimes H_{2}^{T}
|
52
|
+
\end{pmatrix}\tag*{(1)}\\
|
53
|
+
H_{Z} &= \begin{pmatrix}
|
54
|
+
I_{n_{1}}\otimes H_{2} & \,\,H_{1}^{T}\otimes I_{r_{2}}
|
55
|
+
\end{pmatrix}~, \tag*{(2)}
|
56
|
+
\end{align}
|
57
|
+
|
58
|
+
where :math:`H_1` and :math:`H_2` correspond to the parity check matrix of the first and second "seed" codes.
|
59
|
+
|
60
|
+
|
61
|
+
.. [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)
|
62
|
+
"""
|
63
|
+
|
64
|
+
def __init__(
|
65
|
+
self,
|
66
|
+
seed_matrix_1: Union[np.ndarray, scipy.sparse.spmatrix],
|
67
|
+
seed_matrix_2: Union[np.ndarray, scipy.sparse.spmatrix],
|
68
|
+
name: str = None,
|
69
|
+
):
|
70
|
+
self.name = name if name else "Hypergraph product code"
|
71
|
+
|
72
|
+
if not all(
|
73
|
+
isinstance(seed_m, (np.ndarray, scipy.sparse.spmatrix))
|
74
|
+
for seed_m in (seed_matrix_1, seed_matrix_2)
|
75
|
+
):
|
76
|
+
raise TypeError(
|
77
|
+
"The seed matrices must be either numpy arrays or scipy sparse matrices."
|
78
|
+
)
|
79
|
+
|
80
|
+
self.seed_matrix_1 = convert_to_binary_scipy_sparse(seed_matrix_1)
|
81
|
+
self.seed_matrix_2 = convert_to_binary_scipy_sparse(seed_matrix_2)
|
82
|
+
|
83
|
+
# maybe move the below to a private _construct_stabilizer_matrices function?
|
84
|
+
# --------------------------------------------------------------------------
|
85
|
+
self._n1 = seed_matrix_1.shape[1]
|
86
|
+
self._n2 = seed_matrix_2.shape[1]
|
87
|
+
|
88
|
+
self._m1 = seed_matrix_1.shape[0]
|
89
|
+
self._m2 = seed_matrix_2.shape[0]
|
90
|
+
|
91
|
+
x_left = scipy.sparse.kron(seed_matrix_1, scipy.sparse.eye(self._n2))
|
92
|
+
x_right = scipy.sparse.kron(scipy.sparse.eye(self._m1), seed_matrix_2.T)
|
93
|
+
self.x_stabilizer_matrix = scipy.sparse.hstack([x_left, x_right])
|
94
|
+
|
95
|
+
z_left = scipy.sparse.kron(scipy.sparse.eye(self._n1), seed_matrix_2)
|
96
|
+
z_right = scipy.sparse.kron(seed_matrix_1.T, scipy.sparse.eye(self._m2))
|
97
|
+
self.z_stabilizer_matrix = scipy.sparse.hstack([z_left, z_right])
|
98
|
+
# --------------------------------------------------------------------------
|
99
|
+
|
100
|
+
super().__init__(self.x_stabilizer_matrix, self.z_stabilizer_matrix, self.name)
|
101
|
+
|
102
|
+
self.code_distance = None
|
103
|
+
|
104
|
+
def compute_exact_code_distance(self) -> int:
|
105
|
+
r"""
|
106
|
+
Computes the exact code distance of the HGP code.
|
107
|
+
|
108
|
+
Returns
|
109
|
+
-------
|
110
|
+
int
|
111
|
+
The distance of the code.
|
112
|
+
|
113
|
+
Notes
|
114
|
+
-----
|
115
|
+
The distance of a HGP code is given as:
|
116
|
+
|
117
|
+
.. math::
|
118
|
+
|
119
|
+
\min(d_1, d_2, d_1^T, d_2^T)
|
120
|
+
|
121
|
+
corresponding to the distance of the seed codes and the distance of their transposes.
|
122
|
+
"""
|
123
|
+
|
124
|
+
rank_seed_m1 = ldpc.mod2.rank(self.seed_matrix_1)
|
125
|
+
rank_seed_m2 = ldpc.mod2.rank(self.seed_matrix_2)
|
126
|
+
|
127
|
+
if self.seed_matrix_1.shape[1] != rank_seed_m1:
|
128
|
+
self.d1 = ldpc.mod2.compute_exact_code_distance(self.seed_matrix_1)
|
129
|
+
else:
|
130
|
+
self.d1 = np.inf
|
131
|
+
|
132
|
+
if self.seed_matrix_2.shape[1] != rank_seed_m2:
|
133
|
+
self.d2 = ldpc.mod2.compute_exact_code_distance(self.seed_matrix_2)
|
134
|
+
else:
|
135
|
+
self.d2 = np.inf
|
136
|
+
|
137
|
+
# note: rank(A) = rank(A^T):
|
138
|
+
if self.seed_matrix_1.shape[0] != rank_seed_m1:
|
139
|
+
self.d1T = ldpc.mod2.compute_exact_code_distance(self.seed_matrix_1.T)
|
140
|
+
else:
|
141
|
+
self.d1T = np.inf
|
142
|
+
|
143
|
+
if self.seed_matrix_2.shape[0] != rank_seed_m2:
|
144
|
+
self.d2T = ldpc.mod2.compute_exact_code_distance(self.seed_matrix_2.T)
|
145
|
+
else:
|
146
|
+
self.d2T = np.inf
|
147
|
+
|
148
|
+
self.x_code_distance = min(self.d1T, self.d2)
|
149
|
+
self.z_code_distance = min(self.d1, self.d2T)
|
150
|
+
self.code_distance = min(self.x_code_distance, self.z_code_distance)
|
151
|
+
|
152
|
+
return self.code_distance
|
153
|
+
|
154
|
+
def estimate_min_distance(self, timeout_seconds: float = 0.025) -> int:
|
155
|
+
"""
|
156
|
+
Estimate the minimum X and Z distance of the HGP code. Parameters
|
157
|
+
----------
|
158
|
+
timeout_seconds : float, optional
|
159
|
+
Time limit in seconds for the full search. Default: 0.25
|
160
|
+
|
161
|
+
Returns
|
162
|
+
-------
|
163
|
+
int
|
164
|
+
Best estimate of the (overall) code distance found within time limit.
|
165
|
+
|
166
|
+
"""
|
167
|
+
|
168
|
+
rank_seed_m1 = ldpc.mod2.rank(self.seed_matrix_1)
|
169
|
+
rank_seed_m2 = ldpc.mod2.rank(self.seed_matrix_2)
|
170
|
+
|
171
|
+
d1_timeout_seconds = timeout_seconds / 4
|
172
|
+
if self.seed_matrix_1.shape[1] != rank_seed_m1:
|
173
|
+
d1_start_time = time.time()
|
174
|
+
d1_min_estimate, _, _ = ldpc.mod2.estimate_code_distance(
|
175
|
+
self.seed_matrix_1, d1_timeout_seconds, 0
|
176
|
+
)
|
177
|
+
d1_run_time = time.time() - d1_start_time
|
178
|
+
else:
|
179
|
+
d1_min_estimate = np.inf
|
180
|
+
d1_run_time = 0
|
181
|
+
|
182
|
+
d1T_timeout_seconds = (
|
183
|
+
(d1_timeout_seconds * 4 - d1_run_time) / 3
|
184
|
+
if d1_run_time <= d1_timeout_seconds
|
185
|
+
else timeout_seconds / 4
|
186
|
+
)
|
187
|
+
if self.seed_matrix_1.shape[0] != rank_seed_m1:
|
188
|
+
d1T_start_time = time.time()
|
189
|
+
d1T_min_estimate, _, _ = ldpc.mod2.estimate_code_distance(
|
190
|
+
self.seed_matrix_1.T, d1T_timeout_seconds, 0
|
191
|
+
)
|
192
|
+
d1T_run_time = time.time() - d1T_start_time
|
193
|
+
else:
|
194
|
+
d1T_min_estimate = np.inf
|
195
|
+
d1T_run_time = 0
|
196
|
+
|
197
|
+
d2_timeout_seconds = (
|
198
|
+
(d1T_timeout_seconds * 3 - d1T_run_time) / 2
|
199
|
+
if d1T_run_time <= d1T_timeout_seconds
|
200
|
+
else timeout_seconds / 4
|
201
|
+
)
|
202
|
+
if self.seed_matrix_2.shape[1] != rank_seed_m2:
|
203
|
+
d2_start_time = time.time()
|
204
|
+
d2_min_estimate, _, _ = ldpc.mod2.estimate_code_distance(
|
205
|
+
self.seed_matrix_2, d2_timeout_seconds, 0
|
206
|
+
)
|
207
|
+
d2_run_time = time.time() - d2_start_time
|
208
|
+
else:
|
209
|
+
d2_min_estimate = np.inf
|
210
|
+
d2_run_time = 0
|
211
|
+
|
212
|
+
d2T_timeout_seconds = (
|
213
|
+
(d2_timeout_seconds * 2 - d2_run_time)
|
214
|
+
if d2_run_time <= d2_timeout_seconds
|
215
|
+
else timeout_seconds / 4
|
216
|
+
)
|
217
|
+
if self.seed_matrix_2.shape[0] != rank_seed_m2:
|
218
|
+
d2T_min_estimate, _, _ = ldpc.mod2.estimate_code_distance(
|
219
|
+
self.seed_matrix_2.T, d2T_timeout_seconds, 0
|
220
|
+
)
|
221
|
+
else:
|
222
|
+
d2T_min_estimate = np.inf
|
223
|
+
|
224
|
+
self.x_code_distance = min(d1T_min_estimate, d2_min_estimate)
|
225
|
+
self.z_code_distance = min(d1_min_estimate, d2T_min_estimate)
|
226
|
+
self.code_distance = min(self.x_code_distance, self.z_code_distance)
|
227
|
+
|
228
|
+
return self.code_distance
|
229
|
+
|
230
|
+
def compute_logical_basis(
|
231
|
+
self,
|
232
|
+
) -> Tuple[scipy.sparse.spmatrix, scipy.sparse.spmatrix]:
|
233
|
+
"""
|
234
|
+
Compute the logical operator basis for the given HGP code.
|
235
|
+
|
236
|
+
Returns
|
237
|
+
-------
|
238
|
+
Tuple[scipy.sparse.spmatrix, scipy.sparse.spmatrix]
|
239
|
+
Logical X and Z operator bases (lx, lz).
|
240
|
+
"""
|
241
|
+
|
242
|
+
ker_h1 = ldpc.mod2.kernel(self.seed_matrix_1)
|
243
|
+
ker_h2 = ldpc.mod2.kernel(self.seed_matrix_2)
|
244
|
+
ker_h1T = ldpc.mod2.kernel(self.seed_matrix_1.T)
|
245
|
+
ker_h2T = ldpc.mod2.kernel(self.seed_matrix_2.T)
|
246
|
+
|
247
|
+
row_comp_h1 = ldpc.mod2.row_complement_basis(self.seed_matrix_1)
|
248
|
+
row_comp_h2 = ldpc.mod2.row_complement_basis(self.seed_matrix_2)
|
249
|
+
row_comp_h1T = ldpc.mod2.row_complement_basis(self.seed_matrix_1.T)
|
250
|
+
row_comp_h2T = ldpc.mod2.row_complement_basis(self.seed_matrix_2.T)
|
251
|
+
|
252
|
+
temp = scipy.sparse.kron(ker_h1, row_comp_h2)
|
253
|
+
lz1 = scipy.sparse.hstack(
|
254
|
+
[
|
255
|
+
temp,
|
256
|
+
scipy.sparse.csr_matrix(
|
257
|
+
(temp.shape[0], self._m1 * self._m2), dtype=np.uint8
|
258
|
+
),
|
259
|
+
]
|
260
|
+
)
|
261
|
+
|
262
|
+
temp = scipy.sparse.kron(row_comp_h1T, ker_h2T)
|
263
|
+
lz2 = scipy.sparse.hstack(
|
264
|
+
[
|
265
|
+
scipy.sparse.csr_matrix(
|
266
|
+
(temp.shape[0], self._n1 * self._n2), dtype=np.uint8
|
267
|
+
),
|
268
|
+
temp,
|
269
|
+
]
|
270
|
+
)
|
271
|
+
|
272
|
+
self.z_logical_operator_basis = scipy.sparse.csr_matrix(
|
273
|
+
scipy.sparse.vstack([lz1, lz2], dtype=np.uint8)
|
274
|
+
)
|
275
|
+
|
276
|
+
temp = scipy.sparse.kron(row_comp_h1, ker_h2)
|
277
|
+
lx1 = scipy.sparse.hstack(
|
278
|
+
[
|
279
|
+
temp,
|
280
|
+
scipy.sparse.csr_matrix(
|
281
|
+
(temp.shape[0], self._m1 * self._m2), dtype=np.uint8
|
282
|
+
),
|
283
|
+
]
|
284
|
+
)
|
285
|
+
|
286
|
+
temp = scipy.sparse.kron(ker_h1T, row_comp_h2T)
|
287
|
+
lx2 = scipy.sparse.hstack(
|
288
|
+
[
|
289
|
+
scipy.sparse.csr_matrix(
|
290
|
+
(temp.shape[0], self._n1 * self._n2), dtype=np.uint8
|
291
|
+
),
|
292
|
+
temp,
|
293
|
+
]
|
294
|
+
)
|
295
|
+
|
296
|
+
self.x_logical_operator_basis = scipy.sparse.csr_matrix(
|
297
|
+
scipy.sparse.vstack([lx1, lx2], dtype=np.uint8)
|
298
|
+
)
|
299
|
+
|
300
|
+
# Follows the way it is done in CSSCode -> move it into __init__?
|
301
|
+
# ----------------------------------------------------------------
|
302
|
+
self.logical_qubit_count = self.x_logical_operator_basis.shape[0]
|
303
|
+
# ----------------------------------------------------------------
|
304
|
+
|
305
|
+
return (self.x_logical_operator_basis, self.z_logical_operator_basis)
|
306
|
+
|
307
|
+
def __str__(self):
|
308
|
+
"""
|
309
|
+
String representation of the HGP code. Includes the name and [[n, k, d]] properties of the code.
|
310
|
+
|
311
|
+
Returns
|
312
|
+
-------
|
313
|
+
str
|
314
|
+
String representation of the HGP code.
|
315
|
+
"""
|
316
|
+
|
317
|
+
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}]]"
|
318
|
+
|
319
|
+
def _class_specific_save(self):
|
320
|
+
class_specific_data = {
|
321
|
+
"code_distance": self.code_distance
|
322
|
+
if self.code_distance is not None
|
323
|
+
else "?",
|
324
|
+
"x_code_distance": self.x_code_distance
|
325
|
+
if self.x_code_distance is not None
|
326
|
+
else "?",
|
327
|
+
"z_code_distance": self.z_code_distance
|
328
|
+
if self.z_code_distance is not None
|
329
|
+
else "?",
|
330
|
+
"seed_matrix_1": binary_csr_matrix_to_dict(self.seed_matrix_1),
|
331
|
+
"seed_matrix_2": binary_csr_matrix_to_dict(self.seed_matrix_2),
|
332
|
+
"x_logical_operator_basis": binary_csr_matrix_to_dict(
|
333
|
+
self.x_logical_operator_basis
|
334
|
+
)
|
335
|
+
if self._x_logical_operator_basis is not None
|
336
|
+
else "?",
|
337
|
+
"z_logical_operator_basis": binary_csr_matrix_to_dict(
|
338
|
+
self.z_logical_operator_basis
|
339
|
+
)
|
340
|
+
if self._z_logical_operator_basis is not None
|
341
|
+
else "?",
|
342
|
+
}
|
343
|
+
|
344
|
+
return class_specific_data
|
@@ -0,0 +1,100 @@
|
|
1
|
+
import numpy as np
|
2
|
+
import scipy.sparse
|
3
|
+
from qec.code_constructions import StabilizerCode
|
4
|
+
|
5
|
+
|
6
|
+
class PeriodicSurfaceXZZX(StabilizerCode):
|
7
|
+
"""
|
8
|
+
Represents a Periodic Surface XZZX Code, a type of quantum error correction code.
|
9
|
+
|
10
|
+
This code is defined on a standard surface code lattice with periodic boundary conditions.
|
11
|
+
The stabilizers measure XZZX, and the qubits are labeled sequentially from 1 to N, row by row from left to right.
|
12
|
+
|
13
|
+
The code's parity check matrix is defined as follows:
|
14
|
+
|
15
|
+
H = [Hx | Hz]
|
16
|
+
|
17
|
+
where Hz is a repetition code over N qubits, with N = lx * lz + (lx - 1) * (lz - 1).
|
18
|
+
Hz is constructed such that the vertically measured stabilizers have periodic boundary conditions.
|
19
|
+
Hx and Hz are swapped if noise_bias is set to "X".
|
20
|
+
|
21
|
+
Parameters
|
22
|
+
----------
|
23
|
+
lx : int
|
24
|
+
The size of the lattice in the horizontal direction.
|
25
|
+
lz : int
|
26
|
+
The size of the lattice in the vertical direction.
|
27
|
+
noise_bias : str, optional
|
28
|
+
The type of noise bias, default is "Z". This determines which stabilizer Pauli type is defined by the repetition code that spans all qubits.
|
29
|
+
|
30
|
+
Attributes
|
31
|
+
----------
|
32
|
+
hx : scipy.sparse.csr_matrix
|
33
|
+
The parity check matrix for X stabilizers.
|
34
|
+
hz : scipy.sparse.csr_matrix
|
35
|
+
The parity check matrix for Z stabilizers.
|
36
|
+
stabilizer_matrix : scipy.sparse.csr_matrix
|
37
|
+
The combined parity check matrix [Hx | Hz].
|
38
|
+
name : str
|
39
|
+
The name of the code.
|
40
|
+
"""
|
41
|
+
|
42
|
+
def __init__(self, lx: int, lz: int, noise_bias: str = "Z"):
|
43
|
+
# Calculate the total number of qubits
|
44
|
+
N = lx * lz + (lx - 1) * (lz - 1)
|
45
|
+
|
46
|
+
# Generate the parity check matrices for Z and X stabilizers based on noise bias
|
47
|
+
if noise_bias == "X":
|
48
|
+
hz = self._full_row_rank_shift_matrix(
|
49
|
+
N, 0
|
50
|
+
) + self._full_row_rank_shift_matrix(N, 1)
|
51
|
+
hx = self._full_row_rank_shift_matrix(
|
52
|
+
N, lz
|
53
|
+
) + self._full_row_rank_shift_matrix(N, (1 - lz))
|
54
|
+
else: # Default to "Z" noise bias
|
55
|
+
hz = self._full_row_rank_shift_matrix(
|
56
|
+
N, lx
|
57
|
+
) + self._full_row_rank_shift_matrix(N, (1 - lx))
|
58
|
+
hx = self._full_row_rank_shift_matrix(
|
59
|
+
N, 0
|
60
|
+
) + self._full_row_rank_shift_matrix(N, 1)
|
61
|
+
|
62
|
+
# Combine the parity check matrices into a single stabilizer matrix
|
63
|
+
stabilizer_matrix = scipy.sparse.hstack([hx, hz])
|
64
|
+
|
65
|
+
# Initialize the StabilizerCode with the combined parity check matrix
|
66
|
+
super().__init__(
|
67
|
+
stabilizer_matrix, name=f"Periodic Surface XZZX ({lx}x{lz}) Code"
|
68
|
+
)
|
69
|
+
|
70
|
+
def _full_row_rank_shift_matrix(
|
71
|
+
self, n: int, shift: int
|
72
|
+
) -> scipy.sparse.csr_matrix:
|
73
|
+
"""
|
74
|
+
Generate a full-rank shift matrix.
|
75
|
+
|
76
|
+
Parameters
|
77
|
+
----------
|
78
|
+
n : int
|
79
|
+
The size of the matrix.
|
80
|
+
shift : int
|
81
|
+
The shift to apply to the permutation.
|
82
|
+
|
83
|
+
Returns
|
84
|
+
-------
|
85
|
+
scipy.sparse.csr_matrix
|
86
|
+
The full-rank shift matrix.
|
87
|
+
"""
|
88
|
+
# Create the base matrix with an identity matrix and a zero column
|
89
|
+
base = (
|
90
|
+
scipy.sparse.hstack(
|
91
|
+
[scipy.sparse.identity(n - 1), scipy.sparse.csc_matrix((n - 1, 1))]
|
92
|
+
)
|
93
|
+
.astype(np.uint8)
|
94
|
+
.tocsc()
|
95
|
+
)
|
96
|
+
# Create the permutation array
|
97
|
+
perm = np.arange(n)
|
98
|
+
perm = (perm - shift) % n
|
99
|
+
# Apply the permutation to the base matrix
|
100
|
+
return base[:, perm]
|
@@ -0,0 +1,122 @@
|
|
1
|
+
import scipy.sparse
|
2
|
+
from qec.code_constructions import StabilizerCode
|
3
|
+
|
4
|
+
|
5
|
+
class RotatedSurfaceXZZX(StabilizerCode):
|
6
|
+
"""
|
7
|
+
Represents a Rotated Surface XZZX Code, a type of quantum error correction code.
|
8
|
+
|
9
|
+
This code is defined on a rotated surface code lattice of size lx x lz where all stabilizers measure XZZX.
|
10
|
+
The boundary conditions are periodic.
|
11
|
+
|
12
|
+
Parameters
|
13
|
+
----------
|
14
|
+
lx : int
|
15
|
+
The size of the lattice in the horizontal direction.
|
16
|
+
lz : int
|
17
|
+
The size of the lattice in the vertical direction.
|
18
|
+
|
19
|
+
Attributes
|
20
|
+
----------
|
21
|
+
hx : scipy.sparse.csr_matrix
|
22
|
+
The parity check matrix for X stabilizers.
|
23
|
+
hz : scipy.sparse.csr_matrix
|
24
|
+
The parity check matrix for Z stabilizers.
|
25
|
+
stabilizer_matrix : scipy.sparse.csr_matrix
|
26
|
+
The combined parity check matrix [Hx | Hz].
|
27
|
+
name : str
|
28
|
+
The name of the code.
|
29
|
+
|
30
|
+
Notes
|
31
|
+
-----
|
32
|
+
If lx and lz are both even, then the code encodes 2 logical qubits. Otherwise, it encodes 1 logical qubit.
|
33
|
+
The code's parity check matrix can be derived as a composition of two full-rank shift matrices.
|
34
|
+
This code is described in https://arxiv.org/abs/2009.07851.
|
35
|
+
"""
|
36
|
+
|
37
|
+
def __init__(self, lx: int, lz: int):
|
38
|
+
# Calculate the total number of qubits
|
39
|
+
n = lx * lz
|
40
|
+
# Calculate the number of stabilizers
|
41
|
+
m = (lx - 1) * (lz - 1) + lz // 2 + (lz - 1) // 2 + lx // 2 + (lx - 1) // 2
|
42
|
+
|
43
|
+
# Initialize sparse matrices for hx and hz using lil_matrix for efficient construction
|
44
|
+
hx = scipy.sparse.lil_matrix((m, n), dtype=int)
|
45
|
+
hz = scipy.sparse.lil_matrix((m, n), dtype=int)
|
46
|
+
|
47
|
+
# Fill the hx and hz matrices for the main grid
|
48
|
+
for j in range(lx - 1):
|
49
|
+
for k in range(lz - 1):
|
50
|
+
temp = j * (lz - 1)
|
51
|
+
# Set the X and Z stabilizers for the main grid
|
52
|
+
hx[temp + k, j * lz + k] = 1
|
53
|
+
hx[temp + k, j * lz + k + lz + 1] = 1
|
54
|
+
|
55
|
+
hz[temp + k, j * lz + k + 1] = 1
|
56
|
+
hz[temp + k, j * lz + k + lz] = 1
|
57
|
+
|
58
|
+
# Add the extra stabilizers to the top of the lattice
|
59
|
+
temp = (lx - 1) * (lz - 1)
|
60
|
+
count = 0
|
61
|
+
for j in range(0, lz - 1, 2):
|
62
|
+
# Set the Z Pauli components
|
63
|
+
hz[temp + count, j] = 1
|
64
|
+
hz[temp + count, (n - 1) - (lz - 1) + 1 + j] = 1
|
65
|
+
|
66
|
+
# Set the X Pauli components
|
67
|
+
hx[temp + count, j + 1] = 1
|
68
|
+
hx[temp + count, (n - 1) - (lz - 1) + j] = 1
|
69
|
+
|
70
|
+
count += 1
|
71
|
+
|
72
|
+
# Add the extra stabilizers to the bottom of the lattice
|
73
|
+
temp = (lx - 1) * (lz - 1) + lz // 2
|
74
|
+
count = 0
|
75
|
+
for j in range(1, lz - 1, 2):
|
76
|
+
# Set the X Pauli components
|
77
|
+
hx[temp + count, j + 1] = 1
|
78
|
+
hx[temp + count, (n - 1) - (lz - 1) + j] = 1
|
79
|
+
|
80
|
+
# Set the Z Pauli components
|
81
|
+
hz[temp + count, j] = 1
|
82
|
+
hz[temp + count, (n - 1) - (lz - 1) + 1 + j] = 1
|
83
|
+
|
84
|
+
count += 1
|
85
|
+
|
86
|
+
# Add the extra stabilizers to the right of the lattice
|
87
|
+
temp = (lx - 1) * (lz - 1) + lz // 2 + (lz - 1) // 2
|
88
|
+
count = 0
|
89
|
+
for j in range(0, lx - 1, 2):
|
90
|
+
# Set the Z Pauli components
|
91
|
+
hz[temp + count, (j + 1) * lz + lz - 1] = 1
|
92
|
+
hz[temp + count, j * lz] = 1
|
93
|
+
|
94
|
+
# Set the X Pauli components
|
95
|
+
hx[temp + count, (j + 1) * lz] = 1
|
96
|
+
hx[temp + count, j * lz + lz - 1] = 1
|
97
|
+
|
98
|
+
count += 1
|
99
|
+
|
100
|
+
# Add the extra stabilizers to the left of the lattice
|
101
|
+
temp = (lx - 1) * (lz - 1) + lz // 2 + (lz - 1) // 2 + lx // 2
|
102
|
+
count = 0
|
103
|
+
for j in range(1, lx - 1, 2):
|
104
|
+
# Set the Z Pauli components
|
105
|
+
hz[temp + count, (j + 1) * lz + lz - 1] = 1
|
106
|
+
hz[temp + count, j * lz] = 1
|
107
|
+
|
108
|
+
# Set the X Pauli components
|
109
|
+
hx[temp + count, (j + 1) * lz] = 1
|
110
|
+
hx[temp + count, j * lz + lz - 1] = 1
|
111
|
+
|
112
|
+
count += 1
|
113
|
+
|
114
|
+
# Convert hx and hz to CSR format for efficient arithmetic operations and storage
|
115
|
+
hx = hx.tocsc()
|
116
|
+
hz = hz.tocsc()
|
117
|
+
|
118
|
+
# Combine the parity check matrices into a single stabilizer matrix
|
119
|
+
stabilizer_matrix = scipy.sparse.hstack([hx, hz]).tocsr()
|
120
|
+
|
121
|
+
# Initialize the StabilizerCode with the combined parity check matrix
|
122
|
+
super().__init__(stabilizer_matrix, name=f"Rotated XZZX ({lx}x{lz}) Code")
|