from itertools import product
# Define Pauli group operations ignoring phase
paulis = ['I','X','Y','Z']
mult_map = {
('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',
}
# Multiplication ignoring phase on strings
def pmul(a,b):
return ''.join(mult_map[(x,y)] for x,y in zip(a,b))
# Conjugation by CNOT(c,t) on a 5-qubit Pauli string s
def cnot_conj(s, c, t):
s = list(s)
# Rules: X_c -> X_c X_t; Z_t -> Z_c Z_t; Y_c -> Y_c X_t; Y_t -> Z_c Y_t
# We implement by updating characters appropriately; need to combine if both changes apply
def combine(qubit, p):
s[qubit] = mult_map[(s[qubit], p)]
# Apply effect for control
if s[c] == 'X':
combine(t, 'X')
elif s[c] == 'Y':
combine(t, 'X') # Y_c carries X on c and Z on c; effect on target same as X on control
# Apply effect for target
if s[t] == 'Z':
combine(c, 'Z')
elif s[t] == 'Y':
combine(c, 'Z')
return ''.join(s)
# Conjugation by H on qubit q: X<->Z, Y -> -Y (ignore phase so Y stays Y)
def h_conj(s, q):
s = list(s)
if s[q] == 'X':
s[q] = 'Z'
elif s[q] == 'Z':
s[q] = 'X'
# Y remains Y ignoring phase
return ''.join(s)
# Build mapping: final error from each inserted error after a given CNOT
# Circuit order (right-to-left):
# 1) H_1
# 2) CNOT_12
# 3) CNOT_10
# 4) CNOT_23
# 5) CNOT_34
# 6) CNOT_04
# 7) M_4
# We will define a function that, given a list of 5 two-qubit Pauli errors e2,e3,e4,e5,e6 (strings like 'IX' on the pair),
# computes the final 5-qubit Pauli string.
# Helper to embed a two-qubit Pauli on qubits (i,j) into 5-qubit string
def embed_ij(pair, i, j):
s = ['I']*5
s[i] = pair[0]
s[j] = pair[1]
return ''.join(s)
# Apply conjugation function for the remainder of the circuit
def propagate_to_end(err_after_gate_index, P):
# err_after_gate_index in {2,3,4,5,6}
# P is a 5-qubit Pauli string representing an error that occurs just after that gate
s = P
# Conjugate by the subsequent gates in chronological order: next indices > err_after_gate_index
if err_after_gate_index < 3:
s = cnot_conj(s, 1, 0) # gate 3: CNOT_10
if err_after_gate_index < 4:
s = cnot_conj(s, 2, 3) # gate 4: CNOT_23
if err_after_gate_index < 5:
s = cnot_conj(s, 3, 4) # gate 5: CNOT_34
if err_after_gate_index < 6:
s = cnot_conj(s, 0, 4) # gate 6: CNOT_04
# No conjugation by measurement
return s
# Let's verify conj functions by random tests (omitted for brevity)
# Stabilizers for data qubits 0..3
S1 = 'XXXX'
S2 = 'ZZZZ'
# Centralizer predicate for data 0..3
anti_pairs = {('X','Z'),('Z','X'),('X','Y'),('Y','X'),('Y','Z'),('Z','Y')}
def commute(p, q):
# p, q are 4-qubit strings
anti = 0
for a,b in zip(p,q):
if a=='I' or b=='I' or a==b:
continue
if (a,b) in anti_pairs:
anti ^= 1
return anti==0
# Generate group of target codeword stabilizer: <S1,S2, Z_A, Z_B>
X_A = 'XIXI'; X_B = 'XXII'; Z_A = 'ZZII'; Z_B = 'ZIZI'
# Multiply 4-qubit ignoring phase
mult4 = mult_map
def pmul4(a,b):
return ''.join(mult4[(x,y)] for x,y in zip(a,b))
def generate_group(gens):
G = set(['I'*4])
frontier = ['I'*4]
while frontier:
h = frontier.pop()
for g in gens:
nh = pmul4(g, h)
if nh not in G:
G.add(nh)
frontier.append(nh)
return G
state_stab = generate_group([S1, S2, Z_A, Z_B]) # size should be 16
centralizer = set()
for a in product(paulis, repeat=4):
p = ''.join(a)
if commute(p,S1) and commute(p,S2):
centralizer.add(p)
# Now enumerate all 16^5 combinations
pairs = [''.join(p) for p in product(paulis, repeat=2)] # two-qubit Paulis on a pair
# Precompute mapping for each location and pair Pauli to final 5-qubit Pauli
locations = {
2: (1,2), # CNOT_12
3: (1,0), # CNOT_10
4: (2,3), # CNOT_23
5: (3,4), # CNOT_34
6: (0,4), # CNOT_04
}
maps = {k:{} for k in locations}
for k,(i,j) in locations.items():
for pair in pairs:
emb = embed_ij(pair, i, j)
maps[k][pair] = propagate_to_end(k, emb)
# Now enumerate tuples (e2,e3,e4,e5,e6)
from collections import defaultdict
counts_accept = defaultdict(int)
counts_good = defaultdict(int)
counts_tot = defaultdict(int)
for e2 in pairs:
for e3 in pairs:
for e4 in pairs:
for e5 in pairs:
for e6 in pairs:
# Compute final Pauli as product (order doesn't matter up to phase)
s = 'I'*5
for k,e in [(2,e2),(3,e3),(4,e4),(5,e5),(6,e6)]:
s = pmul(s, maps[k][e])
# Split data and ancilla
data = s[:4]
anc = s[4]
# Accept if anc in {I,Z} and data in centralizer
accept = (anc in ('I','Z')) and (data in centralizer)
# Good if accept and data in state_stab
good = accept and (data in state_stab)
# Count by m = number of non-identity errors
m = (e2!='II') + (e3!='II') + (e4!='II') + (e5!='II') + (e6!='II')
counts_tot[m] += 1
if accept:
counts_accept[m] += 1
if good:
counts_good[m] += 1
# Print summaries
print('Total combinations per m (should be C(5,m)*15^m):')
for m in range(6):
print(m, counts_tot[m])
print('Accepted counts per m:', dict(counts_accept))
print('Good counts per m:', dict(counts_good))