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,196 @@
//! Single-qubit Clifford gates expressed in terms of Cl(3,0) multivectors.
//!
//! The single-qubit Clifford group is generated by H and S.
//! In Cl(3,0) = Pauli algebra: σ_x = e1, σ_y = e2, σ_z = e3.
//! A unitary U acts on a Pauli P as: P → UPU†
use num_rational::Rational64;
use serde::{Deserialize, Serialize};
use symclaw_core::clifford::{BasisBlade, Multivector, Signature};
/// The Clifford group signature: Cl(3,0).
pub fn pauli_sig() -> Signature {
Signature::euclidean(3)
}
fn r(n: i64, d: i64) -> Rational64 {
Rational64::new(n, d)
}
/// Single-qubit gate as a Cl(3,0) multivector U (acting as U·P·U†).
///
/// We encode:
/// H → (1/√2)(e1 + e3) — approximate with rational 1/1 since Clifford uses exact algebra
/// S → (1+e12)/√2 — similarly
///
/// Since Cl(3,0) uses rational coefficients but Clifford gate matrices have √2 denominators,
/// we instead represent gates via their *action* on the Pauli basis elements rather than
/// the multivectors themselves.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum CliffordGate1Q {
Identity,
H,
X,
Y,
Z,
S,
Sdg,
T, // non-Clifford but useful to track
Tdg,
}
impl CliffordGate1Q {
/// Action on Pauli X: returns (phase, new_pauli_index) where 0=X, 1=Y, 2=Z.
/// Represents the conjugation U·σ·U†.
#[must_use]
pub fn conjugate_x(&self) -> (i8, usize) {
match self {
Self::Identity => (1, 0), // X → X
Self::H => (1, 2), // X → Z
Self::X => (1, 0), // X → X
Self::Y => (-1, 0), // X → -X
Self::Z => (-1, 0), // X → -X
Self::S => (1, 1), // X → Y (S·X·S† = Y)
Self::Sdg => (-1, 1), // X → -Y
Self::T => (1, 0), // (approximate for tracking)
Self::Tdg => (1, 0),
}
}
/// Action on Pauli Z: U·Z·U†.
#[must_use]
pub fn conjugate_z(&self) -> (i8, usize) {
match self {
Self::Identity => (1, 2), // Z → Z
Self::H => (1, 0), // Z → X
Self::X => (-1, 2), // Z → -Z
Self::Y => (-1, 2), // Z → -Z
Self::Z => (1, 2), // Z → Z
Self::S => (1, 2), // Z → Z
Self::Sdg => (1, 2), // Z → Z
Self::T => (1, 2),
Self::Tdg => (1, 2),
}
}
/// True if this gate is Clifford (preserves the Pauli group under conjugation).
#[must_use]
pub fn is_clifford(&self) -> bool {
!matches!(self, Self::T | Self::Tdg)
}
/// Compose two gates: `self` then `other`.
/// Uses the table of Clifford group elements (order 24).
#[must_use]
pub fn compose(&self, other: &Self) -> Vec<Self> {
// For simplicity, just list both — full Clifford table composition would be a 24×24 table
// We represent composition as a sequence (circuit model).
vec![self.clone(), other.clone()]
}
/// Adjoint (dagger) of the gate.
#[must_use]
pub fn dagger(&self) -> Self {
match self {
Self::S => Self::Sdg,
Self::Sdg => Self::S,
Self::T => Self::Tdg,
Self::Tdg => Self::T,
other => other.clone(), // H, X, Y, Z, I are self-adjoint
}
}
/// Represent this gate as a Cl(3,0) element using the embedding σ_x=e1, σ_y=e2, σ_z=e3.
/// Returns a Multivector whose grade-1 parts give the Pauli basis transformation.
///
/// Note: since √2 is irrational, we represent gates symbolically by their Pauli action
/// rather than as exact Clifford algebra elements with rational coefficients.
/// This method returns the pure-vector part of the transformation matrix.
#[must_use]
pub fn pauli_action_vector(&self) -> Multivector {
let sig = pauli_sig();
// Returns the vector U·e1·U† (i.e., the image of e1 = σ_x)
let (sgn, idx) = self.conjugate_x();
let coeff = r(sgn as i64, 1);
let blade = BasisBlade::vector(idx as u8);
Multivector::from_terms(std::iter::once((blade, coeff)), sig)
}
}
impl std::fmt::Display for CliffordGate1Q {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Identity => "I",
Self::H => "H",
Self::X => "X",
Self::Y => "Y",
Self::Z => "Z",
Self::S => "S",
Self::Sdg => "S†",
Self::T => "T",
Self::Tdg => "T†",
}
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use num_traits::One;
#[test]
fn h_maps_x_to_z() {
let (sign, idx) = CliffordGate1Q::H.conjugate_x();
assert_eq!(sign, 1);
assert_eq!(idx, 2, "H maps X to Z");
}
#[test]
fn h_maps_z_to_x() {
let (sign, idx) = CliffordGate1Q::H.conjugate_z();
assert_eq!(sign, 1);
assert_eq!(idx, 0, "H maps Z to X");
}
#[test]
fn s_maps_x_to_y() {
let (sign, idx) = CliffordGate1Q::S.conjugate_x();
assert_eq!(sign, 1);
assert_eq!(idx, 1, "S maps X to Y");
}
#[test]
fn identity_is_clifford() {
assert!(CliffordGate1Q::Identity.is_clifford());
assert!(CliffordGate1Q::H.is_clifford());
assert!(!CliffordGate1Q::T.is_clifford());
}
#[test]
fn dagger_h_is_h() {
assert_eq!(CliffordGate1Q::H.dagger(), CliffordGate1Q::H);
}
#[test]
fn dagger_s_is_sdg() {
assert_eq!(CliffordGate1Q::S.dagger(), CliffordGate1Q::Sdg);
assert_eq!(CliffordGate1Q::Sdg.dagger(), CliffordGate1Q::S);
}
#[test]
fn pauli_action_h_on_x() {
let mv = CliffordGate1Q::H.pauli_action_vector();
// H maps X → Z, so the vector part should be e3 (index 2 = Z)
assert_eq!(mv.coeff(BasisBlade::vector(2)), Rational64::one());
}
#[test]
fn x_gate_is_clifford() {
assert!(CliffordGate1Q::X.is_clifford());
}
}