use crate::traits::{
AntiHermitianByConstruction, Compact, LieAlgebra, LieGroup, SemiSimple, Simple,
TracelessByConstruction,
};
use ndarray::Array2;
use num_complex::Complex64;
use std::fmt;
use std::marker::PhantomData;
use std::ops::{Add, Mul, MulAssign, Neg, Sub};
#[derive(Clone, Debug, PartialEq)]
pub struct SunAlgebra<const N: usize> {
pub(crate) coefficients: Vec<f64>,
_phantom: PhantomData<[(); N]>,
}
impl<const N: usize> SunAlgebra<N> {
const DIM: usize = {
assert!(
N >= 2,
"SU(N) requires N >= 2: SU(1) is trivial, SU(0) is undefined"
);
N * N - 1
};
#[must_use]
pub fn new(coefficients: Vec<f64>) -> Self {
assert_eq!(
coefficients.len(),
Self::DIM,
"SU({}) algebra requires {} coefficients, got {}",
N,
Self::DIM,
coefficients.len()
);
Self {
coefficients,
_phantom: PhantomData,
}
}
#[must_use]
pub fn coefficients(&self) -> &[f64] {
&self.coefficients
}
#[must_use]
pub fn to_matrix(&self) -> Array2<Complex64> {
let mut matrix = Array2::zeros((N, N));
let i = Complex64::new(0.0, 1.0);
let mut idx = 0;
for row in 0..N {
for col in (row + 1)..N {
let coeff = self.coefficients[idx];
matrix[[row, col]] += i * coeff;
matrix[[col, row]] += i * coeff;
idx += 1;
}
}
for row in 0..N {
for col in (row + 1)..N {
let coeff = self.coefficients[idx];
matrix[[row, col]] += Complex64::new(coeff, 0.0); matrix[[col, row]] += Complex64::new(-coeff, 0.0); idx += 1;
}
}
for k in 0..(N - 1) {
let coeff = self.coefficients[idx];
let k_f = k as f64;
let normalization = 2.0 / ((k_f + 1.0) * (k_f + 2.0));
let scale = normalization.sqrt();
for j in 0..=k {
matrix[[j, j]] += i * coeff * scale;
}
matrix[[k + 1, k + 1]] += i * coeff * scale * (-(k_f + 1.0));
idx += 1;
}
matrix.mapv_inplace(|z| z * 0.5);
matrix
}
#[must_use]
pub fn from_matrix(matrix: &Array2<Complex64>) -> Self {
assert_eq!(matrix.nrows(), N);
assert_eq!(matrix.ncols(), N);
let mut coefficients = vec![0.0; Self::DIM];
let mut idx = 0;
for row in 0..N {
for col in (row + 1)..N {
let val = matrix[[row, col]];
coefficients[idx] = val.im * 2.0;
idx += 1;
}
}
for row in 0..N {
for col in (row + 1)..N {
let val = matrix[[row, col]];
coefficients[idx] = val.re * 2.0;
idx += 1;
}
}
for k in 0..(N - 1) {
let k_f = k as f64;
let normalization = 2.0 / ((k_f + 1.0) * (k_f + 2.0));
let scale = normalization.sqrt();
let mut trace_prod = Complex64::new(0.0, 0.0);
for j in 0..=k {
trace_prod += matrix[[j, j]] * scale;
}
trace_prod += matrix[[k + 1, k + 1]] * (-(k_f + 1.0) * scale);
coefficients[idx] = trace_prod.im;
idx += 1;
}
Self::new(coefficients)
}
}
impl<const N: usize> Add for SunAlgebra<N> {
type Output = Self;
fn add(self, rhs: Self) -> Self {
let coefficients = self
.coefficients
.iter()
.zip(&rhs.coefficients)
.map(|(a, b)| a + b)
.collect();
Self::new(coefficients)
}
}
impl<const N: usize> Add<&SunAlgebra<N>> for SunAlgebra<N> {
type Output = SunAlgebra<N>;
fn add(self, rhs: &SunAlgebra<N>) -> SunAlgebra<N> {
let coefficients = self
.coefficients
.iter()
.zip(&rhs.coefficients)
.map(|(a, b)| a + b)
.collect();
Self::new(coefficients)
}
}
impl<const N: usize> Add<SunAlgebra<N>> for &SunAlgebra<N> {
type Output = SunAlgebra<N>;
fn add(self, rhs: SunAlgebra<N>) -> SunAlgebra<N> {
let coefficients = self
.coefficients
.iter()
.zip(&rhs.coefficients)
.map(|(a, b)| a + b)
.collect();
SunAlgebra::<N>::new(coefficients)
}
}
impl<const N: usize> Add<&SunAlgebra<N>> for &SunAlgebra<N> {
type Output = SunAlgebra<N>;
fn add(self, rhs: &SunAlgebra<N>) -> SunAlgebra<N> {
let coefficients = self
.coefficients
.iter()
.zip(&rhs.coefficients)
.map(|(a, b)| a + b)
.collect();
SunAlgebra::<N>::new(coefficients)
}
}
impl<const N: usize> Sub for SunAlgebra<N> {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
let coefficients = self
.coefficients
.iter()
.zip(&rhs.coefficients)
.map(|(a, b)| a - b)
.collect();
Self::new(coefficients)
}
}
impl<const N: usize> Neg for SunAlgebra<N> {
type Output = Self;
fn neg(self) -> Self {
let coefficients = self.coefficients.iter().map(|x| -x).collect();
Self::new(coefficients)
}
}
impl<const N: usize> Mul<f64> for SunAlgebra<N> {
type Output = Self;
fn mul(self, scalar: f64) -> Self {
let coefficients = self.coefficients.iter().map(|x| x * scalar).collect();
Self::new(coefficients)
}
}
impl<const N: usize> Mul<SunAlgebra<N>> for f64 {
type Output = SunAlgebra<N>;
fn mul(self, rhs: SunAlgebra<N>) -> SunAlgebra<N> {
rhs * self
}
}
impl<const N: usize> LieAlgebra for SunAlgebra<N> {
const DIM: usize = {
assert!(
N >= 2,
"SU(N) requires N >= 2: SU(1) is trivial, SU(0) is undefined"
);
N * N - 1
};
fn zero() -> Self {
Self {
coefficients: vec![0.0; Self::DIM],
_phantom: PhantomData,
}
}
fn add(&self, other: &Self) -> Self {
let coefficients = self
.coefficients
.iter()
.zip(&other.coefficients)
.map(|(a, b)| a + b)
.collect();
Self::new(coefficients)
}
fn scale(&self, scalar: f64) -> Self {
let coefficients = self.coefficients.iter().map(|x| x * scalar).collect();
Self::new(coefficients)
}
fn norm(&self) -> f64 {
self.coefficients
.iter()
.map(|x| x.powi(2))
.sum::<f64>()
.sqrt()
}
fn basis_element(i: usize) -> Self {
assert!(
i < Self::DIM,
"Basis index {} out of range for SU({})",
i,
N
);
let mut coefficients = vec![0.0; Self::DIM];
coefficients[i] = 1.0;
Self::new(coefficients)
}
fn from_components(components: &[f64]) -> Self {
assert_eq!(
components.len(),
Self::DIM,
"Expected {} components for SU({}), got {}",
Self::DIM,
N,
components.len()
);
Self::new(components.to_vec())
}
fn to_components(&self) -> Vec<f64> {
self.coefficients.clone()
}
fn bracket(&self, other: &Self) -> Self {
let x = self.to_matrix();
let y = other.to_matrix();
let commutator = x.dot(&y) - y.dot(&x);
Self::from_matrix(&commutator)
}
#[inline]
fn inner(&self, other: &Self) -> f64 {
self.coefficients
.iter()
.zip(other.coefficients.iter())
.map(|(a, b)| a * b)
.sum()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct SUN<const N: usize> {
pub(crate) matrix: Array2<Complex64>,
}
impl<const N: usize> SUN<N> {
#[must_use]
pub fn matrix(&self) -> &Array2<Complex64> {
&self.matrix
}
#[must_use]
pub fn identity() -> Self {
Self {
matrix: Array2::eye(N),
}
}
#[must_use]
pub fn trace(&self) -> Complex64 {
(0..N).map(|i| self.matrix[[i, i]]).sum()
}
#[must_use]
pub fn verify_unitarity(&self, tolerance: f64) -> bool {
let adjoint = self.matrix.t().mapv(|z| z.conj());
let product = adjoint.dot(&self.matrix);
let identity: Array2<Complex64> = Array2::eye(N);
let diff = &product - &identity;
let norm_sq: f64 = diff.iter().map(num_complex::Complex::norm_sqr).sum();
norm_sq.sqrt() < tolerance
}
#[must_use]
#[allow(clippy::many_single_char_names)] pub fn determinant(&self) -> Complex64 {
if N == 2 {
let a = self.matrix[[0, 0]];
let b = self.matrix[[0, 1]];
let c = self.matrix[[1, 0]];
let d = self.matrix[[1, 1]];
return a * d - b * c;
}
if N == 3 {
let (a, b, c, d, e, f, g, h, i) = {
let m = &self.matrix;
(
m[[0, 0]],
m[[0, 1]],
m[[0, 2]],
m[[1, 0]],
m[[1, 1]],
m[[1, 2]],
m[[2, 0]],
m[[2, 1]],
m[[2, 2]],
)
};
return a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g);
}
Complex64::new(1.0, 0.0)
}
#[must_use]
fn gram_schmidt_project(matrix: Array2<Complex64>) -> Array2<Complex64> {
let mut result: Array2<Complex64> = Array2::zeros((N, N));
for j in 0..N {
let mut col = matrix.column(j).to_owned();
for k in 0..j {
let prev_col = result.column(k);
let proj: Complex64 = prev_col
.iter()
.zip(col.iter())
.map(|(p, c)| p.conj() * c)
.sum();
for i in 0..N {
col[i] -= proj * prev_col[i];
}
}
let norm: f64 = col
.iter()
.map(num_complex::Complex::norm_sqr)
.sum::<f64>()
.sqrt();
debug_assert!(
norm > 1e-14,
"Gram-Schmidt: column {} is linearly dependent (norm = {:.2e}). \
Input matrix is rank-deficient.",
j,
norm
);
if norm > 1e-14 {
for i in 0..N {
result[[i, j]] = col[i] / norm;
}
}
}
if N <= 3 {
let det = if N == 2 {
result[[0, 0]] * result[[1, 1]] - result[[0, 1]] * result[[1, 0]]
} else {
result[[0, 0]] * (result[[1, 1]] * result[[2, 2]] - result[[1, 2]] * result[[2, 1]])
- result[[0, 1]]
* (result[[1, 0]] * result[[2, 2]] - result[[1, 2]] * result[[2, 0]])
+ result[[0, 2]]
* (result[[1, 0]] * result[[2, 1]] - result[[1, 1]] * result[[2, 0]])
};
let det_norm = det.norm();
if det_norm < 1e-14 {
return Array2::eye(N);
}
let det_phase = det / det_norm;
let correction = (det_phase.conj()).powf(1.0 / N as f64);
result.mapv_inplace(|z| z * correction);
}
result
}
#[must_use]
pub fn distance_to_identity(&self) -> f64 {
let identity: Array2<Complex64> = Array2::eye(N);
let diff = &self.matrix - &identity;
diff.iter()
.map(num_complex::Complex::norm_sqr)
.sum::<f64>()
.sqrt()
}
}
impl<const N: usize> approx::AbsDiffEq for SunAlgebra<N> {
type Epsilon = f64;
fn default_epsilon() -> Self::Epsilon {
1e-10
}
fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
self.coefficients
.iter()
.zip(other.coefficients.iter())
.all(|(a, b)| (a - b).abs() < epsilon)
}
}
impl<const N: usize> approx::RelativeEq for SunAlgebra<N> {
fn default_max_relative() -> Self::Epsilon {
1e-10
}
fn relative_eq(
&self,
other: &Self,
epsilon: Self::Epsilon,
max_relative: Self::Epsilon,
) -> bool {
self.coefficients
.iter()
.zip(other.coefficients.iter())
.all(|(a, b)| approx::RelativeEq::relative_eq(a, b, epsilon, max_relative))
}
}
impl<const N: usize> fmt::Display for SunAlgebra<N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "su({})[", N)?;
for (i, c) in self.coefficients.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{:.4}", c)?;
}
write!(f, "]")
}
}
impl<const N: usize> fmt::Display for SUN<N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let dist = self.distance_to_identity();
write!(f, "SU({})(d={:.4})", N, dist)
}
}
impl<const N: usize> Mul<&SUN<N>> for &SUN<N> {
type Output = SUN<N>;
fn mul(self, rhs: &SUN<N>) -> SUN<N> {
SUN {
matrix: self.matrix.dot(&rhs.matrix),
}
}
}
impl<const N: usize> Mul<&SUN<N>> for SUN<N> {
type Output = SUN<N>;
fn mul(self, rhs: &SUN<N>) -> SUN<N> {
&self * rhs
}
}
impl<const N: usize> MulAssign<&SUN<N>> for SUN<N> {
fn mul_assign(&mut self, rhs: &SUN<N>) {
self.matrix = self.matrix.dot(&rhs.matrix);
}
}
impl<const N: usize> LieGroup for SUN<N> {
type Algebra = SunAlgebra<N>;
const MATRIX_DIM: usize = {
assert!(
N >= 2,
"SU(N) requires N >= 2: SU(1) is trivial, SU(0) is undefined"
);
N
};
fn identity() -> Self {
Self::identity()
}
fn compose(&self, other: &Self) -> Self {
Self {
matrix: self.matrix.dot(&other.matrix),
}
}
fn inverse(&self) -> Self {
Self {
matrix: self.matrix.t().mapv(|z| z.conj()),
}
}
fn conjugate_transpose(&self) -> Self {
Self {
matrix: self.matrix.t().mapv(|z| z.conj()),
}
}
fn adjoint_action(&self, algebra_element: &Self::Algebra) -> Self::Algebra {
let x = algebra_element.to_matrix();
let g_inv = self.inverse();
let result = self.matrix.dot(&x).dot(&g_inv.matrix);
SunAlgebra::from_matrix(&result)
}
fn distance_to_identity(&self) -> f64 {
self.distance_to_identity()
}
fn exp(tangent: &Self::Algebra) -> Self {
let x = tangent.to_matrix();
let norm = matrix_frobenius_norm(&x);
let k = if norm > 0.5 {
(norm / 0.5).log2().ceil() as u32
} else {
0
};
let scale_factor = 2_f64.powi(-(k as i32));
let x_scaled = x.mapv(|z| z * scale_factor);
let exp_scaled = matrix_exp_taylor(&x_scaled, 15);
let mut result = exp_scaled;
for _ in 0..k {
result = result.dot(&result);
result = Self::gram_schmidt_project(result);
}
Self { matrix: result }
}
fn log(&self) -> crate::error::LogResult<Self::Algebra> {
use crate::error::LogError;
let dist = self.distance_to_identity();
const MAX_DISTANCE: f64 = 2.0;
if dist > MAX_DISTANCE {
return Err(LogError::NotNearIdentity {
distance: dist,
threshold: MAX_DISTANCE,
});
}
if dist < 1e-14 {
return Ok(SunAlgebra::zero());
}
let identity_matrix: Array2<Complex64> = Array2::eye(N);
let mut current = self.matrix.clone();
let mut num_sqrts: u32 = 0;
const MAX_SQRTS: u32 = 32;
const TARGET_NORM: f64 = 0.5;
while num_sqrts < MAX_SQRTS {
let x_matrix = ¤t - &identity_matrix;
let x_norm = matrix_frobenius_norm(&x_matrix);
if x_norm < TARGET_NORM {
break;
}
current = matrix_sqrt_db(¤t);
num_sqrts += 1;
}
let x_matrix = ¤t - &identity_matrix;
let log_matrix = matrix_log_taylor(&x_matrix, 30);
let scale_factor = (1_u64 << num_sqrts) as f64;
let log_scaled = log_matrix.mapv(|z| z * scale_factor);
Ok(SunAlgebra::from_matrix(&log_scaled))
}
}
fn matrix_frobenius_norm(matrix: &Array2<Complex64>) -> f64 {
matrix
.iter()
.map(num_complex::Complex::norm_sqr)
.sum::<f64>()
.sqrt()
}
fn matrix_exp_taylor(matrix: &Array2<Complex64>, terms: usize) -> Array2<Complex64> {
let n = matrix.nrows();
let mut result = Array2::eye(n); let mut term = Array2::eye(n);
for k in 1..=terms {
term = term.dot(matrix).mapv(|z| z / (k as f64));
result += &term;
}
result
}
fn matrix_log_taylor(matrix: &Array2<Complex64>, terms: usize) -> Array2<Complex64> {
let mut result = matrix.clone(); let mut x_power = matrix.clone();
for k in 2..=terms {
x_power = x_power.dot(matrix);
let sign = if k % 2 == 0 { -1.0 } else { 1.0 };
let coefficient = sign / (k as f64);
result = result + x_power.mapv(|z| z * coefficient);
}
result
}
fn matrix_inverse(a: &Array2<Complex64>) -> Option<Array2<Complex64>> {
let n = a.nrows();
assert_eq!(n, a.ncols());
let mut aug = Array2::<Complex64>::zeros((n, 2 * n));
for i in 0..n {
for j in 0..n {
aug[[i, j]] = a[[i, j]];
}
aug[[i, n + i]] = Complex64::new(1.0, 0.0);
}
for col in 0..n {
let mut max_norm = 0.0;
let mut max_row = col;
for row in col..n {
let norm = aug[[row, col]].norm();
if norm > max_norm {
max_norm = norm;
max_row = row;
}
}
if max_norm < 1e-15 {
return None; }
if max_row != col {
for j in 0..2 * n {
let tmp = aug[[col, j]];
aug[[col, j]] = aug[[max_row, j]];
aug[[max_row, j]] = tmp;
}
}
let pivot = aug[[col, col]];
for j in 0..2 * n {
aug[[col, j]] /= pivot;
}
for row in 0..n {
if row != col {
let factor = aug[[row, col]];
let pivot_row: Vec<Complex64> = (0..2 * n).map(|j| aug[[col, j]]).collect();
for j in 0..2 * n {
aug[[row, j]] -= factor * pivot_row[j];
}
}
}
}
let mut result = Array2::zeros((n, n));
for i in 0..n {
for j in 0..n {
result[[i, j]] = aug[[i, n + j]];
}
}
Some(result)
}
fn matrix_sqrt_db(u: &Array2<Complex64>) -> Array2<Complex64> {
let n = u.nrows();
let mut y = u.clone();
let mut z = Array2::<Complex64>::eye(n);
const MAX_ITERS: usize = 20;
const TOL: f64 = 1e-14;
for _ in 0..MAX_ITERS {
let y_inv = matrix_inverse(&y).unwrap_or_else(|| y.t().mapv(|z| z.conj()));
let z_inv = matrix_inverse(&z).unwrap_or_else(|| z.t().mapv(|z| z.conj()));
let y_new = (&y + &z_inv).mapv(|z| z * 0.5);
let z_new = (&z + &y_inv).mapv(|z| z * 0.5);
let diff = matrix_frobenius_norm(&(&y_new - &y));
y = y_new;
z = z_new;
if diff < TOL {
break;
}
}
y
}
impl<const N: usize> Compact for SUN<N> {}
impl<const N: usize> Simple for SUN<N> {}
impl<const N: usize> SemiSimple for SUN<N> {}
impl<const N: usize> TracelessByConstruction for SunAlgebra<N> {}
impl<const N: usize> AntiHermitianByConstruction for SunAlgebra<N> {}
pub type SU2Generic = SUN<2>;
pub type SU3Generic = SUN<3>;
pub type SU4 = SUN<4>;
pub type SU5 = SUN<5>;
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_sun2_su2_basis_matrices_agree() {
for k in 0..3 {
let m_sun = SunAlgebra::<2>::basis_element(k).to_matrix();
let i = Complex64::new(0.0, 1.0);
let sigma: Array2<Complex64> = match k {
0 => Array2::from_shape_vec(
(2, 2),
vec![
Complex64::new(0.0, 0.0),
Complex64::new(1.0, 0.0),
Complex64::new(1.0, 0.0),
Complex64::new(0.0, 0.0),
],
)
.unwrap(),
1 => Array2::from_shape_vec(
(2, 2),
vec![
Complex64::new(0.0, 0.0),
Complex64::new(0.0, -1.0),
Complex64::new(0.0, 1.0),
Complex64::new(0.0, 0.0),
],
)
.unwrap(),
2 => Array2::from_shape_vec(
(2, 2),
vec![
Complex64::new(1.0, 0.0),
Complex64::new(0.0, 0.0),
Complex64::new(0.0, 0.0),
Complex64::new(-1.0, 0.0),
],
)
.unwrap(),
_ => unreachable!(),
};
let expected = sigma.mapv(|z| i * z * 0.5);
for r in 0..2 {
for c in 0..2 {
assert!(
(m_sun[(r, c)] - expected[(r, c)]).norm() < 1e-10,
"SUN<2> basis {} at ({},{}): got {}, want {}",
k,
r,
c,
m_sun[(r, c)],
expected[(r, c)]
);
}
}
}
}
#[test]
fn test_sun2_su2_brackets_agree() {
use crate::Su2Algebra;
for i in 0..3 {
for j in 0..3 {
let bracket_su2 = Su2Algebra::basis_element(i)
.bracket(&Su2Algebra::basis_element(j))
.to_components();
let bracket_sun = SunAlgebra::<2>::basis_element(i)
.bracket(&SunAlgebra::<2>::basis_element(j))
.to_components();
for k in 0..3 {
assert_relative_eq!(bracket_su2[k], bracket_sun[k], epsilon = 1e-10);
}
}
}
}
#[test]
fn test_sun2_su2_exp_agrees() {
use crate::{Su2Algebra, SU2};
let coeffs = [0.3, -0.2, 0.4];
let g_su2 = SU2::exp(&Su2Algebra::new(coeffs));
let g_sun = SUN::<2>::exp(&SunAlgebra::<2>::from_components(&coeffs));
let m_su2 = g_su2.to_matrix();
let m_sun = g_sun.matrix();
for i in 0..2 {
for j in 0..2 {
assert_relative_eq!(m_su2[i][j].re, m_sun[(i, j)].re, epsilon = 1e-10);
assert_relative_eq!(m_su2[i][j].im, m_sun[(i, j)].im, epsilon = 1e-10);
}
}
}
#[test]
fn test_sun3_su3_basis_matrices_agree() {
use crate::Su3Algebra;
let su3_to_sun: [usize; 8] = [0, 3, 6, 1, 4, 2, 5, 7];
for k in 0..8 {
let m_su3 = Su3Algebra::basis_element(k).to_matrix();
let m_sun = SunAlgebra::<3>::basis_element(su3_to_sun[k]).to_matrix();
for r in 0..3 {
for c in 0..3 {
assert!(
(m_su3[(r, c)] - m_sun[(r, c)]).norm() < 1e-10,
"Basis {} (SU3) vs {} (SUN) at ({},{}): SU3={}, SUN<3>={}",
k,
su3_to_sun[k],
r,
c,
m_su3[(r, c)],
m_sun[(r, c)]
);
}
}
}
}
#[test]
fn test_sun3_su3_brackets_agree() {
use crate::Su3Algebra;
let su3_to_sun: [usize; 8] = [0, 3, 6, 1, 4, 2, 5, 7];
for i in 0..8 {
for j in 0..8 {
let bracket_su3 = Su3Algebra::basis_element(i)
.bracket(&Su3Algebra::basis_element(j))
.to_matrix();
let bracket_sun = SunAlgebra::<3>::basis_element(su3_to_sun[i])
.bracket(&SunAlgebra::<3>::basis_element(su3_to_sun[j]))
.to_matrix();
for r in 0..3 {
for c in 0..3 {
assert!(
(bracket_su3[(r, c)] - bracket_sun[(r, c)]).norm() < 1e-10,
"Bracket [e{},e{}] at ({},{}): SU3={}, SUN<3>={}",
i,
j,
r,
c,
bracket_su3[(r, c)],
bracket_sun[(r, c)]
);
}
}
}
}
}
#[test]
fn test_sun3_su3_exp_agrees() {
use crate::{Su3Algebra, SU3};
let su3_coeffs = [0.1, -0.2, 0.15, 0.08, -0.12, 0.05, 0.1, -0.06];
let su3_elem = Su3Algebra::from_components(&su3_coeffs);
let matrix = su3_elem.to_matrix();
let sun_elem = SunAlgebra::<3>::from_matrix(&matrix);
let g_su3 = SU3::exp(&su3_elem);
let g_sun = SUN::<3>::exp(&sun_elem);
let m_su3 = g_su3.matrix();
let m_sun = g_sun.matrix();
for i in 0..3 {
for j in 0..3 {
assert!(
(m_su3[(i, j)] - m_sun[(i, j)]).norm() < 1e-10,
"exp disagrees at ({},{}): SU3={}, SUN<3>={}",
i,
j,
m_su3[(i, j)],
m_sun[(i, j)]
);
}
}
}
#[test]
fn test_normalization_half_delta() {
for n in [2_usize, 3, 4, 5] {
let dim = n * n - 1;
for a in 0..dim {
let ma = match n {
2 => SunAlgebra::<2>::basis_element(a).to_matrix(),
3 => SunAlgebra::<3>::basis_element(a).to_matrix(),
4 => SunAlgebra::<4>::basis_element(a).to_matrix(),
5 => SunAlgebra::<5>::basis_element(a).to_matrix(),
_ => unreachable!(),
};
let ma_dag = ma.t().mapv(|z| z.conj());
for b in a..dim {
let mb = match n {
2 => SunAlgebra::<2>::basis_element(b).to_matrix(),
3 => SunAlgebra::<3>::basis_element(b).to_matrix(),
4 => SunAlgebra::<4>::basis_element(b).to_matrix(),
5 => SunAlgebra::<5>::basis_element(b).to_matrix(),
_ => unreachable!(),
};
let prod = ma_dag.dot(&mb);
let mut tr = 0.0;
for i in 0..n {
tr += prod[(i, i)].re;
}
let expected = if a == b { 0.5 } else { 0.0 };
assert!(
(tr - expected).abs() < 1e-10,
"SU({}): tr(T{}†T{}) = {:.4}, want {}",
n,
a,
b,
tr,
expected
);
}
}
}
}
#[test]
fn test_bch_exp_log_coherence_sun2() {
use crate::bch::bch_fifth_order;
let x = SunAlgebra::<2>::from_components(&[0.05, -0.03, 0.04]);
let y = SunAlgebra::<2>::from_components(&[-0.02, 0.04, -0.03]);
let direct = SUN::<2>::exp(&x).compose(&SUN::<2>::exp(&y));
let bch_z = bch_fifth_order(&x, &y);
let via_bch = SUN::<2>::exp(&bch_z);
let distance = direct.compose(&via_bch.inverse()).distance_to_identity();
assert!(
distance < 1e-8,
"SUN<2> BCH vs exp·log distance: {:.2e}",
distance
);
}
#[test]
fn test_adjoint_preserves_bracket_su3() {
use crate::{Su3Algebra, SU3};
let x = Su3Algebra::from_components(&[0.3, -0.2, 0.1, 0.15, -0.1, 0.25, -0.15, 0.05]);
let y = Su3Algebra::from_components(&[-0.1, 0.25, -0.15, 0.2, 0.05, -0.1, 0.3, -0.2]);
let g = SU3::exp(&Su3Algebra::from_components(&[0.4, -0.3, 0.2, 0.1, -0.2, 0.15, -0.1, 0.3]));
let lhs = g.adjoint_action(&x.bracket(&y));
let rhs = g.adjoint_action(&x).bracket(&g.adjoint_action(&y));
let diff_matrix = lhs.to_matrix() - rhs.to_matrix();
let mut frobenius = 0.0;
for r in 0..3 {
for c in 0..3 {
frobenius += diff_matrix[(r, c)].norm_sqr();
}
}
assert!(
frobenius.sqrt() < 1e-10,
"SU3 Ad_g([X,Y]) ≠ [Ad_g(X), Ad_g(Y)]: ||diff|| = {:.2e}",
frobenius.sqrt()
);
}
#[test]
fn test_su3_sun3_bracket_coefficient_roundtrip() {
use crate::Su3Algebra;
let x_su3 = Su3Algebra::from_components(&[0.3, -0.2, 0.1, 0.15, -0.1, 0.25, -0.15, 0.05]);
let y_su3 = Su3Algebra::from_components(&[-0.1, 0.25, -0.15, 0.2, 0.05, -0.1, 0.3, -0.2]);
let bracket_su3 = x_su3.bracket(&y_su3);
let m_su3 = bracket_su3.to_matrix();
let x_sun = SunAlgebra::<3>::from_matrix(&x_su3.to_matrix());
let y_sun = SunAlgebra::<3>::from_matrix(&y_su3.to_matrix());
let bracket_sun = x_sun.bracket(&y_sun);
let m_sun = bracket_sun.to_matrix();
for r in 0..3 {
for c in 0..3 {
assert!(
(m_su3[(r, c)] - m_sun[(r, c)]).norm() < 1e-12,
"Bracket matrix disagrees at ({},{}): SU3={}, SUN<3>={}",
r, c, m_su3[(r, c)], m_sun[(r, c)]
);
}
}
let roundtrip = SunAlgebra::<3>::from_matrix(&m_su3).to_matrix();
for r in 0..3 {
for c in 0..3 {
assert!(
(m_su3[(r, c)] - roundtrip[(r, c)]).norm() < 1e-12,
"from_matrix roundtrip failed at ({},{})",
r, c
);
}
}
}
#[test]
fn test_su4_group_axioms() {
let x = SunAlgebra::<4>::from_components(&[
0.1, -0.2, 0.15, 0.08, -0.12, 0.05, 0.1, -0.06, 0.09, -0.11, 0.07, 0.03, -0.08, 0.04,
0.13,
]);
let y = SunAlgebra::<4>::from_components(&[
-0.05, 0.1, -0.08, 0.12, 0.06, -0.15, 0.03, 0.09, -0.07, 0.11, -0.04, 0.14, 0.02, -0.1,
0.08,
]);
let g = SUN::<4>::exp(&x);
let h = SUN::<4>::exp(&y);
let e = SUN::<4>::identity();
assert_relative_eq!(
g.compose(&e).distance_to_identity(),
g.distance_to_identity(),
epsilon = 1e-10
);
assert_relative_eq!(
g.compose(&g.inverse()).distance_to_identity(),
0.0,
epsilon = 1e-9
);
let k = SUN::<4>::exp(&x.scale(0.7));
let lhs = g.compose(&h).compose(&k);
let rhs = g.compose(&h.compose(&k));
assert_relative_eq!(
lhs.compose(&rhs.inverse()).distance_to_identity(),
0.0,
epsilon = 1e-9
);
assert!(g.verify_unitarity(1e-10));
assert!(g.compose(&h).verify_unitarity(1e-10));
}
#[test]
fn test_su5_group_axioms() {
let x = SunAlgebra::<5>::from_components(
&(0..24)
.map(|i| 0.05 * (i as f64 - 12.0).sin())
.collect::<Vec<_>>(),
);
let g = SUN::<5>::exp(&x);
assert_relative_eq!(
g.compose(&SUN::<5>::identity()).distance_to_identity(),
g.distance_to_identity(),
epsilon = 1e-10
);
assert_relative_eq!(
g.compose(&g.inverse()).distance_to_identity(),
0.0,
epsilon = 1e-9
);
assert!(g.verify_unitarity(1e-10));
}
#[test]
fn test_su4_jacobi_identity() {
let triples = [(0, 3, 7), (1, 5, 10), (2, 8, 14), (4, 9, 12)];
for (i, j, k) in triples {
let x = SunAlgebra::<4>::basis_element(i);
let y = SunAlgebra::<4>::basis_element(j);
let z = SunAlgebra::<4>::basis_element(k);
let t1 = x.bracket(&y.bracket(&z));
let t2 = y.bracket(&z.bracket(&x));
let t3 = z.bracket(&x.bracket(&y));
let sum = t1.add(&t2).add(&t3);
assert!(
sum.norm() < 1e-10,
"Jacobi violated for SU(4) basis ({},{},{}): ||sum|| = {:.2e}",
i,
j,
k,
sum.norm()
);
}
}
#[test]
fn test_sun_adjoint_identity_action() {
let e = SUN::<3>::identity();
for i in 0..8 {
let x = SunAlgebra::<3>::basis_element(i);
let ad_x = e.adjoint_action(&x);
for k in 0..8 {
assert_relative_eq!(
x.to_components()[k],
ad_x.to_components()[k],
epsilon = 1e-10
);
}
}
}
#[test]
fn test_sun_adjoint_homomorphism() {
let g = SUN::<3>::exp(&SunAlgebra::<3>::basis_element(0).scale(0.5));
let h = SUN::<3>::exp(&SunAlgebra::<3>::basis_element(3).scale(0.3));
let x = SunAlgebra::<3>::from_components(&[0.1, -0.2, 0.15, 0.08, -0.12, 0.05, 0.1, -0.06]);
let gh = g.compose(&h);
let lhs = gh.adjoint_action(&x);
let rhs = g.adjoint_action(&h.adjoint_action(&x));
for k in 0..8 {
assert_relative_eq!(
lhs.to_components()[k],
rhs.to_components()[k],
epsilon = 1e-9
);
}
}
#[test]
fn test_sun_adjoint_preserves_bracket() {
let g = SUN::<3>::exp(&SunAlgebra::<3>::basis_element(2).scale(0.8));
let x = SunAlgebra::<3>::basis_element(0);
let y = SunAlgebra::<3>::basis_element(4);
let lhs = g.adjoint_action(&x.bracket(&y));
let rhs = g.adjoint_action(&x).bracket(&g.adjoint_action(&y));
for k in 0..8 {
assert_relative_eq!(
lhs.to_components()[k],
rhs.to_components()[k],
epsilon = 1e-9
);
}
}
#[test]
fn test_sun4_adjoint_preserves_norm() {
let x = SunAlgebra::<4>::from_components(&[
0.1, -0.2, 0.15, 0.08, -0.12, 0.05, 0.1, -0.06, 0.09, -0.11, 0.07, 0.03, -0.08, 0.04,
0.13,
]);
let g = SUN::<4>::exp(&SunAlgebra::<4>::basis_element(5).scale(1.2));
let ad_x = g.adjoint_action(&x);
assert_relative_eq!(x.norm(), ad_x.norm(), epsilon = 1e-9);
}
#[test]
fn test_su4_exp_log_roundtrip() {
let x = SunAlgebra::<4>::from_components(&[
0.1, -0.05, 0.08, 0.03, -0.06, 0.02, 0.04, -0.07, 0.09, -0.03, 0.01, 0.05, -0.02, 0.06,
0.04,
]);
let g = SUN::<4>::exp(&x);
assert!(g.verify_unitarity(1e-10));
let x_back = SUN::<4>::log(&g).expect("log should succeed near identity");
let diff: f64 = x
.coefficients()
.iter()
.zip(x_back.coefficients().iter())
.map(|(a, b)| (a - b).powi(2))
.sum::<f64>()
.sqrt();
assert!(diff < 1e-8, "SU(4) exp/log roundtrip error: {:.2e}", diff);
}
#[test]
fn test_sun_algebra_dimensions() {
assert_eq!(SunAlgebra::<2>::DIM, 3); assert_eq!(SunAlgebra::<3>::DIM, 8); assert_eq!(SunAlgebra::<4>::DIM, 15); assert_eq!(SunAlgebra::<5>::DIM, 24); }
#[test]
fn test_sun_algebra_zero() {
let zero = SunAlgebra::<3>::zero();
assert_eq!(zero.coefficients.len(), 8);
assert!(zero.coefficients.iter().all(|&x| x == 0.0));
}
#[test]
fn test_sun_algebra_add_scale() {
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 scaled = x.scale(2.5);
assert_eq!(scaled.coefficients, vec![2.5, 0.0, 0.0]);
}
#[test]
fn test_sun_identity() {
let id = SUN::<3>::identity();
assert!(id.verify_unitarity(1e-10));
assert_relative_eq!(id.distance_to_identity(), 0.0, epsilon = 1e-10);
}
#[test]
fn test_sun_exponential_preserves_unitarity() {
let algebra =
SunAlgebra::<3>::from_components(&[0.5, -0.3, 0.8, 0.2, -0.6, 0.4, 0.1, -0.2]);
let g = SUN::<3>::exp(&algebra);
assert!(
g.verify_unitarity(1e-10),
"Exponential should preserve unitarity"
);
}
#[test]
fn test_sun_exp_identity() {
let zero = SunAlgebra::<4>::zero();
let g = SUN::<4>::exp(&zero);
assert_relative_eq!(g.distance_to_identity(), 0.0, epsilon = 1e-10);
}
#[test]
fn test_sun_group_composition() {
let g1 = SUN::<2>::exp(&SunAlgebra::<2>::basis_element(0).scale(0.5));
let g2 = SUN::<2>::exp(&SunAlgebra::<2>::basis_element(1).scale(0.3));
let product = g1.compose(&g2);
assert!(product.verify_unitarity(1e-10));
}
#[test]
fn test_sun_inverse() {
let algebra =
SunAlgebra::<3>::from_components(&[0.2, 0.3, -0.1, 0.5, -0.2, 0.1, 0.4, -0.3]);
let g = SUN::<3>::exp(&algebra);
let g_inv = g.inverse();
let product = g.compose(&g_inv);
assert_relative_eq!(product.distance_to_identity(), 0.0, epsilon = 1e-9);
}
#[test]
fn test_sun_adjoint_action_preserves_norm() {
let g = SUN::<3>::exp(&SunAlgebra::<3>::basis_element(0).scale(1.2));
let x = SunAlgebra::<3>::basis_element(2).scale(0.5);
let ad_x = g.adjoint_action(&x);
assert_relative_eq!(x.norm(), ad_x.norm(), epsilon = 1e-9);
}
#[test]
fn test_sun_exp_log_roundtrip() {
let x = SunAlgebra::<3>::from_components(&[0.1, -0.2, 0.15, 0.08, -0.12, 0.05, 0.1, -0.06]);
let g = SUN::<3>::exp(&x);
assert!(g.verify_unitarity(1e-10));
let x_back = SUN::<3>::log(&g).expect("log should succeed near identity");
let diff_norm: f64 = x
.coefficients
.iter()
.zip(x_back.coefficients.iter())
.map(|(a, b)| (a - b).powi(2))
.sum::<f64>()
.sqrt();
assert!(
diff_norm < 1e-8,
"SU(3) exp/log roundtrip error: {:.2e}",
diff_norm
);
}
#[test]
fn test_sun_exp_log_roundtrip_su2() {
let x = SunAlgebra::<2>::from_components(&[0.3, -0.2, 0.4]);
let g = SUN::<2>::exp(&x);
assert!(g.verify_unitarity(1e-10));
let x_back = SUN::<2>::log(&g).expect("log should succeed");
let diff_norm: f64 = x
.coefficients
.iter()
.zip(x_back.coefficients.iter())
.map(|(a, b)| (a - b).powi(2))
.sum::<f64>()
.sqrt();
assert!(
diff_norm < 1e-8,
"SU(2) exp/log roundtrip error: {:.2e}",
diff_norm
);
}
#[test]
fn test_sun_log_exp_roundtrip() {
let x = SunAlgebra::<3>::from_components(&[0.2, 0.3, -0.1, 0.5, -0.2, 0.1, 0.4, -0.3]);
let g = SUN::<3>::exp(&x);
let log_g = SUN::<3>::log(&g).expect("log should succeed");
let g_back = SUN::<3>::exp(&log_g);
assert_relative_eq!(
g.distance_to_identity(),
g_back.distance_to_identity(),
epsilon = 1e-8
);
let product = g.compose(&g_back.inverse());
assert_relative_eq!(product.distance_to_identity(), 0.0, epsilon = 1e-8);
}
#[test]
fn test_sun_jacobi_identity() {
let x = SunAlgebra::<3>::basis_element(0);
let y = SunAlgebra::<3>::basis_element(1);
let z = SunAlgebra::<3>::basis_element(2);
let t1 = x.bracket(&y.bracket(&z));
let t2 = y.bracket(&z.bracket(&x));
let t3 = z.bracket(&x.bracket(&y));
let sum = t1.add(&t2).add(&t3);
assert!(
sum.norm() < 1e-10,
"Jacobi identity violated for SU(3): ||sum|| = {:.2e}",
sum.norm()
);
}
#[test]
fn test_sun_bracket_antisymmetry() {
let x = SunAlgebra::<4>::basis_element(0);
let y = SunAlgebra::<4>::basis_element(3);
let xy = x.bracket(&y);
let yx = y.bracket(&x);
for i in 0..SunAlgebra::<4>::DIM {
assert_relative_eq!(xy.coefficients[i], -yx.coefficients[i], epsilon = 1e-10);
}
}
#[test]
fn test_sun_bracket_bilinearity() {
let x = SunAlgebra::<3>::basis_element(0);
let y = SunAlgebra::<3>::basis_element(3);
let z = SunAlgebra::<3>::basis_element(5);
let alpha = 2.5;
let lhs = x.scale(alpha).add(&y).bracket(&z);
let rhs = x.bracket(&z).scale(alpha).add(&y.bracket(&z));
for i in 0..SunAlgebra::<3>::DIM {
assert!(
(lhs.coefficients[i] - rhs.coefficients[i]).abs() < 1e-14,
"Left linearity failed at component {}: {} vs {}",
i,
lhs.coefficients[i],
rhs.coefficients[i]
);
}
let lhs = z.bracket(&x.scale(alpha).add(&y));
let rhs = z.bracket(&x).scale(alpha).add(&z.bracket(&y));
for i in 0..SunAlgebra::<3>::DIM {
assert!(
(lhs.coefficients[i] - rhs.coefficients[i]).abs() < 1e-14,
"Right linearity failed at component {}: {} vs {}",
i,
lhs.coefficients[i],
rhs.coefficients[i]
);
}
}
}