Merge pull request 'test(symclaw-skill): cover handlers_advanced via JSON API' (#10) from ci-doctor/coverage-20260518-201834 into master

Reviewed-on: #10
This commit is contained in:
redclawsystems
2026-05-19 04:39:49 +00:00
commit f4b75db2ee
291 changed files with 130230 additions and 0 deletions
@@ -0,0 +1,292 @@
//! Stabilizer formalism and binary symplectic tableau.
//!
//! A stabilizer state on n qubits is described by n independent commuting
//! Pauli operators that stabilize the state: S|ψ⟩ = |ψ⟩ for each S.
//! The generators are stored in a binary symplectic tableau.
use serde::{Deserialize, Serialize};
use super::group::{Pauli, PauliOp, Phase};
/// Binary symplectic representation of a Pauli operator.
///
/// An n-qubit Pauli P = i^k · X^{x_0}Z^{z_0} ⊗ … ⊗ X^{x_{n-1}}Z^{z_{n-1}}
/// is represented by two bit-vectors (x, z) and a phase bit r:
/// - x[i] = 1 iff the i-th qubit has an X component
/// - z[i] = 1 iff the i-th qubit has a Z component
/// - r = 0 for phase +1, r = 1 for phase -1 (we track only ±1 phases)
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SympRow {
pub x: Vec<bool>,
pub z: Vec<bool>,
pub r: bool, // phase: false = +1, true = -1
}
impl SympRow {
/// Construct from a `PauliOp`.
#[must_use]
pub fn from_pauli(op: &PauliOp) -> Self {
let n = op.n_qubits();
let mut x = vec![false; n];
let mut z = vec![false; n];
for (i, &p) in op.ops.iter().enumerate() {
match p {
Pauli::X => x[i] = true,
Pauli::Z => z[i] = true,
Pauli::Y => {
x[i] = true;
z[i] = true;
}
Pauli::I => {}
}
}
let r = op.phase == Phase::NEG_ONE || op.phase == Phase::NEG_I;
Self { x, z, r }
}
/// Convert back to a `PauliOp`.
#[must_use]
pub fn to_pauli(&self) -> PauliOp {
let n = self.x.len();
let mut ops = vec![Pauli::I; n];
let mut y_count = 0u32;
for (i, op) in ops.iter_mut().enumerate() {
*op = match (self.x[i], self.z[i]) {
(false, false) => Pauli::I,
(true, false) => Pauli::X,
(false, true) => Pauli::Z,
(true, true) => {
y_count += 1;
Pauli::Y
}
};
}
// Y = iXZ, so n Y's contribute phase i^n
let y_phase = y_count % 4;
let base_phase = if self.r { 2u8 } else { 0u8 }; // -1 = i^2
let total_phase = (base_phase + y_phase as u8) % 4;
PauliOp {
phase: Phase(total_phase),
ops,
}
}
/// Rowsum (Aaronson-Gottesman): multiply two symplectic rows.
#[must_use]
pub fn rowsum(h: &Self, i: &Self) -> Self {
let n = h.x.len();
assert_eq!(n, i.x.len());
let mut x = vec![false; n];
let mut z = vec![false; n];
let mut g = 0i32;
for j in 0..n {
let (xi, zi) = (i.x[j], i.z[j]);
let (xh, zh) = (h.x[j], h.z[j]);
x[j] = xi ^ xh;
z[j] = zi ^ zh;
g += phase_contribution(xi, zi, xh, zh);
}
let ri = if i.r { 1i32 } else { 0 };
let rh = if h.r { 1i32 } else { 0 };
// r = (2*ri + 2*rh + g) mod 4 ... then r_new = (sum / 2) mod 2
let total = (2 * ri + 2 * rh + g).rem_euclid(4);
let r = total == 2;
Self { x, z, r }
}
}
/// Phase contribution from the Aaronson-Gottesman rowsum formula.
fn phase_contribution(x1: bool, z1: bool, x2: bool, z2: bool) -> i32 {
match (x1, z1, x2, z2) {
(false, false, _, _) => 0,
(_, _, false, false) => 0,
(true, false, true, false) => 0, // X·X
(true, false, false, true) => 1, // X·Z → iY
(true, false, true, true) => -1, // X·Y → -iZ
(false, true, true, false) => -1, // Z·X → -iY
(false, true, false, true) => 0, // Z·Z
(false, true, true, true) => 1, // Z·Y → iX
(true, true, true, false) => 1, // Y·X → iZ
(true, true, false, true) => -1, // Y·Z → -iX
(true, true, true, true) => 0, // Y·Y
#[allow(unreachable_patterns)]
_ => 0,
}
}
/// Stabilizer tableau for n qubits: 2n rows (n stabilizers + n destabilizers).
///
/// Rows 0..n = destabilizers, rows n..2n = stabilizers.
/// Uses the Aaronson-Gottesman representation.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StabilizerTableau {
pub n: usize,
/// 2n rows
pub rows: Vec<SympRow>,
}
impl StabilizerTableau {
/// |0⟩^⊗n state: stabilized by Z₀, Z₁, …, Zₙ₋₁.
#[must_use]
pub fn zero_state(n: usize) -> Self {
let mut rows = Vec::with_capacity(2 * n);
// Destabilizers: X_0, X_1, …, X_{n-1}
for i in 0..n {
let mut x = vec![false; n];
let z = vec![false; n];
x[i] = true;
rows.push(SympRow { x, z, r: false });
}
// Stabilizers: Z_0, Z_1, …, Z_{n-1}
for i in 0..n {
let x = vec![false; n];
let mut z = vec![false; n];
z[i] = true;
rows.push(SympRow { x, z, r: false });
}
Self { n, rows }
}
/// Apply Hadamard on qubit `a`.
pub fn h(&mut self, a: usize) {
for row in &mut self.rows {
row.x.swap(a, a); // no-op, just to show symmetry
let rx = row.x[a];
let rz = row.z[a];
// H swaps X and Z, and introduces phase -1 when both X and Z are set (Y → -Y)
if rx && rz {
row.r = !row.r;
}
row.x[a] = rz;
row.z[a] = rx;
}
}
/// Apply CNOT with control `a`, target `b`.
pub fn cnot(&mut self, a: usize, b: usize) {
for row in &mut self.rows {
// Phase update: r ^= x[a] & z[b] & (x[b] ^ z[a] ^ 1)
let r_upd = row.x[a] && row.z[b] && (row.x[b] ^ row.z[a] ^ true);
if r_upd {
row.r = !row.r;
}
row.x[b] ^= row.x[a];
row.z[a] ^= row.z[b];
}
}
/// Apply S (phase) gate on qubit `a`: S = diag(1, i).
pub fn s(&mut self, a: usize) {
for row in &mut self.rows {
if row.x[a] && row.z[a] {
row.r = !row.r;
}
row.z[a] ^= row.x[a];
}
}
/// Extract the stabilizer generators (rows n..2n) as PauliOps.
#[must_use]
pub fn stabilizers(&self) -> Vec<PauliOp> {
self.rows[self.n..].iter().map(|r| r.to_pauli()).collect()
}
/// True if the state is stabilized by the given Pauli operator.
#[must_use]
pub fn is_stabilized_by(&self, op: &PauliOp) -> bool {
let row = SympRow::from_pauli(op);
self.rows[self.n..].iter().any(|r| r == &row)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn zero_state_stabilizers() {
let t = StabilizerTableau::zero_state(2);
let stabs = t.stabilizers();
assert_eq!(stabs.len(), 2);
// Z_0 ⊗ I_1
assert_eq!(stabs[0].ops[0], Pauli::Z);
assert_eq!(stabs[0].ops[1], Pauli::I);
// I_0 ⊗ Z_1
assert_eq!(stabs[1].ops[0], Pauli::I);
assert_eq!(stabs[1].ops[1], Pauli::Z);
}
#[test]
fn hadamard_converts_z_to_x() {
let mut t = StabilizerTableau::zero_state(1);
t.h(0);
let stabs = t.stabilizers();
// After H: Z → X stabilizer
assert_eq!(stabs[0].ops[0], Pauli::X);
}
#[test]
fn bell_state_from_tableau() {
// |Φ+⟩ = (|00⟩ + |11⟩)/√2
// Start: |00⟩, apply H⊗I, then CNOT(0→1)
let mut t = StabilizerTableau::zero_state(2);
t.h(0);
t.cnot(0, 1);
let stabs = t.stabilizers();
// Bell state stabilizers: XX and ZZ
let has_xx = stabs
.iter()
.any(|s| s.ops[0] == Pauli::X && s.ops[1] == Pauli::X);
let has_zz = stabs
.iter()
.any(|s| s.ops[0] == Pauli::Z && s.ops[1] == Pauli::Z);
assert!(
has_xx,
"Bell state should have XX stabilizer, got {stabs:?}"
);
assert!(
has_zz,
"Bell state should have ZZ stabilizer, got {stabs:?}"
);
}
#[test]
fn tableau_roundtrip_symrow() {
let op = PauliOp {
phase: Phase::ONE,
ops: vec![Pauli::X, Pauli::Y, Pauli::Z],
};
let row = SympRow::from_pauli(&op);
let back = row.to_pauli();
assert_eq!(back.ops, op.ops);
}
#[test]
fn stabilizer_from_generators() {
let t = StabilizerTableau::zero_state(3);
// |000⟩ is stabilized by Z₀, Z₁, Z₂
let z0 = PauliOp::single(Pauli::Z, 0, 3);
assert!(t.is_stabilized_by(&z0));
}
#[test]
fn rowsum_xx_zz() {
// XZ + ZX should give some valid row
let n = 2;
let row_a = SympRow {
x: vec![true, false],
z: vec![false, true],
r: false,
};
let row_b = SympRow {
x: vec![false, true],
z: vec![true, false],
r: false,
};
let result = SympRow::rowsum(&row_a, &row_b);
assert_eq!(result.x.len(), n);
assert_eq!(result.z.len(), n);
}
}