File size: 5,762 Bytes
eef9104 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | # *******************************************************************************
# 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
|