Checkpoint-3

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\).

Checkpoints

Checkpoint 1

Suppose that we wish to prepare a logical two-qubit GHZ state \((|00\rangle_{AB}+|11\rangle_{AB})/\sqrt{2}\) in the [[4,2,2]] code. To do so, we use the following state preparation circuit:

\[ (CNOT_{03}) (H_0) (CNOT_{21}) (H_2). \]

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.

What is the physical state fidelity of the final physical 4-qubit state at the end of the circuit as a function of the two-qubit gate error rate \(p\)?

Assistant #2

1) Step-by-Step Derivation

  • Ideal state prepared by the circuit (no noise)
  • Start from |0000⟩ on physical qubits 0,1,2,3.
  • The subcircuit H2 then CNOT21 prepares a Bell state on the pair (2,1): H on the control followed by CNOT maps |00⟩ → |Φ+⟩ = (|00⟩+|11⟩)/√2. Hence qubits (2,1) are in |Φ+⟩21. The subsequent gates act on other qubits, so this entanglement is unaffected. (en.wikipedia.org)
  • The subcircuit H0 then CNOT03 prepares a Bell state on the pair (0,3), giving |Φ+⟩03. (en.wikipedia.org)
  • Therefore, the ideal 4-qubit output state is the product of two Bell pairs:
\[ |\psi_{\mathrm{ideal}}\rangle = |\Phi^+\rangle_{21}\otimes|\Phi^+\rangle_{03}. \]
  • Noise model applied
  • After each CNOTij, a 2-qubit depolarizing Pauli channel acts on that pair: with probability 1−p do nothing; with probability p choose uniformly one of the 15 non-identity 2-qubit Paulis P∈{IX,IY,IZ, … , ZZ} and apply P (probability p/15 for each). This is the standard “uniform Pauli” description (the 16 two-qubit Paulis are II plus the 15 non-identity strings). (qiskit.github.io, qiskit.qotlabs.org)
  • In our circuit, the two such channels act on disjoint pairs (2,1) and (0,3), and there are no subsequent gates touching those qubits after each error is applied; thus the final state is
\[ \rho_{\mathrm{out}}=\mathcal{E}_{21}\!\left(|\Phi^+\rangle\!\langle\Phi^+|_{21}\right)\;\otimes\;\mathcal{E}_{03}\!\left(|\Phi^+\rangle\!\langle\Phi^+|_{03}\right), \]
i.e., a tensor product of two independently–noisy Bell pairs.
  • Fidelity for a single noisy Bell pair
  • For a target pure state |ψ⟩, the fidelity with a state ρ is F=⟨ψ|ρ|ψ⟩ (we use the “squared” fidelity convention). For our tensor-product target |ψideal⟩ the total fidelity factorizes as the product of the two pairwise fidelities (see next bullet). (en.wikipedia.org)
  • Because ρout is a product and |ψideal⟩ is a product, the fidelity factorizes:
\[ \]
This follows from ⟨a⊗b| (A⊗B) |a⊗b⟩ = ⟨a|A|a⟩ ⟨b|B|b⟩.
  • Evaluate Fpair for one pair subject to the 2-qubit Pauli channel:
\[ F_{\text{pair}}=\sum_{P} \Pr(P)\,|\langle\Phi^+|P|\Phi^+\rangle|^2, \]
where P runs over {II}∪{15 non-identity Paulis}.
- The Bell state |Φ+⟩ is a simultaneous eigenstate of X⊗X and Z⊗Z with eigenvalue +1, and of Y⊗Y with eigenvalue −1:
\[ (X\!\otimes\!X)|\Phi^+\rangle=|\Phi^+\rangle,\quad (Z\!\otimes\!Z)|\Phi^+\rangle=|\Phi^+\rangle,\quad (Y\!\otimes\!Y)|\Phi^+\rangle=-|\Phi^+\rangle. \]
  Hence these three two-qubit Paulis leave |Φ+ invariant up to a global phase. All other non-identity two-qubit Paulis map |Φ+ to one of the other three (orthogonal) Bell states, giving zero overlap. ([chem.libretexts.org](https://chem.libretexts.org/Bookshelves/Physical_and_Theoretical_Chemistry_Textbook_Maps/Quantum_Tutorials_%28Rioux%29/08%3A_Quantum_Teleportation/8.53%3A_Bell_State_Exercises?utm_source=openai))
- Therefore,
\[ \]
  • Total physical-state fidelity
  • Using Fphys = Fpair^2,
\[ F_{\mathrm{phys}}(p)=\Big(1-\frac{4}{5}p\Big)^2. \]

2) Mathematical Identities Used

  • Bell-state creation by H on the control followed by CNOT: CNOT(H⊗I)|00⟩ = (|00⟩+|11⟩)/√2. (en.wikipedia.org)
  • The 2-qubit Pauli set has 16 elements (II and the 15 non-identity strings); “uniform Pauli” depolarizing distributes weight equally over those non-identity Paulis. (qiskit.qotlabs.org, qiskit.github.io)
  • Bell states are eigenstates of X⊗X, Z⊗Z (and Y⊗Y), so XX, YY, ZZ stabilize |Φ+⟩ up to a phase; the four Bell states form an orthonormal basis. (chem.libretexts.org, en.wikipedia.org)
  • Fidelity with a pure target equals F = ⟨ψ|ρ|ψ⟩. (en.wikipedia.org)

3) Conventions and Units

  • Fidelity is taken as F(|ψ⟩,ρ) = ⟨ψ|ρ|ψ⟩ (not the root-fidelity). (en.wikipedia.org)
  • Error rate p is the probability that a given CNOT is followed by a non-identity 2-qubit Pauli error; each of the 15 non-identity Paulis occurs with probability p/15. This channel acts once per CNOT, on that gate’s two qubits. (qiskit.github.io, qiskit.qotlabs.org)

4) Final Answer: Fphys(p) = (1 − 4p/5)^2.

User #3

The correct answer is: $ F_{\rm{physical}}=(1-\frac{12}{15}p)^2 $

Now let’s solve the next Checkpoint.

Checkpoint 2

Suppose that we wish to prepare a logical two-qubit GHZ state \((|00\rangle_{AB}+|11\rangle_{AB})/\sqrt{2}\) in the [[4,2,2]] code. To do so, we use the following state preparation circuit:

\[ (CNOT_{03}) (H_0) (CNOT_{21}) (H_2). \]

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.

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

Assistant #4

1) Step-by-Step Derivation

  • Ideal output of the circuit (no noise)
  • Start from |0000⟩ on qubits (0,1,2,3).
  • The rightmost subcircuit H2 then CNOT21 prepares a Bell state on qubits (2,1): CNOT(H⊗I)|00⟩ = |Φ+⟩ = (|00⟩+|11⟩)/√2. Hence qubits (2,1) are in |Φ+⟩21. The later gates act only on (0,3). (en.wikipedia.org)
  • The subcircuit H0 then CNOT03 similarly prepares |Φ+⟩03 on (0,3). Therefore the ideal 4-qubit output state is
\[ |\psi_{\mathrm{ideal}}\rangle = |\Phi^+\rangle_{21}\otimes|\Phi^+\rangle_{03}. \]
  • Noise model and its localization
  • After each CNOT, a two-qubit depolarizing Pauli channel acts on that gate’s two qubits: with probability 1−p nothing happens, and with probability p one of the 15 non-identity two-qubit Paulis is applied uniformly (probability p/15 each). The two noisy channels act on disjoint pairs (2,1) and (0,3). (qiskit.github.io, quantumchannelzoo.org)

  • How a two-qubit Pauli acts on a Bell state

  • The Bell states are simultaneous eigenstates of X⊗X and Z⊗Z; in particular,
\[ |\Phi^+\rangle \text{ has } (X\!\otimes\!X)=+1,\ (Z\!\otimes\!Z)=+1. \]
The only two-qubit Paulis that map |Φ+ to itself (up to phase) are XX, YY, ZZ; all the other 12 map it to one of the other three Bell states {Φ−, Ψ+, Ψ−}. Consequently, for one noisy pair,
\[ \Pr(\Phi^+) = 1-\tfrac{12}{15}p,\qquad \Pr(\Phi^-)=\Pr(\Psi^+)=\Pr(\Psi^-)=\tfrac{4}{15}p. \]
The two pairs are independent. ([qubit.guide](https://qubit.guide/7.3-single-stabiliser-states?utm_source=openai))
  • Post-selection on the [[4,2,2]] code stabilizers
  • The code stabilizers are S = ⟨XXXX, ZZZZ⟩. On the product form, XXXX = (X0X3)(X2X1) and ZZZZ = (Z0Z3)(Z2Z1). Measuring XXXX (resp. ZZZZ) on the product of two Bell states returns the product of the XX (resp. ZZ) eigenvalues of the two pairs. We post-select runs with +1 for both stabilizers, which is equivalent to requiring that the two pairs have identical (XX, ZZ) eigenvalue pairs, i.e., they are the same Bell state. (ar5iv.labs.arxiv.org)
  • Therefore the acceptance probability is
\[ =(1-\tfrac{12}{15}p)^2+3\Big(\tfrac{4}{15}p\Big)^2 \]
  • Logical state produced upon acceptance and its fidelity
  • Conditioning on acceptance, the four-qubit state is stabilized by XXXX, ZZZZ and also by IXXI and IZZI with eigenvalues inherited from the (2,1)-pair. Thus the accepted state is exactly the logical Bell state whose (XX, ZZ) logical stabilizers match the pair’s eigenvalues: if both pairs are Φ+, we get the target logical |Φ+⟩AB; if both are Φ−, Ψ+, or Ψ−, we obtain the corresponding other logical Bell state, orthogonal to the target. Hence the logical-state fidelity equals the conditional probability that both pairs are Φ+:
\[ =\frac{\Pr(\Phi^+)^2}{P_{\rm acc}(p)} \]
Equivalently,
\[ \]
showing first-order error suppression under post-selection (distance-2 detection). ([qubit.guide](https://qubit.guide/7.3-single-stabiliser-states?utm_source=openai), [ar5iv.labs.arxiv.org](https://ar5iv.labs.arxiv.org/html/1907.11157v1?utm_source=openai))

2) Mathematical identities and checks used

  • Factorization of stabilizer measurements across the two Bell pairs: XXXX = (X0X3)(X2X1), ZZZZ = (Z0Z3)(Z2Z1). The eigenvalue measured is the product of pairwise eigenvalues. This matches the Bell-state eigenvalue table for XX and ZZ. (qubit.guide)
  • Two-qubit depolarizing channel chooses uniformly among the 15 non-identity two-qubit Pauli operators. (qiskit.github.io)

3) Conventions and Units

  • Fidelity with a pure target state |ψ⟩ is F = ⟨ψ|ρ|ψ⟩.
  • Error rate p is the probability that a given CNOT is followed by a non-identity 2-qubit Pauli error (each of the 15 non-identity Paulis occurs with probability p/15). (qiskit.github.io)

4) Final Answer:

\[ =\frac{(1-\frac{4}{5}p)^2}{1-\frac{8}{5}p+\frac{64}{75}p^2} \]
User #5

The correct answer is: $ F_{\rm{logical}}=1 - \frac{\frac{16}{75}p^2}{1-\frac{8}{5}p + \frac{64}{75}p^2} $

Now let’s solve the next Checkpoint.

Checkpoint 3

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 #6
Tool Call (code to execute)
import numpy as np
from itertools import product

# Define single-qubit 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)
pauli_dict = {'I':I, 'X':X, 'Y':Y, 'Z':Z}
paulis = ['I','X','Y','Z']

# Two-qubit CNOT gate (control c, target t) acting on two-qubit space (control first)
CNOT = np.array([[1,0,0,0],
                 [0,1,0,0],
                 [0,0,0,1],
                 [0,0,1,0]], dtype=complex)
H = (1/np.sqrt(2)) * np.array([[1,1],[1,-1]], dtype=complex)

# Build mapping tables by explicit conjugation for 1-qubit H and 2-qubit CNOT

def single_qubit_conj_map(U):
    # returns dict mapping 'I','X','Y','Z' -> (phase, pauli)
    M = {}
    for p in paulis:
        Pin = pauli_dict[p]
        Pout = U @ Pin @ U.conj().T
        # Find which Pauli (up to phase) equals Pout
        found = False
        for q in paulis:
            Q = pauli_dict[q]
            # Try factors 1, -1, 1j, -1j
            for c in [1, -1, 1j, -1j]:
                if np.allclose(Pout, c*Q):
                    M[p] = (c, q)
                    found = True
                    break
            if found:
                break
        if not found:
            raise RuntimeError('No match for H conjugation of', p)
    return M

H_map = single_qubit_conj_map(H)

# Two-qubit conjugation map for CNOT (control first, target second)

def two_qubit_conj_map(U):
    M = {}
    for pc in paulis:
        for pt in paulis:
            Pin = np.kron(pauli_dict[pc], pauli_dict[pt])
            Pout = U @ Pin @ U.conj().T
            found = False
            for qc in paulis:
                for qt in paulis:
                    Q = np.kron(pauli_dict[qc], pauli_dict[qt])
                    for c in [1, -1, 1j, -1j]:
                        if np.allclose(Pout, c*Q):
                            M[(pc,pt)] = (c, qc, qt)
                            found = True
                            break
                    if found:
                        break
                if found:
                    break
            if not found:
                raise RuntimeError('No match for CNOT conjugation of', pc, pt)
    return M

CNOT_map = two_qubit_conj_map(CNOT)

# Utility: multiply two single-qubit Paulis (with phases)
# returns (phase, resulting_pauli)
mult_table = {}
for a in paulis:
    for b in paulis:
        A = pauli_dict[a]
        B = pauli_dict[b]
        P = A @ B
        found=False
        for c in [1, -1, 1j, -1j]:
            for r in paulis:
                if np.allclose(P, c*pauli_dict[r]):
                    mult_table[(a,b)] = (c, r)
                    found=True
                    break
            if found: break
        if not found:
            raise RuntimeError('No match for mult', a, b)

# Multiply multi-qubit Pauli strings with phase tracking
# strings are like ['X','I','Z','Y','I'] for 5 qubits

def multiply_strings(s1, s2):
    c = 1+0j
    out = []
    for a,b in zip(s1,s2):
        cb, r = mult_table[(a,b)]
        c *= cb
        out.append(r)
    return c, out

# Build projectors as Pauli sums
# P_code on data qubits 0..3: (I + XXXX)(I + ZZZZ)/4 = (I + XXXX + ZZZZ - YYYY)/4

def pauli_string(qubits5, letters, coeff=1+0j):
    # letters length equals number of active qubits; we will place on 5 qubits with last being ancilla optionally
    return (coeff, letters)

# Generate P_accept terms on 5 qubits: (1/8)*(I+Z4) tensor (I + XXXX + ZZZZ - YYYY)
Pacc_terms = []
for anc in ['I','Z']:
    for data in [ ['I','I','I','I'], ['X','X','X','X'], ['Z','Z','Z','Z'], ['Y','Y','Y','Y'] ]:
        coeff = 1/8
        if data == ['Y','Y','Y','Y']:
            coeff *= -1  # because of minus sign
        letters5 = data + [anc]
        Pacc_terms.append((coeff+0j, letters5))

# Build target projector on data qubits: stabilized by XXXX, ZZZZ, Z_A=ZZII, Z_B=ZIZI with +1 eigenvalues
# P_target = (1/16) sum over group generated by these four independent commuting Paulis

Ggens = [
    ['X','X','X','X'],
    ['Z','Z','Z','Z'],
    ['Z','Z','I','I'],  # Z_A
    ['Z','I','Z','I'],  # Z_B
]

# Enumerate group elements
group_elems = {}
for a,b,c,d in product([0,1],[0,1],[0,1],[0,1]):
    coeff = 1+0j
    s = ['I','I','I','I']
    for e, g in zip([a,b,c,d], Ggens):
        if e==1:
            c2, s = multiply_strings(s, g)
            coeff *= c2
    key = tuple(s)
    # Sum coefficients if same Pauli appears (shouldn't happen often)
    group_elems[key] = group_elems.get(key, 0+0j) + coeff

# Convert to list of (coeff, letters)
Ptarget_data_terms = []
for letters, coeff in group_elems.items():
    # Each term has coefficient 1/16 times its coeff from multiplication
    Ptarget_data_terms.append(((coeff/16)+0j, list(letters)))

# Build P_good terms on 5 qubits: (1/2)*(I+Z4) tensor P_target_data
Pgood_terms = []
for anc in ['I','Z']:
    for coeff, letters4 in Ptarget_data_terms:
        Pgood_terms.append(((coeff/2)+0j, letters4 + [anc]))

# Define Heisenberg propagation through the full circuit
# Steps: [H1], [CNOT12 + E1], [CNOT10 + E2], [CNOT23 + E3], [CNOT34 + E4], [CNOT04 + E5]
# We implement backward propagation: for k=last..first: apply E_k^† then conjugate by gate^

# Conjugation helpers

def conj_H_on_qubit(term_letters, q):
    letters = term_letters.copy()
    c = 1+0j
    p = letters[q]
    c1, q1 = H_map[p]
    letters[q] = q1
    c *= c1
    return c, letters

def conj_CNOT_on_pair(term_letters, c_idx, t_idx):
    letters = term_letters.copy()
    pc = letters[c_idx]
    pt = letters[t_idx]
    c1, qc, qt = CNOT_map[(pc,pt)]
    letters[c_idx] = qc
    letters[t_idx] = qt
    return c1, letters

# For depolarizing adjoint factor: if either of the two qubits at indices (a,b) is non-identity

def E_dag_factor(term_letters, a, b, lam):
    return lam if (term_letters[a] != 'I' or term_letters[b] != 'I') else 1.0

# Propagate a term and return its contribution factor to expectation on |00000> (real scalar) as a function of lam

def propagate_term(term_coeff, term_letters, lam):
    c = term_coeff
    letters = term_letters.copy()
    # k=6: E5 on (0,4) then CNOT04
    c *= E_dag_factor(letters, 0, 4, lam)
    c1, letters = conj_CNOT_on_pair(letters, 0, 4)
    c *= c1
    # k=5: E4 on (3,4) then CNOT34
    c *= E_dag_factor(letters, 3, 4, lam)
    c1, letters = conj_CNOT_on_pair(letters, 3, 4)
    c *= c1
    # k=4: E3 on (2,3) then CNOT23
    c *= E_dag_factor(letters, 2, 3, lam)
    c1, letters = conj_CNOT_on_pair(letters, 2, 3)
    c *= c1
    # k=3: E2 on (1,0) then CNOT10
    c *= E_dag_factor(letters, 1, 0, lam)
    c1, letters = conj_CNOT_on_pair(letters, 1, 0)
    c *= c1
    # k=2: E1 on (1,2) then CNOT12
    c *= E_dag_factor(letters, 1, 2, lam)
    c1, letters = conj_CNOT_on_pair(letters, 1, 2)
    c *= c1
    # k=1: H1
    c1, letters = conj_H_on_qubit(letters, 1)
    c *= c1
    # Now compute expectation on |00000>: nonzero only if letters are only I or Z
    for L in letters:
        if L in ('X','Y'):
            return 0.0
    # For I/Z-only string, expectation equals real part of global coefficient c (since <0|Z|0>=1)
    return float(np.real_if_close(c).real)

# Compute A(lam) and G(lam)

def compute_A_G(lam):
    A = 0.0
    for coeff, letters in Pacc_terms:
        A += propagate_term(coeff, letters, lam)
    G = 0.0
    for coeff, letters in Pgood_terms:
        G += propagate_term(coeff, letters, lam)
    return A, G

# Sanity checks: p=0 -> lam=1; both A and G should be 1
for lam in [1.0, 0.0, 0.5]:
    A, G = compute_A_G(lam)
    print('lam=', lam, 'A=', A, 'G=', G, 'F=G/A' if A>0 else 'A=0')

# Now, let's try to express A and G as polynomials in lam
# We can fit polynomials up to degree 5 since there are 5 noise channels
L_vals = np.linspace(0,1,6)
A_coeffs = np.polyfit(L_vals, [compute_A_G(l)[0] for l in L_vals], 5)
G_coeffs = np.polyfit(L_vals, [compute_A_G(l)[1] for l in L_vals], 5)
print('A(lam) poly coeffs (deg 5 -> deg 0):', A_coeffs)
print('G(lam) poly coeffs (deg 5 -> deg 0):', G_coeffs)

# For readability, round coefficients to rationals with small denominators
from fractions import Fraction

def rationalize_coeffs(coeffs):
    rats = []
    for c in coeffs:
        f = Fraction(float(np.real_if_close(c).real)).limit_denominator(10**6)
        rats.append(f)
    return rats

print('A rational coeffs:', rationalize_coeffs(A_coeffs))
print('G rational coeffs:', rationalize_coeffs(G_coeffs))

# Convert lam to p: lam = 1 - 16p/15
# We can express A(p) and G(p) by substituting lam and expanding symbolic polynomial using numpy.poly1d
A_poly_lam = np.poly1d(A_coeffs)
G_poly_lam = np.poly1d(G_coeffs)

import sympy as sp
p = sp.symbols('p', real=True)
lam = 1 - sp.Rational(16,15)*p
# Convert numpy poly1d to sympy poly
A_sym_lam = sum(sp.Rational(Fraction(float(c)).limit_denominator(10**6)) * lam**i for i,c in enumerate(A_poly_lam.c[::-1]))
G_sym_lam = sum(sp.Rational(Fraction(float(c)).limit_denominator(10**6)) * lam**i for i,c in enumerate(G_poly_lam.c[::-1]))
A_sym = sp.simplify(sp.expand(A_sym_lam))
G_sym = sp.simplify(sp.expand(G_sym_lam))
F_sym = sp.simplify(sp.factor(G_sym / A_sym))
print('A_sym(p)=', A_sym)
print('G_sym(p)=', G_sym)
print('F_sym(p)=', F_sym)

# Also expand series up to p^2
print('Series F up to p^3:', sp.series(F_sym, p, 0, 4))
Tool #7
lam= 1.0 A= 0.5 G= 1.0 F=G/A
lam= 0.0 A= 0.125 G= 0.03125 F=G/A
lam= 0.5 A= 0.140625 G= 0.0947265625 F=G/A
A(lam) poly coeffs (deg 5 -> deg 0): [ 2.50000000e-01  1.25000000e-01  5.85596233e-14 -2.33045049e-14
  3.38425206e-15  1.25000000e-01]
G(lam) poly coeffs (deg 5 -> deg 0): [6.56250000e-01 1.25000000e-01 9.37500000e-02 9.37500000e-02
 6.23438555e-16 3.12500000e-02]
A rational coeffs: [Fraction(1, 4), Fraction(1, 8), Fraction(0, 1), Fraction(0, 1), Fraction(0, 1), Fraction(1, 8)]
G rational coeffs: [Fraction(21, 32), Fraction(1, 8), Fraction(3, 32), Fraction(3, 32), Fraction(0, 1), Fraction(1, 32)]
A_sym(p)= -262144*p**5/759375 + 90112*p**4/50625 - 4096*p**3/1125 + 832*p**2/225 - 28*p/15 + 1/2
G_sym(p)= -229376*p**5/253125 + 223232*p**4/50625 - 29312*p**3/3375 + 656*p**2/75 - 68*p/15 + 1
F_sym(p)= 6*(229376*p**5 - 1116160*p**4 + 2198400*p**3 - 2214000*p**2 + 1147500*p - 253125)/((8*p - 15)*(65536*p**4 - 215040*p**3 + 288000*p**2 - 162000*p + 50625))
Series F up to p^3: 2 - 8*p/5 - 736*p**2/225 - 3584*p**3/1125 + O(p**4)
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)
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)

# 5-qubit size
n=5

# basis order qubits [0,1,2,3,4]

# Build 5-qubit identity
I5 = np.eye(2**n, dtype=complex)

# Helper: build single-qubit gate on qubit q

def single_on(q, U):
    ops = [I]*n
    ops[q] = U
    M = ops[0]
    for k in range(1,n):
        M = np.kron(M, ops[k])
    return M

# Helper: build CNOT with control c, target t
# We'll implement using projector form

P0 = np.array([[1,0],[0,0]], dtype=complex)
P1 = np.array([[0,0],[0,1]], dtype=complex)


def CNOT_on(c,t):
    # U = |0><0|_c ⊗ I + |1><1|_c ⊗ X_t
    ops0 = [I]*n
    ops1 = [I]*n
    ops0[c] = P0
    ops1[c] = P1
    # Now we need X on t in ops1
    ops1[t] = X
    M0 = ops0[0]
    for k in range(1,n):
        M0 = np.kron(M0, ops0[k])
    M1 = ops1[0]
    for k in range(1,n):
        M1 = np.kron(M1, ops1[k])
    return M0 + M1

# Build P_code and P_anc0 operators

def pauli_on(qubit, letter):
    return {'I':I, 'X':X, 'Y':Y, 'Z':Z}[letter]

# Data qubits 0..3, ancilla 4

def pauli_string5(letters5):
    M = pauli_on(0, letters5[0])
    for k in range(1,5):
        M = np.kron(M, pauli_on(k, letters5[k]))
    return M

P_code = 0.25*(pauli_string5(['I','I','I','I','I']) +
               pauli_string5(['X','X','X','X','I']) +
               pauli_string5(['Z','Z','Z','Z','I']) -
               pauli_string5(['Y','Y','Y','Y','I']))
P_anc0 = 0.5*(pauli_string5(['I','I','I','I','I']) + pauli_string5(['I','I','I','I','Z']))
P_accept = P_anc0 @ P_code  # they commute, product equals tensor product

# Build target projector on data (0..3): stabilized by XXXX, ZZZZ, ZZII, ZIZI
# We can construct as the unique state vectors and then projector

# We'll compute U_total and |psi>
U = np.eye(2**5, dtype=complex)
U = CNOT_on(0,4) @ U
U = CNOT_on(3,4) @ U
U = CNOT_on(2,3) @ U
U = CNOT_on(1,0) @ U
U = CNOT_on(1,2) @ U
U = single_on(1, H) @ U

# Apply to |00000>
psi0 = np.zeros((2**5,1), dtype=complex)
psi0[0,0] = 1.0
psi = U @ psi0

# Compute acceptance probability
A_direct = (psi.conj().T @ P_accept @ psi)[0,0]
print('A_direct=', A_direct)

# Construct target logical |00>_AB state on data; We need to find 16-term projector
# Build group projector formula

# We'll build projectors on data first using 4-qubit matrices

def pauli_string4(letters4):
    M = pauli_on(0, letters4[0])
    for k in range(1,4):
        M = np.kron(M, pauli_on(k, letters4[k]))
    return M

P_target_data = np.zeros((2**4,2**4), dtype=complex)
Ggens = [
    ['X','X','X','X'],
    ['Z','Z','Z','Z'],
    ['Z','Z','I','I'],
    ['Z','I','Z','I'],
]
# enumerate group elements
for a,b,c,d in product([0,1],[0,1],[0,1],[0,1]):
    M = np.eye(2**4, dtype=complex)
    for e,g in zip([a,b,c,d], Ggens):
        if e==1:
            M = M @ pauli_string4(g)
    P_target_data += M
P_target_data /= 16.0

# Now compute P_good (ancilla 0 tensor target data)
# On 5 qubits, P_good = P_anc0 ⊗ P_target_data
P_good = np.kron(P_target_data, np.array([[1,0],[0,1]], dtype=complex)) * 0.5 + \
         np.kron(P_target_data, np.array([[1,0],[0,-1]], dtype=complex)) * 0.5
# Wait above constructs (I + Z)/2 ⊗ P_target; But better use prior P_anc0 tensored with data projector
P_good = P_anc0 @ (np.kron(P_target_data, np.eye(2)))  # since P_anc0 acts on all 5; yes

G_direct = (psi.conj().T @ P_good @ psi)[0,0]
print('G_direct=', G_direct)
print('F_direct=', G_direct / A_direct)
Tool #9
Traceback (most recent call last):
  File "<stdin>", line 109, in <module>
NameError: name 'product' is not defined

A_direct= (0.24999999999999994+0j)
Assistant #10
import numpy as np
from itertools import product
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)

n=5
I5 = np.eye(2**n, dtype=complex)
P0 = np.array([[1,0],[0,0]], dtype=complex)
P1 = np.array([[0,0],[0,1]], dtype=complex)

def single_on(q, U):
    ops = [I]*n
    ops[q] = U
    M = ops[0]
    for k in range(1,n):
        M = np.kron(M, ops[k])
    return M

def CNOT_on(c,t):
    ops0 = [I]*n
    ops1 = [I]*n
    ops0[c] = P0
    ops1[c] = P1
    ops1[t] = X
    M0 = ops0[0]
    for k in range(1,n):
        M0 = np.kron(M0, ops0[k])
    M1 = ops1[0]
    for k in range(1,n):
        M1 = np.kron(M1, ops1[k])
    return M0 + M1


def pauli_on(letter):
    return {'I':I, 'X':X, 'Y':Y, 'Z':Z}[letter]

def pauli_string5(letters5):
    M = pauli_on(letters5[0])
    for k in range(1,5):
        M = np.kron(M, pauli_on(letters5[k]))
    return M

P_code = 0.25*(pauli_string5(['I','I','I','I','I']) +
               pauli_string5(['X','X','X','X','I']) +
               pauli_string5(['Z','Z','Z','Z','I']) -
               pauli_string5(['Y','Y','Y','Y','I']))
P_anc0 = 0.5*(pauli_string5(['I','I','I','I','I']) + pauli_string5(['I','I','I','I','Z']))
P_accept = P_anc0 @ P_code

U = np.eye(2**5, dtype=complex)
U = CNOT_on(0,4) @ U
U = CNOT_on(3,4) @ U
U = CNOT_on(2,3) @ U
U = CNOT_on(1,0) @ U
U = CNOT_on(1,2) @ U
U = single_on(1, H) @ U
psi0 = np.zeros((2**5,1), dtype=complex)
psi0[0,0] = 1.0
psi = U @ psi0
A_direct = (psi.conj().T @ P_accept @ psi)[0,0]
print('A_direct=', A_direct)

# Build P_target_data projector via group formula

def pauli_string4(letters4):
    M = pauli_on(letters4[0])
    for k in range(1,4):
        M = np.kron(M, pauli_on(letters4[k]))
    return M

Ggens = [
    ['X','X','X','X'],
    ['Z','Z','Z','Z'],
    ['Z','Z','I','I'],
    ['Z','I','Z','I'],
]
P_target_data = np.zeros((2**4,2**4), dtype=complex)
for a,b,c,d in product([0,1],[0,1],[0,1],[0,1]):
    M = np.eye(2**4, dtype=complex)
    for e,g in zip([a,b,c,d], Ggens):
        if e==1:
            M = M @ pauli_string4(g)
    P_target_data += M
P_target_data /= 16.0

# Lift to 5 qubits (data on 0..3, ancilla on 4)
P_good = np.kron(P_target_data, np.eye(2)) @ P_anc0  # they commute
G_direct = (psi.conj().T @ P_good @ psi)[0,0]
print('G_direct=', G_direct)
print('F_direct=', G_direct / A_direct)
Tool #11
A_direct= (0.24999999999999994+0j)
G_direct= (0.24999999999999994+0j)
F_direct= (1+0j)
Assistant #12
Tool Call (code to execute)
# Compute expectations individually
import numpy as np
A_code = (psi.conj().T @ P_code @ psi)[0,0]
A_anc0 = (psi.conj().T @ P_anc0 @ psi)[0,0]
print('A_code=', A_code)
print('A_anc0=', A_anc0)
print('product expected for commuting projectors? (not generally equal)')
Tool #13
Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
NameError: name 'psi' is not defined
Assistant #14
import numpy as np
# Recreate variables quickly
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)

n=5
I5 = np.eye(2**n, dtype=complex)
P0 = np.array([[1,0],[0,0]], dtype=complex)
P1 = np.array([[0,0],[0,1]], dtype=complex)


def single_on(q, U):
    ops = [I]*n
    ops[q] = U
    M = ops[0]
    for k in range(1,n):
        M = np.kron(M, ops[k])
    return M

def CNOT_on(c,t):
    ops0 = [I]*n
    ops1 = [I]*n
    ops0[c] = P0
    ops1[c] = P1
    ops1[t] = X
    M0 = ops0[0]
    for k in range(1,n):
        M0 = np.kron(M0, ops0[k])
    M1 = ops1[0]
    for k in range(1,n):
        M1 = np.kron(M1, ops1[k])
    return M0 + M1


def pauli_on(letter):
    return {'I':I, 'X':X, 'Y':Y, 'Z':Z}[letter]

def pauli_string5(letters5):
    M = pauli_on(letters5[0])
    for k in range(1,5):
        M = np.kron(M, pauli_on(letters5[k]))
    return M

P_code = 0.25*(pauli_string5(['I','I','I','I','I']) +
               pauli_string5(['X','X','X','X','I']) +
               pauli_string5(['Z','Z','Z','Z','I']) -
               pauli_string5(['Y','Y','Y','Y','I']))
P_anc0 = 0.5*(pauli_string5(['I','I','I','I','I']) + pauli_string5(['I','I','I','I','Z']))
P_accept = P_anc0 @ P_code

U = np.eye(2**5, dtype=complex)
U = CNOT_on(0,4) @ U
U = CNOT_on(3,4) @ U
U = CNOT_on(2,3) @ U
U = CNOT_on(1,0) @ U
U = CNOT_on(1,2) @ U
U = single_on(1, H) @ U
psi0 = np.zeros((2**5,1), dtype=complex)
psi0[0,0] = 1.0
psi = U @ psi0

A_code = (psi.conj().T @ P_code @ psi)[0,0]
A_anc0 = (psi.conj().T @ P_anc0 @ psi)[0,0]
A_accept = (psi.conj().T @ P_accept @ psi)[0,0]
print('A_code=', A_code)
print('A_anc0=', A_anc0)
print('A_accept=', A_accept)
Tool #15
A_code= (0.24999999999999994+0j)
A_anc0= (0.9999999999999998+0j)
A_accept= (0.24999999999999994+0j)
Assistant #16
Tool Call (code to execute)
import numpy as np
from itertools import product
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)

n=5
I5 = np.eye(2**n, dtype=complex)
P0 = np.array([[1,0],[0,0]], dtype=complex)
P1 = np.array([[0,0],[0,1]], dtype=complex)

def single_on(q, U):
    ops = [I]*n
    ops[q] = U
    M = ops[0]
    for k in range(1,n):
        M = np.kron(M, ops[k])
    return M

def CNOT_on(c,t):
    ops0 = [I]*n
    ops1 = [I]*n
    ops0[c] = P0
    ops1[c] = P1
    ops1[t] = X
    M0 = ops0[0]
    for k in range(1,n):
        M0 = np.kron(M0, ops0[k])
    M1 = ops1[0]
    for k in range(1,n):
        M1 = np.kron(M1, ops1[k])
    return M0 + M1

# Build the overall U (no noise)
U = np.eye(2**5, dtype=complex)
U = CNOT_on(0,4) @ U
U = CNOT_on(3,4) @ U
U = CNOT_on(2,3) @ U
U = CNOT_on(1,0) @ U
U = CNOT_on(1,2) @ U
U = single_on(1, H) @ U

# Build acceptance Pauli terms
letters_list = []
coeffs = []
for anc in ['I','Z']:
    for data in [ ['I','I','I','I'], ['X','X','X','X'], ['Z','Z','Z','Z'], ['Y','Y','Y','Y'] ]:
        coeff = 1/8
        if data == ['Y','Y','Y','Y']:
            coeff *= -1
        letters5 = data + [anc]
        letters_list.append(letters5)
        coeffs.append(coeff)

# helper to get 5-qubit Pauli matrix from letters
pauli_map={'I':I,'X':X,'Y':Y,'Z':Z}

def pauli5(letters):
    M=pauli_map[letters[0]]
    for k in range(1,5):
        M=np.kron(M, pauli_map[letters[k]])
    return M

# Compute direct transformed observable O0 = U^
O0 = np.zeros((32,32), dtype=complex)
for coeff, letters in zip(coeffs, letters_list):
    O = pauli5(letters)
    O0 += coeff * (U.conj().T @ O @ U)

# Now compute the expectation on |00000><00000|
psi0 = np.zeros((32,1), dtype=complex)
psi0[0,0] = 1.0
val_direct = (psi0.conj().T @ O0 @ psi0)[0,0]
print('direct val =', val_direct)

# Now compute via our previous Pauli conjugation maps stepwise to confirm mismatch
# Rebuild mapping tables
# we need H on qubit 1 and CNOT on pairs (0,4),(3,4),(2,3),(1,0),(1,2)

# create single-qubit map for H
paulis = ['I','X','Y','Z']
H_map = {}
for p in paulis:
    Pin = pauli_map[p]
    Pout = H @ Pin @ H.conj().T
    for q in paulis:
        Q = pauli_map[q]
        for c in [1,-1,1j,-1j]:
            if np.allclose(Pout, c*Q):
                H_map[p]=(c,q)
                break
        if p in H_map:
            break

# two-qubit CNOT conjugation map (control first)
CNOT2 = np.array([[1,0,0,0],
                  [0,1,0,0],
                  [0,0,0,1],
                  [0,0,1,0]], dtype=complex)
CNOT_map={}
for pc in paulis:
    for pt in paulis:
        Pin = np.kron(pauli_map[pc],pauli_map[pt])
        Pout = CNOT2 @ Pin @ CNOT2.conj().T
        for qc in paulis:
            for qt in paulis:
                Q = np.kron(pauli_map[qc], pauli_map[qt])
                for c in [1,-1,1j,-1j]:
                    if np.allclose(Pout, c*Q):
                        CNOT_map[(pc,pt)]=(c,qc,qt)
                        break
                if (pc,pt) in CNOT_map:
                    break
            if (pc,pt) in CNOT_map:
                break

# implement conjugation chain stepwise for each term and accumulate into O0_est

# Conjugation routines

def conj_H_on(letters, q):
    letters=list(letters)
    c, qlet = H_map[letters[q]]
    letters[q]=qlet
    return c, letters

def conj_CNOT(letters, c_idx, t_idx):
    letters=list(letters)
    cphase, qc, qt = CNOT_map[(letters[c_idx], letters[t_idx])]
    letters[c_idx]=qc
    letters[t_idx]=qt
    return cphase, letters

# Compose for each term: O0_est = sum coeff * conj sequence

O0_est = {}

for coeff, letters in zip(coeffs, letters_list):
    cplx = coeff+0j
    L = letters
    # sequence: U^
    # U = CNOT04*CNOT34*CNOT23*CNOT10*CNOT12*H1
    # So conjugation by U^ = H1 * CNOT12 * CNOT10 * CNOT23 * CNOT34 * CNOT04
    # Actually conjugation: O0 = U^
    c1, L = conj_H_on(L, 1)
    cplx *= c1
    c1, L = conj_CNOT(L, 1, 2)
    cplx *= c1
    c1, L = conj_CNOT(L, 1, 0)
    cplx *= c1
    c1, L = conj_CNOT(L, 2, 3)
    cplx *= c1
    c1, L = conj_CNOT(L, 3, 4)
    cplx *= c1
    c1, L = conj_CNOT(L, 0, 4)
    cplx *= c1
    # accumulate
    key = tuple(L)
    O0_est[key] = O0_est.get(key, 0+0j) + cplx

# Now compute expectation using the estimated O0_est
# It is sum over terms cplx * pauli5(L)
expect_est = 0+0j
for L, coeff in O0_est.items():
    # expectation on |00000> is nonzero only if letters are I or Z
    if all(ch in ('I','Z') for ch in L):
        # expectation equals coeff * 1
        expect_est += coeff

print('expect_est=', expect_est)
print('difference:', expect_est - val_direct)
Tool #17
direct val = (0.24999999999999994+0j)
expect_est= (0.25+0j)
difference: (5.551115123125783e-17+0j)
Assistant #18
Tool Call (code to execute)
import numpy as np
from itertools import product
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_map={'I':I,'X':X,'Y':Y,'Z':Z}
paulis = ['I','X','Y','Z']
# Build conjugation maps (as before)
H_map = {}
for p in paulis:
    Pin = pauli_map[p]
    Pout = H @ Pin @ H.conj().T
    for q in paulis:
        Q = pauli_map[q]
        for c in [1,-1,1j,-1j]:
            if np.allclose(Pout, c*Q):
                H_map[p]=(c,q)
                break
        if p in H_map:
            break

CNOT2 = np.array([[1,0,0,0],
                  [0,1,0,0],
                  [0,0,0,1],
                  [0,0,1,0]], dtype=complex)
CNOT_map={}
for pc in paulis:
    for pt in paulis:
        Pin = np.kron(pauli_map[pc],pauli_map[pt])
        Pout = CNOT2 @ Pin @ CNOT2.conj().T
        for qc in paulis:
            for qt in paulis:
                Q = np.kron(pauli_map[qc], pauli_map[qt])
                for c in [1,-1,1j,-1j]:
                    if np.allclose(Pout, c*Q):
                        CNOT_map[(pc,pt)]=(c,qc,qt)
                        break
                if (pc,pt) in CNOT_map:
                    break
            if (pc,pt) in CNOT_map:
                break

# multiply table for building group terms
mult_table = {}
for a in paulis:
    for b in paulis:
        P = pauli_map[a] @ pauli_map[b]
        for c in [1,-1,1j,-1j]:
            for r in paulis:
                if np.allclose(P, c*pauli_map[r]):
                    mult_table[(a,b)] = (c, r)
                    break
            if (a,b) in mult_table: break


def multiply_strings(s1, s2):
    c = 1+0j
    out = []
    for a,b in zip(s1,s2):
        cb, r = mult_table[(a,b)]
        c *= cb
        out.append(r)
    return c, out

# Build P_accept terms (with correct sign): (1/8)*(I+Z4) tensor (I + XXXX + ZZZZ - YYYY)
Pacc_terms = []
for anc in ['I','Z']:
    for data in [ ['I','I','I','I'], ['X','X','X','X'], ['Z','Z','Z','Z'], ['Y','Y','Y','Y'] ]:
        coeff = 1/8
        if data == ['Y','Y','Y','Y']:
            coeff *= -1
        letters5 = data + [anc]
        Pacc_terms.append((coeff+0j, letters5))

# P_good terms: (1/32)*(I+Z4) tensor sum over group generated by XXXX, ZZZZ, ZZII, ZIZI
Ggens = [
    ['X','X','X','X'],
    ['Z','Z','Z','Z'],
    ['Z','Z','I','I'],
    ['Z','I','Z','I'],
]
# enumerate group elements with signs
Ptarget_terms_data = []
seen = {}
for a,b,c,d in product([0,1],[0,1],[0,1],[0,1]):
    coeff = 1+0j
    s = ['I','I','I','I']
    for e, g in zip([a,b,c,d], Ggens):
        if e==1:
            c2, s = multiply_strings(s, g)
            coeff *= c2
    key = tuple(s)
    seen[key] = seen.get(key, 0+0j) + coeff
Ptarget_terms_data = [ (coeff/16, list(letters)) for letters, coeff in seen.items() ]

Pgood_terms = []
for anc in ['I','Z']:
    for coeff, letters4 in Ptarget_terms_data:
        Pgood_terms.append((coeff/2, letters4 + [anc]))

# Heisenberg propagation with noise factors lam per CNOT

def conj_H_on(letters, q):
    letters=list(letters)
    c,qlet = H_map[letters[q]]
    letters[q]=qlet
    return c, letters

def conj_CNOT(letters, c_idx, t_idx):
    letters=list(letters)
    cphase, qc, qt = CNOT_map[(letters[c_idx], letters[t_idx])]
    letters[c_idx]=qc
    letters[t_idx]=qt
    return cphase, letters


def E_dag_factor(letters, a, b, lam):
    return lam if (letters[a] != 'I' or letters[b] != 'I') else 1.0

# propagate one term

def propagate(term_coeff, term_letters, lam):
    c = complex(term_coeff)
    L = list(term_letters)
    # Step 6: E5 then CNOT04
    c *= E_dag_factor(L, 0, 4, lam)
    c1, L = conj_CNOT(L, 0, 4)
    c *= c1
    # Step 5: E4 then CNOT34
    c *= E_dag_factor(L, 3, 4, lam)
    c1, L = conj_CNOT(L, 3, 4)
    c *= c1
    # Step 4: E3 then CNOT23
    c *= E_dag_factor(L, 2, 3, lam)
    c1, L = conj_CNOT(L, 2, 3)
    c *= c1
    # Step 3: E2 then CNOT10
    c *= E_dag_factor(L, 1, 0, lam)
    c1, L = conj_CNOT(L, 1, 0)
    c *= c1
    # Step 2: E1 then CNOT12
    c *= E_dag_factor(L, 1, 2, lam)
    c1, L = conj_CNOT(L, 1, 2)
    c *= c1
    # Step 1: H1
    c1, L = conj_H_on(L, 1)
    c *= c1
    # expectation on |00000>: nonzero only if Z/I only
    if all(ch in ('I','Z') for ch in L):
        return float(np.real(c))
    else:
        return 0.0


def compute_A_G(lam):
    A=0.0
    for coeff, letters in Pacc_terms:
        A += propagate(coeff, letters, lam)
    G=0.0
    for coeff, letters in Pgood_terms:
        G += propagate(coeff, letters, lam)
    return A, G

# Check values
for lam in [1.0, 0.0, 0.5]:
    A,G = compute_A_G(lam)
    print('lam', lam, 'A', A, 'G', G, 'F', (G/A) if A>0 else None)

# Fit polynomials in lam of degree <= 5
L_vals = [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]
A_vals = [compute_A_G(l)[0] for l in L_vals]
G_vals = [compute_A_G(l)[1] for l in L_vals]
Acoeff = np.polyfit(L_vals, A_vals, 5)
Gcoeff = np.polyfit(L_vals, G_vals, 5)
print('Acoeff:', Acoeff)
print('Gcoeff:', Gcoeff)

from fractions import Fraction
print('Acoeff rational approx:', [Fraction(x).limit_denominator() for x in Acoeff])
print('Gcoeff rational approx:', [Fraction(x).limit_denominator() for x in Gcoeff])

# convert to p with lam = 1 - 16p/15
import sympy as sp
p = sp.symbols('p', real=True)
lam = 1 - sp.Rational(16,15)*p
A_poly = sum(sp.Rational(Fraction(float(c)).limit_denominator(10**6)) * lam**i for i,c in enumerate(np.poly1d(Acoeff).c[::-1]))
G_poly = sum(sp.Rational(Fraction(float(c)).limit_denominator(10**6)) * lam**i for i,c in enumerate(np.poly1d(Gcoeff).c[::-1]))
A_sym = sp.simplify(sp.expand(A_poly))
G_sym = sp.simplify(sp.expand(G_poly))
F_sym = sp.simplify(sp.factor(G_sym / A_sym))
print('A_sym(p)=', A_sym)
print('G_sym(p)=', G_sym)
print('F_sym(p)=', F_sym)
print('series F to p^3:', sp.series(F_sym, p, 0, 4))
Tool #19
lam 1.0 A 0.5 G 1.0 F 2.0
lam 0.0 A 0.125 G 0.03125 F 0.25
lam 0.5 A 0.140625 G 0.0947265625 F 0.6736111111111112
Acoeff: [ 2.50000000e-01  1.25000000e-01  5.56209701e-14 -2.32498098e-14
  3.83333001e-15  1.25000000e-01]
Gcoeff: [6.56250000e-01 1.25000000e-01 9.37500000e-02 9.37500000e-02
 7.25642502e-16 3.12500000e-02]
Acoeff rational approx: [Fraction(1, 4), Fraction(1, 8), Fraction(0, 1), Fraction(0, 1), Fraction(0, 1), Fraction(1, 8)]
Gcoeff rational approx: [Fraction(21, 32), Fraction(1, 8), Fraction(3, 32), Fraction(3, 32), Fraction(0, 1), Fraction(1, 32)]
A_sym(p)= -262144*p**5/759375 + 90112*p**4/50625 - 4096*p**3/1125 + 832*p**2/225 - 28*p/15 + 1/2
G_sym(p)= -229376*p**5/253125 + 223232*p**4/50625 - 29312*p**3/3375 + 656*p**2/75 - 68*p/15 + 1
F_sym(p)= 6*(229376*p**5 - 1116160*p**4 + 2198400*p**3 - 2214000*p**2 + 1147500*p - 253125)/((8*p - 15)*(65536*p**4 - 215040*p**3 + 288000*p**2 - 162000*p + 50625))
series F to p^3: 2 - 8*p/5 - 736*p**2/225 - 3584*p**3/1125 + O(p**4)
Assistant #20
Tool Call (code to execute)
import numpy as np
from itertools import product
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_map={'I':I,'X':X,'Y':Y,'Z':Z}
paulis = ['I','X','Y','Z']

# Build conjugation maps
H_map = {}
for p in paulis:
    Pin = pauli_map[p]
    Pout = H @ Pin @ H.conj().T
    for q in paulis:
        Q = pauli_map[q]
        for c in [1,-1,1j,-1j]:
            if np.allclose(Pout, c*Q):
                H_map[p]=(c,q)
                break
        if p in H_map:
            break

CNOT2 = np.array([[1,0,0,0],
                  [0,1,0,0],
                  [0,0,0,1],
                  [0,0,1,0]], dtype=complex)
CNOT_map={}
for pc in paulis:
    for pt in paulis:
        Pin = np.kron(pauli_map[pc],pauli_map[pt])
        Pout = CNOT2 @ Pin @ CNOT2.conj().T
        for qc in paulis:
            for qt in paulis:
                Q = np.kron(pauli_map[qc], pauli_map[qt])
                for c in [1,-1,1j,-1j]:
                    if np.allclose(Pout, c*Q):
                        CNOT_map[(pc,pt)]=(c,qc,qt)
                        break
                if (pc,pt) in CNOT_map:
                    break
            if (pc,pt) in CNOT_map:
                break

# multiply table for building group terms
mult_table = {}
for a in paulis:
    for b in paulis:
        P = pauli_map[a] @ pauli_map[b]
        for c in [1,-1,1j,-1j]:
            for r in paulis:
                if np.allclose(P, c*pauli_map[r]):
                    mult_table[(a,b)] = (c, r)
                    break
            if (a,b) in mult_table: break


def multiply_strings(s1, s2):
    c = 1+0j
    out = []
    for a,b in zip(s1,s2):
        cb, r = mult_table[(a,b)]
        c *= cb
        out.append(r)
    return c, out

# Projector terms
Pacc_terms = []
for anc in ['I','Z']:
    for data in [ ['I','I','I','I'], ['X','X','X','X'], ['Z','Z','Z','Z'], ['Y','Y','Y','Y'] ]:
        coeff = 1/8
        if data == ['Y','Y','Y','Y']:
            coeff *= -1
        Pacc_terms.append((coeff+0j, data + [anc]))

Ggens = [ ['X','X','X','X'], ['Z','Z','Z','Z'], ['Z','Z','I','I'], ['Z','I','Z','I'] ]
Ptarget_terms_data = []
seen = {}
from itertools import product
for a,b,c,d in product([0,1],[0,1],[0,1],[0,1]):
    coeff = 1+0j
    s = ['I','I','I','I']
    for e,g in zip([a,b,c,d], Ggens):
        if e==1:
            c2, s = multiply_strings(s, g)
            coeff *= c2
    seen[tuple(s)] = seen.get(tuple(s), 0+0j) + coeff
Ptarget_terms_data = [ (coeff/16, list(letters)) for letters, coeff in seen.items() ]

Pgood_terms = []
for anc in ['I','Z']:
    for coeff, letters4 in Ptarget_terms_data:
        Pgood_terms.append((coeff/2, letters4 + [anc]))

# Conjugation primitives

def conj_H_on(letters, q):
    letters=list(letters)
    c,qlet = H_map[letters[q]]
    letters[q]=qlet
    return c, letters

def conj_CNOT(letters, c_idx, t_idx):
    letters=list(letters)
    cphase, qc, qt = CNOT_map[(letters[c_idx], letters[t_idx])]
    letters[c_idx]=qc
    letters[t_idx]=qt
    return cphase, letters


def E_dag_factor(letters, a, b, lam):
    return lam if (letters[a] != 'I' or letters[b] != 'I') else 1.0

# Correct order: U1†, then E1† then U2†, then E2† then U3†, ...

def propagate(term_coeff, term_letters, lam):
    c = complex(term_coeff)
    L = list(term_letters)
    # 1) U1† = H1
    c1, L = conj_H_on(L, 1)
    c *= c1
    # 2) E1† on (1,2) then U2† = CNOT12
    c *= E_dag_factor(L, 1, 2, lam)
    c1, L = conj_CNOT(L, 1, 2)
    c *= c1
    # 3) E2† on (1,0) then U3† = CNOT10
    c *= E_dag_factor(L, 1, 0, lam)
    c1, L = conj_CNOT(L, 1, 0)
    c *= c1
    # 4) E3† on (2,3) then U4† = CNOT23
    c *= E_dag_factor(L, 2, 3, lam)
    c1, L = conj_CNOT(L, 2, 3)
    c *= c1
    # 5) E4† on (3,4) then U5† = CNOT34
    c *= E_dag_factor(L, 3, 4, lam)
    c1, L = conj_CNOT(L, 3, 4)
    c *= c1
    # 6) E5† on (0,4) then U6† = CNOT04
    c *= E_dag_factor(L, 0, 4, lam)
    c1, L = conj_CNOT(L, 0, 4)
    c *= c1
    # expectation on |00000>
    if all(ch in ('I','Z') for ch in L):
        return float(np.real(c))
    else:
        return 0.0


def compute_A_G(lam):
    A=0.0
    for coeff, letters in Pacc_terms:
        A += propagate(coeff, letters, lam)
    G=0.0
    for coeff, letters in Pgood_terms:
        G += propagate(coeff, letters, lam)
    return A, G

for lam in [1.0, 0.0, 0.5]:
    A,G = compute_A_G(lam)
    print('lam', lam, 'A', A, 'G', G, 'F', (G/A) if A>0 else None)

# Fit polynomials
L_vals = [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]
A_vals = [compute_A_G(l)[0] for l in L_vals]
G_vals = [compute_A_G(l)[1] for l in L_vals]
Acoeff = np.polyfit(L_vals, A_vals, 5)
Gcoeff = np.polyfit(L_vals, G_vals, 5)
print('Acoeff:', Acoeff)
print('Gcoeff:', Gcoeff)

from fractions import Fraction
print('Acoeff rational approx:', [Fraction(x).limit_denominator() for x in Acoeff])
print('Gcoeff rational approx:', [Fraction(x).limit_denominator() for x in Gcoeff])

# Convert to p
import sympy as sp
p = sp.symbols('p', real=True)
lam = 1 - sp.Rational(16,15)*p
A_poly = sum(sp.Rational(Fraction(float(c)).limit_denominator(10**6)) * lam**i for i,c in enumerate(np.poly1d(Acoeff).c[::-1]))
G_poly = sum(sp.Rational(Fraction(float(c)).limit_denominator(10**6)) * lam**i for i,c in enumerate(np.poly1d(Gcoeff).c[::-1]))
A_sym = sp.simplify(sp.expand(A_poly))
G_sym = sp.simplify(sp.expand(G_poly))
F_sym = sp.simplify(sp.factor(G_sym / A_sym))
print('A_sym(p)=', A_sym)
print('G_sym(p)=', G_sym)
print('F_sym(p)=', F_sym)
print('series F to p^3:', sp.series(F_sym, p, 0, 4))
Tool #21
lam 1.0 A 0.25 G 0.25 F 1.0
lam 0.0 A 0.125 G 0.03125 F 0.25
lam 0.5 A 0.15625 G 0.048828125 F 0.3125
Acoeff: [ 1.88379101e-15 -5.30289398e-15  6.16160601e-15  1.25000000e-01
  6.00522140e-16  1.25000000e-01]
Gcoeff: [6.25000000e-02 1.25000000e-01 1.53657775e-14 3.12500000e-02
 8.42049846e-16 3.12500000e-02]
Acoeff rational approx: [Fraction(0, 1), Fraction(0, 1), Fraction(0, 1), Fraction(1, 8), Fraction(0, 1), Fraction(1, 8)]
Gcoeff rational approx: [Fraction(1, 16), Fraction(1, 8), Fraction(0, 1), Fraction(1, 32), Fraction(0, 1), Fraction(1, 32)]
A_sym(p)= 32*p**2/225 - 4*p/15 + 1/4
G_sym(p)= -65536*p**5/759375 + 28672*p**4/50625 - 512*p**3/375 + 8*p**2/5 - 14*p/15 + 1/4
F_sym(p)= (-262144*p**5 + 1720320*p**4 - 4147200*p**3 + 4860000*p**2 - 2835000*p + 759375)/(3375*(128*p**2 - 240*p + 225))
series F to p^3: 1 - 8*p/3 + 224*p**2/75 - 512*p**3/675 + O(p**4)
Assistant #22
Tool Call (code to execute)
import sympy as sp
p=sp.symbols('p', real=True)
F = (-262144*p**5 + 1720320*p**4 - 4147200*p**3 + 4860000*p**2 - 2835000*p + 759375)/(3375*(128*p**2 - 240*p + 225))
print('F factorized:', sp.factor(F))
print('F simplified rational:', sp.together(sp.simplify(F)))
print('Partial fraction:', sp.apart(F, p))
Tool #23
F factorized: -(262144*p**5 - 1720320*p**4 + 4147200*p**3 - 4860000*p**2 + 2835000*p - 759375)/(3375*(128*p**2 - 240*p + 225))
F simplified rational: (-262144*p**5 + 1720320*p**4 - 4147200*p**3 + 4860000*p**2 - 2835000*p + 759375)/(3375*(128*p**2 - 240*p + 225))
Partial fraction: -2048*p**3/3375 + 128*p**2/45 - 16*p/5 - 15*(16*p - 45)/(4*(128*p**2 - 240*p + 225)) + 1/4
Assistant #24
Tool Call (code to execute)
import sympy as sp
p=sp.symbols('p', real=True)
lam = sp.symbols('lam', real=True)
F_lam = (2*lam**5 + 4*lam**4 + lam**2 + 1) / (4*(1 + lam**2))
F_p = sp.simplify(sp.expand(F_lam.subs(lam, 1 - sp.Rational(16,15)*p)))
print('F_p expanded:', F_p)
# Compare with previous F expression
F_prev = (-262144*p**5 + 1720320*p**4 - 4147200*p**3 + 4860000*p**2 - 2835000*p + 759375)/(3375*(128*p**2 - 240*p + 225))
print('Are they equal?', sp.simplify(F_p - F_prev) == 0)
print('Series up to p^3:', sp.series(F_p, p, 0, 4))
Tool #25
F_p expanded: (-262144*p**5 + 1720320*p**4 - 4147200*p**3 + 4860000*p**2 - 2835000*p + 759375)/(3375*(128*p**2 - 240*p + 225))
Are they equal? True
Series up to p^3: 1 - 8*p/3 + 224*p**2/75 - 512*p**3/675 + O(p**4)
Assistant #26
Tool Call (code to execute)
import numpy as np
from itertools import product
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_map={'I':I,'X':X,'Y':Y,'Z':Z}
paulis = ['I','X','Y','Z']

# conj maps
H_map = {}
for p in paulis:
    Pin = pauli_map[p]
    Pout = H @ Pin @ H.conj().T
    for q in paulis:
        Q = pauli_map[q]
        for c in [1,-1,1j,-1j]:
            if np.allclose(Pout, c*Q):
                H_map[p]=(c,q)
                break
        if p in H_map: break

CNOT2 = np.array([[1,0,0,0],[0,1,0,0],[0,0,0,1],[0,0,1,0]], dtype=complex)
CNOT_map={}
for pc in paulis:
    for pt in paulis:
        Pin=np.kron(pauli_map[pc],pauli_map[pt])
        Pout=CNOT2 @ Pin @ CNOT2.conj().T
        for qc in paulis:
            for qt in paulis:
                Q=np.kron(pauli_map[qc],pauli_map[qt])
                for c in [1,-1,1j,-1j]:
                    if np.allclose(Pout, c*Q):
                        CNOT_map[(pc,pt)]=(c,qc,qt)
                        break
                if (pc,pt) in CNOT_map: break
            if (pc,pt) in CNOT_map: break


# acceptance terms
Pacc_terms = []
for anc in ['I','Z']:
    for data in [ ['I','I','I','I'], ['X','X','X','X'], ['Z','Z','Z','Z'], ['Y','Y','Y','Y'] ]:
        coeff=1/8
        if data == ['Y','Y','Y','Y']:
            coeff *= -1
        Pacc_terms.append((coeff+0j, data + [anc]))

# propagation with count m of hits of E†

def conj_H(L,q):
    c, qlet = H_map[L[q]]
    L = list(L)
    L[q]=qlet
    return c, L

def conj_CNOT(L,cq,tq):
    cphase, qc, qt = CNOT_map[(L[cq], L[tq])]
    L = list(L)
    L[cq]=qc
    L[tq]=qt
    return cphase, L


def propagate_counts(coeff, L):
    c = complex(coeff)
    m = 0
    # 1) U1† = H1
    c1, L = conj_H(L, 1)
    c *= c1
    # 2) E1† on (1,2), then CNOT12
    if (L[1] != 'I' or L[2] != 'I'): m += 1
    c1, L = conj_CNOT(L, 1, 2)
    c *= c1
    # 3) E2† on (1,0), then CNOT10
    if (L[1] != 'I' or L[0] != 'I'): m += 1
    c1, L = conj_CNOT(L, 1, 0)
    c *= c1
    # 4) E3† on (2,3), then CNOT23
    if (L[2] != 'I' or L[3] != 'I'): m += 1
    c1, L = conj_CNOT(L, 2, 3)
    c *= c1
    # 5) E4† on (3,4), then CNOT34
    if (L[3] != 'I' or L[4] != 'I'): m += 1
    c1, L = conj_CNOT(L, 3, 4)
    c *= c1
    # 6) E5† on (0,4), then CNOT04
    if (L[0] != 'I' or L[4] != 'I'): m += 1
    c1, L = conj_CNOT(L, 0, 4)
    c *= c1
    return c, L, m

survivors=[]
for coeff, letters in Pacc_terms:
    c, L, m = propagate_counts(coeff,letters)
    if all(ch in ('I','Z') for ch in L):
        survivors.append((letters, c, L, m))
print('num survivors:', len(survivors))
for init, coeff, L, m in survivors:
    print('init', init, '-> final', L, ' coefficient', coeff, 'm=', m)
Tool #27
num survivors: 2
init ['I', 'I', 'I', 'I', 'I'] -> final ['I', 'I', 'I', 'I', 'I']  coefficient (0.125+0j) m= 0
init ['I', 'I', 'I', 'I', 'Z'] -> final ['Z', 'I', 'I', 'Z', 'Z']  coefficient (0.125+0j) m= 2
Assistant #28
Tool Call (code to execute)
import numpy as np
from itertools import product
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_map={'I':I,'X':X,'Y':Y,'Z':Z}
paulis = ['I','X','Y','Z']
# conj maps
H_map = {}
for p in paulis:
    Pin = pauli_map[p]
    Pout = H @ Pin @ H.conj().T
    for q in paulis:
        Q = pauli_map[q]
        for c in [1,-1,1j,-1j]:
            if np.allclose(Pout, c*Q):
                H_map[p]=(c,q)
                break
        if p in H_map: break

CNOT2 = np.array([[1,0,0,0],[0,1,0,0],[0,0,0,1],[0,0,1,0]], dtype=complex)
CNOT_map={}
for pc in paulis:
    for pt in paulis:
        Pin=np.kron(pauli_map[pc],pauli_map[pt])
        Pout=CNOT2 @ Pin @ CNOT2.conj().T
        for qc in paulis:
            for qt in paulis:
                Q=np.kron(pauli_map[qc], pauli_map[qt])
                for c in [1,-1,1j,-1j]:
                    if np.allclose(Pout, c*Q):
                        CNOT_map[(pc,pt)]=(c,qc,qt)
                        break
                if (pc,pt) in CNOT_map: break
            if (pc,pt) in CNOT_map: break

# group for P_target
mult_table = {}
for a in paulis:
    for b in paulis:
        P = pauli_map[a] @ pauli_map[b]
        for c in [1,-1,1j,-1j]:
            for r in paulis:
                if np.allclose(P, c*pauli_map[r]):
                    mult_table[(a,b)] = (c, r)
                    break
            if (a,b) in mult_table: break


def multiply_strings(s1, s2):
    c = 1+0j
    out = []
    for a,b in zip(s1,s2):
        cb, r = mult_table[(a,b)]
        c *= cb
        out.append(r)
    return c, out

Ggens = [ ['X','X','X','X'], ['Z','Z','Z','Z'], ['Z','Z','I','I'], ['Z','I','Z','I'] ]

Ptarget_terms_data = []
seen = {}
for a,b,c,d in product([0,1],[0,1],[0,1],[0,1]):
    coeff = 1+0j
    s = ['I','I','I','I']
    for e,g in zip([a,b,c,d], Ggens):
        if e==1:
            c2, s = multiply_strings(s, g)
            coeff *= c2
    seen[tuple(s)] = seen.get(tuple(s), 0+0j) + coeff
Ptarget_terms_data = [ (coeff/16, list(letters)) for letters, coeff in seen.items() ]

# P_good terms include anc=I and anc=Z factors of 1/2
Pgood_terms = []
for anc in ['I','Z']:
    for coeff, letters4 in Ptarget_terms_data:
        Pgood_terms.append((coeff/2, letters4 + [anc]))

# Propagate function returning survivors and m counts

def conj_H(L,q):
    c,qlet=H_map[L[q]]
    L=list(L)
    L[q]=qlet
    return c, L

def conj_CNOT(L,cq,tq):
    cphase, qc, qt = CNOT_map[(L[cq], L[tq])]
    L=list(L)
    L[cq]=qc
    L[tq]=qt
    return cphase, L


def propagate_counts(coeff, L):
    c=complex(coeff)
    m=0
    # 1) H1
    c1,L = conj_H(L,1)
    c *= c1
    # 2) E1 on (1,2), CNOT12
    if (L[1] != 'I' or L[2] != 'I'): m += 1
    c1, L = conj_CNOT(L,1,2)
    c *= c1
    # 3) E2 on (1,0), CNOT10
    if (L[1] != 'I' or L[0] != 'I'): m += 1
    c1, L = conj_CNOT(L,1,0)
    c *= c1
    # 4) E3 on (2,3), CNOT23
    if (L[2] != 'I' or L[3] != 'I'): m += 1
    c1, L = conj_CNOT(L,2,3)
    c *= c1
    # 5) E4 on (3,4), CNOT34
    if (L[3] != 'I' or L[4] != 'I'): m += 1
    c1, L = conj_CNOT(L,3,4)
    c *= c1
    # 6) E5 on (0,4), CNOT04
    if (L[0] != 'I' or L[4] != 'I'): m += 1
    c1, L = conj_CNOT(L,0,4)
    c *= c1
    return c, L, m

survivors=[]
for coeff, L in Pgood_terms:
    c, Lf, m = propagate_counts(coeff, L)
    if all(ch in ('I','Z') for ch in Lf):
        survivors.append((L, c, Lf, m))
print('num survivors:', len(survivors))
# Print unique m & initial patterns summary
from collections import Counter
ms = Counter([m for _,_,_,m in survivors])
print('m counts:', ms)
# Sum contributions per m
from math import isclose
S = 0.0
msum = {}
for init, c, Lf, m in survivors:
    S += float(np.real(c))
    msum[m] = msum.get(m, 0.0) + float(np.real(c))
print('sum contrib =', S)
print('by m:', msum)
Tool #29
num survivors: 8
m counts: Counter({4: 4, 5: 2, 0: 1, 2: 1})
sum contrib = 0.25
by m: {0: 0.03125, 4: 0.125, 2: 0.03125, 5: 0.0625}
Assistant #30

1) Step-by-Step Derivation

Goal and model - We prepare the logical |00⟩AB of the [[4,2,2]] code using the circuit U = CNOT04 · CNOT34 · CNOT23 · CNOT10 · CNOT12 · H1, with a two-qubit depolarizing channel after each CNOT (five CNOTs total). We post-select on (i) the ancilla (qubit 4) measuring |0⟩ and (ii) passing both code stabilizer checks XXXX = +1 and ZZZZ = +1 on the data qubits 0–3. - The two-qubit depolarizing channel applied to the pair (i,j) is E(ρ) = (1 − p)ρ + (p/15) ∑P≠I (P ρ P), where the sum is over the 15 non-identity two-qubit Paulis on that pair. This is the standard 2-qubit Pauli-depolarizing model. (quantumcomputing.stackexchange.com, en.wikipedia.org)

Projectors for post-selection and the target logical state - Codespace projector on data (qubits 0–3): Πcode = 1/4 (I + XXXX)(I + ZZZZ) = 1/4 (I + XXXX + ZZZZ − YYYY). This is the usual stabilizer-projector identity ΠS = 2−m ∑S∈S S (here m=2). (arthurpesah.me) - Ancilla post-selection projector: Πanc,0 = (I + Z4)/2. (en.wikipedia.org) - Overall acceptance operator: Pacc = Πanc,0 ⊗ Πcode. - Logical |00⟩AB is stabilized by XXXX, ZZZZ (the code stabilizers) and Z_A = ZZII, Z_B = ZIZI with +1 eigenvalues (given in the problem). Its projector is Π|00⟩ = 1/16 ∑g∈⟨XXXX, ZZZZ, Z_A, Z_B⟩ g on the data qubits. Thus the “good” operator used in the numerator is Pgood = Πanc,0 ⊗ Π|00⟩.

Heisenberg-picture evaluation with the Pauli transfer eigenvalue - For any two-qubit depolarizing channel acting on a given pair, every non-identity Pauli on that pair is an eigen-operator of the adjoint channel E† with the same eigenvalue λ = 1 − 16p/15. Reason: among the 15 non-identity Paulis, 7 commute and 8 anticommute with a fixed non-identity Pauli Q on the pair, hence ∑P≠I P Q P = (7−8) Q = −Q, so E(Q) = (1−p)Q + (p/15)(−Q) = (1 − 16p/15) Q and E† = E for a Pauli channel. (quantumcomputing.stackexchange.com) - Conjugation of Pauli operators by the gates is Clifford: H X H = Z, H Z H = X, H Y H = −Y, and for CNOTc→t: Xc → Xc Xt, Zt → Zc Zt, while Xt → Xt, Zc → Zc. (en.wikipedia.org) - We compute (linearly) the two probabilities A(p) = Tr[Pacc · ρout], G(p) = Tr[Pgood · ρout], with ρout = (E5 ∘ UCNOT04) ∘ ··· ∘ (E1 ∘ UCNOT12) ∘ UH1 (|00000⟩⟨00000|). In the Heisenberg picture, this is Tr[ ρin · O(λ) ] with O(λ) = U1† ∘ (E1†) ∘ U2† ∘ (E2†) ∘ … ∘ U6† ∘ (E5†) (P), where ρin = |00000⟩⟨00000| and P is Pacc or Pgood. - Because Clifford conjugation maps Paulis to Paulis, and E† multiplies any term that is non-identity on the acted pair by λ, the operator O(λ) becomes a weighted Pauli sum. Only terms that are Z/I on every qubit have nonzero expectation on |00000⟩, where ⟨0|Z|0⟩ = 1 and ⟨0|X|0⟩ = ⟨0|Y|0⟩ = 0.

Carrying out the (finite) Pauli bookkeeping - Acceptance A(λ): expanding Pacc = (1/8)(I + Z4) ⊗ (I + XXXX + ZZZZ − YYYY) and propagating each Pauli term backward (using the rules above), only two terms survive on |00000⟩; one accrues zero factors of λ and one accrues two factors. The result is A(λ) = (1 + λ^2)/8. - Good probability G(λ): expanding Pgood = (1/2)(I + Z4) ⊗ (1/16)∑g (data g), eight terms survive; they accrue 0, 2, 4, or 5 factors of λ with respective weights. The sum is G(λ) = (1/32) + (1/32)λ^2 + (1/8)λ^4 + (1/16)λ^5. - Therefore the post-selected logical fidelity is Flogical(p) = G(λ)/A(λ), with λ = 1 − 16p/15.

Closed forms - In terms of λ: Flogical(λ) = [2 λ^5 + 4 λ^4 + λ^2 + 1] / [4 (1 + λ^2)]. - In terms of p (substitute λ = 1 − 16p/15) and simplify: Flogical(p) = [−262144 p^5 + 1720320 p^4 − 4147200 p^3 + 4860000 p^2 − 2835000 p + 759375] / [3375 (128 p^2 − 240 p + 225)]. - Small‑p expansion: Flogical(p) = 1 − (8/3) p + (224/75) p^2 − (512/675) p^3 + O(p^4).

Remarks - The calculation uses only Clifford conjugation identities and that the two‑qubit depolarizing channel multiplies any nontrivial Pauli on the acted pair by λ = 1 − 16p/15. (en.wikipedia.org, quantumcomputing.stackexchange.com) - Πcode and Π|00⟩ are standard stabilizer projectors. (arthurpesah.me) - Πanc,0 implements a ZZ‑parity‑type post‑selection with an ancilla readout. (en.wikipedia.org)

2) Mathematical Identities Used - Stabilizer projector: ΠS = 2−m ∑S∈S S; product form ΠS = ∏i (I + Si)/2. (arthurpesah.me) - Clifford conjugation (Hadamard, CNOT) on Paulis. (en.wikipedia.org) - Two‑qubit depolarizing acts as E†(Q) = λ Q on any non‑identity Pauli Q on the noisy pair, with λ = 1 − 16p/15. (quantumcomputing.stackexchange.com)

3) Conventions and Units - Fidelity with a pure target is F = ⟨ψtarget| ρ |ψtarget⟩; here we compute it conditionally on acceptance. - Error rate p is the probability that a given CNOT is followed by a uniformly random non-identity two‑qubit Pauli.

4) Final Answer: Let λ = 1 − (16/15) p. The logical state fidelity (post‑selected on all detectable code errors and ancilla outcome |0⟩) is Flogical(p) = [2 λ^5 + 4 λ^4 + λ^2 + 1] / [4 (1 + λ^2)] = [−262144 p^5 + 1720320 p^4 − 4147200 p^3 + 4860000 p^2 − 2835000 p + 759375] / [3375 (128 p^2 − 240 p + 225)].

User #31

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 #32
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 ------------------
    lam = 1 - sp.Rational(16, 15) * p
    F_logical = (2*lam**5 + 4*lam**4 + lam**2 + 1) / (4*(1 + lam**2))
    # ---------------------------------------------------------------

    return F_logical