# Let's enumerate Pauli error combinations for Subproblem 3 circuit to obtain acceptance and good counts per k.
from itertools import product
# Single-qubit Pauli to (x,z) bits
P2xz = {
'I': (0,0),
'X': (1,0),
'Z': (0,1),
'Y': (1,1)
}
XZ2P = {(0,0):'I',(1,0):'X',(0,1):'Z',(1,1):'Y'}
nq = 5 # qubits 0..3 data, 4 ancilla
# Helper: multiply two 5-qubit Paulis in (x,z) symplectic form ignoring phase
def mul_pauli(p1, p2):
# p1,p2 are tuples (x_list, z_list) of length nq bits (0/1)
x1,z1 = p1
x2,z2 = p2
x = [(a^b) for a,b in zip(x1,x2)]
z = [(a^b) for a,b in zip(z1,z2)]
return (x,z)
# Convert tensor product of labels into (x,z) over nq qubits
def labels_to_xz(labels):
x=[0]*nq; z=[0]*nq
for q,ch in labels.items():
a,b = P2xz[ch]
x[q]=a; z[q]=b
return (x,z)
# Conjugate a Pauli (x,z) through a CNOT(c,t)
def conj_cnot(p, c, t):
x,z = [u[:] for u in p] # copy
# Update according to mapping described in analysis
xc, zc = x[c], z[c]
xt, zt = x[t], z[t]
# new values
x[c] = xc
z[c] = zc ^ zt
x[t] = xc ^ xt
z[t] = zt
return (x,z)
# Build propagation lookup per location and per 2q Pauli on the acted pair
# Gate order (index and pair):
# 0: CNOT_12, 1: CNOT_10, 2: CNOT_23, 3: CNOT_34, 4: CNOT_04
pairs = [(1,2),(1,0),(2,3),(3,4),(0,4)]
# Subsequent gates list for each loc
subseq = {
0: [(1,0),(2,3),(3,4),(0,4)],
1: [(2,3),(3,4),(0,4)],
2: [(3,4),(0,4)],
3: [(0,4)],
4: []
}
# Note: Each tuple in subseq is (c,t) consistent with qubit indices
# Precompute final propagated Pauli for all 16 two-qubit Paul's at each location
single = ['I','X','Y','Z']
prop = {loc: {} for loc in range(5)} # maps (loc, (a,b) labels) -> final (x,z)
for loc in range(5):
a,b = pairs[loc]
for pa in single:
for pb in single:
labels = {a:pa, b:pb} # others are identity
xz = labels_to_xz(labels)
# propagate through subsequent CNOTs
for c,t in subseq[loc]:
xz = conj_cnot(xz, c, t)
prop[loc][(pa,pb)] = xz
# Now enumerate all 16^5 combinations to classify counts by k (number of non-identity errors)
from collections import defaultdict
N_acc = [0]*6
N_good = [0]*6
# Generators for code checks and target stabilizer (on data qubits only)
def xz_from_data_label(label4):
# label4: dict mapping data q in 0..3 to 'I','X','Y','Z'
labels = {q:ch for q,ch in label4.items()}
return labels_to_xz(labels)
# Data-only helpers: restrict to 0..3 bits
def restrict_data(p):
x,z = p
return (x[:4], z[:4])
# Build data Paulis for code checks and target stabilizers
# sX=XXXX, sZ=ZZZZ
sX = xz_from_data_label({0:'X',1:'X',2:'X',3:'X'})
sZ = xz_from_data_label({0:'Z',1:'Z',2:'Z',3:'Z'})
# Z_A=ZZII, Z_B=ZIZI
ZA = xz_from_data_label({0:'Z',1:'Z'})
ZB = xz_from_data_label({0:'Z',2:'Z'})
# Generate stabilizer group of target |00>_AB: <XXXX, ZZZZ, ZZII, ZIZI>
# We'll list all 16 elements (ignore phase)
def add_mod2(v1,v2):
return [a^b for a,b in zip(v1,v2)]
def tuple4(x,z):
return (tuple(x),tuple(z))
def combine(p_list):
x=[0]*4; z=[0]*4
for xz in p_list:
x = add_mod2(x, xz[0])
z = add_mod2(z, xz[1])
return (x,z)
# create set for membership testing
G = []
for a in range(2):
for b in range(2):
for c in range(2):
for d in range(2):
parts=[]
if a: parts.append(sX)
if b: parts.append(sZ)
if c: parts.append(ZA)
if d: parts.append(ZB)
G.append(combine(parts))
S_target = set(tuple4(*g) if False else (tuple(g[0]),tuple(g[1])) for g in G)
# Function to check commutation of data Pauli p with q (both 4-qubit)
def commute(p,q):
x1,z1 = p
x2,z2 = q
# compute symplectic product sum(x1*z2 + z1*x2) mod2
s = 0
for i in range(4):
s ^= (x1[i] & z2[i]) ^ (z1[i] & x2[i])
return (s==0)
# For speed, precompute all 16 two-qubit Pauli pairs list,
# and map to whether identity or not
pairs16 = [(pa,pb) for pa in single for pb in single]
nonid_mask = {pair: int(not (pair==( 'I','I'))) for pair in pairs16}
# Enumerate
counter=0
for e0 in pairs16: # after CNOT_12 on (1,2)
p0 = prop[0][e0]
n0 = nonid_mask[e0]
for e1 in pairs16: # after CNOT_10 (1,0)
p01 = mul_pauli(p0, prop[1][e1])
k01 = n0 + nonid_mask[e1]
for e2 in pairs16: # after CNOT_23 (2,3)
p012 = mul_pauli(p01, prop[2][e2])
k012 = k01 + nonid_mask[e2]
for e3 in pairs16: # after CNOT_34 (3,4)
p0123 = mul_pauli(p012, prop[3][e3])
k0123 = k012 + nonid_mask[e3]
for e4 in pairs16: # after CNOT_04 (0,4)
p_final = mul_pauli(p0123, prop[4][e4])
k = k0123 + nonid_mask[e4]
x,z = p_final
# Acceptance conditions:
# ancilla (qubit 4) must have x4==0 (i.e., I or Z)
anc_ok = (x[4]==0)
if not anc_ok:
continue
# data must commute with both sX and sZ
pdata = (x[:4], z[:4])
if not (commute(pdata, sX) and commute(pdata, sZ)):
continue
N_acc[k] += 1
# Good if pdata in S_target
if (tuple(pdata[0]), tuple(pdata[1])) in S_target:
N_good[k] += 1
counter += 1
print('N_acc per k:', N_acc)
print('N_good per k:', N_good)
print('Total combos=', 16**5)