Skip to content

Commit 045e7fa

Browse files
author
mkangquantum
committed
hgp canonical codeword fixed
1 parent 1c4f14f commit 045e7fa

File tree

3 files changed

+107
-31
lines changed

3 files changed

+107
-31
lines changed

doc/intro.ipynb

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -98,15 +98,15 @@
9898
"name": "stdout",
9999
"output_type": "stream",
100100
"text": [
101-
"Z0 : [3 5 6 7 8 9] , X0 : [ 36 60 72 84 96 108]\n",
102-
"Z1 : [ 0 1 5 6 8 10] , X1 : [ 37 61 73 85 97 109]\n",
103-
"Z2 : [ 2 4 5 6 8 11] , X2 : [ 38 62 74 86 98 110]\n",
104-
"Z3 : [15 17 18 19 20 21] , X3 : [ 0 12 60 72 96 120]\n",
105-
"Z4 : [12 13 17 18 20 22] , X4 : [ 1 13 61 73 97 121]\n",
106-
"Z5 : [14 16 17 18 20 23] , X5 : [ 2 14 62 74 98 122]\n",
107-
"Z6 : [27 29 30 31 32 33] , X6 : [ 24 48 60 72 96 132]\n",
108-
"Z7 : [24 25 29 30 32 34] , X7 : [ 25 49 61 73 97 133]\n",
109-
"Z8 : [26 28 29 30 32 35] , X8 : [ 26 50 62 74 98 134]\n"
101+
"Z0 : [111 113 114 115 116 117] , X0 : [ 45 69 81 93 105 117]\n",
102+
"Z1 : [108 109 113 114 116 118] , X1 : [ 46 70 82 94 106 118]\n",
103+
"Z2 : [110 112 113 114 116 119] , X2 : [ 47 71 83 95 107 119]\n",
104+
"Z3 : [123 125 126 127 128 129] , X3 : [ 9 21 69 81 105 129]\n",
105+
"Z4 : [120 121 125 126 128 130] , X4 : [ 10 22 70 82 106 130]\n",
106+
"Z5 : [122 124 125 126 128 131] , X5 : [ 11 23 71 83 107 131]\n",
107+
"Z6 : [135 137 138 139 140 141] , X6 : [ 33 57 69 81 105 141]\n",
108+
"Z7 : [132 133 137 138 140 142] , X7 : [ 34 58 70 82 106 142]\n",
109+
"Z8 : [134 136 137 138 140 143] , X8 : [ 35 59 71 83 107 143]\n"
110110
]
111111
}
112112
],
@@ -292,7 +292,7 @@
292292
},
293293
{
294294
"cell_type": "code",
295-
"execution_count": 2,
295+
"execution_count": 12,
296296
"id": "68975ae8",
297297
"metadata": {},
298298
"outputs": [],
@@ -304,7 +304,7 @@
304304
},
305305
{
306306
"cell_type": "code",
307-
"execution_count": 3,
307+
"execution_count": 13,
308308
"id": "d1eb9f95",
309309
"metadata": {},
310310
"outputs": [
@@ -341,7 +341,7 @@
341341
},
342342
{
343343
"cell_type": "code",
344-
"execution_count": 4,
344+
"execution_count": 14,
345345
"id": "56a210c1",
346346
"metadata": {},
347347
"outputs": [
@@ -367,7 +367,7 @@
367367
},
368368
{
369369
"cell_type": "code",
370-
"execution_count": 5,
370+
"execution_count": 15,
371371
"id": "e5bdce14",
372372
"metadata": {},
373373
"outputs": [],
@@ -392,15 +392,15 @@
392392
},
393393
{
394394
"cell_type": "code",
395-
"execution_count": 6,
395+
"execution_count": 16,
396396
"id": "59700b1d",
397397
"metadata": {},
398398
"outputs": [
399399
{
400400
"name": "stderr",
401401
"output_type": "stream",
402402
"text": [
403-
"100%|██████████| 100/100 [00:11<00:00, 8.75it/s]"
403+
"100%|██████████| 100/100 [00:10<00:00, 9.45it/s]"
404404
]
405405
},
406406
{
@@ -455,7 +455,7 @@
455455
"name": "stderr",
456456
"output_type": "stream",
457457
"text": [
458-
"100%|██████████| 100/100 [00:08<00:00, 11.63it/s]\n"
458+
"100%|██████████| 100/100 [00:09<00:00, 10.29it/s]\n"
459459
]
460460
}
461461
],

src/quits/gf2_utility.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import numpy as np
2+
3+
def gf2_rref_pivots(H: np.ndarray):
4+
"""Return pivot columns of H in RREF over GF(2)."""
5+
A = (np.asarray(H) & 1).astype(np.uint8, copy=True)
6+
m, n = A.shape
7+
pivots = []
8+
r = 0
9+
for c in range(n):
10+
if r >= m:
11+
break
12+
rows = np.where(A[r:, c] == 1)[0]
13+
if rows.size == 0:
14+
continue
15+
p = r + int(rows[0])
16+
if p != r:
17+
A[[r, p], :] = A[[p, r], :]
18+
# eliminate column c in all other rows
19+
ones = np.where(A[:, c] == 1)[0]
20+
ones = ones[ones != r]
21+
if ones.size:
22+
A[ones, :] ^= A[r, :]
23+
pivots.append(c)
24+
r += 1
25+
return np.array(pivots, dtype=int)
26+
27+
def gf2_coset_reps_rowspace(H: np.ndarray) -> np.ndarray:
28+
"""
29+
Canonical reps for F2^n / rowspace(H):
30+
pick standard basis e_j for the non-pivot columns of RREF(H).
31+
"""
32+
H = (np.asarray(H) & 1).astype(np.uint8, copy=False)
33+
n = H.shape[1]
34+
piv = set(gf2_rref_pivots(H).tolist())
35+
nonpiv = [c for c in range(n) if c not in piv] # size = n - rank(H) = k (if full row rank)
36+
E = np.zeros((len(nonpiv), n), dtype=np.uint8)
37+
for t, c in enumerate(nonpiv):
38+
E[t, c] = 1
39+
return E
40+
41+
def gf2_rank(H: np.ndarray) -> int:
42+
return len(gf2_rref_pivots(H))
43+
44+
def in_rowspace(v: np.ndarray, H: np.ndarray) -> bool:
45+
"""Check if v is in rowspace(H) over GF(2) by solving H^T a = v."""
46+
H = (np.asarray(H) & 1).astype(np.uint8, copy=False)
47+
v = (np.asarray(v).reshape(-1) & 1).astype(np.uint8, copy=False)
48+
m, n = H.shape
49+
# Solve H^T a = v for a in F2^m
50+
A = H.T.copy()
51+
b = v.copy()
52+
53+
# Gaussian elimination
54+
Aug = np.concatenate([A, b[:, None]], axis=1)
55+
r = 0
56+
pivcol = -np.ones(n, dtype=int)
57+
for c in range(m):
58+
if r >= n:
59+
break
60+
rows = np.where(Aug[r:, c] == 1)[0]
61+
if rows.size == 0:
62+
continue
63+
p = r + int(rows[0])
64+
if p != r:
65+
Aug[[r, p]] = Aug[[p, r]]
66+
pivcol[r] = c
67+
ones = np.where(Aug[:, c] == 1)[0]
68+
ones = ones[ones != r]
69+
if ones.size:
70+
Aug[ones, c:] ^= Aug[r, c:]
71+
r += 1
72+
73+
# infeasible row: 0...0 | 1
74+
if np.any(np.all(Aug[:, :m] == 0, axis=1) & (Aug[:, m] == 1)):
75+
return False
76+
return True

src/quits/qldpc_code.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from scipy.linalg import circulant
99
from ldpc.mod2.mod2_numpy import nullspace
1010
from .ldpc_utility import compute_lz_and_lx
11+
from .gf2_utility import *
1112

1213
# Parent class
1314
class QldpcCode:
@@ -196,25 +197,24 @@ def __init__(self, h1, h2):
196197
self.lz, self.lx = compute_lz_and_lx(self.hx, self.hz)
197198

198199
def get_logicals(self):
199-
'''
200-
:return: Logical operators of the code as a list of tuples (logical_z, logical_x)
201-
where logical_z and logical_x are numpy arrays of shape (num_logicals, num_data_qubits)
202-
The logicals are written in the "canonical form" as described in arXiv:2204.10812
203-
'''
204-
lz = np.zeros((self.k1*self.k2, self.hz.shape[1]), dtype=int)
205-
lx = np.zeros((self.k1*self.k2, self.hx.shape[1]), dtype=int)
200+
"""
201+
Canonical logicals for your HGP convention, assuming k1^T=k2^T=0.
202+
Returns:
203+
lz, lx: shape (k1*k2, num_data_qubits) as uint8.
204+
"""
205+
E1 = gf2_coset_reps_rowspace(self.h1) # (k1, n1) if H1 full row rank
206+
E2 = gf2_coset_reps_rowspace(self.h2) # (k2, n2)
207+
208+
lz = np.zeros((self.k1 * self.k2, self.hz.shape[1]), dtype=np.uint8)
209+
lx = np.zeros((self.k1 * self.k2, self.hx.shape[1]), dtype=np.uint8)
206210

207211
cnt = 0
208212
for i in range(self.k2):
209-
ei = np.zeros(self.h2.shape[1], dtype=int)
210-
ei[i] = 1
211213
for j in range(self.k1):
212-
ej = np.zeros(self.h1.shape[1], dtype=int)
213-
ej[j] = 1
214-
215-
lz[cnt,:self.n1*self.n2] = np.kron(ei, self.l1[j,:])
216-
lx[cnt,:self.n1*self.n2] = np.kron(self.l2[i,:], ej)
217-
214+
# Z: (E2_i ⊗ L1_j | 0)
215+
lz[cnt, :self.n1 * self.n2] = np.kron(E2[i, :], self.l1[j, :]) & 1
216+
# X: (L2_i ⊗ E1_j | 0)
217+
lx[cnt, :self.n1 * self.n2] = np.kron(self.l2[i, :], E1[j, :]) & 1
218218
cnt += 1
219219

220220
return lz, lx

0 commit comments

Comments
 (0)