import numpy as np
# Define single-qubit Pauli matrices and H
I = np.array([[1,0],[0,1]], dtype=complex)
X = np.array([[0,1],[1,0]], dtype=complex)
Y = np.array([[0,-1j],[1j,0]], dtype=complex)
Z = np.array([[1,0],[0,-1]], dtype=complex)
H = (1/np.sqrt(2))*np.array([[1,1],[1,-1]], dtype=complex)
P0 = np.array([[1,0],[0,0]], dtype=complex)
P1 = np.array([[0,0],[0,1]], dtype=complex)
# Tensor utility: embed single-qubit ops on 5-qubit register (q0 q1 q2 q3 q4)
def embed_single(op, q, n=5):
ops = [I]*n
ops[q] = op
out = ops[0]
for k in range(1,n):
out = np.kron(out, ops[k])
return out
def embed_two(op_a, qa, op_b, qb, n=5):
# Place op_a on qa and op_b on qb
ops = [I]*n
ops[qa] = op_a
ops[qb] = op_b
out = ops[0]
for k in range(1,n):
out = np.kron(out, ops[k])
return out
# Build CNOT(c,t) as P0_c ⊗ I_t + P1_c ⊗ X_t embedded in 5 qubits
def CNOT(c,t,n=5):
term0 = embed_single(P0, c, n) @ embed_single(I, t, n) # actually these commute; but simpler use projectors
term1 = embed_single(P1, c, n) @ embed_single(X, t, n)
return term0 + term1
# Build full circuit unitary up to measurement (sequence right-to-left): H1, CNOT12, CNOT10, CNOT23, CNOT34, CNOT04
U_H1 = embed_single(H, 1, 5)
U_C12 = CNOT(1,2,5)
U_C10 = CNOT(1,0,5)
U_C23 = CNOT(2,3,5)
U_C34 = CNOT(3,4,5)
U_C04 = CNOT(0,4,5)
# Sequence application helper applying errors after each CNOT
# Two-qubit Pauli basis for error set on a given pair: all 15 non-identity two-qubit Paulis
paulis = [I, X, Y, Z]
labels = ['I','X','Y','Z']
def pauli_label_to_ops(lbl):
mapping = {'I': I, 'X': X, 'Y': Y, 'Z': Z}
return mapping[lbl[0]], mapping[lbl[1]]
# Build list of two-qubit labels excluding 'II'
labels_2q = []
for a in 'IXYZ':
for b in 'IXYZ':
if not (a=='I' and b=='I'):
labels_2q.append(a+b)
# Error locations list and corresponding (control,target) qubit index pairs for embedding
locs = ['12','10','23','34','04']
pairs = {'12': (1,2), '10': (1,0), '23': (2,3), '34': (3,4), '04': (0,4)}
# Function to apply the circuit with a dictionary of errors: err_dict maps loc-> 2-letter label like 'XI'
def run_with_errors(err_dict):
# initial state |00000>
psi = np.zeros((32,1), dtype=complex)
psi[0,0] = 1.0
# H1
psi = U_H1 @ psi
# CNOT12 then error on (1,2)
psi = U_C12 @ psi
if '12' in err_dict:
a,b = pauli_label_to_ops(err_dict['12'])
E = embed_two(a, pairs['12'][0], b, pairs['12'][1], 5)
psi = E @ psi
# CNOT10 then error
psi = U_C10 @ psi
if '10' in err_dict:
a,b = pauli_label_to_ops(err_dict['10'])
E = embed_two(a, pairs['10'][0], b, pairs['10'][1], 5)
psi = E @ psi
# CNOT23 then error
psi = U_C23 @ psi
if '23' in err_dict:
a,b = pauli_label_to_ops(err_dict['23'])
E = embed_two(a, pairs['23'][0], b, pairs['23'][1], 5)
psi = E @ psi
# CNOT34 then error
psi = U_C34 @ psi
if '34' in err_dict:
a,b = pauli_label_to_ops(err_dict['34'])
E = embed_two(a, pairs['34'][0], b, pairs['34'][1], 5)
psi = E @ psi
# CNOT04 then error
psi = U_C04 @ psi
if '04' in err_dict:
a,b = pauli_label_to_ops(err_dict['04'])
E = embed_two(a, pairs['04'][0], b, pairs['04'][1], 5)
psi = E @ psi
return psi
# Measurement ancilla qubit 4 in Z, post-select m=0
# Projector onto ancilla |0>
Proj0_anc = embed_single(P0, 4, 5)
# Target 4-qubit |00>_AB state is GHZ_4 = (|0000>+|1111>)/sqrt(2)
phi_target = np.zeros((16,1), dtype=complex)
phi_target[0,0] = 1/np.sqrt(2)
phi_target[-1,0] = 1/np.sqrt(2)
# Code stabilizers XXXX and ZZZZ on data qubits 0..3
Sx_data = np.kron(np.kron(np.kron(X,X),X),X)
Sz_data = np.kron(np.kron(np.kron(Z,Z),Z),Z)
# Helper to get post-measurement data state on ancilla outcome 0, acceptance flag for ancilla
def postselect_ancilla0(psi):
v = Proj0_anc @ psi
p0 = float((v.conj().T @ v).real)
if p0 < 1e-12:
return None, 0.0
# Normalize and trace out ancilla by extracting amplitudes where ancilla=0
# Reshape psi to (2,2,2,2,2)
psi_tensor = psi.reshape((2,2,2,2,2))
data0 = psi_tensor[:,:,:,:,0].reshape((16,1))
data0 = data0 / np.sqrt(p0)
return data0, p0
# Check code stabilizer acceptance (+1 eigenvalues of XXXX and ZZZZ)
def code_accepts(data_state):
# Expectation values
v = data_state
valx = complex((v.conj().T @ (Sx_data @ v))[0,0])
valz = complex((v.conj().T @ (Sz_data @ v))[0,0])
# Numerical tolerance
okx = abs(valx.real - 1.0) < 1e-8 and abs(valx.imag) < 1e-8
okz = abs(valz.real - 1.0) < 1e-8 and abs(valz.imag) < 1e-8
return okx and okz
# Fidelity with target
def fidelity_with_target(data_state):
v = data_state
overlap = complex((phi_target.conj().T @ v)[0,0])
return float(abs(overlap)**2)
# Precompute zero-error baseline
psi0 = run_with_errors({})
phi0, p0 = postselect_ancilla0(psi0)
assert p0 > 0.999999999
assert code_accepts(phi0)
F0 = fidelity_with_target(phi0)
# Single-error scan
S1_acc = 0
S1_good = 0
singles_records = []
for loc in locs:
for lbl in labels_2q:
psi = run_with_errors({loc: lbl})
data, pacc = postselect_ancilla0(psi)
A = 1 if pacc > 0.5 else 0 # ancilla acceptance binary
if A == 1 and data is not None:
code_ok = code_accepts(data)
acc = 1 if code_ok else 0
if acc==1:
fid = fidelity_with_target(data)
good = 1 if fid > 0.999999 else 0
else:
good = 0
else:
acc = 0
good = 0
S1_acc += acc
S1_good += good
singles_records.append((loc, lbl, acc, good))
# Double-error scan over ordered pairs k<l (distinct locations)
S2_acc = 0
S2_good = 0
pairs_records = []
for i in range(len(locs)):
for j in range(i+1, len(locs)):
loc_i = locs[i]
loc_j = locs[j]
for lbl_i in labels_2q:
for lbl_j in labels_2q:
psi = run_with_errors({loc_i: lbl_i, loc_j: lbl_j})
data, pacc = postselect_ancilla0(psi)
A = 1 if pacc > 0.5 else 0
if A == 1 and data is not None:
code_ok = code_accepts(data)
acc = 1 if code_ok else 0
if acc==1:
fid = fidelity_with_target(data)
good = 1 if fid > 0.999999 else 0
else:
good = 0
else:
acc = 0
good = 0
S2_acc += acc
S2_good += good
pairs_records.append(((loc_i, lbl_i), (loc_j, lbl_j), acc, good))
print('Baseline F0, p0:', F0, p0)
print('S1_acc, S1_good:', S1_acc, S1_good)
print('S2_acc, S2_good:', S2_acc, S2_good)
# Compute coefficients for expansions
# Denominator D(p):
# d1 = -5 + S1_acc/15
# d2 = 10 - 4*(S1_acc/15) + (S2_acc/225)
d1 = -5 + S1_acc/15.0
d2 = 10 - 4*(S1_acc/15.0) + (S2_acc/225.0)
# Numerator N(p):
# n1 = -5 + S1_good/15
# n2 = 10 - 4*(S1_good/15) + (S2_good/225)
n1 = -5 + S1_good/15.0
n2 = 10 - 4*(S1_good/15.0) + (S2_good/225.0)
# Fidelity expansion F = (1 + n1 p + n2 p^2) / (1 + d1 p + d2 p^2)
# Series to p^2: 1 + (n1 - d1) p + (n2 - d2 - d1*(n1 - d1)) p^2
c1 = n1 - d1
c2 = n2 - d2 - d1*(n1 - d1)
print('d1,d2:', d1, d2)
print('n1,n2:', n1, n2)
print('F expansion coefficients: c1, c2:', c1, c2)
# Also compute acceptance expansion coefficients explicitly
print('Acceptance expansion: 1 + d1 p + d2 p^2')