lie_groups/representation/mod.rs
1//! Representation theory for Lie groups and Lie algebras.
2//!
3//! This module provides tools for working with irreducible representations (irreps),
4//! tensor products, characters, and Casimir operators.
5//!
6//! # Overview
7//!
8//! ## Core Concepts
9//!
10//! - **Irreducible Representations**: Classified by highest weights
11//! - **Casimir Operators**: Invariants that label representations
12//! - **Characters**: Traces of representation matrices (Weyl character formula)
13//! - **Tensor Products**: Clebsch-Gordan / Littlewood-Richardson decomposition
14//!
15//! ## Implemented Groups
16//!
17//! - **SU(2)**: Complete (see `crate::representation` module)
18//! - **SU(3)**: In progress (Casimir operators implemented)
19//! - **SU(N)**: Planned (generic implementation)
20//!
21//! # Physical Applications
22//!
23//! Representation theory is essential for:
24//! - Classifying particle states in QCD (quarks, gluons, hadrons)
25//! - Computing scattering amplitudes (Clebsch-Gordan coefficients)
26//! - Understanding symmetry breaking (Higgs mechanism)
27//! - Gauge fixing via Peter-Weyl decomposition
28//!
29//! # References
30//!
31//! - **Georgi**: "Lie Algebras in Particle Physics" (1999)
32//! - **Slansky**: "Group Theory for Unified Model Building" (1981)
33//! - **Weyl**: "The Theory of Groups and Quantum Mechanics" (1928)
34
35pub mod casimir;
36pub mod su3_irrep;
37
38pub use casimir::Casimir;
39pub use su3_irrep::Su3Irrep;
40
41// ============================================================================
42// SU(2) Representation Theory
43// ============================================================================
44
45use crate::quaternion::UnitQuaternion;
46use crate::su2::SU2;
47
48/// Spin quantum number j (half-integer)
49///
50/// # Mathematical Definition
51///
52/// j ∈ {0, 1/2, 1, 3/2, 2, ...}
53///
54/// Internally represented as 2j (integer) to avoid floating point.
55///
56/// # Physical Meaning
57///
58/// - **j = 0**: Scalar (spin-0)
59/// - **j = 1/2**: Spinor (spin-1/2, fermions)
60/// - **j = 1**: Vector (spin-1, bosons)
61/// - **j = n/2**: General half-integer spin
62#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
63pub struct Spin {
64 /// Twice the spin: `two_j` = 2j
65 /// This allows half-integer spins while using integers
66 two_j: u32,
67}
68
69impl Spin {
70 /// Spin-0 (scalar, trivial representation)
71 pub const ZERO: Spin = Spin { two_j: 0 };
72
73 /// Spin-1/2 (spinor, fundamental representation)
74 pub const HALF: Spin = Spin { two_j: 1 };
75
76 /// Spin-1 (vector, adjoint representation)
77 pub const ONE: Spin = Spin { two_j: 2 };
78
79 /// Get the internal 2j representation (useful for pattern matching).
80 #[inline]
81 #[must_use]
82 pub fn two_j(&self) -> u32 {
83 self.two_j
84 }
85
86 /// Create from half-integer: j = n/2
87 ///
88 /// # Examples
89 /// - `Spin::from_half_integer(0)` → j = 0
90 /// - `Spin::from_half_integer(1)` → j = 1/2
91 /// - `Spin::from_half_integer(2)` → j = 1
92 #[must_use]
93 pub fn from_half_integer(two_j: u32) -> Self {
94 Spin { two_j }
95 }
96
97 /// Create from integer spin (convenience)
98 #[must_use]
99 pub fn from_integer(j: u32) -> Self {
100 Spin { two_j: 2 * j }
101 }
102
103 /// Get the spin value as f64: j
104 #[must_use]
105 pub fn value(&self) -> f64 {
106 f64::from(self.two_j) / 2.0
107 }
108
109 /// Get dimension of this representation: 2j + 1
110 #[must_use]
111 pub fn dimension(&self) -> usize {
112 (self.two_j + 1) as usize
113 }
114
115 /// Check if this is integer spin (boson)
116 #[must_use]
117 pub fn is_integer(&self) -> bool {
118 self.two_j % 2 == 0
119 }
120
121 /// Check if this is half-integer spin (fermion)
122 #[must_use]
123 pub fn is_half_integer(&self) -> bool {
124 self.two_j % 2 == 1
125 }
126}
127
128/// Character of a representation: χⱼ(g) = Tr(D^j(g))
129///
130/// # Mathematical Formula
131///
132/// For g = exp(iθ n⃗·σ⃗/2), the character is:
133/// ```text
134/// χⱼ(θ) = sin((2j+1)θ/2) / sin(θ/2)
135/// ```
136///
137/// This is the **Weyl character formula** for SU(2).
138///
139/// # Properties
140///
141/// 1. **Class function**: χⱼ(hgh⁻¹) = χⱼ(g)
142/// 2. **Orthogonality**: ∫ χⱼ(g) χₖ(g)* dg = δⱼₖ
143/// 3. **Completeness**: Σⱼ χⱼ(g) χⱼ(h)* = δ(g, h)
144///
145/// # Special Values
146///
147/// - χⱼ(e) = 2j + 1 (dimension at identity)
148/// - χⱼ(θ=0) = 2j + 1
149/// - χⱼ(θ=π) = sin((2j+1)π/2) / sin(π/2)
150/// - χⱼ(θ=2π) = (-1)^{2j} × (2j+1) (double cover!)
151#[must_use]
152pub fn character(spin: Spin, angle: f64) -> f64 {
153 use std::f64::consts::PI;
154
155 let j = spin.value();
156 let two_j = spin.two_j;
157 let dim = 2.0 * j + 1.0;
158
159 // Find which multiple of 2π we're nearest to
160 // θ ≈ 2nπ has a singularity where sin(θ/2) = sin(nπ) ≈ 0
161 let n = (angle / (2.0 * PI)).round();
162 let near_multiple = (angle - n * 2.0 * PI).abs() < 1e-10;
163
164 if near_multiple {
165 // At θ = 2nπ, use the limit formula:
166 // χⱼ(2nπ) = (-1)^{2jn} × (2j+1)
167 //
168 // For SU(2) double cover:
169 // - n even: g = I, χ = dim
170 // - n odd: g = -I, χ = (-1)^{2j} × dim
171 let n_int = n as i64;
172 if n_int % 2 == 0 {
173 return dim;
174 }
175 let phase = if two_j % 2 == 0 { 1.0 } else { -1.0 };
176 return phase * dim;
177 }
178
179 // χⱼ(θ) = sin((2j+1)θ/2) / sin(θ/2)
180 let numerator = ((2.0 * j + 1.0) * angle / 2.0).sin();
181 let denominator = (angle / 2.0).sin();
182
183 // Handle near-zero denominator (numerical safety)
184 if denominator.abs() < 1e-10 {
185 // Use L'Hôpital's rule: limit is (2j+1) × cos((2j+1)θ/2) / cos(θ/2)
186 let num_deriv = (2.0 * j + 1.0) * ((2.0 * j + 1.0) * angle / 2.0).cos();
187 let den_deriv = (angle / 2.0).cos();
188 if den_deriv.abs() < 1e-10 {
189 return dim; // Fallback
190 }
191 return num_deriv / den_deriv;
192 }
193
194 numerator / denominator
195}
196
197/// Character of an SU(2) group element
198///
199/// # Algorithm
200///
201/// 1. Convert SU(2) to quaternion
202/// 2. Extract rotation angle θ
203/// 3. Compute χⱼ(θ) = sin((2j+1)θ/2) / sin(θ/2)
204#[must_use]
205pub fn character_su2(spin: Spin, g: &SU2) -> f64 {
206 // Convert to quaternion and get rotation angle
207 let matrix_array = g.to_matrix();
208 let quat = UnitQuaternion::from_matrix(matrix_array);
209 let (_axis, angle) = quat.to_axis_angle();
210
211 character(spin, angle)
212}
213
214/// Clebsch-Gordan decomposition: Vⱼ₁ ⊗ Vⱼ₂ = ⨁ₖ Vₖ
215///
216/// # Mathematical Formula
217///
218/// The tensor product of representations j₁ and j₂ decomposes as:
219/// ```text
220/// j₁ ⊗ j₂ = ⨁ₖ k
221/// ```
222/// where k ranges from |j₁ - j₂| to j₁ + j₂ in integer steps.
223///
224/// # Examples
225///
226/// - 1/2 ⊗ 1/2 = 0 ⊕ 1 (spinor × spinor = scalar + vector)
227/// - 1/2 ⊗ 1 = 1/2 ⊕ 3/2 (spinor × vector)
228/// - 1 ⊗ 1 = 0 ⊕ 1 ⊕ 2 (vector × vector)
229///
230/// # Physical Meaning
231///
232/// This is how angular momenta combine in quantum mechanics:
233/// - Two spin-1/2 particles combine to give spin-0 (singlet) or spin-1 (triplet)
234/// - This explains atomic spectra and chemical bonding
235#[must_use]
236pub fn clebsch_gordan_decomposition(j1: Spin, j2: Spin) -> Vec<Spin> {
237 let min_k = (j1.two_j as i32 - j2.two_j as i32).unsigned_abs();
238 let max_k = j1.two_j + j2.two_j;
239
240 let mut result = Vec::new();
241
242 // k ranges from |j₁ - j₂| to j₁ + j₂ in steps of 2
243 // (steps of 2 because two_j representation)
244 let mut k = min_k;
245 while k <= max_k {
246 result.push(Spin::from_half_integer(k));
247 k += 2;
248 }
249
250 result
251}
252
253/// Character orthogonality relation (analytical)
254///
255/// # Mathematical Statement
256///
257/// Characters of different irreps are orthogonal:
258/// ```text
259/// ∫_{SU(2)} χⱼ(g) χₖ(g)* dg = δⱼₖ
260/// ```
261///
262/// For SU(2), the Haar measure integral gives:
263/// ```text
264/// ∫₀^{2π} χⱼ(θ) χₖ(θ) sin²(θ/2) dθ / π = δⱼₖ
265/// ```
266///
267/// # Peter-Weyl Theorem
268///
269/// This orthogonality is fundamental to the Peter-Weyl theorem,
270/// which states that irreps form a complete orthonormal basis for L²(G).
271#[must_use]
272pub fn character_orthogonality_delta(j1: Spin, j2: Spin) -> bool {
273 // Characters are orthogonal: different spins give 0, same spin gives 1
274 // This is an analytical fact from representation theory
275 j1 == j2
276}
277
278/// Dimension formula: dim(Vⱼ) = 2j + 1
279///
280/// This is the number of basis states in the spin-j representation.
281///
282/// # Examples
283///
284/// - j = 0: 1 state (scalar)
285/// - j = 1/2: 2 states (|↑⟩, |↓⟩)
286/// - j = 1: 3 states (|+1⟩, |0⟩, |-1⟩)
287#[must_use]
288pub fn representation_dimension(spin: Spin) -> usize {
289 spin.dimension()
290}
291
292#[cfg(test)]
293mod tests {
294 use super::*;
295 use std::f64::consts::PI;
296
297 #[test]
298 fn test_spin_values() {
299 assert_eq!(Spin::ZERO.value(), 0.0);
300 assert_eq!(Spin::HALF.value(), 0.5);
301 assert_eq!(Spin::ONE.value(), 1.0);
302 assert_eq!(Spin::from_half_integer(3).value(), 1.5);
303 }
304
305 #[test]
306 fn test_dimension_formula() {
307 // dim(j) = 2j + 1
308 assert_eq!(Spin::ZERO.dimension(), 1); // Scalar
309 assert_eq!(Spin::HALF.dimension(), 2); // Spinor
310 assert_eq!(Spin::ONE.dimension(), 3); // Vector
311 assert_eq!(Spin::from_half_integer(3).dimension(), 4); // j=3/2
312 }
313
314 #[test]
315 fn test_integer_vs_half_integer() {
316 // Integer spins (bosons): j = 0, 1, 2, ...
317 assert!(Spin::ZERO.is_integer());
318 assert!(Spin::ONE.is_integer());
319 assert!(Spin::from_integer(2).is_integer());
320
321 // Half-integer spins (fermions): j = 1/2, 3/2, 5/2, ...
322 assert!(Spin::HALF.is_half_integer());
323 assert!(Spin::from_half_integer(3).is_half_integer()); // j = 3/2
324 assert!(Spin::from_half_integer(5).is_half_integer()); // j = 5/2
325 }
326
327 #[test]
328 fn test_character_at_identity() {
329 // χⱼ(e) = 2j + 1
330 assert_eq!(character(Spin::ZERO, 0.0), 1.0);
331 assert_eq!(character(Spin::HALF, 0.0), 2.0);
332 assert_eq!(character(Spin::ONE, 0.0), 3.0);
333 }
334
335 #[test]
336 fn test_character_at_2pi() {
337 // χⱼ(2π) = (-1)^{2j}
338 let angle = 2.0 * PI;
339
340 // j = 0: (-1)^0 = 1
341 assert!((character(Spin::ZERO, angle) - 1.0).abs() < 1e-10);
342
343 // j = 1/2: (-1)^1 = -1
344 assert!((character(Spin::HALF, angle) - (-2.0)).abs() < 1e-10);
345
346 // j = 1: (-1)^2 = 1
347 assert!((character(Spin::ONE, angle) - 3.0).abs() < 1e-10);
348 }
349
350 #[test]
351 fn test_clebsch_gordan_spinor_times_spinor() {
352 // 1/2 ⊗ 1/2 = 0 ⊕ 1
353 let decomp = clebsch_gordan_decomposition(Spin::HALF, Spin::HALF);
354 assert_eq!(decomp.len(), 2);
355 assert_eq!(decomp[0], Spin::ZERO);
356 assert_eq!(decomp[1], Spin::ONE);
357 }
358
359 #[test]
360 fn test_clebsch_gordan_spinor_times_vector() {
361 // 1/2 ⊗ 1 = 1/2 ⊕ 3/2
362 let decomp = clebsch_gordan_decomposition(Spin::HALF, Spin::ONE);
363 assert_eq!(decomp.len(), 2);
364 assert_eq!(decomp[0], Spin::HALF);
365 assert_eq!(decomp[1], Spin::from_half_integer(3));
366 }
367
368 #[test]
369 fn test_clebsch_gordan_vector_times_vector() {
370 // 1 ⊗ 1 = 0 ⊕ 1 ⊕ 2
371 let decomp = clebsch_gordan_decomposition(Spin::ONE, Spin::ONE);
372 assert_eq!(decomp.len(), 3);
373 assert_eq!(decomp[0], Spin::ZERO);
374 assert_eq!(decomp[1], Spin::ONE);
375 assert_eq!(decomp[2], Spin::from_integer(2));
376 }
377
378 #[test]
379 fn test_character_su2_identity() {
380 let identity = SU2::identity();
381 let chi = character_su2(Spin::ONE, &identity);
382
383 // At identity, χⱼ = 2j + 1 = 3 for j=1
384 assert!((chi - 3.0).abs() < 1e-10);
385 }
386
387 #[test]
388 fn test_dimension_counts() {
389 // Verify dim = 2j + 1 for several spins
390 assert_eq!(representation_dimension(Spin::ZERO), 1);
391 assert_eq!(representation_dimension(Spin::HALF), 2);
392 assert_eq!(representation_dimension(Spin::ONE), 3);
393 assert_eq!(representation_dimension(Spin::from_half_integer(3)), 4);
394 assert_eq!(representation_dimension(Spin::from_integer(2)), 5);
395 }
396
397 #[test]
398 fn test_character_formula_j_equals_half() {
399 // For j = 1/2: χ(θ) = 2cos(θ/2)
400 let angles = [0.0, PI / 4.0, PI / 2.0, PI];
401
402 for &angle in &angles {
403 let chi = character(Spin::HALF, angle);
404 let expected = 2.0 * (angle / 2.0).cos();
405 assert!(
406 (chi - expected).abs() < 1e-10,
407 "j=1/2: χ({angle}) = {chi}, expected {expected}"
408 );
409 }
410 }
411
412 #[test]
413 fn test_character_formula_j_equals_one() {
414 // For j = 1: χ(θ) = 1 + 2cos(θ)
415 let angles = [0.0, PI / 3.0, PI / 2.0, PI];
416
417 for &angle in &angles {
418 let chi = character(Spin::ONE, angle);
419 let expected = 1.0 + 2.0 * angle.cos();
420 assert!(
421 (chi - expected).abs() < 1e-10,
422 "j=1: χ({angle}) = {chi}, expected {expected}"
423 );
424 }
425 }
426}