cpsc-qft / src /cpsc /channel.py
SNAPKITTYWEST's picture
October 2026 main drop: mirror from GitHub
eef9104 verified
Raw History Blame Contribute Delete
5.76 kB
# *******************************************************************************
# Copyright (c) 2026 Ahmad Ali Parr and others.
#
# This program and the accompanying materials are made available under the
# terms of the CPSC Eclipse Strict Copyleft License, Version 1.0, which is
# available in LICENSES/CPSC-ESCL-1.0.txt, or, at your option, under the
# GNU Affero General Public License, Version 3 only, which is available in
# LICENSES/AGPL-3.0.txt. Both options are strict copyleft. There is no
# classpath exception and no permissive relicensing.
#
# SPDX-License-Identifier: LicenseRef-CPSC-ESCL-1.0 OR AGPL-3.0-only
# *******************************************************************************
"""Channel-space evolution: exponentiate H only inside the allowed sector."""
from __future__ import annotations
import numpy as np
from cpsc.hamiltonian import magnetization_of, parity_of, transverse_ising
def sector_basis(
n: int,
*,
parity: int,
magnetization: int | None = None,
) -> list[int]:
basis: list[int] = []
for b in range(1 << n):
if parity_of(b, n) != parity:
continue
if magnetization is not None and magnetization_of(b, n) != magnetization:
continue
basis.append(b)
return basis
def evolve_in_sector(
n: int,
initial_basis: int,
*,
time: float,
j: float = 1.0,
lam: float = 0.0,
conserve_magnetization: bool = False,
) -> np.ndarray:
"""Return the full-space statevector of the sector-restricted evolution.
Z-parity completeness holds only at lambda = 0. For lambda != 0 the
symmetry is prod X. Magnetization pruning is refused unless lambda == 0.
"""
if not 0 <= initial_basis < (1 << n):
raise ValueError("initial basis out of range")
parity = parity_of(initial_basis, n)
mag = magnetization_of(initial_basis, n) if conserve_magnetization else None
if conserve_magnetization and lam != 0.0:
raise ValueError(
"magnetization is not a symmetry of the transverse field; "
"refusing a completeness-breaking projection"
)
allowed = sector_basis(n, parity=parity, magnetization=mag)
h = transverse_ising(n, j=j, lam=lam)
index = {b: i for i, b in enumerate(allowed)}
block = np.zeros((len(allowed), len(allowed)), dtype=np.complex128)
for a in allowed:
for b in allowed:
block[index[a], index[b]] = h[a, b]
evals, evecs = np.linalg.eigh(block)
phase = np.exp(-1j * evals * time)
u = (evecs * phase) @ evecs.conj().T
psi0 = np.zeros(len(allowed), dtype=np.complex128)
psi0[index[initial_basis]] = 1.0
evolved = u @ psi0
full = np.zeros(1 << n, dtype=np.complex128)
for b, amp in zip(allowed, evolved):
full[b] = amp
return full
def leakage(state: np.ndarray, n: int, parity: int) -> float:
"""Probability mass on the wrong Z-parity sector."""
wrong = 0.0
for b, amp in enumerate(state):
if parity_of(b, n) != parity:
wrong += float(np.abs(amp) ** 2)
return wrong
def apply_prod_x(state: np.ndarray, n: int) -> np.ndarray:
out = np.zeros_like(state)
mask = (1 << n) - 1
for basis, amp in enumerate(state):
out[basis ^ mask] += amp
return out
def even_cat(n: int, basis: int) -> np.ndarray:
"""Normalized +1 eigenstate of prod X built from a computational seed."""
state = np.zeros(1 << n, dtype=np.complex128)
state[basis] = 1.0 / np.sqrt(2.0)
state[basis ^ ((1 << n) - 1)] += 1.0 / np.sqrt(2.0)
return state
def z_parity_leakage(state: np.ndarray, n: int, parity: int) -> float:
return leakage(state, n, parity)
def x_parity_leakage(state: np.ndarray, n: int, seed_basis: int) -> float:
"""Mass outside the even prod-X sector."""
del seed_basis
flipped = apply_prod_x(state, n)
projected = 0.5 * (state + flipped)
return float(max(0.0, 1.0 - np.vdot(projected, projected).real))
def _apply_rx(state: np.ndarray, n: int, qubit: int, angle: float) -> np.ndarray:
cos = np.cos(angle)
sin = np.sin(angle)
out = np.zeros_like(state)
bit = 1 << qubit
for basis, amp in enumerate(state):
if amp == 0:
continue
out[basis] += cos * amp
out[basis ^ bit] += -1j * sin * amp
return out
def _apply_rzz(state: np.ndarray, n: int, q1: int, q2: int, angle: float) -> np.ndarray:
out = state.copy()
for basis in range(state.shape[0]):
z1 = 1.0 if ((basis >> q1) & 1) == 0 else -1.0
z2 = 1.0 if ((basis >> q2) & 1) == 0 else -1.0
out[basis] *= np.exp(1j * angle * z1 * z2)
return out
def evolve_trotter(
n: int,
initial_basis: int,
gates,
*,
time: float,
steps: int,
j: float,
lam: float,
conserve_magnetization: bool,
) -> np.ndarray:
"""First-order Trotter product of the admitted local terms."""
if conserve_magnetization:
state = np.zeros(1 << n, dtype=np.complex128)
state[initial_basis] = 1.0
else:
state = even_cat(n, initial_basis)
dt = time / steps
for _ in range(steps):
for gate in gates:
if gate.kind == "magnetization":
a, b = gate.qubits
state = _apply_rzz(state, n, a, b, j * dt)
elif gate.kind in {"illegal", "transverse"}:
state = _apply_rx(state, n, gate.qubits[0], lam * dt)
elif gate.kind == "parity_even":
for q in gate.qubits:
state = _apply_rx(state, n, q, lam * dt)
elif gate.kind == "phase":
continue
else:
raise ValueError(f"no evolution rule for {gate.kind}")
return state