qec 0.3.2__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.
@@ -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")