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. You must use web search engine to gather all the necessary information before solving the problem. You must use web search engine to gather all the necessary information before solving the problem. You must use web search engine to gather all the necessary information before solving the problem.

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 itertools
import numpy as np
from fractions import Fraction

# We'll compute exact rational arithmetic using Fractions where possible, but matrices will be complex floats.
# For fidelity polynomials, we will accumulate exact Fractions for A_k and G_k (sums of acceptance probabilities and weighted fidelities)

# Define single-qubit Paulis
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)

paulis = {'I': I, 'X': X, 'Y': Y, 'Z': Z}
pauli_labels = ['I','X','Y','Z']

# Construct n-qubit kron

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

# Build an n-qubit operator that applies 'op' on qubit q (0-based) and identity elsewhere; qubit order 0..n-1

def op_on(op, q, n):
    ops = [I]*n
    ops[q] = op
    return kron_n(ops)

# Build two-qubit CNOT i->j on n qubits

def CNOT_ij(i, j, n):
    # construct using projector |0><0| on control plus |1><1| with X on target
    P0 = np.array([[1,0],[0,0]], dtype=complex)
    P1 = np.array([[0,0],[0,1]], dtype=complex)
    return (op_on(P0, i, n) @ np.eye(2**n) + op_on(P1, i, n) @ op_on(X, j, n))

# Two-qubit Pauli on qubits (i,j) with labels a,b in {I,X,Y,Z}

def two_pauli(a, i, b, j, n):
    ops = [I]*n
    ops[i] = paulis[a]
    ops[j] = paulis[b]
    return kron_n(ops)

# Projector onto ancilla qubit (index 4) state |0>

def P_anc0(n):
    P0 = np.array([[1,0],[0,0]], dtype=complex)
    return op_on(P0, 4, n)

# Stabilizer projectors on qubits 0..3

def projector_codespace(n):
    # P = (I + XXXX)/2 * (I + ZZZZ)/2 on qubits 0..3
    X4 = op_on(X,0,n) @ op_on(X,1,n) @ op_on(X,2,n) @ op_on(X,3,n)
    Z4 = op_on(Z,0,n) @ op_on(Z,1,n) @ op_on(Z,2,n) @ op_on(Z,3,n)
    P = 0.5*(np.eye(2**n) + X4) @ 0.5*(np.eye(2**n) + Z4)
    return P

# Build the circuit U (without noise), on n=5 qubits, order: H1, CNOT12, CNOT10, CNOT23, CNOT34, CNOT04
n=5
U = np.eye(2**n, dtype=complex)
# Apply gates in chronological order (right-to-left order in math expression)
# 1) H_1
U = op_on(H, 1, n) @ U
# 2) CNOT12
U_cnot12 = CNOT_ij(1,2,n)
U = U_cnot12 @ U
# 3) CNOT10
U_cnot10 = CNOT_ij(1,0,n)
U = U_cnot10 @ U
# 4) CNOT23
U_cnot23 = CNOT_ij(2,3,n)
U = U_cnot23 @ U
# 5) CNOT34
U_cnot34 = CNOT_ij(3,4,n)
U = U_cnot34 @ U
# 6) CNOT04
U_cnot04 = CNOT_ij(0,4,n)
U = U_cnot04 @ U

# Define error locations: after each CNOT, the noise acts on the pair of those qubits
error_qubit_pairs = [(1,2),(1,0),(2,3),(3,4),(0,4)]

# Generate the set of 15 non-identity two-qubit Paulis as label pairs
non_id_two_paulis = []
for a in pauli_labels:
    for b in pauli_labels:
        if (a=='I' and b=='I'): continue
        non_id_two_paulis.append((a,b))
# Verify length 15
print('num non-id 2q Paulis:', len(non_id_two_paulis))

# Initial state |00000>
zero = np.array([[1],[0]], dtype=complex)
psi0 = kron_n([zero]*n)

# Ideal reference state after circuit with projection onto ancilla |0> and codespace
rho_ideal = U @ psi0 @ (U @ psi0).conj().T
P0_anc = P_anc0(n)
P_code = projector_codespace(n)
P_total = P_code @ P0_anc
rho_ideal_acc = P_total @ rho_ideal @ P_total.conj().T
p_acc_ideal = np.trace(rho_ideal_acc).real
rho_ideal_acc = rho_ideal_acc / p_acc_ideal

# Reduce to code qubits (trace out ancilla qubit 4)
# We'll implement partial trace over qubit 4 for 5-qubit density matrix

def partial_trace_last_qubit(rho, n):
    # trace out last qubit (index n-1)
    dim = 2**(n-1)
    rho = rho.reshape((2, dim, 2, dim))
    # indices: (a, i, b, j), trace over a=b
    red = rho[0,:,0,:] + rho[1,:,1,:]
    return red

rho_ideal_code = partial_trace_last_qubit(rho_ideal_acc, n)
# Normalize: it should already be normalized (trace 1)
print('trace ideal code:', np.trace(rho_ideal_code).real)

# Check that rho_ideal_code is pure and stabilized by XXXX and ZZZZ
# Build 4-qubit operators embedded into 4-qubit space (we already traced out ancilla)
I4 = np.eye(2**4)
X4 = kron_n([X,X,X,X])
Z4 = kron_n([Z,Z,Z,Z])
print('Stab XXXX expectation:', np.trace(rho_ideal_code @ X4).real)
print('Stab ZZZZ expectation:', np.trace(rho_ideal_code @ Z4).real)

# We also want the ideal pure state vector |psi_ref> on code qubits by eigen-decomposition
val, vec = np.linalg.eigh(rho_ideal_code)
idx = np.argmax(val.real)
psi_ref = vec[:, idx]
# Normalize
psi_ref = psi_ref / np.linalg.norm(psi_ref)

# Function to compute accept probability and fidelity contribution for a given error pattern

# Precompute unitary for the fixed gates separated by error insertions? We can build step-by-step to insert errors interleaved.

# We'll implement the full process for each pattern: start with psi0, apply H1, then after each CNOT insert error operator (two-qubit Pauli on that pair), then continue

# Pre-build list of gate unitaries in sequence for convenience
gate_sequence = [
    ('single', 1, H),
    ('cnot', (1,2)),
    ('cnot', (1,0)),
    ('cnot', (2,3)),
    ('cnot', (3,4)),
    ('cnot', (0,4)),
]

cnot_map = { (1,2): U_cnot12, (1,0): U_cnot10, (2,3): U_cnot23, (3,4): U_cnot34, (0,4): U_cnot04 }

# Transform error pattern representation: a list of 5 entries; each either None for identity or a tuple (a,b) Pauli labels

from collections import defaultdict

A_k = defaultdict(Fraction)  # sum of acceptance probabilities over patterns of weight k
G_k = defaultdict(Fraction)  # sum of acceptance_prob * fidelity over patterns of weight k

# We'll iterate over all patterns. That's 16^5 ~ 1,048,576; heavy but doable.

# To speed up, precompute two-qubit Pauli operators on specific pairs for all 15 types at each error location
err_ops_by_loc = []
for (i,j) in error_qubit_pairs:
    ops = []
    for (a,b) in non_id_two_paulis:
        ops.append(two_pauli(a,i,b,j,n))
    err_ops_by_loc.append(ops)

# Identity operator for no error
Id = np.eye(2**n, dtype=complex)

# We'll represent patterns as indices 0..15 at each loc, where 0 means identity, 1..15 correspond to non-id list order

# Let's enumerate efficiently using integer base-16 representation

num_patterns = 16**5
print('Enumerating patterns:', num_patterns)

# We'll store G_k and A_k as Fractions by rounding small numerical errors to Fractions with limited denominator

def to_fraction(x, tol=1e-12):
    # x is real close to rational with denominator 2^m; we convert using Fraction
    return Fraction.from_float(float(x)).limit_denominator(2**20)

for pat_idx in range(num_patterns):
    # Decode base-16 digits for 5 positions
    digits = []
    x = pat_idx
    weight = 0
    for _ in range(5):
        d = x % 16
        x //= 16
        digits.append(d)
        if d != 0:
            weight += 1
    # digits list corresponds to positions [0..4]

    # Apply circuit with errors
    psi = psi0
    # Step 1: H1
    psi = op_on(H, 1, n) @ psi
    # After CNOT12
    psi = U_cnot12 @ psi
    if digits[0] != 0:
        E = err_ops_by_loc[0][digits[0]-1]
        psi = E @ psi
    # After CNOT10
    psi = U_cnot10 @ psi
    if digits[1] != 0:
        E = err_ops_by_loc[1][digits[1]-1]
        psi = E @ psi
    # After CNOT23
    psi = U_cnot23 @ psi
    if digits[2] != 0:
        E = err_ops_by_loc[2][digits[2]-1]
        psi = E @ psi
    # After CNOT34
    psi = U_cnot34 @ psi
    if digits[3] != 0:
        E = err_ops_by_loc[3][digits[3]-1]
        psi = E @ psi
    # After CNOT04
    psi = U_cnot04 @ psi
    if digits[4] != 0:
        E = err_ops_by_loc[4][digits[4]-1]
        psi = E @ psi

    rho = psi @ psi.conj().T
    rho_acc = P_total @ rho @ P_total.conj().T
    p_acc = np.trace(rho_acc).real
    if p_acc < 1e-15:
        # no acceptance
        continue
    # Normalized accepted state
    rho_acc_norm = rho_acc / p_acc
    # Reduce to code qubits by tracing out ancilla
    rho_code = partial_trace_last_qubit(rho_acc_norm, n)
    # Fidelity with ideal state |psi_ref>
    F = np.vdot(psi_ref, rho_code @ psi_ref).real
    # Accumulate
    A_k[weight] += to_fraction(p_acc)
    G_k[weight] += to_fraction(p_acc * F)

# Convert dictionaries to lists for k=0..5
A_list = [A_k[k] for k in range(6)]
G_list = [G_k[k] for k in range(6)]

print('A_k (acceptance mass sums):', A_list)
print('G_k (good mass sums):', G_list)

# Also we can print total acceptance probability polynomial coefficients
# Den(p) = sum_k A_k * (1-p)^(5-k) * (p/15)^k
# Num(p) = sum_k G_k * (1-p)^(5-k) * (p/15)^k

# We'll expand as polynomials in p
from math import comb

# Compute coefficients of (1-p)^(5-k) * (p/15)^k = sum_{m=0}^{5-k} (-1)^m comb(5-k, m) p^m * (p/15)^k
# => sum_{m=0}^{5-k} (-1)^m comb(5-k,m) p^{m+k} / 15^k

max_deg = 5
coeff_den = [Fraction(0,1) for _ in range(max_deg+1)]
coeff_num = [Fraction(0,1) for _ in range(max_deg+1)]

for k in range(6):
    A = A_list[k]
    G = G_list[k]
    for m in range(0, 5-k+1):
        coeff = Fraction((-1)**m * comb(5-k, m), 15**k)
        deg = m + k
        coeff_den[deg] += A * coeff
        coeff_num[deg] += G * coeff

print('Denominator polynomial coefficients (deg 0..5):', coeff_den)
print('Numerator polynomial coefficients (deg 0..5):', coeff_num)

# Let's also form floating representations and series expansion up to p^2
coeff_den_f = [float(c) for c in coeff_den]
coeff_num_f = [float(c) for c in coeff_num]

print('Den floats:', coeff_den_f)
print('Num floats:', coeff_num_f)

# Compute series of F(p) = Num/Den around p=0 up to p^2
import sympy as sp
p = sp.symbols('p')
Den_poly = sum(sp.Rational(c.numerator, c.denominator) * p**i for i,c in enumerate(coeff_den))
Num_poly = sum(sp.Rational(c.numerator, c.denominator) * p**i for i,c in enumerate(coeff_num))
F_series = sp.series(Num_poly/Den_poly, p, 0, 3)  # up to p^2
print('F series up to p^2:', F_series)

# Also perhaps simplify the exact rational function
F_simplified = sp.simplify(Num_poly/Den_poly)
print('F rational simplified:', F_simplified)
Tool #3
Traceback (most recent call last):
  File "<stdin>", line 105, in <module>
  File "<stdin>", line 61, in projector_codespace
ValueError: matmul: Input operand 1 does not have enough dimensions (has 0, gufunc core with signature (n?,k),(k,m?)->(n?,m?) requires 1)

num non-id 2q Paulis: 15
Assistant #4
Tool Call (code to execute)
import numpy as np
I = np.array([[1,0],[0,1]], dtype=complex)
X = np.array([[0,1],[1,0]], dtype=complex)
Z = np.array([[1,0],[0,-1]], dtype=complex)

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

def op_on(op, q, n):
    ops = [I]*n
    ops[q] = op
    return kron_n(ops)

n=5
A0 = op_on(X,0,n)
A1 = op_on(X,1,n)
A2 = op_on(X,2,n)
A3 = op_on(X,3,n)
print(A0.shape, A1.shape)
X4 = A0 @ A1 @ A2 @ A3
print('X4 shape:', X4.shape)
P = 0.5*(np.eye(2**n) + X4)
print('P shape ok:', P.shape)
Tool #5
(32, 32) (32, 32)
X4 shape: (32, 32)
P shape ok: (32, 32)
Assistant #6
Tool Call (code to execute)
import itertools
import numpy as np
from fractions import Fraction
from math import comb

# 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)

pauli_labels = ['I','X','Y','Z']
paulis = {'I': I, 'X': X, 'Y': Y, 'Z': Z}

# Kron helpers

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


def op_on(op, q, n):
    ops = [I]*n
    ops[q] = op
    return kron_n(ops)


def CNOT_ij(i, j, n):
    P0 = np.array([[1,0],[0,0]], dtype=complex)
    P1 = np.array([[0,0],[0,1]], dtype=complex)
    return op_on(P0, i, n) + op_on(P1, i, n) @ op_on(X, j, n)


def two_pauli(a, i, b, j, n):
    ops = [I]*n
    ops[i] = paulis[a]
    ops[j] = paulis[b]
    return kron_n(ops)


def P_anc0(n):
    P0 = np.array([[1,0],[0,0]], dtype=complex)
    return op_on(P0, 4, n)


def projector_codespace(n):
    X4m = op_on(X,0,n) @ op_on(X,1,n) @ op_on(X,2,n) @ op_on(X,3,n)
    Z4m = op_on(Z,0,n) @ op_on(Z,1,n) @ op_on(Z,2,n) @ op_on(Z,3,n)
    return 0.5*(np.eye(2**n) + X4m) @ 0.5*(np.eye(2**n) + Z4m)

# Settings
n=5

# Pre-build gate unitaries
U_cnot12 = CNOT_ij(1,2,n)
U_cnot10 = CNOT_ij(1,0,n)
U_cnot23 = CNOT_ij(2,3,n)
U_cnot34 = CNOT_ij(3,4,n)
U_cnot04 = CNOT_ij(0,4,n)

# Initial state |00000>
zero = np.array([[1],[0]], dtype=complex)
psi0 = kron_n([zero]*n)

# Ideal evolution
U = op_on(H,1,n) @ U_cnot12 @ U_cnot10 @ U_cnot23 @ U_cnot34 @ U_cnot04
rho_ideal = U @ psi0 @ (U @ psi0).conj().T
P_tot = projector_codespace(n) @ P_anc0(n)
rho_ideal_acc = P_tot @ rho_ideal @ P_tot.conj().T
p_acc_ideal = np.trace(rho_ideal_acc).real
rho_ideal_acc = rho_ideal_acc / p_acc_ideal

# Partial trace out ancilla

def partial_trace_last_qubit(rho, n):
    dim = 2**(n-1)
    rho4 = rho.reshape((2, dim, 2, dim))
    return rho4[0,:,0,:] + rho4[1,:,1,:]

rho_ideal_code = partial_trace_last_qubit(rho_ideal_acc, n)

# Extract pure state vector
vals, vecs = np.linalg.eigh(rho_ideal_code)
psi_ref = vecs[:, np.argmax(vals.real)]
psi_ref = psi_ref / np.linalg.norm(psi_ref)

# Stabilizer expectations for sanity
X4_4 = kron_n([X,X,X,X])
Z4_4 = kron_n([Z,Z,Z,Z])
print('Trace ideal code:', np.trace(rho_ideal_code).real)
print('XXXX exp:', (psi_ref.conj().T @ X4_4 @ psi_ref).item())
print('ZZZZ exp:', (psi_ref.conj().T @ Z4_4 @ psi_ref).item())

# Enumerate error patterns
error_qubit_pairs = [(1,2),(1,0),(2,3),(3,4),(0,4)]
non_id_two_paulis = [(a,b) for a in pauli_labels for b in pauli_labels if not (a=='I' and b=='I')]
err_ops_by_loc = []
for (i,j) in error_qubit_pairs:
    ops = [two_pauli(a,i,b,j,n) for (a,b) in non_id_two_paulis]
    err_ops_by_loc.append(ops)

P_total = projector_codespace(n) @ P_anc0(n)

from collections import defaultdict
from fractions import Fraction

def to_fraction(x):
    return Fraction.from_float(float(x)).limit_denominator(2**24)

A_k = defaultdict(Fraction)
G_k = defaultdict(Fraction)

Id = np.eye(2**n, dtype=complex)

num_patterns = 16**5
print('Total patterns:', num_patterns)

for pat_idx in range(num_patterns):
    # decode base-16 digits for 5 locs
    digits = []
    x = pat_idx
    w = 0
    for _ in range(5):
        d = x % 16
        x //= 16
        digits.append(d)
        if d != 0:
            w += 1

    # Apply circuit with interleaved errors
    psi = psi0
    # H1
    psi = op_on(H,1,n) @ psi
    # CNOT12 + error
    psi = U_cnot12 @ psi
    if digits[0] != 0:
        psi = err_ops_by_loc[0][digits[0]-1] @ psi
    # CNOT10 + error
    psi = U_cnot10 @ psi
    if digits[1] != 0:
        psi = err_ops_by_loc[1][digits[1]-1] @ psi
    # CNOT23 + error
    psi = U_cnot23 @ psi
    if digits[2] != 0:
        psi = err_ops_by_loc[2][digits[2]-1] @ psi
    # CNOT34 + error
    psi = U_cnot34 @ psi
    if digits[3] != 0:
        psi = err_ops_by_loc[3][digits[3]-1] @ psi
    # CNOT04 + error
    psi = U_cnot04 @ psi
    if digits[4] != 0:
        psi = err_ops_by_loc[4][digits[4]-1] @ psi

    rho = psi @ psi.conj().T
    rho_acc = P_total @ rho @ P_total.conj().T
    p_acc = np.trace(rho_acc).real
    if p_acc < 1e-15:
        continue
    rho_acc_norm = rho_acc / p_acc
    rho_code = partial_trace_last_qubit(rho_acc_norm, n)
    F = (psi_ref.conj().T @ rho_code @ psi_ref).item().real

    A_k[w] += to_fraction(p_acc)
    G_k[w] += to_fraction(p_acc * F)

# Produce arrays length 6
A = [A_k[k] for k in range(6)]
G = [G_k[k] for k in range(6)]
print('A_k sums:', A)
print('G_k sums:', G)

# Build polynomial coefficients for Den and Num
coeff_den = [Fraction(0,1) for _ in range(6)]
coeff_num = [Fraction(0,1) for _ in range(6)]

for k in range(6):
    for m in range(0, 5-k+1):
        coeff = Fraction(((-1)**m) * comb(5-k, m), 15**k)
        deg = m + k
        coeff_den[deg] += A[k] * coeff
        coeff_num[deg] += G[k] * coeff

print('Denominator polynomial coeffs:', coeff_den)
print('Numerator polynomial coeffs:', coeff_num)

# Show floating values and series expansion around p=0 to p^2
import sympy as sp
p = sp.symbols('p')
Den_poly = sum(sp.Rational(c.numerator, c.denominator) * p**i for i,c in enumerate(coeff_den))
Num_poly = sum(sp.Rational(c.numerator, c.denominator) * p**i for i,c in enumerate(coeff_num))
F_ratio = sp.simplify(Num_poly/Den_poly)
F_series = sp.series(F_ratio, p, 0, 4)  # up to p^3 terms
print('F_ratio simplified:', F_ratio)
print('F series:', F_series)
Tool #7
Traceback (most recent call last):
  File "<stdin>", line 71, in <module>
  File "<stdin>", line 52, in projector_codespace
ValueError: matmul: Input operand 1 does not have enough dimensions (has 0, gufunc core with signature (n?,k),(k,m?)->(n?,m?) requires 1)
Assistant #8
Tool Call (code to execute)
import numpy as np
I = np.array([[1,0],[0,1]], dtype=complex)
X = np.array([[0,1],[1,0]], dtype=complex)
Z = np.array([[1,0],[0,-1]], dtype=complex)

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

def op_on(op, q, n):
    ops = [I]*n
    ops[q] = op
    return kron_n(ops)


def projector_codespace(n):
    A0 = op_on(X,0,n)
    A1 = op_on(X,1,n)
    A2 = op_on(X,2,n)
    A3 = op_on(X,3,n)
    print('Shapes:', A0.shape, A1.shape, A2.shape, A3.shape)
    X4m = A0 @ A1 @ A2 @ A3
    Z4m = op_on(Z,0,n) @ op_on(Z,1,n) @ op_on(Z,2,n) @ op_on(Z,3,n)
    P = 0.5*(np.eye(2**n) + X4m) @ 0.5*(np.eye(2**n) + Z4m)
    print('Projector shape:', P.shape)
    return P

projector_codespace(5)
Tool #9
Traceback (most recent call last):
  File "<stdin>", line 30, in <module>
  File "<stdin>", line 26, in projector_codespace
ValueError: matmul: Input operand 1 does not have enough dimensions (has 0, gufunc core with signature (n?,k),(k,m?)->(n?,m?) requires 1)

Shapes: (32, 32) (32, 32) (32, 32) (32, 32)
Assistant #10
Tool Call (code to execute)
import numpy as np
I = np.array([[1,0],[0,1]], dtype=complex)
X = np.array([[0,1],[1,0]], dtype=complex)
Z = np.array([[1,0],[0,-1]], dtype=complex)

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

def op_on(op, q, n):
    ops = [I]*n
    ops[q] = op
    return kron_n(ops)


def projector_codespace(n):
    A0 = op_on(X,0,n)
    A1 = op_on(X,1,n)
    A2 = op_on(X,2,n)
    A3 = op_on(X,3,n)
    B = A0 @ A1
    C = B @ A2
    X4m = C @ A3
    Z4m = op_on(Z,0,n)
    Z4m = Z4m @ op_on(Z,1,n)
    Z4m = Z4m @ op_on(Z,2,n)
    Z4m = Z4m @ op_on(Z,3,n)
    P = 0.5*(np.eye(2**n) + X4m)
    P = P @ (0.5*(np.eye(2**n) + Z4m))
    return P

P = projector_codespace(5)
print(P.shape)
Tool #11
(32, 32)
Assistant #12
Tool Call (code to execute)
import itertools
import numpy as np
from fractions import Fraction
from math import comb

# 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)

pauli_labels = ['I','X','Y','Z']
paulis = {'I': I, 'X': X, 'Y': Y, 'Z': Z}

# Kron helpers

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


def op_on(op, q, n):
    ops = [I]*n
    ops[q] = op
    return kron_n(ops)


def CNOT_ij(i, j, n):
    P0 = np.array([[1,0],[0,0]], dtype=complex)
    P1 = np.array([[0,0],[0,1]], dtype=complex)
    return op_on(P0, i, n) + op_on(P1, i, n) @ op_on(X, j, n)


def two_pauli(a, i, b, j, n):
    ops = [I]*n
    ops[i] = paulis[a]
    ops[j] = paulis[b]
    return kron_n(ops)


def P_anc0(n):
    P0 = np.array([[1,0],[0,0]], dtype=complex)
    return op_on(P0, 4, n)


def projector_codespace(n):
    X4m = op_on(X,0,n)
    X4m = X4m @ op_on(X,1,n)
    X4m = X4m @ op_on(X,2,n)
    X4m = X4m @ op_on(X,3,n)
    Z4m = op_on(Z,0,n)
    Z4m = Z4m @ op_on(Z,1,n)
    Z4m = Z4m @ op_on(Z,2,n)
    Z4m = Z4m @ op_on(Z,3,n)
    P = 0.5*(np.eye(2**n) + X4m)
    P = P @ (0.5*(np.eye(2**n) + Z4m))
    return P

# Settings
n=5

# Pre-build gate unitaries
U_cnot12 = CNOT_ij(1,2,n)
U_cnot10 = CNOT_ij(1,0,n)
U_cnot23 = CNOT_ij(2,3,n)
U_cnot34 = CNOT_ij(3,4,n)
U_cnot04 = CNOT_ij(0,4,n)

# Initial state |00000>
zero = np.array([[1],[0]], dtype=complex)
psi0 = kron_n([zero]*n)

# Ideal evolution
U = op_on(H,1,n)
U = U_cnot12 @ U
U = U_cnot10 @ U
U = U_cnot23 @ U
U = U_cnot34 @ U
U = U_cnot04 @ U

rho_ideal = U @ psi0 @ (U @ psi0).conj().T
P_tot = projector_codespace(n) @ P_anc0(n)
rho_ideal_acc = P_tot @ rho_ideal @ P_tot.conj().T
p_acc_ideal = np.trace(rho_ideal_acc).real
rho_ideal_acc = rho_ideal_acc / p_acc_ideal

# Partial trace out ancilla

def partial_trace_last_qubit(rho, n):
    dim = 2**(n-1)
    rho4 = rho.reshape((2, dim, 2, dim))
    return rho4[0,:,0,:] + rho4[1,:,1,:]

rho_ideal_code = partial_trace_last_qubit(rho_ideal_acc, n)

# Extract pure state vector
vals, vecs = np.linalg.eigh(rho_ideal_code)
psi_ref = vecs[:, np.argmax(vals.real)]
psi_ref = psi_ref / np.linalg.norm(psi_ref)

# Sanity: check stabilizer expectations
X4_4 = kron_n([X,X,X,X])
Z4_4 = kron_n([Z,Z,Z,Z])
print('Trace ideal code:', np.trace(rho_ideal_code).real)
print('XXXX exp:', (psi_ref.conj().T @ X4_4 @ psi_ref).item())
print('ZZZZ exp:', (psi_ref.conj().T @ Z4_4 @ psi_ref).item())

# Enumerate error patterns
error_qubit_pairs = [(1,2),(1,0),(2,3),(3,4),(0,4)]
non_id_two_paulis = [(a,b) for a in pauli_labels for b in pauli_labels if not (a=='I' and b=='I')]
err_ops_by_loc = []
for (i,j) in error_qubit_pairs:
    ops = [two_pauli(a,i,b,j,n) for (a,b) in non_id_two_paulis]
    err_ops_by_loc.append(ops)

P_total = projector_codespace(n) @ P_anc0(n)

from collections import defaultdict
from fractions import Fraction

def to_fraction(x):
    return Fraction.from_float(float(x)).limit_denominator(2**24)

A_k = defaultdict(Fraction)
G_k = defaultdict(Fraction)

num_patterns = 16**5
print('Total patterns:', num_patterns)

for pat_idx in range(num_patterns):
    # decode base-16 digits for 5 locs
    digits = []
    x = pat_idx
    w = 0
    for _ in range(5):
        d = x % 16
        x //= 16
        digits.append(d)
        if d != 0:
            w += 1

    psi = psi0
    psi = op_on(H,1,n) @ psi
    psi = U_cnot12 @ psi
    if digits[0] != 0:
        psi = err_ops_by_loc[0][digits[0]-1] @ psi
    psi = U_cnot10 @ psi
    if digits[1] != 0:
        psi = err_ops_by_loc[1][digits[1]-1] @ psi
    psi = U_cnot23 @ psi
    if digits[2] != 0:
        psi = err_ops_by_loc[2][digits[2]-1] @ psi
    psi = U_cnot34 @ psi
    if digits[3] != 0:
        psi = err_ops_by_loc[3][digits[3]-1] @ psi
    psi = U_cnot04 @ psi
    if digits[4] != 0:
        psi = err_ops_by_loc[4][digits[4]-1] @ psi

    rho = psi @ psi.conj().T
    rho_acc = P_total @ rho @ P_total.conj().T
    p_acc = np.trace(rho_acc).real
    if p_acc < 1e-15:
        continue
    rho_acc_norm = rho_acc / p_acc
    rho_code = partial_trace_last_qubit(rho_acc_norm, n)
    F = (psi_ref.conj().T @ rho_code @ psi_ref).item().real
    A_k[w] += to_fraction(p_acc)
    G_k[w] += to_fraction(p_acc * F)

A = [A_k[k] for k in range(6)]
G = [G_k[k] for k in range(6)]
print('A_k sums:', A)
print('G_k sums:', G)

# Build polynomial coefficients
from math import comb
coeff_den = [Fraction(0,1) for _ in range(6)]
coeff_num = [Fraction(0,1) for _ in range(6)]

for k in range(6):
    for m in range(0, 5-k+1):
        coeff = Fraction(((-1)**m) * comb(5-k, m), 15**k)
        deg = m + k
        coeff_den[deg] += A[k] * coeff
        coeff_num[deg] += G[k] * coeff

print('Den coeffs:', coeff_den)
print('Num coeffs:', coeff_num)

# Series expansion via sympy
import sympy as sp
p = sp.symbols('p')
Den_poly = sum(sp.Rational(c.numerator, c.denominator) * p**i for i,c in enumerate(coeff_den))
Num_poly = sum(sp.Rational(c.numerator, c.denominator) * p**i for i,c in enumerate(coeff_num))
F_ratio = sp.simplify(Num_poly/Den_poly)
F_series = sp.series(F_ratio, p, 0, 4)
print('F_ratio:', F_ratio)
print('F_series:', F_series)
Tool #13
''
Assistant #14
Tool Call (code to execute)
import numpy as np
from fractions import Fraction
from math import comb

# Setup as before but only enumerate up to 2 faults
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)
paulis = {'I': I, 'X': X, 'Y': Y, 'Z': Z}
pauli_labels = ['I','X','Y','Z']

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

def op_on(op, q, n):
    ops = [I]*n
    ops[q] = op
    return kron_n(ops)

def CNOT_ij(i, j, n):
    P0 = np.array([[1,0],[0,0]], dtype=complex)
    P1 = np.array([[0,0],[0,1]], dtype=complex)
    return op_on(P0, i, n) + op_on(P1, i, n) @ op_on(X, j, n)

def two_pauli(a, i, b, j, n):
    ops = [I]*n
    ops[i] = paulis[a]
    ops[j] = paulis[b]
    return kron_n(ops)


def P_anc0(n):
    P0 = np.array([[1,0],[0,0]], dtype=complex)
    return op_on(P0, 4, n)

def projector_codespace(n):
    X4m = op_on(X,0,n)
    X4m = X4m @ op_on(X,1,n)
    X4m = X4m @ op_on(X,2,n)
    X4m = X4m @ op_on(X,3,n)
    Z4m = op_on(Z,0,n)
    Z4m = Z4m @ op_on(Z,1,n)
    Z4m = Z4m @ op_on(Z,2,n)
    Z4m = Z4m @ op_on(Z,3,n)
    P = 0.5*(np.eye(2**n) + X4m)
    P = P @ (0.5*(np.eye(2**n) + Z4m))
    return P

n=5
U_cnot12 = CNOT_ij(1,2,n)
U_cnot10 = CNOT_ij(1,0,n)
U_cnot23 = CNOT_ij(2,3,n)
U_cnot34 = CNOT_ij(3,4,n)
U_cnot04 = CNOT_ij(0,4,n)

zero = np.array([[1],[0]], dtype=complex)
psi0 = kron_n([zero]*n)

U = op_on(H,1,n)
U = U_cnot12 @ U
U = U_cnot10 @ U
U = U_cnot23 @ U
U = U_cnot34 @ U
U = U_cnot04 @ U

rho_ideal = U @ psi0 @ (U @ psi0).conj().T
P_total = projector_codespace(n) @ P_anc0(n)
rho_ideal_acc = P_total @ rho_ideal @ P_total.conj().T
p_acc_ideal = np.trace(rho_ideal_acc).real
rho_ideal_acc = rho_ideal_acc / p_acc_ideal

# Partial trace out ancilla

def partial_trace_last_qubit(rho, n):
    dim = 2**(n-1)
    rho4 = rho.reshape((2, dim, 2, dim))
    return rho4[0,:,0,:] + rho4[1,:,1,:]

rho_ideal_code = partial_trace_last_qubit(rho_ideal_acc, n)
vals, vecs = np.linalg.eigh(rho_ideal_code)
psi_ref = vecs[:, np.argmax(vals.real)]
psi_ref = psi_ref / np.linalg.norm(psi_ref)

# Error setup
error_qubit_pairs = [(1,2),(1,0),(2,3),(3,4),(0,4)]
non_id_two_paulis = [(a,b) for a in pauli_labels for b in pauli_labels if not (a=='I' and b=='I')]
err_ops_by_loc = []
for (i,j) in error_qubit_pairs:
    ops = [two_pauli(a,i,b,j,n) for (a,b) in non_id_two_paulis]
    err_ops_by_loc.append(ops)

from collections import defaultdict
A_k = defaultdict(Fraction)
G_k = defaultdict(Fraction)

def to_fraction(x):
    return Fraction.from_float(float(x)).limit_denominator(2**24)

# Enumerate k=0
# pattern all zeros
psi = psi0
psi = op_on(H,1,n) @ psi
psi = U_cnot12 @ psi
psi = U_cnot10 @ psi
psi = U_cnot23 @ psi
psi = U_cnot34 @ psi
psi = U_cnot04 @ psi
rho = psi @ psi.conj().T
rho_acc = P_total @ rho @ P_total.conj().T
p_acc = np.trace(rho_acc).real
rho_acc_norm = rho_acc / p_acc
rho_code = partial_trace_last_qubit(rho_acc_norm, n)
F = (psi_ref.conj().T @ rho_code @ psi_ref).item().real
A_k[0] += to_fraction(p_acc)
G_k[0] += to_fraction(p_acc * F)

# Enumerate k=1: choose one location and one non-identity Pauli
for loc in range(5):
    for idx in range(15):
        psi = psi0
        psi = op_on(H,1,n) @ psi
        psi = U_cnot12 @ psi
        if loc == 0:
            psi = err_ops_by_loc[0][idx] @ psi
        psi = U_cnot10 @ psi
        if loc == 1:
            psi = err_ops_by_loc[1][idx] @ psi
        psi = U_cnot23 @ psi
        if loc == 2:
            psi = err_ops_by_loc[2][idx] @ psi
        psi = U_cnot34 @ psi
        if loc == 3:
            psi = err_ops_by_loc[3][idx] @ psi
        psi = U_cnot04 @ psi
        if loc == 4:
            psi = err_ops_by_loc[4][idx] @ psi
        rho = psi @ psi.conj().T
        rho_acc = P_total @ rho @ P_total.conj().T
        p_acc = np.trace(rho_acc).real
        if p_acc < 1e-15:
            continue
        rho_acc_norm = rho_acc / p_acc
        rho_code = partial_trace_last_qubit(rho_acc_norm, n)
        F = (psi_ref.conj().T @ rho_code @ psi_ref).item().real
        A_k[1] += to_fraction(p_acc)
        G_k[1] += to_fraction(p_acc * F)

# Enumerate k=2: choose two locations (loc1<loc2), and a Pauli at each (15*15)
from itertools import combinations
for loc1, loc2 in combinations(range(5), 2):
    for idx1 in range(15):
        for idx2 in range(15):
            psi = psi0
            psi = op_on(H,1,n) @ psi
            psi = U_cnot12 @ psi
            if loc1 == 0 or loc2 == 0:
                # apply the one(s) that match
                if loc1 == 0:
                    psi = err_ops_by_loc[0][idx1] @ psi
                if loc2 == 0:
                    psi = err_ops_by_loc[0][idx2] @ psi
            psi = U_cnot10 @ psi
            if loc1 == 1 or loc2 == 1:
                if loc1 == 1:
                    psi = err_ops_by_loc[1][idx1] @ psi
                if loc2 == 1:
                    psi = err_ops_by_loc[1][idx2] @ psi
            psi = U_cnot23 @ psi
            if loc1 == 2 or loc2 == 2:
                if loc1 == 2:
                    psi = err_ops_by_loc[2][idx1] @ psi
                if loc2 == 2:
                    psi = err_ops_by_loc[2][idx2] @ psi
            psi = U_cnot34 @ psi
            if loc1 == 3 or loc2 == 3:
                if loc1 == 3:
                    psi = err_ops_by_loc[3][idx1] @ psi
                if loc2 == 3:
                    psi = err_ops_by_loc[3][idx2] @ psi
            psi = U_cnot04 @ psi
            if loc1 == 4 or loc2 == 4:
                if loc1 == 4:
                    psi = err_ops_by_loc[4][idx1] @ psi
                if loc2 == 4:
                    psi = err_ops_by_loc[4][idx2] @ psi
            rho = psi @ psi.conj().T
            rho_acc = P_total @ rho @ P_total.conj().T
            p_acc = np.trace(rho_acc).real
            if p_acc < 1e-15:
                continue
            rho_acc_norm = rho_acc / p_acc
            rho_code = partial_trace_last_qubit(rho_acc_norm, n)
            F = (psi_ref.conj().T @ rho_code @ psi_ref).item().real
            A_k[2] += to_fraction(p_acc)
            G_k[2] += to_fraction(p_acc * F)

# Print A_k and G_k for k=0,1,2
print('A_k sums (k=0..2):', [A_k[k] for k in range(3)])
print('G_k sums (k=0..2):', [G_k[k] for k in range(3)])

# Now build series up to p^2
# Den ≈ Σ_{k=0}^2 A_k * (1-p)^(5-k) * (p/15)^k
# Num ≈ Σ_{k=0}^2 G_k * (1-p)^(5-k) * (p/15)^k
import sympy as sp
p = sp.symbols('p')
Den = 0
Num = 0
for k in range(3):
    Ak = sp.Rational(A_k[k].numerator, A_k[k].denominator)
    Gk = sp.Rational(G_k[k].numerator, G_k[k].denominator)
    Den += Ak * (1-p)**(5-k) * (p/15)**k
    Num += Gk * (1-p)**(5-k) * (p/15)**k
F_series = sp.series(sp.simplify(Num/Den), p, 0, 3)  # up to p^2
print('F_series up to p^2:', F_series)
Tool #15
A_k sums (k=0..2): [Fraction(1, 1), Fraction(7, 1), Fraction(282, 1)]
G_k sums (k=0..2): [Fraction(1, 2), Fraction(7, 2), Fraction(69, 1)]
F_series up to p^2: 1/2 - 8*p**2/25 + O(p**3)
Assistant #16
Tool Call (code to execute)
import numpy as np
from numpy.linalg import eigvalsh

# Reuse variables from previous cell (they persist in the python tool? Assume not). Let's reconstruct minimal elements
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)

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

def op_on(op, q, n):
    ops = [I]*n
    ops[q] = op
    return kron_n(ops)

def CNOT_ij(i, j, n):
    P0 = np.array([[1,0],[0,0]], dtype=complex)
    P1 = np.array([[0,0],[0,1]], dtype=complex)
    return op_on(P0, i, n) + op_on(P1, i, n) @ op_on(X, j, n)

def projector_codespace(n):
    X4m = op_on(X,0,n)
    X4m = X4m @ op_on(X,1,n)
    X4m = X4m @ op_on(X,2,n)
    X4m = X4m @ op_on(X,3,n)
    Z4m = op_on(Z,0,n)
    Z4m = Z4m @ op_on(Z,1,n)
    Z4m = Z4m @ op_on(Z,2,n)
    Z4m = Z4m @ op_on(Z,3,n)
    P = 0.5*(np.eye(2**n) + X4m)
    P = P @ (0.5*(np.eye(2**n) + Z4m))
    return P

def P_anc0(n):
    P0 = np.array([[1,0],[0,0]], dtype=complex)
    return op_on(P0, 4, n)

n=5
U = op_on(H,1,n)
U = CNOT_ij(1,2,n) @ U
U = CNOT_ij(1,0,n) @ U
U = CNOT_ij(2,3,n) @ U
U = CNOT_ij(3,4,n) @ U
U = CNOT_ij(0,4,n) @ U
zero = np.array([[1],[0]], dtype=complex)
psi0 = kron_n([zero]*n)
psi = U @ psi0
rho = psi @ psi.conj().T
P = projector_codespace(n) @ P_anc0(n)
rho_acc = P @ rho @ P.conj().T
p_acc = np.trace(rho_acc).real
print('p_acc ideal =', p_acc)
rho_acc /= p_acc
# Trace out ancilla
rho5 = rho_acc.reshape((2,16,2,16))
rho_code = rho5[0,:,0,:] + rho5[1,:,1,:]
# Purity
evals = eigvalsh(rho_code)
print('eigenvalues:', evals)
print('purity Tr(rho^2)=', np.trace(rho_code @ rho_code).real)
Tool #17
p_acc ideal = 0.9999999999999998
eigenvalues: [0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.5 0.5]
purity Tr(rho^2)= 0.5
Assistant #18
Tool Call (code to execute)
import numpy as np
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)

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

def op_on(op, q, n):
    ops = [I]*n
    ops[q] = op
    return kron_n(ops)

def CNOT_ij(i, j, n):
    P0 = np.array([[1,0],[0,0]], dtype=complex)
    P1 = np.array([[0,0],[0,1]], dtype=complex)
    return op_on(P0, i, n) + op_on(P1, i, n) @ op_on(X, j, n)

n=5
U = op_on(H,1,n)
U = CNOT_ij(1,2,n) @ U
U = CNOT_ij(1,0,n) @ U
U = CNOT_ij(2,3,n) @ U
U = CNOT_ij(3,4,n) @ U
U = CNOT_ij(0,4,n) @ U
zero = np.array([[1],[0]], dtype=complex)
psi0 = kron_n([zero]*n)
psi = U @ psi0
# Construct XXXX on qubits 0..3 in 5-qubit space
X4_5 = op_on(X,0,n)
X4_5 = X4_5 @ op_on(X,1,n)
X4_5 = X4_5 @ op_on(X,2,n)
X4_5 = X4_5 @ op_on(X,3,n)
Z4_5 = op_on(Z,0,n)
Z4_5 = Z4_5 @ op_on(Z,1,n)
Z4_5 = Z4_5 @ op_on(Z,2,n)
Z4_5 = Z4_5 @ op_on(Z,3,n)

ex_X = (psi.conj().T @ X4_5 @ psi).item()
ex_Z = (psi.conj().T @ Z4_5 @ psi).item()
print('Expect XXXX:', ex_X)
print('Expect ZZZZ:', ex_Z)
# Also ancilla Z expectation (|0> expectation +1)
Z_anc = op_on(Z,4,n)
print('Ancilla Z:', (psi.conj().T @ Z_anc @ psi).item())
Tool #19
Expect XXXX: (0.9999999999999998+0j)
Expect ZZZZ: (0.9999999999999998+0j)
Ancilla Z: (0.9999999999999998+0j)
Assistant #20
Tool Call (code to execute)
import numpy as np
from fractions import Fraction
from math import comb

# 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)

pauli_labels = ['I','X','Y','Z']
paulis = {'I': I, 'X': X, 'Y': Y, 'Z': Z}

# Kron helpers

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


def op_on(op, q, n):
    ops = [I]*n
    ops[q] = op
    return kron_n(ops)


def CNOT_ij(i, j, n):
    P0 = np.array([[1,0],[0,0]], dtype=complex)
    P1 = np.array([[0,0],[0,1]], dtype=complex)
    return op_on(P0, i, n) + op_on(P1, i, n) @ op_on(X, j, n)


def two_pauli(a, i, b, j, n):
    ops = [I]*n
    ops[i] = paulis[a]
    ops[j] = paulis[b]
    return kron_n(ops)


def P_anc0(n):
    P0 = np.array([[1,0],[0,0]], dtype=complex)
    return op_on(P0, 4, n)


def projector_codespace(n):
    X4m = op_on(X,0,n)
    X4m = X4m @ op_on(X,1,n)
    X4m = X4m @ op_on(X,2,n)
    X4m = X4m @ op_on(X,3,n)
    Z4m = op_on(Z,0,n)
    Z4m = Z4m @ op_on(Z,1,n)
    Z4m = Z4m @ op_on(Z,2,n)
    Z4m = Z4m @ op_on(Z,3,n)
    P = 0.5*(np.eye(2**n) + X4m)
    P = P @ (0.5*(np.eye(2**n) + Z4m))
    return P

n=5

# Gates
U_cnot12 = CNOT_ij(1,2,n)
U_cnot10 = CNOT_ij(1,0,n)
U_cnot23 = CNOT_ij(2,3,n)
U_cnot34 = CNOT_ij(3,4,n)
U_cnot04 = CNOT_ij(0,4,n)

zero = np.array([[1],[0]], dtype=complex)
psi0 = kron_n([zero]*n)

# Ideal evolution
U = op_on(H,1,n)
U = U_cnot12 @ U
U = U_cnot10 @ U
U = U_cnot23 @ U
U = U_cnot34 @ U
U = U_cnot04 @ U

rho_ideal = U @ psi0 @ (U @ psi0).conj().T
P_total = projector_codespace(n) @ P_anc0(n)
rho_ideal_acc = P_total @ rho_ideal @ P_total.conj().T
p_acc_ideal = np.trace(rho_ideal_acc).real
rho_ideal_acc = rho_ideal_acc / p_acc_ideal

# Correct partial trace of last qubit (ancilla index 4)

def partial_trace_last_qubit(rho, n):
    dim = 2**(n-1)
    rho4 = rho.reshape((dim,2,dim,2))
    # indices: (i,a; j,b), trace over a=b
    return rho4[:,0,:,0] + rho4[:,1,:,1]

rho_ideal_code = partial_trace_last_qubit(rho_ideal_acc, n)
# Purity
print('p_acc_ideal:', p_acc_ideal)
print('purity Tr(rho^2)=', np.trace(rho_ideal_code @ rho_ideal_code).real)

# Extract psi_ref
vals, vecs = np.linalg.eigh(rho_ideal_code)
psi_ref = vecs[:, np.argmax(vals.real)]
psi_ref = psi_ref / np.linalg.norm(psi_ref)
F_check = (psi_ref.conj().T @ rho_ideal_code @ psi_ref).item().real
print('F_check vs rho_ideal_code:', F_check)

# Setup errors
error_qubit_pairs = [(1,2),(1,0),(2,3),(3,4),(0,4)]
non_id_two_paulis = [(a,b) for a in pauli_labels for b in pauli_labels if not (a=='I' and b=='I')]
err_ops_by_loc = []
for (i,j) in error_qubit_pairs:
    ops = [two_pauli(a,i,b,j,n) for (a,b) in non_id_two_paulis]
    err_ops_by_loc.append(ops)

from collections import defaultdict
A_k = defaultdict(Fraction)
G_k = defaultdict(Fraction)


def to_fraction(x):
    return Fraction.from_float(float(x)).limit_denominator(2**24)

# k=0
psi = psi0
psi = op_on(H,1,n) @ psi
psi = U_cnot12 @ psi
psi = U_cnot10 @ psi
psi = U_cnot23 @ psi
psi = U_cnot34 @ psi
psi = U_cnot04 @ psi
rho = psi @ psi.conj().T
rho_acc = P_total @ rho @ P_total.conj().T
p_acc = np.trace(rho_acc).real
rho_acc_norm = rho_acc / p_acc
rho_code = partial_trace_last_qubit(rho_acc_norm, n)
F = (psi_ref.conj().T @ rho_code @ psi_ref).item().real
print('k=0: p_acc, F=', p_acc, F)
A_k[0] += to_fraction(p_acc)
G_k[0] += to_fraction(p_acc * F)

# k=1
for loc in range(5):
    for idx in range(15):
        psi = psi0
        psi = op_on(H,1,n) @ psi
        psi = U_cnot12 @ psi
        if loc == 0:
            psi = err_ops_by_loc[0][idx] @ psi
        psi = U_cnot10 @ psi
        if loc == 1:
            psi = err_ops_by_loc[1][idx] @ psi
        psi = U_cnot23 @ psi
        if loc == 2:
            psi = err_ops_by_loc[2][idx] @ psi
        psi = U_cnot34 @ psi
        if loc == 3:
            psi = err_ops_by_loc[3][idx] @ psi
        psi = U_cnot04 @ psi
        if loc == 4:
            psi = err_ops_by_loc[4][idx] @ psi
        rho = psi @ psi.conj().T
        rho_acc = P_total @ rho @ P_total.conj().T
        p_acc = np.trace(rho_acc).real
        if p_acc < 1e-15:
            continue
        rho_acc_norm = rho_acc / p_acc
        rho_code = partial_trace_last_qubit(rho_acc_norm, n)
        F = (psi_ref.conj().T @ rho_code @ psi_ref).item().real
        A_k[1] += to_fraction(p_acc)
        G_k[1] += to_fraction(p_acc * F)

# k=2
from itertools import combinations
for loc1, loc2 in combinations(range(5), 2):
    for idx1 in range(15):
        for idx2 in range(15):
            psi = psi0
            psi = op_on(H,1,n) @ psi
            psi = U_cnot12 @ psi
            if loc1 == 0:
                psi = err_ops_by_loc[0][idx1] @ psi
            if loc2 == 0:
                psi = err_ops_by_loc[0][idx2] @ psi
            psi = U_cnot10 @ psi
            if loc1 == 1:
                psi = err_ops_by_loc[1][idx1] @ psi
            if loc2 == 1:
                psi = err_ops_by_loc[1][idx2] @ psi
            psi = U_cnot23 @ psi
            if loc1 == 2:
                psi = err_ops_by_loc[2][idx1] @ psi
            if loc2 == 2:
                psi = err_ops_by_loc[2][idx2] @ psi
            psi = U_cnot34 @ psi
            if loc1 == 3:
                psi = err_ops_by_loc[3][idx1] @ psi
            if loc2 == 3:
                psi = err_ops_by_loc[3][idx2] @ psi
            psi = U_cnot04 @ psi
            if loc1 == 4:
                psi = err_ops_by_loc[4][idx1] @ psi
            if loc2 == 4:
                psi = err_ops_by_loc[4][idx2] @ psi
            rho = psi @ psi.conj().T
            rho_acc = P_total @ rho @ P_total.conj().T
            p_acc = np.trace(rho_acc).real
            if p_acc < 1e-15:
                continue
            rho_acc_norm = rho_acc / p_acc
            rho_code = partial_trace_last_qubit(rho_acc_norm, n)
            F = (psi_ref.conj().T @ rho_code @ psi_ref).item().real
            A_k[2] += to_fraction(p_acc)
            G_k[2] += to_fraction(p_acc * F)

print('A_k:', [A_k[k] for k in range(3)])
print('G_k:', [G_k[k] for k in range(3)])

# Series up to p^2
import sympy as sp
p = sp.symbols('p')
Den = 0
Num = 0
for k in range(3):
    Ak = sp.Rational(A_k[k].numerator, A_k[k].denominator)
    Gk = sp.Rational(G_k[k].numerator, G_k[k].denominator)
    Den += Ak * (1-p)**(5-k) * (p/15)**k
    Num += Gk * (1-p)**(5-k) * (p/15)**k
F_series = sp.series(sp.simplify(Num/Den), p, 0, 3)
print('F_series:', F_series)
Tool #21
p_acc_ideal: 0.9999999999999998
purity Tr(rho^2)= 1.0
F_check vs rho_ideal_code: 1.0000000000000002
k=0: p_acc, F= 0.9999999999999998 1.0000000000000002
A_k: [Fraction(1, 1), Fraction(7, 1), Fraction(282, 1)]
G_k: [Fraction(1, 1), Fraction(7, 1), Fraction(138, 1)]
F_series: 1 - 16*p**2/25 + O(p**3)
Assistant #22
Tool Call (code to execute)
import numpy as np
from fractions import Fraction
from itertools import combinations

# Reuse definitions
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)
pauli_labels = ['I','X','Y','Z']
paulis = {'I': I, 'X': X, 'Y': Y, 'Z': Z}

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

def op_on(op, q, n):
    ops = [I]*n
    ops[q] = op
    return kron_n(ops)

def CNOT_ij(i, j, n):
    P0 = np.array([[1,0],[0,0]], dtype=complex)
    P1 = np.array([[0,0],[0,1]], dtype=complex)
    return op_on(P0, i, n) + op_on(P1, i, n) @ op_on(X, j, n)

def two_pauli(a, i, b, j, n):
    ops = [I]*n
    ops[i] = paulis[a]
    ops[j] = paulis[b]
    return kron_n(ops)

def P_anc0(n):
    P0 = np.array([[1,0],[0,0]], dtype=complex)
    return op_on(P0, 4, n)

def projector_codespace(n):
    X4m = op_on(X,0,n)
    X4m = X4m @ op_on(X,1,n)
    X4m = X4m @ op_on(X,2,n)
    X4m = X4m @ op_on(X,3,n)
    Z4m = op_on(Z,0,n)
    Z4m = Z4m @ op_on(Z,1,n)
    Z4m = Z4m @ op_on(Z,2,n)
    Z4m = Z4m @ op_on(Z,3,n)
    P = 0.5*(np.eye(2**n) + X4m)
    P = P @ (0.5*(np.eye(2**n) + Z4m))
    return P

def partial_trace_last_qubit(rho, n):
    dim = 2**(n-1)
    rho4 = rho.reshape((dim,2,dim,2))
    return rho4[:,0,:,0] + rho4[:,1,:,1]

n=5
U_cnot12 = CNOT_ij(1,2,n)
U_cnot10 = CNOT_ij(1,0,n)
U_cnot23 = CNOT_ij(2,3,n)
U_cnot34 = CNOT_ij(3,4,n)
U_cnot04 = CNOT_ij(0,4,n)
zero = np.array([[1],[0]], dtype=complex)
psi0 = kron_n([zero]*n)

# Reference state
U = op_on(H,1,n)
U = U_cnot12 @ U
U = U_cnot10 @ U
U = U_cnot23 @ U
U = U_cnot34 @ U
U = U_cnot04 @ U
rho_ideal = U @ psi0 @ (U @ psi0).conj().T
P_total = projector_codespace(n) @ P_anc0(n)
rho_ideal_acc = P_total @ rho_ideal @ P_total.conj().T
rho_ideal_acc /= np.trace(rho_ideal_acc)
rho_ref = partial_trace_last_qubit(rho_ideal_acc, n)
# Extract pure reference vector
vals, vecs = np.linalg.eigh(rho_ref)
psi_ref = vecs[:, np.argmax(vals.real)]
psi_ref = psi_ref / np.linalg.norm(psi_ref)

# Errors
affected_pairs = [(1,2),(1,0),(2,3),(3,4),(0,4)]
non_id_two_paulis = [(a,b) for a in pauli_labels for b in pauli_labels if not (a=='I' and b=='I')]
err_ops_by_loc = []
for (i,j) in affected_pairs:
    err_ops_by_loc.append([two_pauli(a,i,b,j,n) for (a,b) in non_id_two_paulis])

P_total = projector_codespace(n) @ P_anc0(n)

from fractions import Fraction

def to_fraction(x):
    return Fraction.from_float(float(x)).limit_denominator(2**24)

A3 = Fraction(0,1)
G3 = Fraction(0,1)

for locs in combinations(range(5), 3):
    for idx1 in range(15):
        for idx2 in range(15):
            for idx3 in range(15):
                psi = psi0
                psi = op_on(H,1,n) @ psi
                psi = U_cnot12 @ psi
                if 0 in locs:
                    # order: whichever index matches which location
                    # We'll map locs to indices in sequence
                    pass
                # We'll apply errors sequentially per location
                if 0 in locs:
                    # assign appropriate idx based on position in locs
                    loc_idx = locs.index(0)
                    idx = [idx1, idx2, idx3][loc_idx]
                    psi = err_ops_by_loc[0][idx] @ psi
                psi = U_cnot10 @ psi
                if 1 in locs:
                    loc_idx = locs.index(1)
                    idx = [idx1, idx2, idx3][loc_idx]
                    psi = err_ops_by_loc[1][idx] @ psi
                psi = U_cnot23 @ psi
                if 2 in locs:
                    loc_idx = locs.index(2)
                    idx = [idx1, idx2, idx3][loc_idx]
                    psi = err_ops_by_loc[2][idx] @ psi
                psi = U_cnot34 @ psi
                if 3 in locs:
                    loc_idx = locs.index(3)
                    idx = [idx1, idx2, idx3][loc_idx]
                    psi = err_ops_by_loc[3][idx] @ psi
                psi = U_cnot04 @ psi
                if 4 in locs:
                    loc_idx = locs.index(4)
                    idx = [idx1, idx2, idx3][loc_idx]
                    psi = err_ops_by_loc[4][idx] @ psi
                rho = psi @ psi.conj().T
                rho_acc = P_total @ rho @ P_total.conj().T
                p_acc = np.trace(rho_acc).real
                if p_acc < 1e-15:
                    continue
                rho_acc /= p_acc
                rho_code = partial_trace_last_qubit(rho_acc, n)
                F = (psi_ref.conj().T @ rho_code @ psi_ref).item().real
                A3 += to_fraction(p_acc)
                G3 += to_fraction(p_acc * F)

print('A3, G3:', A3, G3)
Tool #23
A3, G3: 4222 1198
Assistant #24
Tool Call (code to execute)
from fractions import Fraction
import sympy as sp

A = [Fraction(1,1), Fraction(7,1), Fraction(282,1), Fraction(4222,1)]
G = [Fraction(1,1), Fraction(7,1), Fraction(138,1), Fraction(1198,1)]

p = sp.symbols('p')
Den = 0
Num = 0
for k in range(4):
    Ak = sp.Rational(A[k].numerator, A[k].denominator)
    Gk = sp.Rational(G[k].numerator, G[k].denominator)
    Den += Ak * (1-p)**(5-k) * (p/15)**k
    Num += Gk * (1-p)**(5-k) * (p/15)**k
F_series = sp.series(sp.simplify(Num/Den), p, 0, 4)  # up to p^3
print('F series up to p^3:', F_series)
Tool #25
F series up to p^3: 1 - 16*p**2/25 - 704*p**3/375 + O(p**4)
Assistant #26

Step-by-step derivation

1) Code, logicals, and target state - We use the [[4,2,2]] stabilizer code with generators S = {XXXX, ZZZZ} on physical qubits 0,1,2,3. A standard logical basis is |00⟩L = (|0000⟩+|1111⟩)/√2, |01⟩L = (|0011⟩+|1100⟩)/√2, |10⟩L = (|0101⟩+|1010⟩)/√2, |11⟩L = (|0110⟩+|1001⟩)/√2. These satisfy the given logical operators X_A=X I X I, X_B=X X I I, Z_A=Z Z I I, Z_B=Z I Z I (up to stabilizers). The target is |00⟩AB ≡ |00⟩L. [Error Correction Zoo entry; stabilizers and codewords. (errorcorrectionzoo.org)]

2) Circuit and noise model - Circuit (right-to-left in time): H1, CNOT12, CNOT10, CNOT23, CNOT34, CNOT04, M4, where qubit 4 is an ancilla. The circuit ideally prepares |00⟩L on data (0–3) with ancilla |0⟩. - Two-qubit depolarizing noise: after each CNOTij we apply a Pauli drawn uniformly from the 15 non-identity two-qubit Paulis on qubits i,j with probability p/15; with probability 1−p no error occurs. This is the standard two-qubit depolarizing channel specialization of the Pauli/depolarizing model. (quantumchannelzoo.org, en.wikipedia.org, algassert.com)

3) Post-selection and figure of merit - Post-selection: accept only if (i) ancilla measurement M4 returns |0⟩, and (ii) both stabilizers XXXX and ZZZZ measure +1 on the data block (i.e. the state lies in the codespace). - Let U be the ideal unitary of the circuit (without noise). Define the ideal accepted data state ρref by applying U to |00000⟩, projecting onto ancilla |0⟩ and the codespace, and tracing out the ancilla. This ρref = |00⟩L⟨00| is pure (verified explicitly below). - For a given pattern E of Pauli faults, let ρE be the resulting pure state after the circuit. The accepted (unnormalized) state is Πacc ρE Πacc with Πacc = (|0⟩⟨0|)4 ⊗ [(I+XXXX)/2][(I+ZZZZ)/2]. The acceptance probability for E is pact(E) = Tr[Πacc ρE]. The normalized accepted data state is ρE,acc = Tr4[Πacc ρE Πacc] / pact(E). - The conditional logical-state fidelity is F(p) = ⟨00|L ρacc(p) |00⟩L = Num(p)/Den(p), where Num(p) = ΣE PE(p) pact(E) ⟨00|L ρE,acc |00⟩L, Den(p) = ΣE PE(p) pact(E), and PE(p) is the probability of error pattern E given the independent two-qubit depolarizing noise after each of the five CNOTs.

4) Why the linear term in p vanishes (fault-tolerance intuition) - The code has distance 2 (detects any single-qubit error). The ancilla pattern CNOT34, CNOT04 is a flag that turns any single two-qubit-gate fault that could otherwise propagate to a harmful weight-2 pattern into a detected event (either ancilla flips to |1⟩ or a stabilizer flips to −1). Consequently, any single noise event that is accepted must act trivially on the logical state (stabilizer or a Z-type logical which leaves |00⟩L invariant), so it does not reduce fidelity. Therefore F(p) = 1 − O(p^2). This qualitative statement is standard for flagged, post-selected, distance-2 state-preparation circuits. (We also verify it exactly below by enumeration.)

5) Exact small‑p expansion by enumerating all accepted 0-, 1-, and 2‑fault patterns - We compute Num(p) and Den(p) exactly up to O(p^2) by stabilizer/state-vector simulation of the 5‑qubit circuit, interleaving each CNOT with either identity (prob. 1−p) or one of the 15 two-qubit Paulis on that gate’s qubits (prob. p/15), followed by the ideal projectors Πacc. For each pattern we accumulate pact(E) and the accepted logical fidelity ⟨00|L ρE,acc |00⟩L. - Grouping by the number k of non-identity faults gives Num(p) = Σk Gk (1−p)^{5−k} (p/15)^k, Den(p) = Σk Ak (1−p)^{5−k} (p/15)^k, where Ak = ΣE∈Wk pact(E) and Gk = ΣE∈Wk pact(E) ⟨00|L ρE,acc |00⟩L, with Wk the set of patterns having exactly k non-identity errors. - Exact enumeration yields A0 = 1, G0 = 1, A1 = 7, G1 = 7, A2 = 282, G2 = 138. (Interpretation: among all single-fault patterns, the total acceptance “mass” is 7 and all accepted ones preserve the target state; at two faults there is accepted weight but roughly half cause a logical flip.) - Expand (1−p)^5, (1−p)^4, (1−p)^3 to O(p^2), substitute the Ak,Gk above, and form the ratio. Since the linear coefficients match (A1=G1), the O(p) term cancels. The O(p^2) coefficient is the difference of the quadratic coefficients: F(p) = 1 − (A2 − G2)/15^2 · p^2 + O(p^3) = 1 − (282−138)/225 · p^2 + O(p^3) = 1 − (144/225) p^2 + O(p^3) = 1 − (16/25) p^2 + O(p^3).

6) Optional: cubic term - Extending the exact enumeration to all 3‑fault patterns (still tractable) gives A3 = 4222, G3 = 1198. Including these produces F(p) = 1 − 16 p^2/25 − 704 p^3/375 + O(p^4).

7) Independent checks - Noiseless circuit prepares a state with expectation values ⟨XXXX⟩=⟨ZZZZ⟩=+1 on the data, ancilla Z=+1, and the reduced data state is pure (|00⟩L), confirming the reference. This was verified by explicit calculation with the given circuit.

Conventions and units - All gates are ideal except for the two-qubit depolarizing channel after each CNOT (five CNOTs total). Single-qubit H and the ideal projective measurements are assumed perfect. The parameter p is the per-2q-gate error probability for a non-identity two-qubit Pauli drawn uniformly from the 15 options.

Final Answer: The logical two-qubit state fidelity of the accepted (post-selected) output is

F(p) = 1 − 16 p^2/25 + O(p^3),

i.e., to leading order F(p) = 1 − 0.64 p^2 + O(p^3). Including the next term from exact 3‑fault enumeration gives

F(p) = 1 − 16 p^2/25 − 704 p^3/375 + O(p^4).

Citations: - [[4,2,2]] code, stabilizers, and codewords: Error Correction Zoo, “([[4,2,2]]) Four-qubit code.” (errorcorrectionzoo.org) - Depolarizing/Pauli channel background and equal-weight Pauli-error model: Quantum Channel Zoo (depolarizing channel) and Wikipedia (depolarizing channel). (quantumchannelzoo.org, en.wikipedia.org) - Equal weighting of the 15 non-identity two-qubit Paulis for the 2‑qubit depolarizing channel (worked-out perspective): Algassert blog. (algassert.com)

User #27

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 #28
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 - sp.Rational(704, 375)*p**3
    # ---------------------------------------------------------------

    return F_logical