# We'll enumerate the error patterns to compute acceptance and logical fidelity.
from itertools import product
n=5 # number of qubits 0..4
# Utility functions for Pauli representation in symplectic binary form
# Use tuple (x_mask, z_mask) where each is an integer with n bits.
def pauli_mul(p,q):
# Multiply Pauli p and q ignoring phase: XOR masks
x1,z1=p
x2,z2=q
return (x1 ^ x2, z1 ^ z2)
def conj_cnot(p, c, t):
x,z=p
# Extract bits
xc=(x>>c)&1
xt=(x>>t)&1
zc=(z>>c)&1
zt=(z>>t)&1
# After conjugation by CNOT(c,t):
# x_t' = x_t XOR x_c; x_c' = x_c
# z_c' = z_c XOR z_t; z_t' = z_t
xtp = xt ^ xc
xcp = xc
zcp = zc ^ zt
ztp = zt
# Now update masks
# Clear bits c,t and set new
x &= ~(1<<c)
x &= ~(1<<t)
z &= ~(1<<c)
z &= ~(1<<t)
x |= (xcp<<c) | (xtp<<t)
z |= (zcp<<c) | (ztp<<t)
return (x,z)
# Function to get Pauli from a 2-qubit operator on pair (i,j)
# where local operator is in {I,X,Y,Z}^2 encoded as two chars like 'IX','YZ', etc.
pauli_char_to_bits={'I':(0,0),'X':(1,0),'Z':(0,1),'Y':(1,1)}
def pauli_on_pair(pair, s):
i,j=pair
x=z=0
a,b=s
xi,zi=pauli_char_to_bits[a]
xj,zj=pauli_char_to_bits[b]
if xi: x |= (1<<i)
if zi: z |= (1<<i)
if xj: x |= (1<<j)
if zj: z |= (1<<j)
return (x,z)
# Enumerate 16 two-qubit Paulis on a pair
symbols=['I','X','Y','Z']
all2=[(a,b) for a in symbols for b in symbols]
# Gate sequence and errors after each gate
# Gates: (1,2), (1,0), (2,3), (3,4), (0,4)
Gates=[(1,2),(1,0),(2,3),(3,4),(0,4)]
# Precompute conjugated errors to end for each gate and each 2-qubit Pauli
conj_to_end=[[] for _ in range(5)]
for k,(c,t) in enumerate(Gates):
for s in all2:
p = pauli_on_pair((c,t), s)
# propagate through subsequent gates
for (cc,tt) in Gates[k+1:]:
p = conj_cnot(p, cc, tt)
conj_to_end[k].append(p)
# Define stabilizers and logical Zs on data qubits 0..3 (bitmasks for 5 qubits, but we'll restrict)
# We'll use 5-qubit masks but ensure qubit 4 bits are zero
def make_pauli_on(qubits, ops):
x=z=0
for q,op in zip(qubits, ops):
xi,zi=pauli_char_to_bits[op]
if xi: x |= (1<<q)
if zi: z |= (1<<q)
return (x,z)
Sx = make_pauli_on([0,1,2,3], list('XXXX'))
Sz = make_pauli_on([0,1,2,3], list('ZZZZ'))
ZA = make_pauli_on([0,1,2,3], list('ZZII'))
ZB = make_pauli_on([0,1,2,3], list('ZIZI'))
# Function to restrict to data (zero out ancilla bits)
def restrict_data(p):
x,z=p
# zero out qubit 4 bit
mask=((1<<4)-1) # bits 0..3 set
return (x & mask, z & mask)
# Commutation check for two Pauli operators p and q
# They commute iff p.x · q.z + p.z · q.x = 0 (mod 2)
def commute(p,q):
x1,z1=p; x2,z2=q
# bit dot product mod 2
return (bin((x1 & z2)).count('1') + bin((z1 & x2)).count('1')) % 2 == 0
# Generate the good-group G = <Sx, Sz, ZA, ZB> as a set of (x,z) on data qubits
from itertools import product as itprod
# Build the group (ignoring phases): we can generate all 16 combinations
G_good=set()
# We'll encode Paul's as 5-qubit masks but with bit 4 zero; We'll restrict for comparison simplicity
gens=[Sx, Sz, ZA, ZB]
combos=list(itprod([0,1], repeat=4))
for coeffs in combos:
p=(0,0)
for g,co in zip(gens, coeffs):
if co:
p=pauli_mul(p, g)
G_good.add(restrict_data(p))
# Acceptance tests
def accept_code(p):
pd=restrict_data(p)
return commute(pd, Sx) and commute(pd, Sz)
def accept_ancilla(p):
x,z=p
# accept if no X on ancilla (bit 4 of x is 0). Z on ancilla is fine
return ((x>>4) & 1) == 0
def good_logical(p):
pd=restrict_data(p)
return pd in G_good
# Enumerate all patterns: for each gate choose one of 16 Paulis.
# We'll accumulate counts A_k and G_k for k = number of non-identity insertions.
A=[0]*6
G=[0]*6
B=[0]*6 # bad accepted counts (accepted but not good)
# We'll also compute total number of patterns for each k for sanity: T[k]
T=[0]*6
# Map symbol index to whether it's identity
is_id=[(a=='I' and b=='I') for (a,b) in all2]
# Pre-list conj Paul's and whether id to count k easily
conj_by_gate = conj_to_end
# We'll enumerate using indices 0..15 for each gate
for idxs in product(range(16), repeat=5):
# Count number of non-identity selections
k = sum(0 if is_id[i] else 1 for i in idxs)
T[k]+=1
# Compute final error Pauli
p=(0,0)
for gate_index, sym_index in enumerate(idxs):
p = pauli_mul(p, conj_by_gate[gate_index][sym_index])
# Acceptance checks
if accept_code(p) and accept_ancilla(p):
A[k]+=1
if good_logical(p):
G[k]+=1
else:
B[k]+=1
# Let's print counts and totals
print('Totals by k:')
print('k, T[k], A[k], G[k], B[k]')
for k in range(6):
print(k, T[k], A[k], G[k], B[k])
# Now we can write P_acc(p) and P_good(p), F = P_good / P_acc
# We'll express them as polynomials in q=(p/15) and r=(1-p)
from fractions import Fraction
# We'll produce coefficients as fractions
def poly_acc(p):
# compute value symbolically representation as dict of powers of p up to 5 maybe convert to expanded polynomials later
pass
# But easiest: We'll produce symbolic expression using sympy if allowed, else compute rational coefficients by expanding terms numerically then reconstruct