pub mod casimir;
pub mod su3_irrep;
pub use casimir::Casimir;
pub use su3_irrep::Su3Irrep;
use crate::quaternion::UnitQuaternion;
use crate::su2::SU2;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Spin {
two_j: u32,
}
impl Spin {
pub const ZERO: Spin = Spin { two_j: 0 };
pub const HALF: Spin = Spin { two_j: 1 };
pub const ONE: Spin = Spin { two_j: 2 };
#[inline]
#[must_use]
pub fn two_j(&self) -> u32 {
self.two_j
}
#[must_use]
pub fn from_half_integer(two_j: u32) -> Self {
Spin { two_j }
}
#[must_use]
pub fn from_integer(j: u32) -> Self {
Spin { two_j: 2 * j }
}
#[must_use]
pub fn value(&self) -> f64 {
f64::from(self.two_j) / 2.0
}
#[must_use]
pub fn dimension(&self) -> usize {
(self.two_j + 1) as usize
}
#[must_use]
pub fn is_integer(&self) -> bool {
self.two_j % 2 == 0
}
#[must_use]
pub fn is_half_integer(&self) -> bool {
self.two_j % 2 == 1
}
}
#[must_use]
pub fn character(spin: Spin, angle: f64) -> f64 {
use std::f64::consts::PI;
let j = spin.value();
let two_j = spin.two_j;
let dim = 2.0 * j + 1.0;
let n = (angle / (2.0 * PI)).round();
let near_multiple = (angle - n * 2.0 * PI).abs() < 1e-10;
if near_multiple {
let n_int = n as i64;
if n_int % 2 == 0 {
return dim;
}
let phase = if two_j % 2 == 0 { 1.0 } else { -1.0 };
return phase * dim;
}
let numerator = ((2.0 * j + 1.0) * angle / 2.0).sin();
let denominator = (angle / 2.0).sin();
if denominator.abs() < 1e-10 {
let num_deriv = (2.0 * j + 1.0) * ((2.0 * j + 1.0) * angle / 2.0).cos();
let den_deriv = (angle / 2.0).cos();
if den_deriv.abs() < 1e-10 {
return dim; }
return num_deriv / den_deriv;
}
numerator / denominator
}
#[must_use]
pub fn character_su2(spin: Spin, g: &SU2) -> f64 {
let matrix_array = g.to_matrix();
let quat = UnitQuaternion::from_matrix(matrix_array);
let (_axis, angle) = quat.to_axis_angle();
character(spin, angle)
}
#[must_use]
pub fn clebsch_gordan_decomposition(j1: Spin, j2: Spin) -> Vec<Spin> {
let min_k = (j1.two_j as i32 - j2.two_j as i32).unsigned_abs();
let max_k = j1.two_j + j2.two_j;
let mut result = Vec::new();
let mut k = min_k;
while k <= max_k {
result.push(Spin::from_half_integer(k));
k += 2;
}
result
}
#[must_use]
pub fn character_orthogonality_delta(j1: Spin, j2: Spin) -> bool {
j1 == j2
}
#[must_use]
pub fn representation_dimension(spin: Spin) -> usize {
spin.dimension()
}
#[cfg(test)]
mod tests {
use super::*;
use std::f64::consts::PI;
#[test]
fn test_spin_values() {
assert_eq!(Spin::ZERO.value(), 0.0);
assert_eq!(Spin::HALF.value(), 0.5);
assert_eq!(Spin::ONE.value(), 1.0);
assert_eq!(Spin::from_half_integer(3).value(), 1.5);
}
#[test]
fn test_dimension_formula() {
assert_eq!(Spin::ZERO.dimension(), 1); assert_eq!(Spin::HALF.dimension(), 2); assert_eq!(Spin::ONE.dimension(), 3); assert_eq!(Spin::from_half_integer(3).dimension(), 4); }
#[test]
fn test_integer_vs_half_integer() {
assert!(Spin::ZERO.is_integer());
assert!(Spin::ONE.is_integer());
assert!(Spin::from_integer(2).is_integer());
assert!(Spin::HALF.is_half_integer());
assert!(Spin::from_half_integer(3).is_half_integer()); assert!(Spin::from_half_integer(5).is_half_integer()); }
#[test]
fn test_character_at_identity() {
assert_eq!(character(Spin::ZERO, 0.0), 1.0);
assert_eq!(character(Spin::HALF, 0.0), 2.0);
assert_eq!(character(Spin::ONE, 0.0), 3.0);
}
#[test]
fn test_character_at_2pi() {
let angle = 2.0 * PI;
assert!((character(Spin::ZERO, angle) - 1.0).abs() < 1e-10);
assert!((character(Spin::HALF, angle) - (-2.0)).abs() < 1e-10);
assert!((character(Spin::ONE, angle) - 3.0).abs() < 1e-10);
}
#[test]
fn test_clebsch_gordan_spinor_times_spinor() {
let decomp = clebsch_gordan_decomposition(Spin::HALF, Spin::HALF);
assert_eq!(decomp.len(), 2);
assert_eq!(decomp[0], Spin::ZERO);
assert_eq!(decomp[1], Spin::ONE);
}
#[test]
fn test_clebsch_gordan_spinor_times_vector() {
let decomp = clebsch_gordan_decomposition(Spin::HALF, Spin::ONE);
assert_eq!(decomp.len(), 2);
assert_eq!(decomp[0], Spin::HALF);
assert_eq!(decomp[1], Spin::from_half_integer(3));
}
#[test]
fn test_clebsch_gordan_vector_times_vector() {
let decomp = clebsch_gordan_decomposition(Spin::ONE, Spin::ONE);
assert_eq!(decomp.len(), 3);
assert_eq!(decomp[0], Spin::ZERO);
assert_eq!(decomp[1], Spin::ONE);
assert_eq!(decomp[2], Spin::from_integer(2));
}
#[test]
fn test_character_su2_identity() {
let identity = SU2::identity();
let chi = character_su2(Spin::ONE, &identity);
assert!((chi - 3.0).abs() < 1e-10);
}
#[test]
fn test_dimension_counts() {
assert_eq!(representation_dimension(Spin::ZERO), 1);
assert_eq!(representation_dimension(Spin::HALF), 2);
assert_eq!(representation_dimension(Spin::ONE), 3);
assert_eq!(representation_dimension(Spin::from_half_integer(3)), 4);
assert_eq!(representation_dimension(Spin::from_integer(2)), 5);
}
#[test]
fn test_character_formula_j_equals_half() {
let angles = [0.0, PI / 4.0, PI / 2.0, PI];
for &angle in &angles {
let chi = character(Spin::HALF, angle);
let expected = 2.0 * (angle / 2.0).cos();
assert!(
(chi - expected).abs() < 1e-10,
"j=1/2: χ({angle}) = {chi}, expected {expected}"
);
}
}
#[test]
fn test_character_formula_j_equals_one() {
let angles = [0.0, PI / 3.0, PI / 2.0, PI];
for &angle in &angles {
let chi = character(Spin::ONE, angle);
let expected = 1.0 + 2.0 * angle.cos();
assert!(
(chi - expected).abs() < 1e-10,
"j=1: χ({angle}) = {chi}, expected {expected}"
);
}
}
}