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:
@@ -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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user