Skip to main content

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}