qec 0.2.1__py3-none-any.whl → 0.2.2__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,4 @@
1
+ from .stabilizer_code import StabilizerCode
2
+ from .css_code import CSSCode
3
+ from .hgp_code import HypergraphProductCode
4
+
@@ -1,4 +1,4 @@
1
- from qec.stabilizer_code import StabilizerCode
1
+ from qec.code_constructions import StabilizerCode
2
2
  from qec.utils.sparse_binary_utils import convert_to_binary_scipy_sparse
3
3
 
4
4
  # Added / ammended from old code
@@ -10,7 +10,7 @@ from ldpc import BpOsdDecoder
10
10
  from tqdm import tqdm
11
11
  import time
12
12
  import logging
13
- from typing import Optional
13
+ from typing import Optional, Sequence
14
14
 
15
15
  logging.basicConfig(level=logging.DEBUG)
16
16
 
@@ -76,6 +76,9 @@ class CSSCode(StabilizerCode):
76
76
  self.x_logical_operator_basis = None
77
77
  self.z_logical_operator_basis = None
78
78
 
79
+ self.x_code_distance = None
80
+ self.z_code_distance = None
81
+
79
82
  # Check if the input matrices are NumPy arrays or SciPy sparse matrices
80
83
  if not isinstance(x_stabilizer_matrix, (np.ndarray, scipy.sparse.spmatrix)):
81
84
  raise TypeError(
@@ -182,7 +185,7 @@ class CSSCode(StabilizerCode):
182
185
 
183
186
  # TODO: Add a function to save the logical operator basis to a file
184
187
 
185
- def check_valid_logical_xz_basis(self) -> bool:
188
+ def check_valid_logical_basis(self) -> bool:
186
189
  """
187
190
  Validate that the stored logical operators form a proper logical basis for the code.
188
191
 
@@ -408,7 +411,7 @@ class CSSCode(StabilizerCode):
408
411
  Returns
409
412
  -------
410
413
  int
411
- Best estimate of code distance found within time limit.
414
+ Best estimate of code distance found within the time limit.
412
415
  """
413
416
  start_time = time.time()
414
417
 
@@ -419,13 +422,13 @@ class CSSCode(StabilizerCode):
419
422
  ):
420
423
  self.compute_logical_basis()
421
424
 
422
- # Setup decoders for X and Z logical operators
423
- bp_osd_x, x_stack, _, x_min_distance, x_max_distance = (
425
+ # Setup decoders and parameters for both X and Z
426
+ bp_osd_z, x_stack, full_rank_x, x_min_distance, x_max_distance = (
424
427
  self._setup_distance_estimation_decoder(
425
428
  self.x_stabilizer_matrix, self.x_logical_operator_basis, decoder
426
429
  )
427
430
  )
428
- bp_osd_z, z_stack, _, z_min_distance, z_max_distance = (
431
+ bp_osd_x, z_stack, full_rank_z, z_min_distance, z_max_distance = (
429
432
  self._setup_distance_estimation_decoder(
430
433
  self.z_stabilizer_matrix, self.z_logical_operator_basis, decoder
431
434
  )
@@ -434,57 +437,106 @@ class CSSCode(StabilizerCode):
434
437
  candidate_logicals_x = []
435
438
  candidate_logicals_z = []
436
439
 
437
- # Search loop
440
+ x_weight_one_searched = 0
441
+ z_weight_one_searched = 0
442
+
438
443
  with tqdm(total=timeout_seconds, desc="Estimating distance") as pbar:
439
444
  while time.time() - start_time < timeout_seconds:
440
445
  elapsed = time.time() - start_time
441
446
  pbar.update(elapsed - pbar.n)
442
447
 
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
448
+ if np.random.rand() < 0.5:
449
+ # X Logical operators
450
+ if x_weight_one_searched < self.z_logical_operator_basis.shape[0]:
451
+ dummy_syndrome_x = np.zeros(z_stack.shape[0], dtype=np.uint8)
452
+ dummy_syndrome_x[
453
+ full_rank_z.shape[0] + x_weight_one_searched
454
+ ] = 1
455
+ x_weight_one_searched += 1
456
+ else:
457
+ dummy_syndrome_x = self._generate_random_logical_combination_for_distance_estimation(
458
+ z_stack, p, self.z_stabilizer_matrix.shape[0]
459
+ )
460
+
461
+ candidate_x = bp_osd_x.decode(dummy_syndrome_x)
462
+ x_weight = np.count_nonzero(candidate_x)
463
+ if x_weight < x_min_distance:
464
+ x_min_distance = x_weight
465
+
466
+ if x_weight < x_max_distance and reduce_logical_basis:
467
+ candidate_logicals_x.append(candidate_x)
468
+
469
+ # Reduce X logical operator basis independently
470
+ if len(candidate_logicals_x) >= 5:
471
+ self._reduce_logical_operator_basis(
472
+ candidate_logicals_x, []
473
+ )
474
+ (
475
+ bp_osd_x,
476
+ z_stack,
477
+ full_rank_z,
478
+ z_min_distance,
479
+ z_max_distance,
480
+ ) = self._setup_distance_estimation_decoder(
481
+ self.z_stabilizer_matrix,
482
+ self.z_logical_operator_basis,
483
+ decoder,
484
+ )
485
+ candidate_logicals_x = []
486
+ x_weight_one_searched = 0
487
+
488
+ else:
489
+ # Z Logical operators
490
+ if z_weight_one_searched < self.x_logical_operator_basis.shape[0]:
491
+ dummy_syndrome_z = np.zeros(x_stack.shape[0], dtype=np.uint8)
492
+ dummy_syndrome_z[
493
+ full_rank_x.shape[0] + z_weight_one_searched
494
+ ] = 1
495
+ z_weight_one_searched += 1
496
+ else:
497
+ dummy_syndrome_z = self._generate_random_logical_combination_for_distance_estimation(
498
+ x_stack, p, self.x_stabilizer_matrix.shape[0]
499
+ )
500
+
501
+ candidate_z = bp_osd_z.decode(dummy_syndrome_z)
502
+ z_weight = np.count_nonzero(candidate_z)
503
+ if z_weight < z_min_distance:
504
+ z_min_distance = z_weight
505
+
506
+ if z_weight < z_max_distance and reduce_logical_basis:
507
+ candidate_logicals_z.append(candidate_z)
508
+
509
+ # Reduce Z logical operator basis independently
510
+ if len(candidate_logicals_z) >= 5:
511
+ self._reduce_logical_operator_basis(
512
+ [], candidate_logicals_z
513
+ )
514
+ (
515
+ bp_osd_z,
516
+ x_stack,
517
+ full_rank_x,
518
+ x_min_distance,
519
+ x_max_distance,
520
+ ) = self._setup_distance_estimation_decoder(
521
+ self.x_stabilizer_matrix,
522
+ self.x_logical_operator_basis,
523
+ decoder,
524
+ )
525
+ candidate_logicals_z = []
526
+ z_weight_one_searched = 0
527
+
528
+ x_weights, z_weights = self.logical_basis_weights()
474
529
  pbar.set_description(
475
- f"Estimating distance: dx <= {x_min_distance}, dz <= {z_min_distance}"
530
+ f"Estimating distance: dx <= {x_min_distance}, dz <= {z_min_distance}, x-weights: {np.mean(x_weights):.2f}, z-weights: {np.mean(z_weights):.2f}"
476
531
  )
477
532
 
478
- # Update distances and reduce logical bases if applicable
533
+ self._reduce_logical_operator_basis(candidate_logicals_x, candidate_logicals_z)
534
+
535
+ # Update distances
479
536
  self.x_code_distance = x_min_distance
480
537
  self.z_code_distance = z_min_distance
481
538
  self.code_distance = min(x_min_distance, z_min_distance)
482
539
 
483
- if reduce_logical_basis:
484
- self._reduce_logical_operator_basis(
485
- candidate_logicals_x, candidate_logicals_z
486
- )
487
-
488
540
  return self.code_distance
489
541
 
490
542
  def _setup_distance_estimation_decoder(
@@ -569,28 +621,140 @@ class CSSCode(StabilizerCode):
569
621
 
570
622
  return dummy_syndrome
571
623
 
624
+ def _reduce_logical_operator_basis(
625
+ self,
626
+ candidate_logicals_x: Union[Sequence, np.ndarray, scipy.sparse.spmatrix] = [],
627
+ candidate_logicals_z: Union[Sequence, np.ndarray, scipy.sparse.spmatrix] = [],
628
+ ):
629
+ """
630
+ Reduce the logical operator bases (for X and Z) to include lower-weight logicals.
631
+
632
+ Parameters
633
+ ----------
634
+ candidate_logicals_x : Union[Sequence, np.ndarray, scipy.sparse.spmatrix], optional
635
+ A list or array of candidate X logical operators to consider for reducing the X basis.
636
+ Defaults to an empty list.
637
+ candidate_logicals_z : Union[Sequence, np.ndarray, scipy.sparse.spmatrix], optional
638
+ A list or array of candidate Z logical operators to consider for reducing the Z basis.
639
+ Defaults to an empty list.
640
+ """
641
+ # Reduce X logical operator basis
642
+ if candidate_logicals_x:
643
+ # Convert candidates to a sparse matrix if they aren't already
644
+ if not isinstance(candidate_logicals_x, scipy.sparse.spmatrix):
645
+ candidate_logicals_x = scipy.sparse.csr_matrix(candidate_logicals_x)
646
+
647
+ # Stack the candidate X logicals with the existing X logicals
648
+ temp_x = scipy.sparse.vstack(
649
+ [candidate_logicals_x, self.x_logical_operator_basis]
650
+ ).tocsr()
651
+
652
+ # Calculate Hamming weights for sorting
653
+ x_row_weights = temp_x.getnnz(axis=1)
654
+ sorted_x_rows = np.argsort(x_row_weights)
655
+ temp_x = temp_x[sorted_x_rows, :]
656
+
657
+ # Add the X stabilizer matrix to the top of the stack
658
+ temp_x = scipy.sparse.vstack([self.x_stabilizer_matrix, temp_x]).tocsr()
659
+
660
+ # Determine rank of the X stabilizer matrix
661
+ rank_hx = ldpc.mod2.rank(self.x_stabilizer_matrix)
662
+
663
+ # Perform row reduction to find a new X logical basis
664
+ pivots_x = ldpc.mod2.pivot_rows(temp_x)
665
+ self.x_logical_operator_basis = temp_x[pivots_x[rank_hx:], :]
666
+
667
+ # Reduce Z logical operator basis
668
+ if candidate_logicals_z:
669
+ # Convert candidates to a sparse matrix if they aren't already
670
+ if not isinstance(candidate_logicals_z, scipy.sparse.spmatrix):
671
+ candidate_logicals_z = scipy.sparse.csr_matrix(candidate_logicals_z)
672
+
673
+ # Stack the candidate Z logicals with the existing Z logicals
674
+ temp_z = scipy.sparse.vstack(
675
+ [candidate_logicals_z, self.z_logical_operator_basis]
676
+ ).tocsr()
677
+
678
+ # Calculate Hamming weights for sorting
679
+ z_row_weights = temp_z.getnnz(axis=1)
680
+ sorted_z_rows = np.argsort(z_row_weights)
681
+ temp_z = temp_z[sorted_z_rows, :]
682
+
683
+ # Add the Z stabilizer matrix to the top of the stack
684
+ temp_z = scipy.sparse.vstack([self.z_stabilizer_matrix, temp_z]).tocsr()
685
+
686
+ # Determine rank of the Z stabilizer matrix
687
+ rank_hz = ldpc.mod2.rank(self.z_stabilizer_matrix)
688
+
689
+ # Perform row reduction to find a new Z logical basis
690
+ pivots_z = ldpc.mod2.pivot_rows(temp_z)
691
+ self.z_logical_operator_basis = temp_z[pivots_z[rank_hz:], :]
692
+
572
693
  def fix_logical_operators(self, fix_logical: str = "X"):
694
+ """
695
+ Create a canonical basis of logical operators where X-logical and Z-logical operators pairwise anticommute.
696
+
697
+ Parameters
698
+ ----------
699
+ fix_logical : str, optional
700
+ Specify which logical operator basis to fix. "X" adjusts Z-logicals based on X-logicals, and "Z" adjusts
701
+ X-logicals based on Z-logicals. Default is "X".
702
+
703
+ Raises
704
+ ------
705
+ TypeError
706
+ If `fix_logical` is not a string.
707
+ ValueError
708
+ If `fix_logical` is not "X" or "Z".
709
+
710
+ Returns
711
+ -------
712
+ bool
713
+ True if the logical operator basis is valid after fixing; False otherwise.
714
+
715
+ Notes
716
+ -----
717
+ This method ensures that the symplectic product of the logical bases results in the identity matrix.
718
+ If any issues occur during the adjustment, the method logs an error.
719
+ """
573
720
  if not isinstance(fix_logical, str):
574
721
  raise TypeError("fix_logical parameter must be a string")
575
722
 
576
723
  if fix_logical.lower() == "x":
577
724
  temp = self.z_logical_operator_basis @ self.x_logical_operator_basis.T
578
725
  temp.data = temp.data % 2
579
- temp = ldpc.mod2.inverse(temp)
726
+ temp = scipy.sparse.csr_matrix(ldpc.mod2.inverse(temp), dtype=np.uint8)
580
727
  self.z_logical_operator_basis = temp @ self.z_logical_operator_basis
581
728
  self.z_logical_operator_basis.data = self.z_logical_operator_basis.data % 2
582
729
 
583
730
  elif fix_logical.lower() == "z":
584
731
  temp = self.x_logical_operator_basis @ self.z_logical_operator_basis.T
585
732
  temp.data = temp.data % 2
586
- temp = ldpc.mod2.inverse(temp)
733
+ temp = scipy.sparse.csr_matrix(ldpc.mod2.inverse(temp), dtype=np.uint8)
587
734
  self.x_logical_operator_basis = temp @ self.x_logical_operator_basis
588
735
  self.x_logical_operator_basis.data = self.x_logical_operator_basis.data % 2
589
736
  else:
590
737
  raise ValueError("Invalid fix_logical parameter")
591
738
 
592
- @property
593
- def logical_operator_weights(self) -> Tuple[np.ndarray, np.ndarray]:
739
+ try:
740
+ assert self.check_valid_logical_basis()
741
+ except AssertionError:
742
+ logging.error("Logical basis is not valid after fixing logical operators.")
743
+ return False
744
+
745
+ try:
746
+ lx_lz = self.x_logical_operator_basis @ self.z_logical_operator_basis.T
747
+ lx_lz.data = lx_lz.data % 2
748
+ assert (
749
+ lx_lz != scipy.sparse.eye(self.logical_qubit_count, format="csr")
750
+ ).nnz == 0
751
+ except AssertionError:
752
+ logging.error("Logical basis is not valid after fixing logical operators.")
753
+ return False
754
+
755
+ return True
756
+
757
+ def logical_basis_weights(self) -> Tuple[np.ndarray, np.ndarray]:
594
758
  x_weights = []
595
759
  z_weights = []
596
760
  for i in range(self.logical_qubit_count):
@@ -0,0 +1,264 @@
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
+ class HypergraphProductCode(CSSCode):
11
+ """
12
+ Implements a Hypergraph Product (HGP) code - derived from two classical linear binary codes.
13
+
14
+ Parameters
15
+ ----------
16
+ seed_matrix_1 :
17
+ A classical linear binary code used as a "seed" in the HGP construction method.
18
+ seed_matrix_2 :
19
+ A classical linear binary code used as a "seed" in the HGP construction method.
20
+ name : str, default = None
21
+ The name of the code. If None, the name is set to: "Hypergraph product code"
22
+
23
+ Attributes
24
+ ----------
25
+ seed_matrix_1 : scipy.sparse.spmatrix
26
+ The input seed_matrix_1 stored as a scipy sparse matrix.
27
+ seed_matrix_2 : scipy.sparse.spmatrix
28
+ The input seed_matrix_2 stored as a scipy sparse matrix.
29
+ _n1 : int
30
+ Number of columns in seed_matrix_1
31
+ _n2 : int
32
+ Number of columns in seed_matrix_2
33
+ _m1 : int
34
+ Number of rows in seed_matrix_1 (the number of columns of it's transpose)
35
+ _m2 : int
36
+ Number of rows in seed_matrix_2 (the number of columns of it's transpose)
37
+
38
+ Notes
39
+ -----
40
+
41
+ The X and Z stabilizer matrices are given by [1]_:
42
+
43
+ .. math::
44
+
45
+ \begin{align}
46
+ H_{X} &= \begin{pmatrix}
47
+ H_{1}\otimes I_{n_{2}} & \,\,I_{r_{1}}\otimes H_{2}^{T}
48
+ \end{pmatrix}\tag*{(1)}\\
49
+ H_{Z} &= \begin{pmatrix}
50
+ I_{n_{1}}\otimes H_{2} & \,\,H_{1}^{T}\otimes I_{r_{2}}
51
+ \end{pmatrix}~, \tag*{(2)}
52
+ \end{align}
53
+
54
+ where :math:`H_1` and :math:`H_2` correspond to the parity check matrix of the first and second "seed" codes.
55
+
56
+
57
+ .. [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)
58
+ """
59
+
60
+ def __init__(
61
+ self,
62
+ seed_matrix_1 : Union[np.ndarray, scipy.sparse.spmatrix],
63
+ seed_matrix_2 : Union[np.ndarray, scipy.sparse.spmatrix],
64
+ name : str = None
65
+ ):
66
+
67
+ self.name = name if name else "Hypergraph product code"
68
+
69
+ if not all(isinstance(seed_m, (np.ndarray, scipy.sparse.spmatrix)) for seed_m in (seed_matrix_1, seed_matrix_2)):
70
+ raise TypeError("The seed matrices must be either numpy arrays or scipy sparse matrices.")
71
+
72
+ self.seed_matrix_1 = convert_to_binary_scipy_sparse(seed_matrix_1)
73
+ self.seed_matrix_2 = convert_to_binary_scipy_sparse(seed_matrix_2)
74
+
75
+ # maybe move the below to a private _construct_stabilizer_matrices function?
76
+ # --------------------------------------------------------------------------
77
+ self._n1 = seed_matrix_1.shape[1]
78
+ self._n2 = seed_matrix_2.shape[1]
79
+
80
+ self._m1 = seed_matrix_1.shape[0]
81
+ self._m2 = seed_matrix_2.shape[0]
82
+
83
+ x_left = scipy.sparse.kron(seed_matrix_1, scipy.sparse.eye(self._n2))
84
+ x_right = scipy.sparse.kron(scipy.sparse.eye(self._m1), seed_matrix_2.T)
85
+ self.x_stabilizer_matrix = scipy.sparse.hstack([x_left, x_right])
86
+
87
+ z_left = scipy.sparse.kron(scipy.sparse.eye(self._n1), seed_matrix_2)
88
+ z_right = scipy.sparse.kron(seed_matrix_1.T, scipy.sparse.eye(self._m2))
89
+ self.z_stabilizer_matrix = scipy.sparse.hstack([z_left, z_right])
90
+ # --------------------------------------------------------------------------
91
+
92
+ super().__init__(self.x_stabilizer_matrix, self.z_stabilizer_matrix, self.name)
93
+
94
+
95
+ def compute_exact_code_distance(self) -> int:
96
+ """
97
+ Computes the exact code distance of the HGP code.
98
+
99
+ Returns
100
+ -------
101
+ int
102
+ The distance of the code.
103
+
104
+ Notes
105
+ -----
106
+ The distance of a HGP code is given as:
107
+
108
+ .. math::
109
+
110
+ \min(d_1, d_2, d_1^T, d_2^T)
111
+
112
+ corresponding to the distance of the seed codes and the distance of their transposes.
113
+ """
114
+
115
+ rank_seed_m1 = ldpc.mod2.rank(self.seed_matrix_1)
116
+ rank_seed_m2 = ldpc.mod2.rank(self.seed_matrix_2)
117
+
118
+ if self.seed_matrix_1.shape[1] != rank_seed_m1:
119
+ self.d1 = ldpc.mod2.compute_exact_code_distance(self.seed_matrix_1)
120
+ else:
121
+ self.d1 = np.inf
122
+
123
+ if self.seed_matrix_2.shape[1] != rank_seed_m2:
124
+ self.d2 = ldpc.mod2.compute_exact_code_distance(self.seed_matrix_2)
125
+ else:
126
+ self.d2 = np.inf
127
+
128
+ # note: rank(A) = rank(A^T):
129
+ if self.seed_matrix_1.shape[0] != rank_seed_m1:
130
+ self.d1T = ldpc.mod2.compute_exact_code_distance(self.seed_matrix_1.T)
131
+ else:
132
+ self.d1T = np.inf
133
+
134
+ if self.seed_matrix_2.shape[0] != rank_seed_m2:
135
+ self.d2T = ldpc.mod2.compute_exact_code_distance(self.seed_matrix_2.T)
136
+ else:
137
+ self.d2T = np.inf
138
+
139
+ self.x_code_distance = min(self.d1T, self.d2)
140
+ self.z_code_distance = min(self.d1, self.d2T)
141
+ self.code_distance = min(self.x_code_distance, self.z_code_distance)
142
+
143
+ return self.code_distance
144
+
145
+
146
+ def estimate_min_distance(self, timeout_seconds: float = 0.025) -> int:
147
+ """
148
+ Estimate the minimum X and Z distance of the HGP code. Parameters
149
+ ----------
150
+ timeout_seconds : float, optional
151
+ Time limit in seconds for the full search. Default: 0.25
152
+
153
+ Returns
154
+ -------
155
+ int
156
+ Best estimate of the (overall) code distance found within time limit.
157
+
158
+ """
159
+
160
+ rank_seed_m1 = ldpc.mod2.rank(self.seed_matrix_1)
161
+ rank_seed_m2 = ldpc.mod2.rank(self.seed_matrix_2)
162
+
163
+
164
+ d1_timeout_seconds = timeout_seconds/4
165
+ if self.seed_matrix_1.shape[1] != rank_seed_m1:
166
+ d1_start_time = time.time()
167
+ d1_min_estimate, _, _ = ldpc.mod2.estimate_code_distance(self.seed_matrix_1, d1_timeout_seconds, 0)
168
+ d1_run_time = time.time() - d1_start_time
169
+ else:
170
+ d1_min_estimate = np.inf
171
+ d1_run_time = 0
172
+
173
+ d1T_timeout_seconds = (d1_timeout_seconds * 4 - d1_run_time)/3 if d1_run_time <= d1_timeout_seconds else timeout_seconds/4
174
+ if self.seed_matrix_1.shape[0] != rank_seed_m1:
175
+ d1T_start_time = time.time()
176
+ d1T_min_estimate, _, _ = ldpc.mod2.estimate_code_distance(self.seed_matrix_1.T, d1T_timeout_seconds, 0)
177
+ d1T_run_time = time.time() - d1T_start_time
178
+ else:
179
+ d1T_min_estimate = np.inf
180
+ d1T_run_time = 0
181
+
182
+ d2_timeout_seconds = (d1T_timeout_seconds * 3 - d1T_run_time)/2 if d1T_run_time <= d1T_timeout_seconds else timeout_seconds/4
183
+ if self.seed_matrix_2.shape[1] != rank_seed_m2:
184
+ d2_start_time = time.time()
185
+ d2_min_estimate, _, _ = ldpc.mod2.estimate_code_distance(self.seed_matrix_2, d2_timeout_seconds, 0)
186
+ d2_run_time = time.time() - d2_start_time
187
+ else:
188
+ d2_min_estimate = np.inf
189
+ d2_run_time = 0
190
+
191
+
192
+ d2T_timeout_seconds = (d2_timeout_seconds * 2 - d2_run_time) if d2_run_time <= d2_timeout_seconds else timeout_seconds/4
193
+ if self.seed_matrix_2.shape[0] != rank_seed_m2:
194
+ d2T_min_estimate, _, _ = ldpc.mod2.estimate_code_distance(self.seed_matrix_2.T, d2T_timeout_seconds, 0)
195
+ else:
196
+ d2T_min_estimate = np.inf
197
+
198
+ self.x_code_distance = min(d1T_min_estimate, d2_min_estimate)
199
+ self.z_code_distance = min(d1_min_estimate, d2T_min_estimate)
200
+ self.code_distance = min(self.x_code_distance, self.z_code_distance)
201
+
202
+
203
+ return self.code_distance
204
+
205
+
206
+
207
+ def compute_logical_basis(self) -> Tuple[scipy.sparse.spmatrix, scipy.sparse.spmatrix]:
208
+ """
209
+ Compute the logical operator basis for the given HGP code.
210
+
211
+ Returns
212
+ -------
213
+ Tuple[scipy.sparse.spmatrix, scipy.sparse.spmatrix]
214
+ Logical X and Z operator bases (lx, lz).
215
+ """
216
+
217
+ ker_h1 = ldpc.mod2.kernel(self.seed_matrix_1)
218
+ ker_h2 = ldpc.mod2.kernel(self.seed_matrix_2)
219
+ ker_h1T = ldpc.mod2.kernel(self.seed_matrix_1.T)
220
+ ker_h2T = ldpc.mod2.kernel(self.seed_matrix_2.T)
221
+
222
+ row_comp_h1 = ldpc.mod2.row_complement_basis(self.seed_matrix_1)
223
+ row_comp_h2 = ldpc.mod2.row_complement_basis(self.seed_matrix_2)
224
+ row_comp_h1T = ldpc.mod2.row_complement_basis(self.seed_matrix_1.T)
225
+ row_comp_h2T = ldpc.mod2.row_complement_basis(self.seed_matrix_2.T)
226
+
227
+ temp = scipy.sparse.kron(ker_h1, row_comp_h2)
228
+ lz1 = scipy.sparse.hstack([temp, scipy.sparse.csr_matrix((temp.shape[0], self._m1*self._m2), dtype=np.uint8)])
229
+
230
+ temp = scipy.sparse.kron(row_comp_h1T, ker_h2T)
231
+ lz2 = scipy.sparse.hstack([scipy.sparse.csr_matrix((temp.shape[0], self._n1*self._n2), dtype=np.uint8), temp])
232
+
233
+ self.z_logical_operator_basis = scipy.sparse.vstack([lz1, lz2], dtype=np.uint8)
234
+
235
+
236
+ temp = scipy.sparse.kron(row_comp_h1, ker_h2)
237
+ lx1 = scipy.sparse.hstack([temp, scipy.sparse.csr_matrix((temp.shape[0], self._m1*self._m2), dtype=np.uint8)])
238
+
239
+ temp = scipy.sparse.kron(ker_h1T, row_comp_h2T)
240
+ lx2 = scipy.sparse.hstack([scipy.sparse.csr_matrix((temp.shape[0], self._n1*self._n2), dtype=np.uint8), temp])
241
+
242
+ self.x_logical_operator_basis= scipy.sparse.vstack([lx1, lx2], dtype = np.uint8)
243
+
244
+ # Follows the way it is done in CSSCode -> move it into __init__?
245
+ #----------------------------------------------------------------
246
+ self.logical_qubit_count = self.x_logical_operator_basis.shape[0]
247
+ #----------------------------------------------------------------
248
+
249
+ return (self.x_logical_operator_basis, self.z_logical_operator_basis)
250
+
251
+
252
+ def __str__(self):
253
+ """
254
+ String representation of the HGP code. Includes the name and [[n, k, d]] properties of the code.
255
+
256
+ Returns
257
+ -------
258
+ str
259
+ String representation of the HGP code.
260
+ """
261
+
262
+ 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}]]"
263
+
264
+
@@ -0,0 +1 @@
1
+ from .five_qubit_code import FiveQubitCode
@@ -1,4 +1,4 @@
1
- from qec.stabilizer_code.stabilizer_code import StabilizerCode
1
+ from qec.code_constructions import StabilizerCode
2
2
 
3
3
 
4
4
  class FiveQubitCode(StabilizerCode):
@@ -0,0 +1 @@
1
+ from .codetables_de import CodeTablesDE
@@ -1,4 +1,4 @@
1
- from qec.stabilizer_code import StabilizerCode
1
+ from qec.code_constructions import StabilizerCode
2
2
  from qec.utils.codetables_de_utils import get_codetables_de_matrix, pcm_to_csr_matrix
3
3
 
4
4
 
@@ -0,0 +1,146 @@
1
+ Metadata-Version: 2.2
2
+ Name: qec
3
+ Version: 0.2.2
4
+ Summary: Python Tools for Quantum Error Correction
5
+ Author-email: Joschka Roffe <joschka@roffe.eu>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2023 qec.Codes
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Classifier: Development Status :: 4 - Beta
29
+ Requires-Python: >=3.8
30
+ Description-Content-Type: text/markdown
31
+ License-File: LICENSE
32
+ Requires-Dist: ldpc
33
+ Requires-Dist: numpy>=1.24.0
34
+ Requires-Dist: scipy>=1.9.3
35
+ Requires-Dist: requests
36
+ Requires-Dist: beautifulsoup4
37
+
38
+ <h1>
39
+ <p align="center">
40
+ <br>QEC
41
+ </h1>
42
+ <p align="center">
43
+ Python Tools for Quantum Error Correction
44
+ <br />
45
+ <a href="https://qec.codes/">Website</a>
46
+ ·
47
+ <a href="#features">Features</a>
48
+ ·
49
+ <a href="#installation">Installation</a>
50
+ ·
51
+ <a href="#examples">Examples</a>
52
+ </p>
53
+ </p>
54
+
55
+ # Features
56
+
57
+ ## Code constructions and fundamental analysis
58
+ We currently provide the construction methods for the stabilizer generators of the QEC code families below, along with optimized methods to obtain their fundamental properties. All the classes allow to:
59
+
60
+ - obtain the physical qubit count
61
+ - obtain the logical qubit count
62
+ - calculate the exact code distance
63
+ - estimate the minimum code distance
64
+ - obtain a logical basis
65
+
66
+ The classes (along with their extre methods) are:
67
+
68
+ - Hyperprgarph Product (HGP) codes, with methods:
69
+ - construct the x and z stabiliser matrices from the seed codes
70
+ - obtain the canonical basis (work in progress)
71
+
72
+ - Calderbank-Shor-Steane (CSS) codes
73
+ - check that the seed codes satisfy the CSS criteria
74
+
75
+ - Stabiliser codes
76
+ - check that the input stabiliser matrix is valid
77
+
78
+ ## Circuit compilation
79
+ Work in progress.
80
+
81
+ # Installation
82
+
83
+ Simply do:
84
+ ```bash
85
+ pip install qec
86
+ ```
87
+
88
+ or obtain a local copy of the package by cloning it, then navigate into the created folder:
89
+
90
+ ```bash
91
+ git clone git@github.com:qec-codes/qec.git
92
+ cd qec
93
+ ```
94
+
95
+ Finally, install the package:
96
+
97
+ ```bash
98
+ pip install -e .
99
+ ```
100
+
101
+ You are all set! To import the package use:
102
+
103
+ ```python
104
+ In [1]: import qec
105
+
106
+ In [2]: qec.__version__
107
+ Out [2]: '0.1.0'
108
+
109
+ ```
110
+
111
+ # Examples
112
+
113
+ In this example we are going to create the Steane code, and obtain its fundamental code properties. We start by initialising its seed matrices (the [7, 4, 3] Hamming code):
114
+ ```python
115
+ In [1]: import numpy as np
116
+ In [2]: from qec.code_constructions import CSSCode
117
+
118
+ In [3]: hamming_code = np.array([[1, 0, 0, 1, 0, 1, 1],
119
+ [0, 1, 0, 1, 1, 0, 1],
120
+ [0, 0, 1, 0, 1, 1, 1]])
121
+ ```
122
+ as the Steane code is part of the CSS code family, we can use the `CSSCode` class:
123
+
124
+ ```python
125
+ In [4]: steane_code = CSSCode(x_stabilizer_matrix = hamming_code,
126
+ z_stabilizer_matrix = hamming_code,
127
+ name = "Steane")
128
+ In [5]: print(steane_code)
129
+ Out [6]: Steane Code: [[N=7, K=1, dx<=3, dz<=3]]
130
+ ```
131
+
132
+ we can see that all the fundamental properties (N - physical qubit number, K - logical qubit number, dx - X distance, dz - Z distance) are pre-calculated for us. We can continue our analysis by taking a look at the x and z logical basis of the code:
133
+
134
+ ```python
135
+ In [7]: print(steane_code.x_logical_operator_basis.toarray())
136
+ Out [8]: [[1 1 0 1 0 0 0]]
137
+ In [9]: print(steane_code.z_logical_operator_basis.toarray())
138
+ Out [10]: [[1 1 0 1 0 0 0]]
139
+ ```
140
+
141
+
142
+
143
+
144
+
145
+
146
+
@@ -0,0 +1,18 @@
1
+ qec/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ qec/code_constructions/__init__.py,sha256=MWyNUCS5mYcDzjGr_nbr2-ifkJigRJIzsw7sQIQXDfM,119
3
+ qec/code_constructions/css_code.py,sha256=nIaC24QhQwrmWQNhmX-X4TzlkmNAYoxN0feTdWjhswo,32376
4
+ qec/code_constructions/hgp_code.py,sha256=VAKU2lfZnDXSmAk6xFZCYT5b0hbtXpEoQUw4qHFcaEk,10419
5
+ qec/code_constructions/stabilizer_code.py,sha256=I5u8JKZu88ioC4E2nBJ-00xCmnL8nU6kdAvwYOfmNRk,22138
6
+ qec/code_instances/__init__.py,sha256=z6jOPjekDIx0jgbRFThI95zUzLzjl9Dh89RBzxsT_BE,43
7
+ qec/code_instances/five_qubit_code.py,sha256=ZHyusTEnnnmUv4QxPLXQSKbEQv2QOoBqn2V9N4SgrQE,2177
8
+ qec/codetables_de/__init__.py,sha256=dQBgkBK_DlnI9OrOcfarM7HUbj9NKyO-9QSvItD2cyY,40
9
+ qec/codetables_de/codetables_de.py,sha256=SdOaS7OTMt76uLa1GJXGydMTmwJdNwTDnQyD4SBQDIM,3626
10
+ qec/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ qec/utils/binary_pauli_utils.py,sha256=BSlngYDdRICu0aVu4u_m0bvLicohORyGxfk5eRER7TQ,13245
12
+ qec/utils/codetables_de_utils.py,sha256=S1wcVGJkkASQQ5s71QAsYBmpyE-3xTb6UsvgMfQtuiw,9469
13
+ qec/utils/sparse_binary_utils.py,sha256=Y9xfGKzOGFiVTyhb6iF6N7-5oMY6Ah9oLrnv8HhSBHA,1965
14
+ qec-0.2.2.dist-info/LICENSE,sha256=1b_xwNz1znYBfEaCL6pN2gNBAn8pQIjDRs_UhDp1EJI,1066
15
+ qec-0.2.2.dist-info/METADATA,sha256=Soh5GrdXcP861-epLVwGnKlk7tVrrtVVc1xKFTowbhA,4509
16
+ qec-0.2.2.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
17
+ qec-0.2.2.dist-info/top_level.txt,sha256=d8l_7pJ5u9uWdviNp0FUK-j8VPZqywkDek7qa4NDank,4
18
+ qec-0.2.2.dist-info/RECORD,,
@@ -1,2 +0,0 @@
1
- from qec.quantum_codes.five_qubit_code import FiveQubitCode
2
- from qec.quantum_codes.codetables_de import CodeTablesDE
@@ -1 +0,0 @@
1
- from qec.stabilizer_code.stabilizer_code import StabilizerCode
@@ -1,82 +0,0 @@
1
- Metadata-Version: 2.2
2
- Name: qec
3
- Version: 0.2.1
4
- Summary: Python Tools for Quantum Error Correction
5
- Author-email: Joschka Roffe <joschka@roffe.eu>
6
- License: MIT License
7
-
8
- Copyright (c) 2023 qec.Codes
9
-
10
- Permission is hereby granted, free of charge, to any person obtaining a copy
11
- of this software and associated documentation files (the "Software"), to deal
12
- in the Software without restriction, including without limitation the rights
13
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
- copies of the Software, and to permit persons to whom the Software is
15
- furnished to do so, subject to the following conditions:
16
-
17
- The above copyright notice and this permission notice shall be included in all
18
- copies or substantial portions of the Software.
19
-
20
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
- SOFTWARE.
27
-
28
- Classifier: Development Status :: 4 - Beta
29
- Requires-Python: >=3.8
30
- Description-Content-Type: text/markdown
31
- License-File: LICENSE
32
- Requires-Dist: ldpc
33
- Requires-Dist: numpy>=1.24.0
34
- Requires-Dist: scipy>=1.9.3
35
- Requires-Dist: requests
36
- Requires-Dist: beautifulsoup4
37
-
38
- <h1 align="center">QEC </h1>
39
- <h3 align="center"> Python Tools for Quantum Error Correction </h3>
40
-
41
-
42
- - Website: https://qec.codes/
43
-
44
- # Table of contents
45
- 1. [Features](#features)
46
- 2. [Installation](#installation)
47
- 3. [Getting started](#getting-started)
48
-
49
-
50
- # Features
51
-
52
- Here will go the description of all our fancy features!
53
-
54
- # Installation
55
-
56
- Obtain a local copy of the package by cloning it, then navigate into the created folder:
57
-
58
- ```bash
59
- git clone git@github.com:qec-codes/qec.git
60
- cd qec
61
- ```
62
-
63
- Finally, install the package:
64
-
65
- ```bash
66
- pip install -e .
67
- ```
68
-
69
- You are all set! To import the package use:
70
-
71
- ```python
72
- In [1]: import qec
73
-
74
- In [2]: qec.__version__
75
- Out [2]: '0.1.0'
76
-
77
- ```
78
-
79
- # Getting started
80
-
81
- A few simple examples
82
-
@@ -1,16 +0,0 @@
1
- qec/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- qec/quantum_codes/__init__.py,sha256=DQ1ztrq-vBpTyoehaMWOhals46tRj553Jmkq68bDk-E,117
3
- qec/quantum_codes/codetables_de.py,sha256=loBDBOK2cbDJ5moKmIx2MXg6e30XEPrEYau19bbDgac,3623
4
- qec/quantum_codes/five_qubit_code.py,sha256=0zrGLyIpfyKwYG7uL00yMcM5PdhQGF17_MiI2qTMhOk,2190
5
- qec/stabilizer_code/__init__.py,sha256=L5UMjHBlvfQBhkNlEZYSkyaHvNOcDHjc3oxYibMYHRk,63
6
- qec/stabilizer_code/css_code.py,sha256=JhNiBHqfwu4OgMVUsXl6yJ4L5KNW4Dn2Sf0beBdAl2s,24763
7
- qec/stabilizer_code/stabilizer_code.py,sha256=I5u8JKZu88ioC4E2nBJ-00xCmnL8nU6kdAvwYOfmNRk,22138
8
- qec/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- qec/utils/binary_pauli_utils.py,sha256=BSlngYDdRICu0aVu4u_m0bvLicohORyGxfk5eRER7TQ,13245
10
- qec/utils/codetables_de_utils.py,sha256=S1wcVGJkkASQQ5s71QAsYBmpyE-3xTb6UsvgMfQtuiw,9469
11
- qec/utils/sparse_binary_utils.py,sha256=Y9xfGKzOGFiVTyhb6iF6N7-5oMY6Ah9oLrnv8HhSBHA,1965
12
- qec-0.2.1.dist-info/LICENSE,sha256=1b_xwNz1znYBfEaCL6pN2gNBAn8pQIjDRs_UhDp1EJI,1066
13
- qec-0.2.1.dist-info/METADATA,sha256=AbWaMM6fYb65-0lUw6qWuywZigdtHseO-6QAbNZK0QM,2367
14
- qec-0.2.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
15
- qec-0.2.1.dist-info/top_level.txt,sha256=d8l_7pJ5u9uWdviNp0FUK-j8VPZqywkDek7qa4NDank,4
16
- qec-0.2.1.dist-info/RECORD,,
File without changes
File without changes