from itertools import product
# Define Pauli multiplication ignoring global phase
mul_table = {
('I','I'):'I', ('I','X'):'X', ('I','Y'):'Y', ('I','Z'):'Z',
('X','I'):'X', ('X','X'):'I', ('X','Y'):'Z', ('X','Z'):'Y',
('Y','I'):'Y', ('Y','X'):'Z', ('Y','Y'):'I', ('Y','Z'):'X',
('Z','I'):'Z', ('Z','X'):'Y', ('Z','Y'):'X', ('Z','Z'):'I'
}
def pauli_mul(p,q):
# p and q are strings over {I,X,Y,Z} of same length
return ''.join(mul_table[(pi,qi)] for pi,qi in zip(p,q))
# Conjugation of a Pauli string by CNOT(control, target)
# Returns U P U^†
def conj_by_cnot(P, c, t):
P = list(P)
# Copy to avoid interfering updates; apply rules based on original
Pc, Pt = P[c], P[t]
# If X on control, multiply X on target
if Pc in ('X','Y'):
# multiply current P[t] by X
P[t] = mul_table[(P[t],'X')]
# If Z on target, multiply Z on control
if Pt in ('Z','Y'):
P[c] = mul_table[(P[c],'Z')]
return ''.join(P)
# Commutation check via symplectic product
symp_map = {'I':(0,0),'X':(1,0),'Y':(1,1),'Z':(0,1)}
def anticommutes(P,Q):
# P,Q strings same length
s=0
for p,q in zip(P,Q):
xp,zp = symp_map[p]
xq,zq = symp_map[q]
s ^= (xp & zq) ^ (zp & xq)
return s==1
# Circuit specification
# Qubits indices: 0,1,2,3 are data; 4 is ancilla
# Time order (after H1): gates are g0: CNOT12, g1: CNOT10, g2: CNOT23, g3: CNOT34, g4: CNOT04
cnots = [(1,2),(1,0),(2,3),(3,4),(0,4)]
# Precompute propagation to end for each gate and each local 2-qubit Pauli
single_qubit_labels = 'IXYZ'
paulis2 = [a+b for a in single_qubit_labels for b in single_qubit_labels] # 16 including II
# Map from two-qubit Pauli (on control,target) to full 5-qubit Pauli after propagation
prop_effects = [] # list of dicts for each gate
for gi,(c,t) in enumerate(cnots):
effects = {}
# Past gates after this gate
later = cnots[gi+1:]
for P2 in paulis2:
# Embed into 5-qubit string at positions c and t
P = ['I']*5
P[c], P[t] = P2[0], P2[1]
P = ''.join(P)
# propagate through later CNOTs
for (cc,tt) in later:
P = conj_by_cnot(P, cc, tt)
effects[P2] = P
prop_effects.append(effects)
# Stabilizers extended to 5 qubits (ancilla I)
S1 = 'XXXXI' # XXXX on 0..3
S2 = 'ZZZZI' # ZZZZ on 0..3
# Logical Zs (on data)
Z_A = 'ZZI I'.replace(' ','') # Z on 0,1
Z_B = 'ZIZ I'.replace(' ','') # Z on 0,2
# Extend with ancilla I
Z_A5 = Z_A + 'I'
Z_B5 = Z_B + 'I'
# Enumerate all patterns
# For each gate choose one of 16 paulis (identity or non-identity)
counts = {
'accepted_total':[0]*6, # by k errors
'accepted_good':[0]*6,
'accepted_bad':[0]*6
}
# Precompute identity detection etc for speed? We'll do on the fly.
# Iterate over all choices of P2 for each gate (5 nested loops)
# We'll optimize by using integer indexes and lookups.
paulis2_list = paulis2
len(paulis2_list)
from itertools import product
# For speed, convert to list of 16 ints representing non-identity vs identity
# We'll define helper to compute final error by multiplying strings
def is_identity2(P2):
return P2 == 'II'
# We'll also precompute 5x16 mapping to 5-qubit strings P_effects
prop = prop_effects
# Multiplication function across 5-qubit strings
def multiply_many(Ps):
out = 'IIIII'
for P in Ps:
if P != 'IIIII':
out = pauli_mul(out,P)
return out
# Main loop
n_total = 0
for idxs in product(range(16), repeat=5):
n_total += 1
Ps = []
k = 0
for gi,idx in enumerate(idxs):
P2 = paulis2_list[idx]
if P2 != 'II':
k += 1
Ps.append(prop[gi][P2])
# total propagated error at end
Ptot = multiply_many(Ps)
# Ancilla acceptance: ancilla Pauli must be I or Z (no X or Y)
anc = Ptot[4]
if anc in ('X','Y'):
continue # rejected by ancilla measurement (since ideal ancilla is |0>)
# Code detection acceptance: data part must commute with both stabilizers
# Equivalent to Ptot must commute with S1,S2 since ancilla I/Z commutes with them trivially
if anticommutes(Ptot, S1) or anticommutes(Ptot, S2):
continue
# accepted
counts['accepted_total'][k] += 1
# Goodness: if data commutes with both Z_A and Z_B -> logical |00> unchanged
if (not anticommutes(Ptot, Z_A5)) and (not anticommutes(Ptot, Z_B5)):
counts['accepted_good'][k] += 1
else:
counts['accepted_bad'][k] += 1
counts, n_total