mod sealed {
use super::super::rplus::RPlus;
use super::super::so3::{So3Algebra, SO3};
use super::super::su2::{Su2Algebra, SU2};
use super::super::su3::{Su3Algebra, SU3};
use super::super::sun::{SunAlgebra, SUN};
use super::super::u1::{U1Algebra, U1};
pub trait SealedCompact {}
pub trait SealedAbelian {}
pub trait SealedSimple {}
pub trait SealedSemiSimple {}
pub trait SealedTraceless {}
pub trait SealedAntiHermitian {}
impl SealedCompact for SU2 {}
impl SealedCompact for SU3 {}
impl SealedCompact for U1 {}
impl SealedCompact for SO3 {}
impl<const N: usize> SealedCompact for SUN<N> {}
impl SealedAbelian for U1 {}
impl SealedAbelian for RPlus {}
impl SealedSimple for SU2 {}
impl SealedSimple for SU3 {}
impl SealedSimple for SO3 {}
impl<const N: usize> SealedSimple for SUN<N> {}
impl SealedSemiSimple for SU2 {}
impl SealedSemiSimple for SU3 {}
impl SealedSemiSimple for SO3 {}
impl<const N: usize> SealedSemiSimple for SUN<N> {}
impl SealedTraceless for Su2Algebra {}
impl SealedTraceless for Su3Algebra {}
impl SealedTraceless for So3Algebra {}
impl<const N: usize> SealedTraceless for SunAlgebra<N> {}
impl SealedAntiHermitian for Su2Algebra {}
impl SealedAntiHermitian for Su3Algebra {}
impl SealedAntiHermitian for So3Algebra {}
impl SealedAntiHermitian for U1Algebra {}
impl<const N: usize> SealedAntiHermitian for SunAlgebra<N> {}
}
pub trait Compact: LieGroup + sealed::SealedCompact {}
pub trait Abelian: LieGroup + sealed::SealedAbelian {}
pub trait Simple: SemiSimple + sealed::SealedSimple {}
pub trait SemiSimple: LieGroup + sealed::SealedSemiSimple {}
pub trait TracelessByConstruction: LieAlgebra + sealed::SealedTraceless {}
pub trait AntiHermitianByConstruction: LieAlgebra + sealed::SealedAntiHermitian {}
pub trait LieAlgebra: Clone + Sized + std::fmt::Debug + PartialEq {
const DIM: usize;
#[must_use]
fn zero() -> Self;
#[must_use]
fn add(&self, other: &Self) -> Self;
#[must_use]
fn scale(&self, scalar: f64) -> Self;
#[must_use]
fn norm(&self) -> f64;
#[must_use]
fn basis_element(i: usize) -> Self;
#[must_use]
fn from_components(components: &[f64]) -> Self;
#[must_use]
fn to_components(&self) -> Vec<f64>;
#[must_use]
fn bracket(&self, other: &Self) -> Self;
#[must_use]
fn inner(&self, other: &Self) -> f64 {
let v = self.to_components();
let w = other.to_components();
v.iter().zip(w.iter()).map(|(vi, wi)| vi * wi).sum()
}
}
pub trait LieGroup: Clone + Sized + std::fmt::Debug {
const MATRIX_DIM: usize;
type Algebra: LieAlgebra;
#[must_use]
fn identity() -> Self;
#[must_use]
fn compose(&self, other: &Self) -> Self;
#[must_use]
fn inverse(&self) -> Self;
#[must_use]
fn conjugate_transpose(&self) -> Self;
#[must_use]
fn adjoint_action(&self, algebra_element: &Self::Algebra) -> Self::Algebra;
#[must_use]
fn distance_to_identity(&self) -> f64;
#[must_use]
fn distance(&self, other: &Self) -> f64 {
self.inverse().compose(other).distance_to_identity()
}
#[must_use]
fn is_near_identity(&self, tolerance: f64) -> bool {
self.distance_to_identity() < tolerance
}
#[must_use]
fn exp(tangent: &Self::Algebra) -> Self;
fn log(&self) -> crate::error::LogResult<Self::Algebra>;
#[must_use]
fn trace_identity() -> f64 {
Self::MATRIX_DIM as f64
}
#[must_use]
fn reorthogonalize(&self) -> Self {
match self.log() {
Ok(algebra) => Self::exp(&algebra),
Err(_) => {
Self::identity()
}
}
}
#[must_use]
fn geodesic(&self, other: &Self, t: f64) -> Option<Self> {
let delta = self.inverse().compose(other);
let tangent = delta.log().ok()?;
Some(self.compose(&Self::exp(&tangent.scale(t))))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Clone, Debug, PartialEq)]
struct U1Algebra {
value: f64,
}
impl LieAlgebra for U1Algebra {
const DIM: usize = 1;
fn zero() -> Self {
Self { value: 0.0 }
}
fn add(&self, other: &Self) -> Self {
Self {
value: self.value + other.value,
}
}
fn scale(&self, scalar: f64) -> Self {
Self {
value: self.value * scalar,
}
}
fn norm(&self) -> f64 {
self.value.abs()
}
fn basis_element(i: usize) -> Self {
assert_eq!(i, 0, "U(1) algebra is 1-dimensional");
Self { value: 1.0 }
}
fn from_components(components: &[f64]) -> Self {
assert_eq!(components.len(), 1);
Self {
value: components[0],
}
}
fn to_components(&self) -> Vec<f64> {
vec![self.value]
}
fn bracket(&self, _other: &Self) -> Self {
Self::zero() }
}
#[derive(Clone, Debug, PartialEq)]
struct U1 {
angle: f64, }
impl LieGroup for U1 {
const MATRIX_DIM: usize = 1;
type Algebra = U1Algebra;
fn identity() -> Self {
Self { angle: 0.0 }
}
fn compose(&self, other: &Self) -> Self {
Self {
angle: (self.angle + other.angle) % (2.0 * std::f64::consts::PI),
}
}
fn inverse(&self) -> Self {
Self {
angle: (-self.angle).rem_euclid(2.0 * std::f64::consts::PI),
}
}
fn conjugate_transpose(&self) -> Self {
self.inverse() }
fn adjoint_action(&self, algebra_element: &Self::Algebra) -> Self::Algebra {
algebra_element.clone()
}
fn distance_to_identity(&self) -> f64 {
let normalized = self.angle.rem_euclid(2.0 * std::f64::consts::PI);
let dist = normalized.min(2.0 * std::f64::consts::PI - normalized);
dist.abs()
}
fn exp(tangent: &Self::Algebra) -> Self {
Self {
angle: tangent.value.rem_euclid(2.0 * std::f64::consts::PI),
}
}
fn log(&self) -> crate::error::LogResult<Self::Algebra> {
Ok(U1Algebra { value: self.angle })
}
}
#[test]
fn test_identity_law() {
let g = U1 { angle: 1.0 };
let e = U1::identity();
let g_times_e = g.compose(&e);
assert!((g_times_e.angle - g.angle).abs() < 1e-10);
}
#[test]
fn test_inverse_law() {
let g = U1 { angle: 2.5 };
let g_inv = g.inverse();
let product = g.compose(&g_inv);
assert!(product.is_near_identity(1e-10));
}
#[test]
fn test_associativity() {
let g1 = U1 { angle: 0.5 };
let g2 = U1 { angle: 1.2 };
let g3 = U1 { angle: 0.8 };
let left = g1.compose(&g2.compose(&g3));
let right = g1.compose(&g2).compose(&g3);
assert!((left.angle - right.angle).abs() < 1e-10);
}
#[test]
fn test_distance_symmetry() {
let g = U1 { angle: 1.5 };
let g_inv = g.inverse();
let d1 = g.distance_to_identity();
let d2 = g_inv.distance_to_identity();
assert!((d1 - d2).abs() < 1e-10);
}
#[test]
fn test_is_near_identity() {
let e = U1::identity();
assert!(e.is_near_identity(1e-10));
let g = U1 { angle: 1e-12 };
assert!(g.is_near_identity(1e-10));
let h = U1 { angle: 0.1 };
assert!(!h.is_near_identity(1e-10));
}
#[test]
fn test_exponential_map() {
let zero = U1Algebra::zero();
let exp_zero = U1::exp(&zero);
assert!(exp_zero.is_near_identity(1e-10));
let tangent = U1Algebra { value: 1.5 };
let group_elem = U1::exp(&tangent);
assert!((group_elem.angle - 1.5).abs() < 1e-10);
}
#[test]
fn test_matrix_dim() {
assert_eq!(U1::MATRIX_DIM, 1);
}
#[test]
fn test_lie_algebra_zero() {
let zero = U1Algebra::zero();
assert_eq!(zero.value, 0.0);
}
#[test]
fn test_lie_algebra_add() {
let v = U1Algebra { value: 1.0 };
let w = U1Algebra { value: 2.0 };
let sum = v.add(&w);
assert_eq!(sum.value, 3.0);
}
#[test]
fn test_lie_algebra_scale() {
let v = U1Algebra { value: 2.0 };
let scaled = v.scale(3.0);
assert_eq!(scaled.value, 6.0);
}
#[test]
fn test_lie_algebra_norm() {
let v = U1Algebra { value: -3.0 };
assert_eq!(v.norm(), 3.0);
}
#[test]
fn test_lie_algebra_basis() {
let basis = U1Algebra::basis_element(0);
assert_eq!(basis.value, 1.0);
}
#[test]
fn test_lie_algebra_from_components() {
let v = U1Algebra::from_components(&[4.5]);
assert_eq!(v.value, 4.5);
}
#[test]
fn test_matrix_dim_available_at_compile_time() {
assert_eq!(U1::MATRIX_DIM, 1);
}
#[test]
fn test_reorthogonalize_prevents_drift_su2() {
use crate::SU2;
let mut g_no_reorth = SU2::identity();
for _ in 0..1000 {
g_no_reorth = g_no_reorth.compose(&SU2::rotation_x(0.01));
}
let unitarity_error_no_reorth = g_no_reorth
.conjugate_transpose()
.compose(&g_no_reorth)
.distance_to_identity();
let mut g_with_reorth = SU2::identity();
for i in 0..1000 {
g_with_reorth = g_with_reorth.compose(&SU2::rotation_x(0.01));
if i % 100 == 0 {
g_with_reorth = g_with_reorth.reorthogonalize();
}
}
let unitarity_error_with_reorth = g_with_reorth
.conjugate_transpose()
.compose(&g_with_reorth)
.distance_to_identity();
assert!(
unitarity_error_with_reorth < unitarity_error_no_reorth,
"Reorthogonalization should reduce drift: {} vs {}",
unitarity_error_with_reorth,
unitarity_error_no_reorth
);
assert!(
unitarity_error_with_reorth < 1e-7,
"With reorthogonalization, drift should be minimal: {}",
unitarity_error_with_reorth
);
}
#[test]
fn test_reorthogonalize_idempotent() {
use crate::SU2;
use std::f64::consts::PI;
let g = SU2::rotation_y(PI / 3.0);
let g1 = g.reorthogonalize();
let g2 = g1.reorthogonalize();
assert!(
g1.distance(&g2) < 1e-14,
"Reorthogonalization should be idempotent"
);
}
#[test]
fn test_reorthogonalize_preserves_element() {
use crate::SU2;
use std::f64::consts::PI;
let g = SU2::rotation_z(PI / 4.0);
let g_reorth = g.reorthogonalize();
assert!(
g.distance(&g_reorth) < 1e-12,
"Reorthogonalization should preserve element"
);
let unitarity_error = g_reorth
.conjugate_transpose()
.compose(&g_reorth)
.distance_to_identity();
assert!(
unitarity_error < 1e-14,
"Reorthogonalized element should be on manifold: {}",
unitarity_error
);
}
use crate::so3::{So3Algebra, SO3};
use crate::su2::{Su2Algebra, SU2};
use crate::su3::Su3Algebra;
#[test]
fn test_su2_algebra_operators() {
let x = Su2Algebra([1.0, 2.0, 3.0]);
let y = Su2Algebra([0.5, 1.0, 1.5]);
let sum = x + y;
assert_eq!(sum.0, [1.5, 3.0, 4.5]);
let diff = x - y;
assert_eq!(diff.0, [0.5, 1.0, 1.5]);
let neg = -x;
assert_eq!(neg.0, [-1.0, -2.0, -3.0]);
let scaled = x * 2.0;
assert_eq!(scaled.0, [2.0, 4.0, 6.0]);
let scaled2 = 2.0 * x;
assert_eq!(scaled2.0, [2.0, 4.0, 6.0]);
}
#[test]
fn test_su2_group_mul_operator() {
let g = SU2::rotation_x(0.3);
let h = SU2::rotation_y(0.7);
let product_op = &g * &h;
let product_compose = g.compose(&h);
assert_eq!(product_op.matrix(), product_compose.matrix());
let mut g2 = g.clone();
g2 *= &h;
assert_eq!(g2.matrix(), product_compose.matrix());
}
#[test]
fn test_so3_algebra_operators() {
let x = So3Algebra([1.0, 0.0, 0.0]);
let y = So3Algebra([0.0, 1.0, 0.0]);
let sum = x + y;
assert_eq!(sum.0, [1.0, 1.0, 0.0]);
let scaled = 3.0 * x;
assert_eq!(scaled.0, [3.0, 0.0, 0.0]);
let neg = -y;
assert_eq!(neg.0, [0.0, -1.0, 0.0]);
}
#[test]
fn test_so3_group_mul_operator() {
let r1 = SO3::rotation_x(0.5);
let r2 = SO3::rotation_z(0.3);
let product = &r1 * &r2;
let expected = r1.compose(&r2);
assert!(product.distance(&expected) < 1e-14);
}
#[test]
fn test_u1_algebra_operators() {
let x = crate::U1Algebra(1.5);
let y = crate::U1Algebra(0.5);
assert_eq!((x + y).0, 2.0);
assert_eq!((x - y).0, 1.0);
assert_eq!((-x).0, -1.5);
assert_eq!((x * 3.0).0, 4.5);
assert_eq!((3.0 * x).0, 4.5);
}
#[test]
fn test_u1_group_mul_operator() {
let g = crate::U1::from_angle(1.0);
let h = crate::U1::from_angle(2.0);
let product = &g * &h;
let expected = g.compose(&h);
assert!(product.distance(&expected) < 1e-14);
}
#[test]
fn test_rplus_algebra_operators() {
let x = crate::RPlusAlgebra(2.0);
let y = crate::RPlusAlgebra(0.5);
assert_eq!((x + y).0, 2.5);
assert_eq!((x - y).0, 1.5);
assert_eq!((-x).0, -2.0);
assert_eq!((x * 4.0).0, 8.0);
assert_eq!((4.0 * x).0, 8.0);
}
#[test]
fn test_su3_algebra_operators() {
let x = Su3Algebra([1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]);
let y = Su3Algebra([0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]);
let sum = x + y;
assert_eq!(sum.0[0], 1.0);
assert_eq!(sum.0[1], 1.0);
let scaled = 2.0 * x;
assert_eq!(scaled.0[0], 2.0);
}
#[test]
fn test_sun_algebra_operators() {
use crate::sun::SunAlgebra;
let x = SunAlgebra::<2>::basis_element(0);
let y = SunAlgebra::<2>::basis_element(1);
let sum = &x + &y;
assert_eq!(sum.coefficients, vec![1.0, 1.0, 0.0]);
let diff = x.clone() - y.clone();
assert_eq!(diff.coefficients, vec![1.0, -1.0, 0.0]);
let neg = -x.clone();
assert_eq!(neg.coefficients, vec![-1.0, 0.0, 0.0]);
let scaled = x.clone() * 5.0;
assert_eq!(scaled.coefficients, vec![5.0, 0.0, 0.0]);
let scaled2 = 5.0 * x;
assert_eq!(scaled2.coefficients, vec![5.0, 0.0, 0.0]);
}
#[test]
fn test_sun_group_mul_operator() {
use crate::sun::SUN;
let g = SUN::<3>::identity();
let h = SUN::<3>::identity();
let product = &g * &h;
assert!(product.distance_to_identity() < 1e-14);
}
#[test]
fn test_algebra_operator_consistency_with_trait_methods() {
let x = Su2Algebra([0.3, -0.7, 1.2]);
let y = Su2Algebra([0.5, 0.1, -0.4]);
let op_sum = x + y;
let trait_sum = LieAlgebra::add(&x, &y);
assert_eq!(op_sum.0, trait_sum.0);
let op_scaled = x * 2.5;
let trait_scaled = x.scale(2.5);
assert_eq!(op_scaled.0, trait_scaled.0);
}
#[test]
fn test_su2_log_returns_err_for_drifted_matrix() {
use crate::SU2;
use num_complex::Complex64;
let mut bad = SU2::identity();
bad.matrix[[0, 0]] = Complex64::new(2.0, 0.0); let result = SU2::log(&bad);
assert!(result.is_err(), "log should fail for non-unitary matrix");
}
#[test]
fn test_su2_log_at_near_2pi_returns_err() {
use crate::SU2;
let near_cut = SU2::exp(&crate::Su2Algebra([
0.0,
0.0,
std::f64::consts::PI * 2.0 - 1e-14,
]));
let _result = SU2::log(&near_cut);
}
#[test]
fn test_so3_log_at_pi_returns_err() {
use crate::so3::SO3;
let r = SO3::rotation_x(std::f64::consts::PI);
let result = SO3::log(&r);
assert!(
result.is_err(),
"SO(3) log at π rotation should return singularity error"
);
}
#[test]
fn test_bch_error_for_invalid_order() {
use crate::bch::{bch_safe, BchError};
use crate::Su2Algebra;
let x = Su2Algebra([0.1, 0.0, 0.0]);
let y = Su2Algebra([0.0, 0.1, 0.0]);
let result = bch_safe::<SU2>(&x, &y, 7);
assert!(
matches!(result, Err(BchError::InvalidOrder(7))),
"BCH should reject invalid order"
);
}
#[test]
fn test_su2_so3_double_cover() {
use crate::so3::{So3Algebra, SO3};
use crate::su2::{Su2Algebra, SU2};
let angles = [0.3, 1.0, std::f64::consts::PI / 2.0, 2.5];
for &angle in &angles {
let g_su2 = SU2::rotation_z(angle);
let r_so3 = SO3::rotation_z(angle);
let x_su2 = Su2Algebra([1.0, 0.0, 0.0]);
let x_so3 = So3Algebra([1.0, 0.0, 0.0]);
let ad_su2 = g_su2.adjoint_action(&x_su2);
let ad_so3 = r_so3.adjoint_action(&x_so3);
let su2_rotated = [ad_su2.0[0], ad_su2.0[1], ad_su2.0[2]];
let so3_rotated = [ad_so3.0[0], ad_so3.0[1], ad_so3.0[2]];
for i in 0..3 {
assert!(
(su2_rotated[i].abs() - so3_rotated[i].abs()) < 1e-10
|| (su2_rotated[i] + so3_rotated[i]).abs() < 1e-10
|| (su2_rotated[i] - so3_rotated[i]).abs() < 1e-10,
"SU(2)/SO(3) adjoint mismatch at angle {}: su2={:?} so3={:?}",
angle,
su2_rotated,
so3_rotated
);
}
}
}
#[test]
fn test_su2_minus_identity_is_same_so3_rotation() {
use crate::su2::{Su2Algebra, SU2};
let g = SU2::exp(&Su2Algebra([0.0, 0.0, std::f64::consts::PI]));
let minus_g = SU2::exp(&Su2Algebra([0.0, 0.0, -std::f64::consts::PI]));
let x = Su2Algebra([1.0, 2.0, 3.0]);
let ad_g = g.adjoint_action(&x);
let ad_minus_g = minus_g.adjoint_action(&x);
for i in 0..3 {
assert!(
(ad_g.0[i] - ad_minus_g.0[i]).abs() < 1e-10,
"±I should give same adjoint action"
);
}
}
}