//! 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 { // 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()); } }