use num_complex::Complex64;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Su3Irrep {
pub p: u32,
pub q: u32,
}
impl Su3Irrep {
pub const TRIVIAL: Su3Irrep = Su3Irrep { p: 0, q: 0 };
pub const FUNDAMENTAL: Su3Irrep = Su3Irrep { p: 1, q: 0 };
pub const ANTIFUNDAMENTAL: Su3Irrep = Su3Irrep { p: 0, q: 1 };
pub const ADJOINT: Su3Irrep = Su3Irrep { p: 1, q: 1 };
pub const SYMMETRIC: Su3Irrep = Su3Irrep { p: 2, q: 0 };
#[must_use]
pub fn new(p: u32, q: u32) -> Self {
Su3Irrep { p, q }
}
#[must_use]
pub fn dimension(&self) -> usize {
let p = self.p as usize;
let q = self.q as usize;
((p + 1) * (q + 1) * (p + q + 2)) / 2
}
#[must_use]
pub fn conjugate(&self) -> Self {
Su3Irrep {
p: self.q,
q: self.p,
}
}
#[must_use]
pub fn is_real(&self) -> bool {
self.p == self.q
}
#[must_use]
pub fn casimir_eigenvalue(&self) -> f64 {
let p = self.p as f64;
let q = self.q as f64;
(p * p + q * q + p * q + 3.0 * p + 3.0 * q) / 3.0
}
pub fn representation_matrix(
&self,
g: &[[Complex64; 3]; 3],
) -> crate::RepresentationResult<Vec<Vec<Complex64>>> {
match (self.p, self.q) {
(0, 0) => {
Ok(vec![vec![Complex64::new(1.0, 0.0)]])
}
(1, 0) => {
Ok((0..3).map(|i| (0..3).map(|j| g[i][j]).collect()).collect())
}
(0, 1) => {
Ok((0..3)
.map(|i| (0..3).map(|j| g[i][j].conj()).collect())
.collect())
}
(1, 1) => {
Ok(self.adjoint_representation_matrix(g))
}
(2, 0) => {
Ok(self.symmetric_tensor_matrix(g))
}
_ => Err(crate::RepresentationError::UnsupportedRepresentation {
representation: format!("({},{})", self.p, self.q),
reason: "Requires tensor product construction (not yet implemented)".to_string(),
}),
}
}
#[allow(clippy::trivially_copy_pass_by_ref)]
fn adjoint_representation_matrix(&self, g: &[[Complex64; 3]; 3]) -> Vec<Vec<Complex64>> {
use crate::su3::Su3Algebra;
let g_inv: [[Complex64; 3]; 3] =
std::array::from_fn(|i| std::array::from_fn(|j| g[j][i].conj()));
let mut result = vec![vec![Complex64::new(0.0, 0.0); 8]; 8];
for a in 0..8 {
let t_a = Su3Algebra::gell_mann_matrix(a);
let mut gt_a = [[Complex64::new(0.0, 0.0); 3]; 3];
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
gt_a[i][j] += g[i][k] * t_a[[k, j]];
}
}
}
let mut conjugated = [[Complex64::new(0.0, 0.0); 3]; 3];
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
conjugated[i][j] += gt_a[i][k] * g_inv[k][j];
}
}
}
for b in 0..8 {
let t_b = Su3Algebra::gell_mann_matrix(b);
let mut trace = Complex64::new(0.0, 0.0);
for i in 0..3 {
for j in 0..3 {
trace += t_b[[i, j]] * conjugated[j][i];
}
}
result[b][a] = trace / 2.0;
}
}
result
}
#[allow(clippy::trivially_copy_pass_by_ref)]
fn symmetric_tensor_matrix(&self, g: &[[Complex64; 3]; 3]) -> Vec<Vec<Complex64>> {
let sqrt2 = std::f64::consts::SQRT_2;
let pairs = [(0, 0), (1, 1), (2, 2), (0, 1), (0, 2), (1, 2)];
let mut result = vec![vec![Complex64::new(0.0, 0.0); 6]; 6];
for (col_idx, &(i, j)) in pairs.iter().enumerate() {
for (row_idx, &(k, l)) in pairs.iter().enumerate() {
let coeff;
if i == j && k == l {
coeff = g[k][i] * g[l][j];
} else if i == j && k != l {
coeff = Complex64::new(sqrt2, 0.0) * g[k][i] * g[l][i];
} else if i != j && k == l {
coeff = Complex64::new(sqrt2, 0.0) * g[k][i] * g[k][j];
} else {
coeff = g[k][i] * g[l][j] + g[k][j] * g[l][i];
}
result[row_idx][col_idx] = coeff;
}
}
result
}
#[must_use]
pub fn character(&self, g: &[[Complex64; 3]; 3]) -> Complex64 {
match (self.p, self.q) {
(0, 0) => Complex64::new(1.0, 0.0),
(1, 0) => {
g[0][0] + g[1][1] + g[2][2]
}
(0, 1) => {
(g[0][0] + g[1][1] + g[2][2]).conj()
}
(1, 1) => {
let tr = g[0][0] + g[1][1] + g[2][2];
tr.norm_sqr() - Complex64::new(1.0, 0.0)
}
(2, 0) => {
let tr = g[0][0] + g[1][1] + g[2][2];
let mut tr_sq = Complex64::new(0.0, 0.0);
for i in 0..3 {
for j in 0..3 {
tr_sq += g[i][j] * g[j][i];
}
}
(tr * tr + tr_sq) / 2.0
}
(0, 2) => {
let tr = (g[0][0] + g[1][1] + g[2][2]).conj();
let mut tr_sq = Complex64::new(0.0, 0.0);
for i in 0..3 {
for j in 0..3 {
tr_sq += g[i][j].conj() * g[j][i].conj();
}
}
(tr * tr + tr_sq) / 2.0
}
_ => {
self.weyl_character(g)
}
}
}
#[allow(clippy::many_single_char_names, clippy::trivially_copy_pass_by_ref)]
fn weyl_character(&self, g: &[[Complex64; 3]; 3]) -> Complex64 {
let eigenvalues = self.extract_eigenvalues(g);
let p = self.p as i32;
let q = self.q as i32;
let a = p + 1;
let b = q + 1;
let z = eigenvalues;
let mut numerator = Complex64::new(0.0, 0.0);
let mut denominator = Complex64::new(0.0, 0.0);
let perms = [
([0, 1, 2], 1.0),
([1, 0, 2], -1.0),
([0, 2, 1], -1.0),
([2, 1, 0], -1.0),
([1, 2, 0], 1.0),
([2, 0, 1], 1.0),
];
for &(perm, sign) in &perms {
let m = [a + b, b, 0];
let term = sign
* z[0].powf(m[perm[0]] as f64)
* z[1].powf(m[perm[1]] as f64)
* z[2].powf(m[perm[2]] as f64);
numerator += term;
let rho = [2, 1, 0];
let denom_term = sign
* z[0].powf(rho[perm[0]] as f64)
* z[1].powf(rho[perm[1]] as f64)
* z[2].powf(rho[perm[2]] as f64);
denominator += denom_term;
}
if denominator.norm() < 1e-10 {
Complex64::new(self.dimension() as f64, 0.0)
} else {
numerator / denominator
}
}
#[allow(clippy::trivially_copy_pass_by_ref)]
fn extract_eigenvalues(&self, g: &[[Complex64; 3]; 3]) -> [Complex64; 3] {
let is_identity = (0..3).all(|i| {
(0..3).all(|j| {
let expected = if i == j { 1.0 } else { 0.0 };
(g[i][j] - Complex64::new(expected, 0.0)).norm() < 1e-10
})
});
if is_identity {
return [Complex64::new(1.0, 0.0); 3];
}
let tr = g[0][0] + g[1][1] + g[2][2];
let mut tr_sq = Complex64::new(0.0, 0.0);
for i in 0..3 {
for j in 0..3 {
tr_sq += g[i][j] * g[j][i];
}
}
let c2 = (tr * tr - tr_sq) / 2.0;
self.solve_cubic(tr, c2, Complex64::new(1.0, 0.0))
}
#[allow(clippy::many_single_char_names, clippy::trivially_copy_pass_by_ref)]
fn solve_cubic(&self, a: Complex64, b: Complex64, c: Complex64) -> [Complex64; 3] {
let p = b - a * a / 3.0;
let q = c - a * b / 3.0 + 2.0 * a * a * a / 27.0;
let discriminant = q * q / 4.0 + p * p * p / 27.0;
let sqrt_disc = discriminant.sqrt();
let u = (-q / 2.0 + sqrt_disc).powf(1.0 / 3.0);
let v = (-q / 2.0 - sqrt_disc).powf(1.0 / 3.0);
let omega = Complex64::new(-0.5, 3.0_f64.sqrt() / 2.0);
let offset = a / 3.0;
[
u + v + offset,
omega * u + omega.conj() * v + offset,
omega.conj() * u + omega * v + offset,
]
}
}
#[cfg(test)]
mod tests {
use super::*;
fn identity_3x3() -> [[Complex64; 3]; 3] {
[
[
Complex64::new(1.0, 0.0),
Complex64::new(0.0, 0.0),
Complex64::new(0.0, 0.0),
],
[
Complex64::new(0.0, 0.0),
Complex64::new(1.0, 0.0),
Complex64::new(0.0, 0.0),
],
[
Complex64::new(0.0, 0.0),
Complex64::new(0.0, 0.0),
Complex64::new(1.0, 0.0),
],
]
}
fn diagonal_su3(theta1: f64, theta2: f64) -> [[Complex64; 3]; 3] {
let z1 = Complex64::new(theta1.cos(), theta1.sin());
let z2 = Complex64::new(theta2.cos(), theta2.sin());
let z3 = Complex64::new((-(theta1 + theta2)).cos(), (-(theta1 + theta2)).sin());
[
[z1, Complex64::new(0.0, 0.0), Complex64::new(0.0, 0.0)],
[Complex64::new(0.0, 0.0), z2, Complex64::new(0.0, 0.0)],
[Complex64::new(0.0, 0.0), Complex64::new(0.0, 0.0), z3],
]
}
#[test]
fn test_dimension_formula() {
assert_eq!(Su3Irrep::TRIVIAL.dimension(), 1);
assert_eq!(Su3Irrep::FUNDAMENTAL.dimension(), 3);
assert_eq!(Su3Irrep::ANTIFUNDAMENTAL.dimension(), 3);
assert_eq!(Su3Irrep::ADJOINT.dimension(), 8);
assert_eq!(Su3Irrep::SYMMETRIC.dimension(), 6);
assert_eq!(Su3Irrep::new(3, 0).dimension(), 10);
}
#[test]
fn test_conjugate() {
assert_eq!(Su3Irrep::FUNDAMENTAL.conjugate(), Su3Irrep::ANTIFUNDAMENTAL);
assert_eq!(Su3Irrep::ANTIFUNDAMENTAL.conjugate(), Su3Irrep::FUNDAMENTAL);
assert_eq!(Su3Irrep::ADJOINT.conjugate(), Su3Irrep::ADJOINT);
}
#[test]
fn test_real_representations() {
assert!(Su3Irrep::ADJOINT.is_real());
assert!(!Su3Irrep::FUNDAMENTAL.is_real());
assert!(!Su3Irrep::ANTIFUNDAMENTAL.is_real());
}
#[test]
fn test_casimir_eigenvalues() {
assert!((Su3Irrep::TRIVIAL.casimir_eigenvalue() - 0.0).abs() < 1e-10);
assert!((Su3Irrep::FUNDAMENTAL.casimir_eigenvalue() - 4.0 / 3.0).abs() < 1e-10);
assert!((Su3Irrep::ADJOINT.casimir_eigenvalue() - 3.0).abs() < 1e-10);
}
#[test]
fn test_representation_matrix_identity() {
let id = identity_3x3();
let trivial = Su3Irrep::TRIVIAL.representation_matrix(&id).unwrap();
assert_eq!(trivial.len(), 1);
assert!((trivial[0][0] - Complex64::new(1.0, 0.0)).norm() < 1e-10);
let fund = Su3Irrep::FUNDAMENTAL.representation_matrix(&id).unwrap();
assert_eq!(fund.len(), 3);
for i in 0..3 {
for j in 0..3 {
let expected = if i == j { 1.0 } else { 0.0 };
assert!((fund[i][j] - Complex64::new(expected, 0.0)).norm() < 1e-10);
}
}
let adj = Su3Irrep::ADJOINT.representation_matrix(&id).unwrap();
assert_eq!(adj.len(), 8);
for i in 0..8 {
for j in 0..8 {
let expected = if i == j { 1.0 } else { 0.0 };
assert!(
(adj[i][j] - Complex64::new(expected, 0.0)).norm() < 1e-10,
"Adjoint[{}][{}] = {:?}, expected {}",
i,
j,
adj[i][j],
expected
);
}
}
}
#[test]
fn test_character_at_identity() {
let id = identity_3x3();
assert!((Su3Irrep::TRIVIAL.character(&id) - Complex64::new(1.0, 0.0)).norm() < 1e-10);
assert!((Su3Irrep::FUNDAMENTAL.character(&id) - Complex64::new(3.0, 0.0)).norm() < 1e-10);
assert!((Su3Irrep::ADJOINT.character(&id) - Complex64::new(8.0, 0.0)).norm() < 1e-10);
assert!((Su3Irrep::SYMMETRIC.character(&id) - Complex64::new(6.0, 0.0)).norm() < 1e-10);
}
#[test]
fn test_character_diagonal() {
let g = diagonal_su3(0.5, 0.3);
let z1 = Complex64::new(0.5_f64.cos(), 0.5_f64.sin());
let z2 = Complex64::new(0.3_f64.cos(), 0.3_f64.sin());
let z3 = Complex64::new((-0.8_f64).cos(), (-0.8_f64).sin());
let chi_fund = Su3Irrep::FUNDAMENTAL.character(&g);
let expected_fund = z1 + z2 + z3;
assert!(
(chi_fund - expected_fund).norm() < 1e-10,
"Fund char: got {:?}, expected {:?}",
chi_fund,
expected_fund
);
let chi_adj = Su3Irrep::ADJOINT.character(&g);
let tr = z1 + z2 + z3;
let expected_adj = tr.norm_sqr() - Complex64::new(1.0, 0.0);
assert!(
(chi_adj - expected_adj).norm() < 1e-10,
"Adj char: got {:?}, expected {:?}",
chi_adj,
expected_adj
);
}
#[test]
fn test_symmetric_representation() {
let id = identity_3x3();
let sym = Su3Irrep::SYMMETRIC.representation_matrix(&id).unwrap();
assert_eq!(sym.len(), 6);
for i in 0..6 {
for j in 0..6 {
let expected = if i == j { 1.0 } else { 0.0 };
assert!(
(sym[i][j] - Complex64::new(expected, 0.0)).norm() < 1e-10,
"Sym[{}][{}] = {:?}, expected {}",
i,
j,
sym[i][j],
expected
);
}
}
}
#[test]
fn test_adjoint_is_homomorphism() {
let g1 = diagonal_su3(0.3, 0.5);
let g2 = diagonal_su3(0.7, -0.2);
let mut g1g2 = [[Complex64::new(0.0, 0.0); 3]; 3];
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
g1g2[i][j] += g1[i][k] * g2[k][j];
}
}
}
let adj = Su3Irrep::ADJOINT;
let rho_g1 = adj.representation_matrix(&g1).unwrap();
let rho_g2 = adj.representation_matrix(&g2).unwrap();
let rho_g1g2 = adj.representation_matrix(&g1g2).unwrap();
let mut product = vec![vec![Complex64::new(0.0, 0.0); 8]; 8];
for i in 0..8 {
for j in 0..8 {
for k in 0..8 {
product[i][j] += rho_g1[i][k] * rho_g2[k][j];
}
}
}
for i in 0..8 {
for j in 0..8 {
assert!(
(rho_g1g2[i][j] - product[i][j]).norm() < 1e-10,
"Homomorphism failed at [{},{}]: {:?} vs {:?}",
i,
j,
rho_g1g2[i][j],
product[i][j]
);
}
}
}
#[test]
fn test_representation_matrices_are_unitary() {
let g = diagonal_su3(0.4, 0.6);
for irrep in [
Su3Irrep::TRIVIAL,
Su3Irrep::FUNDAMENTAL,
Su3Irrep::ANTIFUNDAMENTAL,
Su3Irrep::ADJOINT,
] {
let rho = irrep.representation_matrix(&g).unwrap();
let dim = rho.len();
let mut product = vec![vec![Complex64::new(0.0, 0.0); dim]; dim];
for i in 0..dim {
for j in 0..dim {
for k in 0..dim {
product[i][j] += rho[k][i].conj() * rho[k][j];
}
}
}
for i in 0..dim {
for j in 0..dim {
let expected = if i == j { 1.0 } else { 0.0 };
assert!(
(product[i][j] - Complex64::new(expected, 0.0)).norm() < 1e-9,
"Unitarity failed for {:?} at [{},{}]: {:?}",
irrep,
i,
j,
product[i][j]
);
}
}
}
}
#[test]
fn test_character_equals_trace_of_representation() {
let g = diagonal_su3(0.25, 0.75);
for irrep in [
Su3Irrep::TRIVIAL,
Su3Irrep::FUNDAMENTAL,
Su3Irrep::ADJOINT,
Su3Irrep::SYMMETRIC,
] {
let rho = irrep.representation_matrix(&g).unwrap();
let trace: Complex64 = (0..rho.len()).map(|i| rho[i][i]).sum();
let character = irrep.character(&g);
assert!(
(trace - character).norm() < 1e-10,
"Character mismatch for {:?}: Tr={:?}, χ={:?}",
irrep,
trace,
character
);
}
}
#[test]
fn test_character_is_class_function() {
let g = diagonal_su3(0.3, 0.5);
let h: [[Complex64; 3]; 3] = [
[
Complex64::new(0.0, 0.0),
Complex64::new(1.0, 0.0),
Complex64::new(0.0, 0.0),
],
[
Complex64::new(0.0, 0.0),
Complex64::new(0.0, 0.0),
Complex64::new(1.0, 0.0),
],
[
Complex64::new(1.0, 0.0),
Complex64::new(0.0, 0.0),
Complex64::new(0.0, 0.0),
],
];
let h_inv: [[Complex64; 3]; 3] = std::array::from_fn(|i| std::array::from_fn(|j| h[j][i]));
let mut hg = [[Complex64::new(0.0, 0.0); 3]; 3];
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
hg[i][j] += h[i][k] * g[k][j];
}
}
}
let mut conjugated = [[Complex64::new(0.0, 0.0); 3]; 3];
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
conjugated[i][j] += hg[i][k] * h_inv[k][j];
}
}
}
for irrep in [Su3Irrep::FUNDAMENTAL, Su3Irrep::ADJOINT] {
let chi_g = irrep.character(&g);
let chi_conj = irrep.character(&conjugated);
assert!(
(chi_g - chi_conj).norm() < 1e-10,
"Class function failed for {:?}: χ(g)={:?}, χ(hgh⁻¹)={:?}",
irrep,
chi_g,
chi_conj
);
}
}
#[test]
fn test_unsupported_representation_error() {
let g = identity_3x3();
let irrep_3_0 = Su3Irrep::new(3, 0);
let result = irrep_3_0.representation_matrix(&g);
assert!(result.is_err(), "Should return error for unsupported (3,0)");
if let Err(e) = result {
assert!(
format!("{:?}", e).contains("UnsupportedRepresentation"),
"Error should be UnsupportedRepresentation"
);
}
let irrep_0_2 = Su3Irrep::new(0, 2);
let result = irrep_0_2.representation_matrix(&g);
assert!(result.is_err(), "Should return error for unsupported (0,2)");
let irrep_2_1 = Su3Irrep::new(2, 1);
let result = irrep_2_1.representation_matrix(&g);
assert!(result.is_err(), "Should return error for unsupported (2,1)");
let supported = [
Su3Irrep::TRIVIAL,
Su3Irrep::FUNDAMENTAL,
Su3Irrep::ANTIFUNDAMENTAL,
Su3Irrep::ADJOINT,
Su3Irrep::SYMMETRIC,
];
for irrep in supported {
let result = irrep.representation_matrix(&g);
assert!(
result.is_ok(),
"({},{}) should be supported",
irrep.p,
irrep.q
);
}
}
#[test]
fn test_solve_cubic_cube_roots_of_unity() {
let irrep = Su3Irrep::TRIVIAL;
let roots = irrep.solve_cubic(
Complex64::new(0.0, 0.0),
Complex64::new(0.0, 0.0),
Complex64::new(-1.0, 0.0),
);
for root in &roots {
let cube = root.powi(3);
assert!(
(cube - Complex64::new(1.0, 0.0)).norm() < 1e-6,
"Root {} cubed should equal 1, got {}",
root,
cube
);
}
let distinct = (roots[0] - roots[1]).norm() > 1e-6
&& (roots[1] - roots[2]).norm() > 1e-6
&& (roots[0] - roots[2]).norm() > 1e-6;
assert!(distinct, "Cube roots of unity should be distinct");
let real_roots: Vec<_> = roots.iter().filter(|r| r.im.abs() < 1e-6).collect();
assert_eq!(real_roots.len(), 1, "Should have exactly one real root");
assert!(
(real_roots[0].re - 1.0).abs() < 1e-6,
"Real root should be 1"
);
}
#[test]
fn test_solve_cubic_triple_root() {
let irrep = Su3Irrep::TRIVIAL;
let roots = irrep.solve_cubic(
Complex64::new(0.0, 0.0),
Complex64::new(0.0, 0.0),
Complex64::new(0.0, 0.0),
);
for root in &roots {
assert!(root.norm() < 1e-6, "Triple root at 0: got {}", root);
}
}
#[test]
fn test_character_at_identity_equals_dimension() {
let identity = identity_3x3();
let test_cases = [
(Su3Irrep::TRIVIAL, 1),
(Su3Irrep::FUNDAMENTAL, 3),
(Su3Irrep::ANTIFUNDAMENTAL, 3),
(Su3Irrep::ADJOINT, 8),
(Su3Irrep::SYMMETRIC, 6),
(Su3Irrep::new(3, 0), 10),
(Su3Irrep::new(0, 3), 10),
(Su3Irrep::new(2, 1), 15),
];
for (irrep, expected_dim) in test_cases {
let chi = irrep.character(&identity);
assert!(
(chi.re - expected_dim as f64).abs() < 1e-10,
"χ(e) for ({},{}) should be {}, got {}",
irrep.p,
irrep.q,
expected_dim,
chi
);
assert!(chi.im.abs() < 1e-10, "χ(e) should be real, got {}", chi);
}
}
#[test]
fn test_weyl_character_formula_consistency() {
let irrep = Su3Irrep::ADJOINT;
let g1 = diagonal_su3(0.5, 0.3);
let g2 = diagonal_su3(0.5, 0.3);
let chi1 = irrep.character(&g1);
let chi2 = irrep.character(&g2);
assert!(
(chi1 - chi2).norm() < 1e-10,
"Same diagonal elements should give same character: {} vs {}",
chi1,
chi2
);
let g3 = diagonal_su3(0.7, 0.1);
let chi3 = irrep.character(&g3);
assert!(
chi3.norm() > 0.0,
"Character should be non-zero for non-identity"
);
}
}