Challenge

System #0

You are a physics research assistant specializing in solving complex, research-level problems using precise, step-by-step reasoning.

Input Problems will be provided in Markdown format.

Output (Markdown format)

  1. Step-by-Step Derivation - Show every non-trivial step in the solution. Justify steps using relevant physical laws, theorems, mathematical identities or numerical codes.
  2. Mathematical Typesetting - Use LaTeX for all mathematics: $...$ for inline expressions, $$...$$ for display equations.
  3. Conventions and Units - Follow the unit system and conventions specified in the problem.
  4. Final Answer - At the end of the solution, start a new line with “Final Answer:”, and present the final result.

    For final answers involving values, follow the precision requirements specified in the problem. If no precision is specified: - If an exact value is possible, provide it (e.g., \$\sqrt(2)\$, \$\pi/4\$). - If exact form is not feasible, retain at least 12 significant digits in the result.

  5. Formatting Compliance - If the user requests a specific output format (e.g., code, table), provide the final answer accordingly.

User #1

Problem setup:

In quantum error correction, you encode quantum states into logical states made of many qubits in order to improve their resilience to errors. In quantum error detection, you do the same but can only detect the presence of errors and not correct them. In this problem, we will consider a single [[4,2,2]] quantum error detection code, which encodes two logical qubits into four physical qubits, and investigate how robust logical quantum operations in this code are to quantum errors.

Our convention is that the four physical qubits in the [[4,2,2]] code are labelled 0,1,2,3. The two logical qubits are labelled A and B. The stabilizers are \(XXXX\) and \(ZZZZ\), where \(X\) and \(Z\) are Pauli matrices. The logical \(X\) and \(Z\) operators on the two qubits are \(X_A = XIXI\), \(X_B=XXII\), \(Z_A = ZZII\), \(Z_B = ZIZI\), up to multiplication by stabilizers.

We will consider different state preparation circuits consisting of controlled not \(CNOT_{ij}\) gates, where \(CNOT_{ij}\) has control qubit \(i\) and target qubit \(j\). As a simple model of quantum errors in hardware, we will suppose that each \(CNOT_{ij}\) gate in the circuit has a two qubit depolarizing error channel following it that produces one of the 15 non-identity two-qubit Paulis with equal probability \(p/15\). The probability \(p\) indicates the probability of an error in a single two-qubit gate. We will assess the logical infidelity of certain state preparation protocols as a function of the physical infidelity \(p\).

Challenge

Suppose that we prepare a logical two-qubit \(|00\rangle_{AB}\) state in the [[4,2,2]] code. To do so, we introduce an ancilla qubit, qubit 4, and use the following state preparation circuit:

\[ M_4 (CNOT_{04}) (CNOT_{34}) (CNOT_{23}) (CNOT_{10}) (CNOT_{12}) (H_1) \]

Note that this equation is written in matrix multiplication order, while the quantum operations in the circuit occur in the reverse order (from right-to-left in the above equation). \(H\) is a single-qubit Hadamard gate and \(M\) is a single-qubit measurement. The ancilla is used to detect errors in the state preparation circuit and makes the circuit fault-tolerant. If the ancilla measurement is \(|0\rangle\) (\(|1\rangle\)), the state preparation succeeds (fails).

What is the logical state fidelity of the final 2-qubit logical state at the end of the circuit as a function of two-qubit gate error rate \(p\), assuming the state is post-selected on all detectable errors in the code and on the ancilla qubit measuring \(|0\rangle\)?

Assistant #2
Tool Call (code to execute)
import numpy as np
from itertools import product, combinations

# Define Pauli matrices
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)

paulis = {'I': I, 'X': X, 'Y': Y, 'Z': Z}

# Single-qubit gates
H = (1/np.sqrt(2))*np.array([[1,1],[1,-1]], dtype=complex)

# Tensor utility: build n-qubit operator from list of single-qubit ops (leftmost is qubit 0)
def kronN(ops):
    out = np.array([[1]], dtype=complex)
    for op in ops:
        out = np.kron(out, op)
    return out

# n-qubit identity
def In(n):
    out = np.array([[1]], dtype=complex)
    for _ in range(n):
        out = np.kron(out, I)
    return out

nq = 5  # qubits 0..4 (4 data + ancilla)

# Build CNOT for arbitrary control c, target t on nq qubits
def CNOT(n, c, t):
    # Using projector method: |0><0|_c ⊗ I + |1><1|_c ⊗ X_t
    P0 = np.array([[1,0],[0,0]], dtype=complex)
    P1 = np.array([[0,0],[0,1]], dtype=complex)
    ops0 = [I]*n
    ops1 = [I]*n
    ops0[c] = P0
    ops1[c] = P1
    term0 = kronN(ops0)
    ops1[t] = X
    term1 = kronN(ops1)
    return term0 + term1

# Projectors for ancilla measurement on qubit 4
Pi_a0 = kronN([I,I,I,I, np.array([[1,0],[0,0]], dtype=complex)])
Pi_a1 = kronN([I,I,I,I, np.array([[0,0],[0,1]], dtype=complex)])

# Code states on data qubits 0..3
# |00>_L = (|0000> + |1111>)/sqrt2
basis_4 = np.eye(16, dtype=complex)
ket0000 = np.zeros((16,1), dtype=complex); ket0000[0,0] = 1
ket1111 = np.zeros((16,1), dtype=complex); ket1111[-1,0] = 1
ketGHZp = (ket0000 + ket1111)/np.sqrt(2)

# Logical X operators per problem
# X_A = X I X I on qubits 0..3
# X_B = X X I I on qubits 0..3
XA = kronN([X,I,X,I])
XB = kronN([X,X,I,I])

# Generate code basis |00>, |10>=XA|00>, |01>=XB|00>, |11>=XA XB |00>
ket00 = ketGHZp
ket10 = XA @ ket00
ket01 = XB @ ket00
ket11 = XA @ ket01

# Normalize (should be normalized already)
for k in [ket00, ket10, ket01, ket11]:
    k /= np.linalg.norm(k)

# Projector onto code space P_code = sum |ij><ij|
P_code = np.zeros((16,16), dtype=complex)
for k in [ket00, ket10, ket01, ket11]:
    P_code += k @ k.conj().T

# Projector onto |00> logical
P_00 = ket00 @ ket00.conj().T

# Expand to 5-qubit operators by tensoring with ancilla identity where necessary
# We'll need to apply gates and errors on full 5-qubit space; For code projectors we act only on 0..3
# So to compute probabilities after ancilla projection, we will extract the data state by partial projection.

# Build the noiseless unitary for the circuit (up to just before measuring ancilla)
U = np.eye(2**nq, dtype=complex)
# Apply H on qubit 1
ops = [I]*nq; ops[1] = H
U = kronN(ops) @ U
# Apply CNOTs in order: 1->2, 1->0, 2->3, 3->4, 0->4
cnots = [(1,2), (1,0), (2,3), (3,4), (0,4)]
CNOTs = [CNOT(nq, c, t) for (c,t) in cnots]

# We'll build functions to apply errors after each CNOT

# List all two-qubit Pauli pairs (including II); return as 5-qubit operator placed on the two qubits of given pair
labels = ['I','X','Y','Z']
pauli_pairs = []  # tuples (label_c, label_t, matrix 4x4)
for a in labels:
    for b in labels:
        pauli_pairs.append((a,b, np.kron(paulis[a], paulis[b])))

# Map location index -> pair of qubit indices
loc_qubits = {i: cnots[i] for i in range(5)}

# For each pair (a,b), build the 5-qubit operator at location i
from functools import lru_cache

@lru_cache(None)
def pair_op_on_loc(i, a, b):
    q1, q2 = loc_qubits[i]
    ops = [I]*nq
    ops[q1] = paulis[a]
    ops[q2] = paulis[b]
    return kronN(ops)

# Initial state |00000>
ket0_5 = np.zeros((2**nq,1), dtype=complex); ket0_5[0,0] = 1

# Build a function to apply the circuit with a specified error pattern up to 2 faults
# error_pattern: dict mapping location index i (0..4) -> (a, b) Pauli labels (non-identity permitted; 'I','I' means no error at that site)

def run_with_errors(error_pattern):
    state = ket0_5.copy()
    # H
    ops = [I]*nq; ops[1] = H
    state = kronN(ops) @ state
    # For each CNOT + error after it
    for i, (c,t) in enumerate(cnots):
        state = CNOTs[i] @ state
        a,b = error_pattern.get(i, ('I','I'))
        if not (a=='I' and b=='I'):
            state = pair_op_on_loc(i,a,b) @ state
    return state

# Build projection operators for data (0..3) only
# To compute <ψ| Π_a0 ⊗ P_code |ψ> etc, we'll expand them into full 5-qubit space by tensoring with ancilla projectors.
P_code_5 = kronN([None,None,None,None,None])  # placeholder
# Build tensored operators conveniently

def lift_data_op_to_5(op4):
    # op4 acts on qubits 0..3, ancilla identity on qubit 4
    return np.kron(op4, I)

P_code_5 = lift_data_op_to_5(P_code)
P_00_5   = lift_data_op_to_5(P_00)

# Now enumerate 0,1,2 faults, compute contributions to numerator and denominator as functions of p

# Two-qubit non-identity pairs
nonid_pairs = [(a,b) for (a,b,_) in pauli_pairs if not (a=='I' and b=='I')]

# For clarity, precompute the weights coefficients for each k when expanding to O(p^2)
# For a given pattern with k faults, its weight = (1-p)^(5-k) * (p/15)^k.
# We'll accumulate contributions for polynomial up to p^2: we represent as coeffs [c0, c1, c2] such that sum = c0 + c1 p + c2 p^2

from collections import defaultdict

num_coeff = np.array([0.0, 0.0, 0.0])  # numerator coefficients up to p^2
 den_coeff = np.array([0.0, 0.0, 0.0])  # denominator coefficients up to p^2
Tool #3
  File "<stdin>", line 159
    den_coeff = np.array([0.0, 0.0, 0.0])  # denominator coefficients up to p^2
IndentationError: unexpected indent
Assistant #4
import numpy as np
from itertools import product, combinations

# Define Pauli matrices
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)

paulis = {'I': I, 'X': X, 'Y': Y, 'Z': Z}

# Single-qubit gates
H = (1/np.sqrt(2))*np.array([[1,1],[1,-1]], dtype=complex)

# Tensor utility: build n-qubit operator from list of single-qubit ops (leftmost is qubit 0)
def kronN(ops):
    out = np.array([[1]], dtype=complex)
    for op in ops:
        out = np.kron(out, op)
    return out

# n-qubit identity
def In(n):
    out = np.array([[1]], dtype=complex)
    for _ in range(n):
        out = np.kron(out, I)
    return out

nq = 5  # qubits 0..4 (4 data + ancilla)

# Build CNOT for arbitrary control c, target t on nq qubits
def CNOT(n, c, t):
    # Using projector method: |0><0|_c ⊗ I + |1><1|_c ⊗ X_t
    P0 = np.array([[1,0],[0,0]], dtype=complex)
    P1 = np.array([[0,0],[0,1]], dtype=complex)
    ops0 = [I]*n
    ops1 = [I]*n
    ops0[c] = P0
    ops1[c] = P1
    term0 = kronN(ops0)
    ops1[t] = X
    term1 = kronN(ops1)
    return term0 + term1

# Projectors for ancilla measurement on qubit 4
Pi_a0 = kronN([I,I,I,I, np.array([[1,0],[0,0]], dtype=complex)])
Pi_a1 = kronN([I,I,I,I, np.array([[0,0],[0,1]], dtype=complex)])

# Code states on data qubits 0..3
# |00>_L = (|0000> + |1111>)/sqrt2
ket0000 = np.zeros((16,1), dtype=complex); ket0000[0,0] = 1
ket1111 = np.zeros((16,1), dtype=complex); ket1111[-1,0] = 1
ketGHZp = (ket0000 + ket1111)/np.sqrt(2)

# Logical X operators per problem
XA = kronN([X,I,X,I])
XB = kronN([X,X,I,I])

ket00 = ketGHZp.copy()
ket10 = XA @ ket00
ket01 = XB @ ket00
ket11 = XA @ ket01

# Normalize
for k in [ket00, ket10, ket01, ket11]:
    k /= np.linalg.norm(k)

# Projector onto code space P_code = sum |ij><ij|
P_code = np.zeros((16,16), dtype=complex)
for k in [ket00, ket10, ket01, ket11]:
    P_code += k @ k.conj().T

# Projector onto |00> logical
P_00 = ket00 @ ket00.conj().T

# Build the noiseless unitary for the circuit (up to just before measuring ancilla)
U = np.eye(2**nq, dtype=complex)
# Apply H on qubit 1
ops = [I]*nq; ops[1] = H
U = kronN(ops) @ U
# Apply CNOTs in order: 1->2, 1->0, 2->3, 3->4, 0->4
cnots = [(1,2), (1,0), (2,3), (3,4), (0,4)]
CNOTs = [CNOT(nq, c, t) for (c,t) in cnots]

# Build list of Pauli pairs
labels = ['I','X','Y','Z']
pauli_pairs = []  # tuples (label_c, label_t, matrix 4x4)
for a in labels:
    for b in labels:
        pauli_pairs.append((a,b, np.kron(paulis[a], paulis[b])))

# Map location index -> pair of qubit indices
loc_qubits = {i: cnots[i] for i in range(5)}

# For each pair (a,b), build the 5-qubit operator at location i
from functools import lru_cache

@lru_cache(None)
def pair_op_on_loc(i, a, b):
    q1, q2 = loc_qubits[i]
    ops = [I]*nq
    ops[q1] = paulis[a]
    ops[q2] = paulis[b]
    return kronN(ops)

# Initial state |00000>
ket0_5 = np.zeros((2**nq,1), dtype=complex); ket0_5[0,0] = 1

# Function to run circuit with specified error pattern (dictionary loc->(a,b))
def run_with_errors(error_pattern):
    state = ket0_5.copy()
    # H on qubit 1
    ops = [I]*nq; ops[1] = H
    state = kronN(ops) @ state
    # For each CNOT + error after it
    for i, (c,t) in enumerate(cnots):
        state = CNOTs[i] @ state
        a,b = error_pattern.get(i, ('I','I'))
        if not (a=='I' and b=='I'):
            state = pair_op_on_loc(i,a,b) @ state
    return state

# Lift 4-qubit data operators to 5-qubit by tensoring ancilla identity
P_code_5 = np.kron(P_code, I)
P_00_5   = np.kron(P_00, I)

# Two-qubit non-identity pairs
nonid_pairs = [(a,b) for (a,b,_) in pauli_pairs if not (a=='I' and b=='I')]

# We'll accumulate contributions to numerator and denominator polynomials up to O(p^2)
num_c0 = 0.0; num_c1 = 0.0; num_c2 = 0.0
den_c0 = 0.0; den_c1 = 0.0; den_c2 = 0.0

# Helper: For a given final pure state |psi>, compute probabilities:
# p_anc0 = <psi|Pi_a0|psi>
# After projecting ancilla=0, unnormalized data state is (⟨0|_a psi). We compute acceptance for code P_code and for |00>.

def contributions_for_state(state):
    # Probability ancilla=0
    p0 = float(np.real(state.conj().T @ Pi_a0 @ state))
    if p0 < 1e-12:
        return 0.0, 0.0
    # Unnormalized post-selected state (data+ancilla) after ancilla=0 projection
    post = Pi_a0 @ state
    # Now compute overlap with code projector on data
    # But P_code_5 acts only on data; ancilla projector already applied
    w_code = float(np.real(post.conj().T @ P_code_5 @ post))
    w_00   = float(np.real(post.conj().T @ P_00_5   @ post))
    return w_code, w_00

# 0 faults
state0 = run_with_errors({})
w_code0, w_000 = contributions_for_state(state0)
# Weight polynomial for 0 faults: (1-p)^5 = 1 - 5p + 10 p^2 + O(p^3)
num_c0 += w_000
num_c1 += -5*w_000
num_c2 += 10*w_000

den_c0 += w_code0
den_c1 += -5*w_code0
den_c2 += 10*w_code0

# 1 fault patterns
for i in range(5):
    for (a,b) in nonid_pairs:
        state = run_with_errors({i:(a,b)})
        w_code, w_00 = contributions_for_state(state)
        # Weight factor: (1-p)^4 * (p/15) = (p/15) - 4 (p^2/15) + O(p^3)
        num_c1 += (1/15)*w_00
        num_c2 += (-4/15)*w_00
        den_c1 += (1/15)*w_code
        den_c2 += (-4/15)*w_code

# 2 fault patterns (i<j)
for i, j in combinations(range(5), 2):
    for (a1,b1) in nonid_pairs:
        for (a2,b2) in nonid_pairs:
            state = run_with_errors({i:(a1,b1), j:(a2,b2)})
            w_code, w_00 = contributions_for_state(state)
            # Weight: (1-p)^3 * (p/15)^2 = (p^2/225) + O(p^3)
            num_c2 += (1/225)*w_00
            den_c2 += (1/225)*w_code

# Compile polynomial coefficients for fidelity: F(p) = (N0 + N1 p + N2 p^2) / (D0 + D1 p + D2 p^2)
num_coeffs = np.array([num_c0, num_c1, num_c2])
den_coeffs = np.array([den_c0, den_c1, den_c2])

num_coeffs, den_coeffs
Tool #5
''
Assistant #6
Tool Call (code to execute)
import numpy as np
print('num_coeffs:', num_coeffs)
print('den_coeffs:', den_coeffs)

# Let's also compute the series expansion for F up to O(p^2):
# F = (N0 + N1 p + N2 p^2) / (D0 + D1 p + D2 p^2)
# Expand: Let D = D0(1 + d1 p + d2 p^2), with d1=D1/D0, d2=D2/D0.
# N = N0(1 + n1 p + n2 p^2), with n1=N1/N0, n2=N2/N0.
# Then F = (N0/D0) * (1 + n1 p + n2 p^2) * (1 - d1 p + (d1^2 - d2) p^2) + O(p^3).
# Multiply out: F = (N0/D0) * [1 + (n1 - d1) p + (n2 - d2 + d1^2 - d1*n1) p^2]

N0, N1, N2 = num_coeffs
D0, D1, D2 = den_coeffs
n1 = N1/N0; n2 = N2/N0
d1 = D1/D0; d2 = D2/D0
c0 = N0/D0
c1 = c0*(n1 - d1)
c2 = c0*(n2 - d2 + d1**2 - d1*n1)
print('Series F(p) ≈ c0 + c1 p + c2 p^2')
print('c0, c1, c2 =', c0, c1, c2)
Tool #7
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
NameError: name 'num_coeffs' is not defined
Assistant #8
# Recreate variables from previous cell (since state not persistent?)
import numpy as np
from itertools import product, combinations

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)
paulis = {'I': I, 'X': X, 'Y': Y, 'Z': Z}
H = (1/np.sqrt(2))*np.array([[1,1],[1,-1]], dtype=complex)

def kronN(ops):
    out = np.array([[1]], dtype=complex)
    for op in ops:
        out = np.kron(out, op)
    return out

def In(n):
    out = np.array([[1]], dtype=complex)
    for _ in range(n):
        out = np.kron(out, I)
    return out

nq=5

def CNOT(n,c,t):
    P0 = np.array([[1,0],[0,0]], dtype=complex)
    P1 = np.array([[0,0],[0,1]], dtype=complex)
    ops0=[I]*n; ops1=[I]*n
    ops0[c]=P0; ops1[c]=P1
    term0=kronN(ops0)
    ops1[t]=X
    term1=kronN(ops1)
    return term0+term1

Pi_a0 = kronN([I,I,I,I, np.array([[1,0],[0,0]], dtype=complex)])

ket0000 = np.zeros((16,1), dtype=complex); ket0000[0,0]=1
ket1111 = np.zeros((16,1), dtype=complex); ket1111[-1,0]=1
ketGHZp = (ket0000 + ket1111)/np.sqrt(2)
XA = kronN([X,I,X,I])
XB = kronN([X,X,I,I])
ket00 = ketGHZp.copy(); ket10 = XA@ket00; ket01 = XB@ket00; ket11 = XA@ket01
for k in [ket00, ket10, ket01, ket11]:
    k /= np.linalg.norm(k)
P_code = np.zeros((16,16), dtype=complex)
for k in [ket00, ket10, ket01, ket11]:
    P_code += k @ k.conj().T
P_00 = ket00 @ ket00.conj().T

cnots = [(1,2), (1,0), (2,3), (3,4), (0,4)]
CNOTs = [CNOT(nq, c, t) for (c,t) in cnots]
labels=['I','X','Y','Z']
pauli_pairs=[(a,b, np.kron(paulis[a], paulis[b])) for a in labels for b in labels]
loc_qubits = {i: cnots[i] for i in range(5)}
from functools import lru_cache
@lru_cache(None)
def pair_op_on_loc(i,a,b):
    q1,q2 = loc_qubits[i]
    ops=[I]*nq
    ops[q1]=paulis[a]; ops[q2]=paulis[b]
    return kronN(ops)
ket0_5 = np.zeros((2**nq,1), dtype=complex); ket0_5[0,0]=1

def run_with_errors(error_pattern):
    state = ket0_5.copy()
    ops=[I]*nq; ops[1]=H
    state = kronN(ops) @ state
    for i,(c,t) in enumerate(cnots):
        state = CNOTs[i] @ state
        a,b = error_pattern.get(i, ('I','I'))
        if not (a=='I' and b=='I'):
            state = pair_op_on_loc(i,a,b) @ state
    return state

P_code_5 = np.kron(P_code, I)
P_00_5   = np.kron(P_00, I)
nonid_pairs = [(a,b) for (a,b,_) in pauli_pairs if not (a=='I' and b=='I')]

def contributions_for_state(state):
    p0 = float(np.real(state.conj().T @ Pi_a0 @ state))
    if p0 < 1e-12:
        return 0.0, 0.0
    post = Pi_a0 @ state
    w_code = float(np.real(post.conj().T @ P_code_5 @ post))
    w_00   = float(np.real(post.conj().T @ P_00_5   @ post))
    return w_code, w_00

num_c0 = 0.0; num_c1 = 0.0; num_c2 = 0.0
 den_c0 = 0.0; den_c1 = 0.0; den_c2 = 0.0
Tool #9
  File "<stdin>", line 90
    den_c0 = 0.0; den_c1 = 0.0; den_c2 = 0.0
IndentationError: unexpected indent
Assistant #10
import numpy as np
from itertools import product, combinations

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)
paulis = {'I': I, 'X': X, 'Y': Y, 'Z': Z}
H = (1/np.sqrt(2))*np.array([[1,1],[1,-1]], dtype=complex)

def kronN(ops):
    out = np.array([[1]], dtype=complex)
    for op in ops:
        out = np.kron(out, op)
    return out

def In(n):
    out = np.array([[1]], dtype=complex)
    for _ in range(n):
        out = np.kron(out, I)
    return out

nq=5

def CNOT(n,c,t):
    P0 = np.array([[1,0],[0,0]], dtype=complex)
    P1 = np.array([[0,0],[0,1]], dtype=complex)
    ops0=[I]*n; ops1=[I]*n
    ops0[c]=P0; ops1[c]=P1
    term0=kronN(ops0)
    ops1[t]=X
    term1=kronN(ops1)
    return term0+term1

Pi_a0 = kronN([I,I,I,I, np.array([[1,0],[0,0]], dtype=complex)])

ket0000 = np.zeros((16,1), dtype=complex); ket0000[0,0]=1
ket1111 = np.zeros((16,1), dtype=complex); ket1111[-1,0]=1
ketGHZp = (ket0000 + ket1111)/np.sqrt(2)
XA = kronN([X,I,X,I])
XB = kronN([X,X,I,I])
ket00 = ketGHZp.copy(); ket10 = XA@ket00; ket01 = XB@ket00; ket11 = XA@ket01
for k in [ket00, ket10, ket01, ket11]:
    k /= np.linalg.norm(k)
P_code = np.zeros((16,16), dtype=complex)
for k in [ket00, ket10, ket01, ket11]:
    P_code += k @ k.conj().T
P_00 = ket00 @ ket00.conj().T

cnots = [(1,2), (1,0), (2,3), (3,4), (0,4)]
CNOTs = [CNOT(nq, c, t) for (c,t) in cnots]
labels=['I','X','Y','Z']
pauli_pairs=[(a,b, np.kron(paulis[a], paulis[b])) for a in labels for b in labels]
loc_qubits = {i: cnots[i] for i in range(5)}
from functools import lru_cache
@lru_cache(None)
def pair_op_on_loc(i,a,b):
    q1,q2 = loc_qubits[i]
    ops=[I]*nq
    ops[q1]=paulis[a]; ops[q2]=paulis[b]
    return kronN(ops)
ket0_5 = np.zeros((2**nq,1), dtype=complex); ket0_5[0,0]=1

def run_with_errors(error_pattern):
    state = ket0_5.copy()
    ops=[I]*nq; ops[1]=H
    state = kronN(ops) @ state
    for i,(c,t) in enumerate(cnots):
        state = CNOTs[i] @ state
        a,b = error_pattern.get(i, ('I','I'))
        if not (a=='I' and b=='I'):
            state = pair_op_on_loc(i,a,b) @ state
    return state

P_code_5 = np.kron(P_code, I)
P_00_5   = np.kron(P_00, I)
nonid_pairs = [(a,b) for (a,b,_) in pauli_pairs if not (a=='I' and b=='I')]

def contributions_for_state(state):
    p0 = float(np.real(state.conj().T @ Pi_a0 @ state))
    if p0 < 1e-12:
        return 0.0, 0.0
    post = Pi_a0 @ state
    w_code = float(np.real(post.conj().T @ P_code_5 @ post))
    w_00   = float(np.real(post.conj().T @ P_00_5   @ post))
    return w_code, w_00

num_c0 = 0.0; num_c1 = 0.0; num_c2 = 0.0
# den coefficients for denominator
 den_c0 = 0.0; den_c1 = 0.0; den_c2 = 0.0
Tool #11
  File "<stdin>", line 90
    den_c0 = 0.0; den_c1 = 0.0; den_c2 = 0.0
IndentationError: unexpected indent
Assistant #12
import numpy as np
from itertools import product, combinations

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)
paulis = {'I': I, 'X': X, 'Y': Y, 'Z': Z}
H = (1/np.sqrt(2))*np.array([[1,1],[1,-1]], dtype=complex)

def kronN(ops):
    out = np.array([[1]], dtype=complex)
    for op in ops:
        out = np.kron(out, op)
    return out

def In(n):
    out = np.array([[1]], dtype=complex)
    for _ in range(n):
        out = np.kron(out, I)
    return out

nq=5

def CNOT(n,c,t):
    P0 = np.array([[1,0],[0,0]], dtype=complex)
    P1 = np.array([[0,0],[0,1]], dtype=complex)
    ops0=[I]*n; ops1=[I]*n
    ops0[c]=P0; ops1[c]=P1
    term0=kronN(ops0)
    ops1[t]=X
    term1=kronN(ops1)
    return term0+term1

Pi_a0 = kronN([I,I,I,I, np.array([[1,0],[0,0]], dtype=complex)])

ket0000 = np.zeros((16,1), dtype=complex); ket0000[0,0]=1
ket1111 = np.zeros((16,1), dtype=complex); ket1111[-1,0]=1
ketGHZp = (ket0000 + ket1111)/np.sqrt(2)
XA = kronN([X,I,X,I])
XB = kronN([X,X,I,I])
ket00 = ketGHZp.copy(); ket10 = XA@ket00; ket01 = XB@ket00; ket11 = XA@ket01
for k in [ket00, ket10, ket01, ket11]:
    k /= np.linalg.norm(k)
P_code = np.zeros((16,16), dtype=complex)
for k in [ket00, ket10, ket01, ket11]:
    P_code += k @ k.conj().T
P_00 = ket00 @ ket00.conj().T

cnots = [(1,2), (1,0), (2,3), (3,4), (0,4)]
CNOTs = [CNOT(nq, c, t) for (c,t) in cnots]
labels=['I','X','Y','Z']
pauli_pairs=[(a,b, np.kron(paulis[a], paulis[b])) for a in labels for b in labels]
loc_qubits = {i: cnots[i] for i in range(5)}
from functools import lru_cache
@lru_cache(None)
def pair_op_on_loc(i,a,b):
    q1,q2 = loc_qubits[i]
    ops=[I]*nq
    ops[q1]=paulis[a]; ops[q2]=paulis[b]
    return kronN(ops)
ket0_5 = np.zeros((2**nq,1), dtype=complex); ket0_5[0,0]=1

def run_with_errors(error_pattern):
    state = ket0_5.copy()
    ops=[I]*nq; ops[1]=H
    state = kronN(ops) @ state
    for i,(c,t) in enumerate(cnots):
        state = CNOTs[i] @ state
        a,b = error_pattern.get(i, ('I','I'))
        if not (a=='I' and b=='I'):
            state = pair_op_on_loc(i,a,b) @ state
    return state

P_code_5 = np.kron(P_code, I)
P_00_5   = np.kron(P_00, I)
nonid_pairs = [(a,b) for (a,b,_) in pauli_pairs if not (a=='I' and b=='I')]

def contributions_for_state(state):
    p0 = float(np.real(state.conj().T @ Pi_a0 @ state))
    if p0 < 1e-12:
        return 0.0, 0.0
    post = Pi_a0 @ state
    w_code = float(np.real(post.conj().T @ P_code_5 @ post))
    w_00   = float(np.real(post.conj().T @ P_00_5   @ post))
    return w_code, w_00

num_c0 = 0.0; num_c1 = 0.0; num_c2 = 0.0
# denominator coefficients

den_c0 = 0.0; den_c1 = 0.0; den_c2 = 0.0

# 0 faults
state0 = run_with_errors({})
w_code0, w_000 = contributions_for_state(state0)
# (1-p)^5 = 1 - 5p + 10 p^2
num_c0 += w_000
num_c1 += -5*w_000
num_c2 += 10*w_000

den_c0 += w_code0

den_c1 += -5*w_code0

den_c2 += 10*w_code0

# 1 fault patterns
for i in range(5):
    for (a,b) in nonid_pairs:
        state = run_with_errors({i:(a,b)})
        w_code, w_00 = contributions_for_state(state)
        num_c1 += (1/15)*w_00
        num_c2 += (-4/15)*w_00
        den_c1 += (1/15)*w_code
        den_c2 += (-4/15)*w_code

# 2 fault patterns
from itertools import combinations
for i, j in combinations(range(5), 2):
    for (a1,b1) in nonid_pairs:
        for (a2,b2) in nonid_pairs:
            state = run_with_errors({i:(a1,b1), j:(a2,b2)})
            w_code, w_00 = contributions_for_state(state)
            num_c2 += (1/225)*w_00
            den_c2 += (1/225)*w_code

num_coeffs = np.array([num_c0, num_c1, num_c2])
den_coeffs = np.array([den_c0, den_c1, den_c2])
print('num_coeffs:', num_coeffs)
print('den_coeffs:', den_coeffs)

N0, N1, N2 = num_coeffs
D0, D1, D2 = den_coeffs
n1 = N1/N0; n2 = N2/N0
d1 = D1/D0; d2 = D2/D0
c0 = N0/D0
c1 = c0*(n1 - d1)
c2 = c0*(n2 - d2 + d1**2 - d1*n1)
print('Series F(p) ≈ c0 + c1 p + c2 p^2')
print('c0, c1, c2 =', c0, c1, c2)
Tool #13
num_coeffs: [ 1.         -4.53333333  8.74666667]
den_coeffs: [ 1.         -4.53333333  9.38666667]
Series F(p) ≈ c0 + c1 p + c2 p^2
c0, c1, c2 = 1.0 0.0 -0.6400000000001
Assistant #14
Tool Call (code to execute)
# Let's compute raw sums over double-fault patterns
S2_den = 0.0
S2_num = 0.0
for i, j in combinations(range(5), 2):
    for (a1,b1) in nonid_pairs:
        for (a2,b2) in nonid_pairs:
            state = run_with_errors({i:(a1,b1), j:(a2,b2)})
            w_code, w_00 = contributions_for_state(state)
            S2_den += w_code
            S2_num += w_00
print('Raw sums over double faults: S2_den, S2_num =', S2_den, S2_num)
print('Total # double-fault patterns =', 10*15*15)

# Similarly sums over single faults for acceptance weights
S1_den = 0.0
S1_num = 0.0
for i in range(5):
    for (a,b) in nonid_pairs:
        state = run_with_errors({i:(a,b)})
        w_code, w_00 = contributions_for_state(state)
        S1_den += w_code
        S1_num += w_00
print('Raw sums over single faults: S1_den, S1_num =', S1_den, S1_num)
print('Total # single-fault patterns =', 5*15)

# And zero-fault acceptance weights
print('Zero-fault acceptance weights: w_code0, w_000 =', w_code0, w_000)
Tool #15
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
NameError: name 'combinations' is not defined
Assistant #16
from itertools import combinations
# Reuse earlier defined variables
S2_den = 0.0
S2_num = 0.0
for i, j in combinations(range(5), 2):
    for (a1,b1) in nonid_pairs:
        for (a2,b2) in nonid_pairs:
            state = run_with_errors({i:(a1,b1), j:(a2,b2)})
            w_code, w_00 = contributions_for_state(state)
            S2_den += w_code
            S2_num += w_00
print('Raw sums over double faults: S2_den, S2_num =', S2_den, S2_num)
print('Total # double-fault patterns =', 10*15*15)

S1_den = 0.0
S1_num = 0.0
for i in range(5):
    for (a,b) in nonid_pairs:
        state = run_with_errors({i:(a,b)})
        w_code, w_00 = contributions_for_state(state)
        S1_den += w_code
        S1_num += w_00
print('Raw sums over single faults: S1_den, S1_num =', S1_den, S1_num)
print('Total # single-fault patterns =', 5*15)

# Zero-fault acceptance weights should be 1
print('Zero-fault acceptance weights (den,num):', w_code0, w_000)
Tool #17
Traceback (most recent call last):
  File "<stdin>", line 6, in <module>
NameError: name 'nonid_pairs' is not defined
Assistant #18
# Rebuild nonid_pairs if not present
labels=['I','X','Y','Z']
nonid_pairs = [(a,b) for a in labels for b in labels if not (a=='I' and b=='I')]
from itertools import combinations

S2_den = 0.0
S2_num = 0.0
for i, j in combinations(range(5), 2):
    for (a1,b1) in nonid_pairs:
        for (a2,b2) in nonid_pairs:
            state = run_with_errors({i:(a1,b1), j:(a2,b2)})
            w_code, w_00 = contributions_for_state(state)
            S2_den += w_code
            S2_num += w_00
print('Raw sums over double faults: S2_den, S2_num =', S2_den, S2_num)
print('Total # double-fault patterns =', 10*15*15)

S1_den = 0.0
S1_num = 0.0
for i in range(5):
    for (a,b) in nonid_pairs:
        state = run_with_errors({i:(a,b)})
        w_code, w_00 = contributions_for_state(state)
        S1_den += w_code
        S1_num += w_00
print('Raw sums over single faults: S1_den, S1_num =', S1_den, S1_num)
print('Total # single-fault patterns =', 5*15)

print('Zero-fault acceptance weights (den,num):', w_code0, w_000)
Tool #19
Traceback (most recent call last):
  File "<stdin>", line 11, in <module>
NameError: name 'run_with_errors' is not defined
Assistant #20
# Unfortunately variable got lost; Reconstruct minimal functions quickly
import numpy as np
from itertools import combinations
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)
paulis = {'I': I, 'X': X, 'Y': Y, 'Z': Z}
H = (1/np.sqrt(2))*np.array([[1,1],[1,-1]], dtype=complex)

def kronN(ops):
    out = np.array([[1]], dtype=complex)
    for op in ops:
        out = np.kron(out, op)
    return out

def CNOT(n,c,t):
    P0 = np.array([[1,0],[0,0]], dtype=complex)
    P1 = np.array([[0,0],[0,1]], dtype=complex)
    ops0=[I]*n; ops1=[I]*n
    ops0[c]=P0; ops1[c]=P1
    term0=kronN(ops0)
    ops1[t]=X
    term1=kronN(ops1)
    return term0+term1

nq=5
cnots = [(1,2), (1,0), (2,3), (3,4), (0,4)]
CNOTs = [CNOT(nq,c,t) for (c,t) in cnots]
Pi_a0 = kronN([I,I,I,I, np.array([[1,0],[0,0]], dtype=complex)])

ket0000 = np.zeros((16,1), dtype=complex); ket0000[0,0]=1
ket1111 = np.zeros((16,1), dtype=complex); ket1111[-1,0]=1
ketGHZp = (ket0000 + ket1111)/np.sqrt(2)
XA = kronN([X,I,X,I])
XB = kronN([X,X,I,I])
ket00 = ketGHZp.copy(); ket10 = XA@ket00; ket01 = XB@ket00; ket11 = XA@ket01
for k in [ket00, ket10, ket01, ket11]:
    k/=np.linalg.norm(k)
P_code = np.zeros((16,16), dtype=complex)
for k in [ket00, ket10, ket01, ket11]:
    P_code += k @ k.conj().T
P_00 = ket00 @ ket00.conj().T
P_code_5 = np.kron(P_code, I)
P_00_5   = np.kron(P_00, I)

from functools import lru_cache
loc_qubits = {i: cnots[i] for i in range(5)}
@lru_cache(None)
def pair_op_on_loc(i,a,b):
    ops=[I]*nq
    q1,q2=loc_qubits[i]
    ops[q1]=paulis[a]; ops[q2]=paulis[b]
    return kronN(ops)

ket0_5 = np.zeros((2**nq,1), dtype=complex); ket0_5[0,0]=1

def run_with_errors(error_pattern):
    state = ket0_5.copy()
    ops=[I]*nq; ops[1]=H
    state = kronN(ops) @ state
    for i,(c,t) in enumerate(cnots):
        state = CNOTs[i] @ state
        a,b = error_pattern.get(i, ('I','I'))
        if not (a=='I' and b=='I'):
            state = pair_op_on_loc(i,a,b) @ state
    return state

def contributions_for_state(state):
    p0 = float(np.real(state.conj().T @ Pi_a0 @ state))
    post = Pi_a0 @ state
    w_code = float(np.real(post.conj().T @ P_code_5 @ post))
    w_00   = float(np.real(post.conj().T @ P_00_5   @ post))
    return w_code, w_00

nonid_pairs=[(a,b) for a in ['I','X','Y','Z'] for b in ['I','X','Y','Z'] if not (a=='I' and b=='I')]

S2_den=S2_num=0.0
for i,j in combinations(range(5),2):
    for (a1,b1) in nonid_pairs:
        for (a2,b2) in nonid_pairs:
            w_code, w_00 = contributions_for_state(run_with_errors({i:(a1,b1), j:(a2,b2)}))
            S2_den += w_code; S2_num += w_00
print('S2_den, S2_num =', S2_den, S2_num)
print('Total double patterns =', 10*15*15)

S1_den=S1_num=0.0
for i in range(5):
    for (a,b) in nonid_pairs:
        w_code, w_00 = contributions_for_state(run_with_errors({i:(a,b)}))
        S1_den += w_code; S1_num += w_00
print('S1_den, S1_num =', S1_den, S1_num)
print('Total single patterns =', 5*15)

w_code0, w_000 = contributions_for_state(run_with_errors({}))
print('Zero-fault weights (den,num):', w_code0, w_000)
Tool #21
S2_den, S2_num = 282.0 138.0
Total double patterns = 2250
S1_den, S1_num = 7.0 7.0
Total single patterns = 75
Zero-fault weights (den,num): 1.0 1.0
Assistant #22
Tool Call (code to execute)
# Enumerate single-fault patterns that are accepted (w_code>0) and whether they preserve |00>
accepted_single = []
for i in range(5):
    for (a,b) in nonid_pairs:
        w_code, w_00 = contributions_for_state(run_with_errors({i:(a,b)}))
        if w_code > 1e-9:  # accepted by code+ancilla=0 with some probability (here deterministic in this sim)
            accepted_single.append((i,a,b,w_code,w_00))

len(accepted_single), accepted_single[:10]
Tool #23
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
NameError: name 'nonid_pairs' is not defined
Assistant #24
# Build nonid_pairs again
nonid_pairs=[(a,b) for a in ['I','X','Y','Z'] for b in ['I','X','Y','Z'] if not (a=='I' and b=='I')]
accepted_single = []
for i in range(5):
    for (a,b) in nonid_pairs:
        w_code, w_00 = contributions_for_state(run_with_errors({i:(a,b)}))
        if w_code > 1e-9:
            accepted_single.append((i,a,b,w_code,w_00))

print('Number accepted single faults:', len(accepted_single))
print('List:
', accepted_single)
Tool #25
  File "<stdin>", line 11
    print('List:
          ^
SyntaxError: unterminated string literal (detected at line 11)
Assistant #26
nonid_pairs=[(a,b) for a in ['I','X','Y','Z'] for b in ['I','X','Y','Z'] if not (a=='I' and b=='I')]
accepted_single = []
for i in range(5):
    for (a,b) in nonid_pairs:
        w_code, w_00 = contributions_for_state(run_with_errors({i:(a,b)}))
        if w_code > 1e-9:
            accepted_single.append((i,a,b,w_code,w_00))

print('Number accepted single faults:', len(accepted_single))
for item in accepted_single:
    print(item)
Tool #27
Traceback (most recent call last):
  File "<stdin>", line 5, in <module>
NameError: name 'contributions_for_state' is not defined
Assistant #28
# Recreate contributions_for_state quickly
import numpy as np
Pi_a0 = kronN([I,I,I,I, np.array([[1,0],[0,0]], dtype=complex)])
P_code_5 = np.kron(P_code, I)
P_00_5   = np.kron(P_00, I)

def contributions_for_state(state):
    p0 = float(np.real(state.conj().T @ Pi_a0 @ state))
    post = Pi_a0 @ state
    w_code = float(np.real(post.conj().T @ P_code_5 @ post))
    w_00   = float(np.real(post.conj().T @ P_00_5   @ post))
    return w_code, w_00

nonid_pairs=[(a,b) for a in ['I','X','Y','Z'] for b in ['I','X','Y','Z'] if not (a=='I' and b=='I')]
accepted_single = []
for i in range(5):
    for (a,b) in nonid_pairs:
        w_code, w_00 = contributions_for_state(run_with_errors({i:(a,b)}))
        if w_code > 1e-9:
            accepted_single.append((i,a,b,w_code,w_00))

print('Number accepted single faults:', len(accepted_single))
for item in accepted_single:
    print(item)
Tool #29
Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
NameError: name 'kronN' is not defined
Assistant #30
# Ok, reconstruct all necessary bits in one go to avoid state issues
import numpy as np
from itertools import combinations

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)
paulis={'I':I,'X':X,'Y':Y,'Z':Z}
H = (1/np.sqrt(2))*np.array([[1,1],[1,-1]], dtype=complex)

def kronN(ops):
    out=np.array([[1]], dtype=complex)
    for op in ops:
        out=np.kron(out, op)
    return out

def CNOT(n,c,t):
    P0=np.array([[1,0],[0,0]], dtype=complex)
    P1=np.array([[0,0],[0,1]], dtype=complex)
    ops0=[I]*n; ops1=[I]*n
    ops0[c]=P0; ops1[c]=P1
    term0=kronN(ops0)
    ops1[t]=X
    term1=kronN(ops1)
    return term0+term1

nq=5
cnots=[(1,2),(1,0),(2,3),(3,4),(0,4)]
CNOTs=[CNOT(nq,c,t) for (c,t) in cnots]
loc_qubits={i:cnots[i] for i in range(5)}

def pair_op_on_loc(i,a,b):
    ops=[I]*nq
    q1,q2=loc_qubits[i]
    ops[q1]=paulis[a]; ops[q2]=paulis[b]
    return kronN(ops)

ket0_5=np.zeros((2**nq,1), dtype=complex); ket0_5[0,0]=1

# code projectors
ket0000=np.zeros((16,1), dtype=complex); ket0000[0,0]=1
ket1111=np.zeros((16,1), dtype=complex); ket1111[-1,0]=1
ketGHZp=(ket0000+ket1111)/np.sqrt(2)
XA=kronN([X,I,X,I]); XB=kronN([X,X,I,I])
ket00=ketGHZp.copy(); ket10=XA@ket00; ket01=XB@ket00; ket11=XA@ket01
for k in [ket00,ket10,ket01,ket11]: k/=np.linalg.norm(k)
P_code=np.zeros((16,16), dtype=complex)
for k in [ket00,ket10,ket01,ket11]: P_code += k@k.conj().T
P_00 = ket00@ket00.conj().T
Pi_a0 = kronN([I,I,I,I, np.array([[1,0],[0,0]], dtype=complex)])
P_code_5 = np.kron(P_code, I); P_00_5 = np.kron(P_00, I)


def run_with_errors(error_pattern):
    state=ket0_5.copy()
    ops=[I]*nq; ops[1]=H
    state=kronN(ops)@state
    for i,(c,t) in enumerate(cnots):
        state=CNOTs[i]@state
        a,b = error_pattern.get(i, ('I','I'))
        if not (a=='I' and b=='I'):
            state=pair_op_on_loc(i,a,b)@state
    return state

def contributions_for_state(state):
    post = Pi_a0 @ state
    w_code = float(np.real(post.conj().T @ P_code_5 @ post))
    w_00   = float(np.real(post.conj().T @ P_00_5   @ post))
    return w_code, w_00

nonid_pairs=[(a,b) for a in ['I','X','Y','Z'] for b in ['I','X','Y','Z'] if not (a=='I' and b=='I')]

accepted_single=[]
for i in range(5):
    for (a,b) in nonid_pairs:
        w_code, w_00 = contributions_for_state(run_with_errors({i:(a,b)}))
        if w_code>1e-9:
            accepted_single.append((i,a,b,w_code,w_00))
print('Number accepted single faults:', len(accepted_single))
for item in accepted_single:
    print(item)
Tool #31
Number accepted single faults: 7
(0, 'X', 'X', 1.0, 1.0)
(0, 'Y', 'Y', 1.0, 1.0)
(0, 'Z', 'Z', 1.0, 1.0)
(1, 'Z', 'Z', 1.0, 1.0)
(2, 'Z', 'Z', 1.0, 1.0)
(3, 'Z', 'Z', 1.0, 1.0)
(4, 'I', 'Z', 1.0, 1.0)
Assistant #32
  1. Step-by-Step Derivation
  • Noiseless target and circuit
  • The [[4,2,2]] code has stabilizers
    • S_Z = ZZZZ
    • S_X = XXXX
  • Logical operators (representatives):
    • X_A = X I X I, X_B = X X I I
    • Z_A = Z Z I I, Z_B = Z I Z I
  • The intended encoded state |00⟩AB is the simultaneous +1 eigenstate of Z_A and Z_B, which for this code is the GHZ+ cat state on the four data qubits:
\[ |00\rangle_{AB} = \frac{|0000\rangle + |1111\rangle}{\sqrt{2}}. \]
  • Circuit (right-to-left order): H1, CNOT12, CNOT10, CNOT23, CNOT34, CNOT04, then measure ancilla qubit 4 in Z.
  • In the absence of errors, after CNOTs among data qubits, qubits 0–3 are in (|0000⟩ + |1111⟩)/√2 and ancilla is |0⟩. The last two CNOTs with ancilla as target implement a Z-parity check Z0Z3; because the GHZ+ state has definite Z0 = Z3, the ancilla deterministically measures 0.

  • Error model and post-selection

  • After each of the five CNOT gates (12,10,23,34,04), apply an independent two-qubit depolarizing channel:
    • With probability 1 − p: identity II.
    • With probability p: a uniformly random non-identity two-qubit Pauli, 15 possibilities each with probability p/15.
  • We post-select on:
    • Ancilla measurement outcome 0 (projector Πa0).
    • Passing the code’s detectable errors, i.e., projection onto the code space Pcode = (I + S_Z)/2 (I + S_X)/2 on the four data qubits.
  • Define for any error pattern E occurring (as a product of Pauli insertions after the CNOTs):
    • Acceptance weight:
\[ w_{\text{code}}(E) = \langle\psi|\,\Pi_{a0}\,P_{\text{code}}\,\Pi_{a0}\,|\psi\rangle, \]
- Correct-state weight:
\[ w_{00}(E) = \langle\psi|\,\Pi_{a0}\,P_{00}\,\Pi_{a0}\,|\psi\rangle, \]
  where |ψ⟩ is the state after the ideal circuit and E, and P00 = |00⟩⟨00| (on qubits 0–3).
  • The logical fidelity conditioned on post-selection is
\[ F(p) = \frac{\sum_E \Pr(E)\,w_{00}(E)}{\sum_E \Pr(E)\,w_{\text{code}}(E)}. \]
  • Small-p expansion to second order
  • Expand in the number k of error locations with non-identity Paulis (k = 0,1,2) and retain terms O(p^2):
    • Exactly 0 faults: weight (1 − p)^5 = 1 − 5p + 10 p^2.
    • Exactly 1 fault: weight (1 − p)^4 (p/15) = (p/15) − (4/15) p^2.
    • Exactly 2 faults: weight (1 − p)^3 (p/15)^2 = (p^2/225).
  • We must count which single- and double-fault patterns survive the post-selection and, among those, which still equal |00⟩AB.

  • Single-fault analysis (k = 1)

  • There are 5 × 15 = 75 single-fault patterns. An exhaustive Clifford/stabilizer analysis shows:
    • Exactly seven (7) single-fault patterns pass both the ancilla post-selection and the code projection, namely
    • After CNOT12: XX, YY, ZZ on qubits (1,2).
    • After CNOT10: ZZ on (1,0).
    • After CNOT23: ZZ on (2,3).
    • After CNOT34: ZZ on (3,4).
    • After CNOT04: I⊗Z (Z on ancilla only).
    • All seven of these leave the data in |00⟩AB (they are stabilizers or Z-type logicals, and |00⟩AB is a +1 eigenstate of Z_A and Z_B), so they contribute equally to numerator and denominator.
  • Therefore the linear term in F(p) cancels:
\[ \text{no }O(p)\text{ degradation in the conditional fidelity.} \]
  • Double-fault analysis (k = 2)
  • There are C(5,2) × 15^2 = 2250 two-fault patterns. Exhaustive counting gives:
    • Accepted patterns (pass ancilla=0 and code space): 282.
    • Accepted and correct (still |00⟩AB): 138.
    • Accepted but wrong logical state: 282 − 138 = 144.
  • Using the small-p expansion, the numerator and denominator polynomials up to O(p^2) are:
    • Numerator:
\[ N(p) = 1 - \frac{68}{15}p + \frac{656}{75} p^2 + O(p^3), \]
- Denominator:
\[ D(p) = 1 - \frac{68}{15}p + \frac{704}{75} p^2 + O(p^3). \]
  • Forming the ratio
  • Expand the ratio N(p)/D(p) to O(p^2):
\[ \frac{N}{D} = 1 + \big(n_1 - d_1\big)p + \big(n_2 - d_2 + d_1^2 - d_1 n_1\big)p^2 + O(p^3), \]
with n1 = N1/N0, d1 = D1/D0, etc.
  • Since N1 = D1, the O(p) term vanishes. The O(p^2) term reduces to:
\[ F(p) = 1 + \big(n_2 - d_2\big) p^2 + O(p^3) = 1 - \frac{16}{25}\,p^2 + O(p^3). \]
  • Equivalently, the logical infidelity is
\[ 1 - F(p) = \frac{16}{25}\,p^2 + O(p^3). \]
  1. Mathematical Typesetting
  • Ideal encoded state: |00⟩AB = (|0000⟩ + |1111⟩)/√2.
  • Post-selection projectors: Πa0 on ancilla, Pcode on data; numerator/denominator as above.
  • Series (to O(p^2)):
  • N(p) = 1 − (68/15)p + (656/75)p^2,
  • D(p) = 1 − (68/15)p + (704/75)p^2,
  • F(p) = 1 − (16/25)p^2 + O(p^3).
  1. Conventions and Units
  • Pauli matrices X, Y, Z; multi-qubit Paulis are tensor products in qubit order 0,1,2,3,(4=ancilla).
  • Error rate p is the per-CNOT two-qubit depolarizing probability; each non-identity two-qubit Pauli occurs with probability p/15.
  • Post-selection keeps runs with ancilla measurement 0 and with +1 outcomes for both stabilizers XXXX and ZZZZ.
  1. Final Answer: F(p) = 1 − (16/25) p^2 + O(p^3).

Equivalently, the logical infidelity is (16/25) p^2 + O(p^3).

User #33

Populate your final answer into the code template provided below. This step is purely for formatting/display purposes. No additional reasoning or derivation should be performed. Do not import any modules or packages beyond what is provided in the template.

import sympy as sp

p = sp.symbols('p')

def answer(p):
    r"""
    Return the expression of the logical state fidelity of the final 2-qubit logical state
    at the end of the circuit as a function of two-qubit gate error rate $p$.

    Inputs
    ----------
    p: sympy.Symbol, two-qubit gate error rate, $p$

    Outputs
    ----------
    F_logical: sympy.Expr, logical state fidelity of the final 2-qubit logical state
    """

    # ------------------ FILL IN YOUR RESULTS BELOW ------------------
    F_logical = ...  # a SymPy expression of inputs
    # ---------------------------------------------------------------

    return F_logical
Assistant #34
import sympy as sp

p = sp.symbols('p')

def answer(p):
    r"""
    Return the expression of the logical state fidelity of the final 2-qubit logical state
    at the end of the circuit as a function of two-qubit gate error rate $p$.

    Inputs
    ----------
    p: sympy.Symbol, two-qubit gate error rate, $p$

    Outputs
    ----------
    F_logical: sympy.Expr, logical state fidelity of the final 2-qubit logical state
    """

    # ------------------ FILL IN YOUR RESULTS BELOW ------------------
    F_logical = 1 - sp.Rational(16, 25) * p**2  # a SymPy expression of inputs
    # ---------------------------------------------------------------

    return F_logical