qec 0.0.11__py3-none-any.whl → 0.2.1__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,609 @@
1
+ from qec.stabilizer_code import StabilizerCode
2
+ from qec.utils.sparse_binary_utils import convert_to_binary_scipy_sparse
3
+
4
+ # Added / ammended from old code
5
+ from typing import Union, Tuple
6
+ import numpy as np
7
+ import ldpc.mod2
8
+ import scipy
9
+ from ldpc import BpOsdDecoder
10
+ from tqdm import tqdm
11
+ import time
12
+ import logging
13
+ from typing import Optional
14
+
15
+ logging.basicConfig(level=logging.DEBUG)
16
+
17
+
18
+ class CSSCode(StabilizerCode):
19
+ """
20
+ A class for generating and manipulating Calderbank-Shor-Steane (CSS) quantum error-correcting codes.
21
+
22
+ Prameters
23
+ ---------
24
+ x_stabilizer_matrix (hx): Union[np.ndarray, scipy.sparse.spmatrix]
25
+ The X-check matrix.
26
+ z_stabilizer_matrix (hz): Union[np.ndarray, scipy.sparse.spmatrix]
27
+ The Z-check matrix.
28
+ name: str, optional
29
+ A name for this CSS code. Defaults to "CSS code".
30
+
31
+ Attributes
32
+ ----------
33
+ x_stabilizer_matrix (hx): Union[np.ndarray, scipy.sparse.spmatrix]
34
+ The X-check matrix.
35
+ z_stabilizer_matrix (hz): Union[np.ndarray, scipy.sparse.spmatrix]
36
+ The Z-check matrix.
37
+ name (str):
38
+ A name for this CSS code.
39
+ physical_qubit_count (N): int
40
+ The number of physical qubits in the code.
41
+ logical_qubit_count (K): int
42
+ The number of logical qubits in the code. Dimension of the code.
43
+ code_distance (d): int
44
+ (Not computed by default) Minimum distance of the code.
45
+ x_logical_operator_basis (lx): (Union[np.ndarray, scipy.sparse.spmatrix]
46
+ Logical X operator basis.
47
+ z_logical_operator_basis (lz): (Union[np.ndarray, scipy.sparse.spmatrix]
48
+ Logical Z operator basis.
49
+ """
50
+
51
+ def __init__(
52
+ self,
53
+ x_stabilizer_matrix: Union[np.ndarray, scipy.sparse.spmatrix],
54
+ z_stabilizer_matrix: Union[np.ndarray, scipy.sparse.spmatrix],
55
+ name: str = None,
56
+ ):
57
+ """
58
+ Initialise a new instance of the CSSCode class.
59
+
60
+ Parameters
61
+ ----------
62
+ x_stabilizer_matrix (hx): Union[np.ndarray, scipy.sparse.spmatrix]
63
+ The X-check matrix.
64
+ z_stabilizer_matrix (hz): Union[np.ndarray, scipy.sparse.spmatrix]
65
+ The Z-check matrix.
66
+ name: str, optional
67
+ A name for this CSS code. Defaults to "CSS code".
68
+ """
69
+
70
+ # Assign a default name if none is provided
71
+ if name is None:
72
+ self.name = "CSS code"
73
+ else:
74
+ self.name = name
75
+
76
+ self.x_logical_operator_basis = None
77
+ self.z_logical_operator_basis = None
78
+
79
+ # Check if the input matrices are NumPy arrays or SciPy sparse matrices
80
+ if not isinstance(x_stabilizer_matrix, (np.ndarray, scipy.sparse.spmatrix)):
81
+ raise TypeError(
82
+ "Please provide x and z stabilizer matrices as either a numpy array or a scipy sparse matrix."
83
+ )
84
+
85
+ # Convert matrices to sparse representation and set them as class attributes (replaced the old code "convert_to_sparse")
86
+ self.x_stabilizer_matrix = convert_to_binary_scipy_sparse(x_stabilizer_matrix)
87
+ self.z_stabilizer_matrix = convert_to_binary_scipy_sparse(z_stabilizer_matrix)
88
+
89
+ # Calculate the number of physical qubits from the matrix dimension
90
+ self.physical_qubit_count = self.x_stabilizer_matrix.shape[1]
91
+
92
+ # Validate the number of qubits for both matrices
93
+ try:
94
+ assert self.physical_qubit_count == self.z_stabilizer_matrix.shape[1]
95
+ except AssertionError:
96
+ raise ValueError(
97
+ f"Input matrices x_stabilizer_matrix and z_stabilizer_matrix must have the same number of columns.\
98
+ Current column count, x_stabilizer_matrix: {x_stabilizer_matrix.shape[1]}; z_stabilizer_matrix: {z_stabilizer_matrix.shape[1]}"
99
+ )
100
+
101
+ # Validate if the input matrices commute
102
+ try:
103
+ assert not np.any(
104
+ (self.x_stabilizer_matrix @ self.z_stabilizer_matrix.T).data % 2
105
+ )
106
+ except AssertionError:
107
+ raise ValueError(
108
+ "Input matrices hx and hz do not commute. I.e. they do not satisfy\
109
+ the requirement that hx@hz.T = 0."
110
+ )
111
+
112
+ # Compute a basis of the logical operators
113
+ self.compute_logical_basis()
114
+
115
+ def compute_logical_basis(self):
116
+ """
117
+ Compute the logical operator basis for the given CSS code.
118
+
119
+ Returns
120
+ -------
121
+ Tuple[scipy.sparse.spmatrix, scipy.sparse.spmatrix]
122
+ Logical X and Z operator bases (lx, lz).
123
+
124
+ Notes
125
+ -----
126
+ This method uses the kernel of the X and Z stabilizer matrices to find operators that commute with all the stabilizers,
127
+ and then identifies the subsets of which are not themselves linear combinations of the stabilizers.
128
+ """
129
+
130
+ # Compute the kernel of hx and hz matrices
131
+
132
+ # Z logicals
133
+
134
+ # Compute the kernel of hx
135
+ ker_hx = ldpc.mod2.kernel(self.x_stabilizer_matrix) # kernel of X-stabilisers
136
+ # Sort the rows of ker_hx by weight
137
+ row_weights = ker_hx.getnnz(axis=1)
138
+ sorted_rows = np.argsort(row_weights)
139
+ ker_hx = ker_hx[sorted_rows, :]
140
+ # Z logicals are elements of ker_hx (that commute with all the X-stabilisers) that are not linear combinations of Z-stabilisers
141
+ logical_stack = scipy.sparse.vstack([self.z_stabilizer_matrix, ker_hx]).tocsr()
142
+ self.rank_hz = ldpc.mod2.rank(self.z_stabilizer_matrix)
143
+ # The first self.rank_hz pivot_rows of logical_stack are the Z-stabilisers. The remaining pivot_rows are the Z logicals
144
+ pivots = ldpc.mod2.pivot_rows(logical_stack)
145
+ self.z_logical_operator_basis = logical_stack[pivots[self.rank_hz :], :]
146
+
147
+ # X logicals
148
+
149
+ # Compute the kernel of hz
150
+ ker_hz = ldpc.mod2.kernel(self.z_stabilizer_matrix)
151
+ # Sort the rows of ker_hz by weight
152
+ row_weights = ker_hz.getnnz(axis=1)
153
+ sorted_rows = np.argsort(row_weights)
154
+ ker_hz = ker_hz[sorted_rows, :]
155
+ # X logicals are elements of ker_hz (that commute with all the Z-stabilisers) that are not linear combinations of X-stabilisers
156
+ logical_stack = scipy.sparse.vstack([self.x_stabilizer_matrix, ker_hz]).tocsr()
157
+ self.rank_hx = ldpc.mod2.rank(self.x_stabilizer_matrix)
158
+ # The first self.rank_hx pivot_rows of logical_stack are the X-stabilisers. The remaining pivot_rows are the X logicals
159
+ pivots = ldpc.mod2.pivot_rows(logical_stack)
160
+ self.x_logical_operator_basis = logical_stack[pivots[self.rank_hx :], :]
161
+
162
+ # set the dimension of the code (i.e. the number of logical qubits)
163
+ self.logical_qubit_count = self.x_logical_operator_basis.shape[0]
164
+
165
+ # find the minimum weight logical operators
166
+ self.x_code_distance = self.physical_qubit_count
167
+ self.z_code_distance = self.physical_qubit_count
168
+
169
+ for i in range(self.logical_qubit_count):
170
+ if self.x_logical_operator_basis[i].nnz < self.x_code_distance:
171
+ self.x_code_distance = self.x_logical_operator_basis[i].nnz
172
+ if self.z_logical_operator_basis[i].nnz < self.z_code_distance:
173
+ self.z_code_distance = self.z_logical_operator_basis[i].nnz
174
+ self.code_distance = np.min([self.x_code_distance, self.z_code_distance])
175
+
176
+ # FIXME: How does this differ from rank_hx and rank_hz descibed above (ldpc.mod2.rank())?
177
+ # compute the hx and hz rank
178
+ self.rank_hx = self.physical_qubit_count - ker_hx.shape[0]
179
+ self.rank_hz = self.physical_qubit_count - ker_hz.shape[0]
180
+
181
+ return (self.x_logical_operator_basis, self.z_logical_operator_basis)
182
+
183
+ # TODO: Add a function to save the logical operator basis to a file
184
+
185
+ def check_valid_logical_xz_basis(self) -> bool:
186
+ """
187
+ Validate that the stored logical operators form a proper logical basis for the code.
188
+
189
+ Checks that they commute with the stabilizers, pairwise anti-commute, and have full rank.
190
+
191
+ Returns
192
+ -------
193
+ bool
194
+ True if the logical operators form a valid basis, otherwise False.
195
+ """
196
+
197
+ # If logical bases are not computed yet, compute them
198
+ if (
199
+ self.x_logical_operator_basis is None
200
+ or self.z_logical_operator_basis is None
201
+ ):
202
+ self.x_logical_operator_basis, self.z_logical_operator_basis = (
203
+ self.compute_logical_basis(
204
+ self.x_stabilizer_matrix, self.z_stabilizer_matrix
205
+ )
206
+ )
207
+ self.logical_qubit_count = self.x_logical_operator_basis.shape[0]
208
+
209
+ try:
210
+ # Test dimension
211
+ assert (
212
+ self.logical_qubit_count
213
+ == self.z_logical_operator_basis.shape[0]
214
+ == self.x_logical_operator_basis.shape[0]
215
+ ), "Logical operator basis dimensions do not match."
216
+
217
+ # Check logical basis linearly independent (i.e. full rank)
218
+ assert (
219
+ ldpc.mod2.rank(self.x_logical_operator_basis)
220
+ == self.logical_qubit_count
221
+ ), "X logical operator basis is not full rank, and hence not linearly independent."
222
+ assert (
223
+ ldpc.mod2.rank(self.z_logical_operator_basis)
224
+ == self.logical_qubit_count
225
+ ), "Z logical operator basis is not full rank, and hence not linearly independent."
226
+
227
+ # Perform various tests to validate the logical bases
228
+
229
+ # Check that the logical operators commute with the stabilizers
230
+ try:
231
+ assert not np.any(
232
+ (self.x_logical_operator_basis @ self.z_stabilizer_matrix.T).data
233
+ % 2
234
+ ), "X logical operators do not commute with Z stabilizers."
235
+ except AssertionError as e:
236
+ logging.error(e)
237
+ return False
238
+
239
+ try:
240
+ assert not np.any(
241
+ (self.z_logical_operator_basis @ self.x_stabilizer_matrix.T).data
242
+ % 2
243
+ ), "Z logical operators do not commute with X stabilizers."
244
+ except AssertionError as e:
245
+ logging.error(e)
246
+ return False
247
+
248
+ # Check that the logical operators anticommute with each other (by checking that the rank of the product is full rank)
249
+ test = self.x_logical_operator_basis @ self.z_logical_operator_basis.T
250
+ test.data = test.data % 2
251
+ assert (
252
+ ldpc.mod2.rank(test) == self.logical_qubit_count
253
+ ), "Logical operators do not pairwise anticommute."
254
+
255
+ test = self.z_logical_operator_basis @ self.x_logical_operator_basis.T
256
+ test.data = test.data % 2
257
+ assert (
258
+ ldpc.mod2.rank(test) == self.logical_qubit_count
259
+ ), "Logical operators do not pairwise anticommute."
260
+
261
+ # TODO: Check that the logical operators are not themselves stabilizers?
262
+
263
+ except AssertionError as e:
264
+ logging.error(e)
265
+ return False
266
+
267
+ return True
268
+
269
+ def compute_exact_code_distance(
270
+ self, timeout: float = 0.5
271
+ ) -> Tuple[Optional[int], Optional[int], float]:
272
+ """
273
+ Compute the exact distance of the CSS code by searching through linear combinations
274
+ of logical operators and stabilisers, ensuring balanced progress between X and Z searches.
275
+
276
+ Parameters
277
+ ----------
278
+ timeout : float, optional
279
+ The time limit (in seconds) for the exhaustive search. Default is 0.5 seconds.
280
+ To obtain the exact distance, set to `np.inf`.
281
+
282
+ Returns
283
+ -------
284
+ Tuple[Optional[int], Optional[int], float]
285
+ A tuple containing:
286
+ - The best-known X distance of the code (or None if no X distance was found)
287
+ - The best-known Z distance of the code (or None if no Z distance was found)
288
+ - The fraction of total combinations considered before timeout
289
+
290
+ Notes
291
+ -----
292
+ - Searches X and Z combinations in an interleaved manner to ensure balanced progress
293
+ - For each type (X/Z):
294
+ - We compute the row span of both stabilisers and logical operators
295
+ - For every logical operator in the logical span, we add (mod 2) each stabiliser
296
+ - We compute the Hamming weight of each candidate operator
297
+ - We track the minimal Hamming weight encountered
298
+ """
299
+ start_time = time.time()
300
+
301
+ # Get stabiliser spans
302
+ x_stabiliser_span = ldpc.mod2.row_span(self.x_stabilizer_matrix)[1:]
303
+ z_stabiliser_span = ldpc.mod2.row_span(self.z_stabilizer_matrix)[1:]
304
+
305
+ # Get logical spans
306
+ x_logical_span = ldpc.mod2.row_span(self.x_logical_operator_basis)[1:]
307
+ z_logical_span = ldpc.mod2.row_span(self.z_logical_operator_basis)[1:]
308
+
309
+ # Initialize distances
310
+ if self.x_code_distance is None:
311
+ x_code_distance = np.inf
312
+ else:
313
+ x_code_distance = self.x_code_distance
314
+
315
+ if self.z_code_distance is None:
316
+ z_code_distance = np.inf
317
+ else:
318
+ z_code_distance = self.z_code_distance
319
+
320
+ # Prepare iterators for both X and Z combinations
321
+ x_combinations = (
322
+ (x_l, x_s) for x_l in x_logical_span for x_s in x_stabiliser_span
323
+ )
324
+ z_combinations = (
325
+ (z_l, z_s) for z_l in z_logical_span for z_s in z_stabiliser_span
326
+ )
327
+
328
+ total_x_combinations = x_stabiliser_span.shape[0] * x_logical_span.shape[0]
329
+ total_z_combinations = z_stabiliser_span.shape[0] * z_logical_span.shape[0]
330
+ total_combinations = total_x_combinations + total_z_combinations
331
+ combinations_considered = 0
332
+
333
+ # Create iterables that we can exhaust
334
+ x_iter = iter(x_combinations)
335
+ z_iter = iter(z_combinations)
336
+ x_exhausted = False
337
+ z_exhausted = False
338
+
339
+ while not (x_exhausted and z_exhausted):
340
+ if time.time() - start_time > timeout:
341
+ break
342
+
343
+ # Try X combination if not exhausted
344
+ if not x_exhausted:
345
+ try:
346
+ x_logical, x_stabiliser = next(x_iter)
347
+ candidate_x = x_logical + x_stabiliser
348
+ candidate_x.data %= 2
349
+ x_weight = candidate_x.getnnz()
350
+ if x_weight < x_code_distance:
351
+ x_code_distance = x_weight
352
+ combinations_considered += 1
353
+ except StopIteration:
354
+ x_exhausted = True
355
+
356
+ # Try Z combination if not exhausted
357
+ if not z_exhausted:
358
+ try:
359
+ z_logical, z_stabiliser = next(z_iter)
360
+ candidate_z = z_logical + z_stabiliser
361
+ candidate_z.data %= 2
362
+ z_weight = candidate_z.getnnz()
363
+ if z_weight < z_code_distance:
364
+ z_code_distance = z_weight
365
+ combinations_considered += 1
366
+ except StopIteration:
367
+ z_exhausted = True
368
+
369
+ # Update code distances
370
+ self.x_code_distance = x_code_distance if x_code_distance != np.inf else None
371
+ self.z_code_distance = z_code_distance if z_code_distance != np.inf else None
372
+ self.code_distance = (
373
+ min(x_code_distance, z_code_distance)
374
+ if x_code_distance != np.inf and z_code_distance != np.inf
375
+ else None
376
+ )
377
+
378
+ # Calculate fraction of combinations considered
379
+ fraction_considered = combinations_considered / total_combinations
380
+
381
+ return (
382
+ int(x_code_distance) if x_code_distance != np.inf else None,
383
+ int(z_code_distance) if z_code_distance != np.inf else None,
384
+ fraction_considered,
385
+ )
386
+
387
+ def estimate_min_distance(
388
+ self,
389
+ timeout_seconds: float = 0.25,
390
+ p: float = 0.25,
391
+ reduce_logical_basis: bool = False,
392
+ decoder: Optional[BpOsdDecoder] = None,
393
+ ) -> int:
394
+ """
395
+ Estimate the minimum distance of the CSS code using a BP+OSD decoder-based search.
396
+
397
+ Parameters
398
+ ----------
399
+ timeout_seconds : float, optional
400
+ Time limit in seconds for the search. Default: 0.25
401
+ p : float, optional
402
+ Probability for including each logical operator in trial combinations. Default: 0.25
403
+ reduce_logical_basis : bool, optional
404
+ Whether to attempt reducing the logical operator basis. Default: False
405
+ decoder : Optional[BpOsdDecoder], optional
406
+ Pre-configured BP+OSD decoder. If None, initializes with default settings.
407
+
408
+ Returns
409
+ -------
410
+ int
411
+ Best estimate of code distance found within time limit.
412
+ """
413
+ start_time = time.time()
414
+
415
+ # Ensure logical operator bases are computed
416
+ if (
417
+ self.x_logical_operator_basis is None
418
+ or self.z_logical_operator_basis is None
419
+ ):
420
+ self.compute_logical_basis()
421
+
422
+ # Setup decoders for X and Z logical operators
423
+ bp_osd_x, x_stack, _, x_min_distance, x_max_distance = (
424
+ self._setup_distance_estimation_decoder(
425
+ self.x_stabilizer_matrix, self.x_logical_operator_basis, decoder
426
+ )
427
+ )
428
+ bp_osd_z, z_stack, _, z_min_distance, z_max_distance = (
429
+ self._setup_distance_estimation_decoder(
430
+ self.z_stabilizer_matrix, self.z_logical_operator_basis, decoder
431
+ )
432
+ )
433
+
434
+ candidate_logicals_x = []
435
+ candidate_logicals_z = []
436
+
437
+ # Search loop
438
+ with tqdm(total=timeout_seconds, desc="Estimating distance") as pbar:
439
+ while time.time() - start_time < timeout_seconds:
440
+ elapsed = time.time() - start_time
441
+ pbar.update(elapsed - pbar.n)
442
+
443
+ # Generate random logical combinations for X
444
+ dummy_syndrome_x = (
445
+ self._generate_random_logical_combination_for_distance_estimation(
446
+ x_stack, p, self.x_stabilizer_matrix.shape[0]
447
+ )
448
+ )
449
+ candidate_x = bp_osd_x.decode(dummy_syndrome_x)
450
+ x_weight = np.count_nonzero(candidate_x)
451
+
452
+ if x_weight < x_min_distance:
453
+ x_min_distance = x_weight
454
+
455
+ if x_weight < x_max_distance and reduce_logical_basis:
456
+ candidate_logicals_x.append(candidate_x)
457
+
458
+ # Generate random logical combinations for Z
459
+ dummy_syndrome_z = (
460
+ self._generate_random_logical_combination_for_distance_estimation(
461
+ z_stack, p, self.z_stabilizer_matrix.shape[0]
462
+ )
463
+ )
464
+ candidate_z = bp_osd_z.decode(dummy_syndrome_z)
465
+ z_weight = np.count_nonzero(candidate_z)
466
+
467
+ if z_weight < z_min_distance:
468
+ z_min_distance = z_weight
469
+
470
+ if z_weight < z_max_distance and reduce_logical_basis:
471
+ candidate_logicals_z.append(candidate_z)
472
+
473
+ # Update progress bar description
474
+ pbar.set_description(
475
+ f"Estimating distance: dx <= {x_min_distance}, dz <= {z_min_distance}"
476
+ )
477
+
478
+ # Update distances and reduce logical bases if applicable
479
+ self.x_code_distance = x_min_distance
480
+ self.z_code_distance = z_min_distance
481
+ self.code_distance = min(x_min_distance, z_min_distance)
482
+
483
+ if reduce_logical_basis:
484
+ self._reduce_logical_operator_basis(
485
+ candidate_logicals_x, candidate_logicals_z
486
+ )
487
+
488
+ return self.code_distance
489
+
490
+ def _setup_distance_estimation_decoder(
491
+ self, stabilizer_matrix, logical_operator_basis, decoder=None
492
+ ) -> Tuple[BpOsdDecoder, scipy.sparse.spmatrix, scipy.sparse.spmatrix, int, int]:
493
+ """
494
+ Helper function to set up the BP+OSD decoder for distance estimation.
495
+
496
+ Parameters
497
+ ----------
498
+ stabilizer_matrix : scipy.sparse.spmatrix
499
+ Stabilizer matrix of the code.
500
+ logical_operator_basis : scipy.sparse.spmatrix
501
+ Logical operator basis of the code.
502
+ decoder : Optional[BpOsdDecoder], optional
503
+ Pre-configured decoder. If None, initializes with default settings.
504
+
505
+ Returns
506
+ -------
507
+ Tuple[BpOsdDecoder, scipy.sparse.spmatrix, scipy.sparse.spmatrix, int, int]
508
+ Decoder, stacked matrix, stabilizer matrix, minimum distance, and maximum distance.
509
+ """
510
+ # Remove redundant rows from stabilizer matrix
511
+ p_rows = ldpc.mod2.pivot_rows(stabilizer_matrix)
512
+ full_rank_stabilizer_matrix = stabilizer_matrix[p_rows]
513
+
514
+ # Build a stacked matrix of stabilizers and logicals
515
+ stack = scipy.sparse.vstack(
516
+ [full_rank_stabilizer_matrix, logical_operator_basis]
517
+ ).tocsr()
518
+
519
+ # Initial distance estimate from current logicals
520
+ min_distance = np.min(logical_operator_basis.getnnz(axis=1))
521
+ max_distance = np.max(logical_operator_basis.getnnz(axis=1))
522
+
523
+ # Set up BP+OSD decoder if not provided
524
+ if decoder is None:
525
+ decoder = BpOsdDecoder(
526
+ stack,
527
+ error_rate=0.1,
528
+ max_iter=10,
529
+ bp_method="ms",
530
+ schedule="parallel",
531
+ ms_scaling_factor=1.0,
532
+ osd_method="osd_0",
533
+ osd_order=0,
534
+ )
535
+
536
+ return decoder, stack, full_rank_stabilizer_matrix, min_distance, max_distance
537
+
538
+ def _generate_random_logical_combination_for_distance_estimation(
539
+ self, stack: scipy.sparse.spmatrix, p: float, stabilizer_count: int
540
+ ) -> np.ndarray:
541
+ """
542
+ Generate a random logical combination for the BP+OSD decoder.
543
+
544
+ Parameters
545
+ ----------
546
+ stack : scipy.sparse.spmatrix
547
+ The stacked stabilizer and logical operator matrix.
548
+ p : float
549
+ Probability for including each logical operator in the combination.
550
+ stabilizer_count : int
551
+ Number of stabilizer rows in the stacked matrix.
552
+
553
+ Returns
554
+ -------
555
+ np.ndarray
556
+ Randomly generated syndrome vector.
557
+ """
558
+ random_mask = np.random.choice([0, 1], size=stack.shape[0], p=[1 - p, p])
559
+ random_mask[:stabilizer_count] = (
560
+ 0 # Ensure no stabilizer-only rows are selected
561
+ )
562
+
563
+ while not np.any(random_mask):
564
+ random_mask = np.random.choice([0, 1], size=stack.shape[0], p=[1 - p, p])
565
+ random_mask[:stabilizer_count] = 0
566
+
567
+ dummy_syndrome = np.zeros(stack.shape[0], dtype=np.uint8)
568
+ dummy_syndrome[np.nonzero(random_mask)[0]] = 1
569
+
570
+ return dummy_syndrome
571
+
572
+ def fix_logical_operators(self, fix_logical: str = "X"):
573
+ if not isinstance(fix_logical, str):
574
+ raise TypeError("fix_logical parameter must be a string")
575
+
576
+ if fix_logical.lower() == "x":
577
+ temp = self.z_logical_operator_basis @ self.x_logical_operator_basis.T
578
+ temp.data = temp.data % 2
579
+ temp = ldpc.mod2.inverse(temp)
580
+ self.z_logical_operator_basis = temp @ self.z_logical_operator_basis
581
+ self.z_logical_operator_basis.data = self.z_logical_operator_basis.data % 2
582
+
583
+ elif fix_logical.lower() == "z":
584
+ temp = self.x_logical_operator_basis @ self.z_logical_operator_basis.T
585
+ temp.data = temp.data % 2
586
+ temp = ldpc.mod2.inverse(temp)
587
+ self.x_logical_operator_basis = temp @ self.x_logical_operator_basis
588
+ self.x_logical_operator_basis.data = self.x_logical_operator_basis.data % 2
589
+ else:
590
+ raise ValueError("Invalid fix_logical parameter")
591
+
592
+ @property
593
+ def logical_operator_weights(self) -> Tuple[np.ndarray, np.ndarray]:
594
+ x_weights = []
595
+ z_weights = []
596
+ for i in range(self.logical_qubit_count):
597
+ x_weights.append(self.x_logical_operator_basis[i].nnz)
598
+ z_weights.append(self.z_logical_operator_basis[i].nnz)
599
+
600
+ return (np.array(x_weights), np.array(z_weights))
601
+
602
+ def __str__(self):
603
+ """
604
+ Return a string representation of the CSSCode object.
605
+
606
+ Returns:
607
+ str: String representation of the CSS code.
608
+ """
609
+ return f"{self.name} Code: [[N={self.physical_qubit_count}, K={self.logical_qubit_count}, dx<={self.x_code_distance}, dz<={self.z_code_distance}]]"