qec 0.2.7__py3-none-any.whl → 0.2.9__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,308 +0,0 @@
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 convert_to_binary_scipy_sparse
9
-
10
-
11
- class HypergraphProductCode(CSSCode):
12
- """
13
- Implements a Hypergraph Product (HGP) code - derived from two classical linear binary codes.
14
-
15
- Parameters
16
- ----------
17
- seed_matrix_1 :
18
- A classical linear binary code used as a "seed" in the HGP construction method.
19
- seed_matrix_2 :
20
- A classical linear binary code used as a "seed" in the HGP construction method.
21
- name : str, default = None
22
- The name of the code. If None, the name is set to: "Hypergraph product code"
23
-
24
- Attributes
25
- ----------
26
- seed_matrix_1 : scipy.sparse.spmatrix
27
- The input seed_matrix_1 stored as a scipy sparse matrix.
28
- seed_matrix_2 : scipy.sparse.spmatrix
29
- The input seed_matrix_2 stored as a scipy sparse matrix.
30
- _n1 : int
31
- Number of columns in seed_matrix_1
32
- _n2 : int
33
- Number of columns in seed_matrix_2
34
- _m1 : int
35
- Number of rows in seed_matrix_1 (the number of columns of it's transpose)
36
- _m2 : int
37
- Number of rows in seed_matrix_2 (the number of columns of it's transpose)
38
-
39
- Notes
40
- -----
41
-
42
- The X and Z stabilizer matrices are given by [1]_:
43
-
44
- .. math::
45
-
46
- \begin{align}
47
- H_{X} &= \begin{pmatrix}
48
- H_{1}\otimes I_{n_{2}} & \,\,I_{r_{1}}\otimes H_{2}^{T}
49
- \end{pmatrix}\tag*{(1)}\\
50
- H_{Z} &= \begin{pmatrix}
51
- I_{n_{1}}\otimes H_{2} & \,\,H_{1}^{T}\otimes I_{r_{2}}
52
- \end{pmatrix}~, \tag*{(2)}
53
- \end{align}
54
-
55
- where :math:`H_1` and :math:`H_2` correspond to the parity check matrix of the first and second "seed" codes.
56
-
57
-
58
- .. [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)
59
- """
60
-
61
- def __init__(
62
- self,
63
- seed_matrix_1: Union[np.ndarray, scipy.sparse.spmatrix],
64
- seed_matrix_2: Union[np.ndarray, scipy.sparse.spmatrix],
65
- name: str = None,
66
- ):
67
- self.name = name if name else "Hypergraph product code"
68
-
69
- if not all(
70
- isinstance(seed_m, (np.ndarray, scipy.sparse.spmatrix))
71
- for seed_m in (seed_matrix_1, seed_matrix_2)
72
- ):
73
- raise TypeError(
74
- "The seed matrices must be either numpy arrays or scipy sparse matrices."
75
- )
76
-
77
- self.seed_matrix_1 = convert_to_binary_scipy_sparse(seed_matrix_1)
78
- self.seed_matrix_2 = convert_to_binary_scipy_sparse(seed_matrix_2)
79
-
80
- # maybe move the below to a private _construct_stabilizer_matrices function?
81
- # --------------------------------------------------------------------------
82
- self._n1 = seed_matrix_1.shape[1]
83
- self._n2 = seed_matrix_2.shape[1]
84
-
85
- self._m1 = seed_matrix_1.shape[0]
86
- self._m2 = seed_matrix_2.shape[0]
87
-
88
- x_left = scipy.sparse.kron(seed_matrix_1, scipy.sparse.eye(self._n2))
89
- x_right = scipy.sparse.kron(scipy.sparse.eye(self._m1), seed_matrix_2.T)
90
- self.x_stabilizer_matrix = scipy.sparse.hstack([x_left, x_right])
91
-
92
- z_left = scipy.sparse.kron(scipy.sparse.eye(self._n1), seed_matrix_2)
93
- z_right = scipy.sparse.kron(seed_matrix_1.T, scipy.sparse.eye(self._m2))
94
- self.z_stabilizer_matrix = scipy.sparse.hstack([z_left, z_right])
95
- # --------------------------------------------------------------------------
96
-
97
- super().__init__(self.x_stabilizer_matrix, self.z_stabilizer_matrix, self.name)
98
-
99
- def compute_exact_code_distance(self) -> int:
100
- """
101
- Computes the exact code distance of the HGP code.
102
-
103
- Returns
104
- -------
105
- int
106
- The distance of the code.
107
-
108
- Notes
109
- -----
110
- The distance of a HGP code is given as:
111
-
112
- .. math::
113
-
114
- \min(d_1, d_2, d_1^T, d_2^T)
115
-
116
- corresponding to the distance of the seed codes and the distance of their transposes.
117
- """
118
-
119
- rank_seed_m1 = ldpc.mod2.rank(self.seed_matrix_1)
120
- rank_seed_m2 = ldpc.mod2.rank(self.seed_matrix_2)
121
-
122
- if self.seed_matrix_1.shape[1] != rank_seed_m1:
123
- self.d1 = ldpc.mod2.compute_exact_code_distance(self.seed_matrix_1)
124
- else:
125
- self.d1 = np.inf
126
-
127
- if self.seed_matrix_2.shape[1] != rank_seed_m2:
128
- self.d2 = ldpc.mod2.compute_exact_code_distance(self.seed_matrix_2)
129
- else:
130
- self.d2 = np.inf
131
-
132
- # note: rank(A) = rank(A^T):
133
- if self.seed_matrix_1.shape[0] != rank_seed_m1:
134
- self.d1T = ldpc.mod2.compute_exact_code_distance(self.seed_matrix_1.T)
135
- else:
136
- self.d1T = np.inf
137
-
138
- if self.seed_matrix_2.shape[0] != rank_seed_m2:
139
- self.d2T = ldpc.mod2.compute_exact_code_distance(self.seed_matrix_2.T)
140
- else:
141
- self.d2T = np.inf
142
-
143
- self.x_code_distance = min(self.d1T, self.d2)
144
- self.z_code_distance = min(self.d1, self.d2T)
145
- self.code_distance = min(self.x_code_distance, self.z_code_distance)
146
-
147
- return self.code_distance
148
-
149
- def estimate_min_distance(self, timeout_seconds: float = 0.025) -> int:
150
- """
151
- Estimate the minimum X and Z distance of the HGP code. Parameters
152
- ----------
153
- timeout_seconds : float, optional
154
- Time limit in seconds for the full search. Default: 0.25
155
-
156
- Returns
157
- -------
158
- int
159
- Best estimate of the (overall) code distance found within time limit.
160
-
161
- """
162
-
163
- rank_seed_m1 = ldpc.mod2.rank(self.seed_matrix_1)
164
- rank_seed_m2 = ldpc.mod2.rank(self.seed_matrix_2)
165
-
166
- d1_timeout_seconds = timeout_seconds / 4
167
- if self.seed_matrix_1.shape[1] != rank_seed_m1:
168
- d1_start_time = time.time()
169
- d1_min_estimate, _, _ = ldpc.mod2.estimate_code_distance(
170
- self.seed_matrix_1, d1_timeout_seconds, 0
171
- )
172
- d1_run_time = time.time() - d1_start_time
173
- else:
174
- d1_min_estimate = np.inf
175
- d1_run_time = 0
176
-
177
- d1T_timeout_seconds = (
178
- (d1_timeout_seconds * 4 - d1_run_time) / 3
179
- if d1_run_time <= d1_timeout_seconds
180
- else timeout_seconds / 4
181
- )
182
- if self.seed_matrix_1.shape[0] != rank_seed_m1:
183
- d1T_start_time = time.time()
184
- d1T_min_estimate, _, _ = ldpc.mod2.estimate_code_distance(
185
- self.seed_matrix_1.T, d1T_timeout_seconds, 0
186
- )
187
- d1T_run_time = time.time() - d1T_start_time
188
- else:
189
- d1T_min_estimate = np.inf
190
- d1T_run_time = 0
191
-
192
- d2_timeout_seconds = (
193
- (d1T_timeout_seconds * 3 - d1T_run_time) / 2
194
- if d1T_run_time <= d1T_timeout_seconds
195
- else timeout_seconds / 4
196
- )
197
- if self.seed_matrix_2.shape[1] != rank_seed_m2:
198
- d2_start_time = time.time()
199
- d2_min_estimate, _, _ = ldpc.mod2.estimate_code_distance(
200
- self.seed_matrix_2, d2_timeout_seconds, 0
201
- )
202
- d2_run_time = time.time() - d2_start_time
203
- else:
204
- d2_min_estimate = np.inf
205
- d2_run_time = 0
206
-
207
- d2T_timeout_seconds = (
208
- (d2_timeout_seconds * 2 - d2_run_time)
209
- if d2_run_time <= d2_timeout_seconds
210
- else timeout_seconds / 4
211
- )
212
- if self.seed_matrix_2.shape[0] != rank_seed_m2:
213
- d2T_min_estimate, _, _ = ldpc.mod2.estimate_code_distance(
214
- self.seed_matrix_2.T, d2T_timeout_seconds, 0
215
- )
216
- else:
217
- d2T_min_estimate = np.inf
218
-
219
- self.x_code_distance = min(d1T_min_estimate, d2_min_estimate)
220
- self.z_code_distance = min(d1_min_estimate, d2T_min_estimate)
221
- self.code_distance = min(self.x_code_distance, self.z_code_distance)
222
-
223
- return self.code_distance
224
-
225
- def compute_logical_basis(
226
- self,
227
- ) -> Tuple[scipy.sparse.spmatrix, scipy.sparse.spmatrix]:
228
- """
229
- Compute the logical operator basis for the given HGP code.
230
-
231
- Returns
232
- -------
233
- Tuple[scipy.sparse.spmatrix, scipy.sparse.spmatrix]
234
- Logical X and Z operator bases (lx, lz).
235
- """
236
-
237
- ker_h1 = ldpc.mod2.kernel(self.seed_matrix_1)
238
- ker_h2 = ldpc.mod2.kernel(self.seed_matrix_2)
239
- ker_h1T = ldpc.mod2.kernel(self.seed_matrix_1.T)
240
- ker_h2T = ldpc.mod2.kernel(self.seed_matrix_2.T)
241
-
242
- row_comp_h1 = ldpc.mod2.row_complement_basis(self.seed_matrix_1)
243
- row_comp_h2 = ldpc.mod2.row_complement_basis(self.seed_matrix_2)
244
- row_comp_h1T = ldpc.mod2.row_complement_basis(self.seed_matrix_1.T)
245
- row_comp_h2T = ldpc.mod2.row_complement_basis(self.seed_matrix_2.T)
246
-
247
- temp = scipy.sparse.kron(ker_h1, row_comp_h2)
248
- lz1 = scipy.sparse.hstack(
249
- [
250
- temp,
251
- scipy.sparse.csr_matrix(
252
- (temp.shape[0], self._m1 * self._m2), dtype=np.uint8
253
- ),
254
- ]
255
- )
256
-
257
- temp = scipy.sparse.kron(row_comp_h1T, ker_h2T)
258
- lz2 = scipy.sparse.hstack(
259
- [
260
- scipy.sparse.csr_matrix(
261
- (temp.shape[0], self._n1 * self._n2), dtype=np.uint8
262
- ),
263
- temp,
264
- ]
265
- )
266
-
267
- self.z_logical_operator_basis = scipy.sparse.vstack([lz1, lz2], dtype=np.uint8)
268
-
269
- temp = scipy.sparse.kron(row_comp_h1, ker_h2)
270
- lx1 = scipy.sparse.hstack(
271
- [
272
- temp,
273
- scipy.sparse.csr_matrix(
274
- (temp.shape[0], self._m1 * self._m2), dtype=np.uint8
275
- ),
276
- ]
277
- )
278
-
279
- temp = scipy.sparse.kron(ker_h1T, row_comp_h2T)
280
- lx2 = scipy.sparse.hstack(
281
- [
282
- scipy.sparse.csr_matrix(
283
- (temp.shape[0], self._n1 * self._n2), dtype=np.uint8
284
- ),
285
- temp,
286
- ]
287
- )
288
-
289
- self.x_logical_operator_basis = scipy.sparse.vstack([lx1, lx2], dtype=np.uint8)
290
-
291
- # Follows the way it is done in CSSCode -> move it into __init__?
292
- # ----------------------------------------------------------------
293
- self.logical_qubit_count = self.x_logical_operator_basis.shape[0]
294
- # ----------------------------------------------------------------
295
-
296
- return (self.x_logical_operator_basis, self.z_logical_operator_basis)
297
-
298
- def __str__(self):
299
- """
300
- String representation of the HGP code. Includes the name and [[n, k, d]] properties of the code.
301
-
302
- Returns
303
- -------
304
- str
305
- String representation of the HGP code.
306
- """
307
-
308
- 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}]]"