import itertools
from functools import lru_cache
# Define Pauli multiplication ignoring global phases
paulis = ['I','X','Y','Z']
mult_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 pmul(a,b):
# a,b: single-qubit pauli labels
return mult_table[(a,b)]
# Multiply two 5-qubit pauli strings (ignoring phase)
def str_mul(P, Q):
return tuple(pmul(p,q) for p,q in zip(P,Q))
I5 = tuple('I'*5)
def cnot_conj(P, control, target):
# Conjugate 5-qubit Pauli string P by CNOT_{control->target}
P = list(P)
pc, pt = P[control], P[target]
# Map according to standard rules
# Control mapping
if pc == 'X':
# X_c -> X_c X_t
if P[target] == 'I':
P[target] = 'X'
elif P[target] == 'X':
P[target] = 'I'
elif P[target] == 'Y':
P[target] = 'Z'
elif P[target] == 'Z':
P[target] = 'Y'
elif pc == 'Y':
# Y_c -> Y_c X_t
if P[target] == 'I':
P[target] = 'X'
elif P[target] == 'X':
P[target] = 'I'
elif P[target] == 'Y':
P[target] = 'Z'
elif P[target] == 'Z':
P[target] = 'Y'
# P[control] remains 'Y'
# Z_c unchanged
# Target mapping
pt = P[target]
if P[target] == 'Z':
# Z_t -> Z_c Z_t
# Multiply Z on control position ignoring phase
P[control] = pmul(P[control], 'Z')
elif P[target] == 'Y':
# Y_t = i X_t Z_t -> Z_c Y_t
P[control] = pmul(P[control], 'Z')
# Y_t unaffected otherwise
# X_t unchanged
return tuple(P)
# Test cnot conjugation against known cases
# Quick sanity checks
assert cnot_conj( ('X','I','I','I','I'), 0,1 ) == ('X','X','I','I','I')
assert cnot_conj( ('Z','I','I','I','I'), 0,1 ) == ('Z','I','I','I','I')
assert cnot_conj( ('I','X','I','I','I'), 0,1 ) == ('I','X','I','I','I')
assert cnot_conj( ('I','Z','I','I','I'), 0,1 ) == ('Z','Z','I','I','I')
# Build mapping from location 2-qubit pauli to final 5-qubit pauli after conjugating through later gates
locations = [ (1,2), (1,0), (2,3), (3,4), (0,4) ] # ordered L1..L5
# Subsequent gates for each location
subseq_cnots = {
0: [(1,0),(2,3),(3,4),(0,4)], # after L1
1: [(2,3),(3,4),(0,4)], # after L2
2: [(3,4),(0,4)], # after L3
3: [(0,4)], # after L4
4: [], # after L5
}
# Generate all 2-qubit Paulis including II
pauli2 = [ (a,b) for a in paulis for b in paulis ]
# Embedding function: place a 2-qubit pauli on 5-qubit string
def embed2(p2, qpos):
P = ['I']*5
(i,j) = qpos
P[i] = p2[0]
P[j] = p2[1]
return tuple(P)
# Forward-conjugation mapping
maps = [] # list of dicts for each location: (p2)-> 5-qubit pauli after conjugating through later cnots
for li, qpos in enumerate(locations):
d = {}
for p2 in pauli2:
P = embed2(p2, qpos)
# Conjugate through all subsequent CNots in order
for (c,t) in subseq_cnots[li]:
P = cnot_conj(P, c, t)
d[p2] = P
maps.append(d)
# Define measured operators on final state: ancilla Z4, data XXXX and ZZZZ
Z4 = tuple(['I','I','I','I','Z'])
XXXX = tuple(['X','X','X','X','I'])
ZZZZ = tuple(['Z','Z','Z','Z','I'])
# Target state stabilizer generators: on data: XXXX, ZZZZ, Z_A=Z0Z1, Z_B=Z0Z2 ; ancilla Z4
ZA = tuple(['Z','Z','I','I','I'])
ZB = tuple(['Z','I','Z','I','I'])
def gen_group(generators):
# generators: list of 5-qubit pauli strings; return set of all products ignoring phase
gs = generators
group = set([I5])
for g in gs:
new = set()
for h in group:
new.add(str_mul(h, g))
group |= new
return group
# Stabilizer group (ignoring phase) of target 5-qubit state |0>_4 \otimes |00>_L
G_target = gen_group([Z4, XXXX, ZZZZ, ZA, ZB])
len_G = len(G_target)
len_G