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    pub 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    /// Create from half-integer: j = n/2
80    ///
81    /// # Examples
82    /// - `Spin::from_half_integer(0)` → j = 0
83    /// - `Spin::from_half_integer(1)` → j = 1/2
84    /// - `Spin::from_half_integer(2)` → j = 1
85    #[must_use]
86    pub fn from_half_integer(two_j: u32) -> Self {
87        Spin { two_j }
88    }
89
90    /// Create from integer spin (convenience)
91    #[must_use]
92    pub fn from_integer(j: u32) -> Self {
93        Spin { two_j: 2 * j }
94    }
95
96    /// Get the spin value as f64: j
97    #[must_use]
98    pub fn value(&self) -> f64 {
99        f64::from(self.two_j) / 2.0
100    }
101
102    /// Get dimension of this representation: 2j + 1
103    #[must_use]
104    pub fn dimension(&self) -> usize {
105        (self.two_j + 1) as usize
106    }
107
108    /// Check if this is integer spin (boson)
109    #[must_use]
110    pub fn is_integer(&self) -> bool {
111        self.two_j % 2 == 0
112    }
113
114    /// Check if this is half-integer spin (fermion)
115    #[must_use]
116    pub fn is_half_integer(&self) -> bool {
117        self.two_j % 2 == 1
118    }
119}
120
121/// Character of a representation: χⱼ(g) = Tr(D^j(g))
122///
123/// # Mathematical Formula
124///
125/// For g = exp(iθ n⃗·σ⃗/2), the character is:
126/// ```text
127/// χⱼ(θ) = sin((2j+1)θ/2) / sin(θ/2)
128/// ```
129///
130/// This is the **Weyl character formula** for SU(2).
131///
132/// # Properties
133///
134/// 1. **Class function**: χⱼ(hgh⁻¹) = χⱼ(g)
135/// 2. **Orthogonality**: ∫ χⱼ(g) χₖ(g)* dg = δⱼₖ
136/// 3. **Completeness**: Σⱼ χⱼ(g) χⱼ(h)* = δ(g, h)
137///
138/// # Special Values
139///
140/// - χⱼ(e) = 2j + 1 (dimension at identity)
141/// - χⱼ(θ=0) = 2j + 1
142/// - χⱼ(θ=π) = sin((2j+1)π/2) / sin(π/2)
143/// - χⱼ(θ=2π) = (-1)^{2j} × (2j+1) (double cover!)
144#[must_use]
145pub fn character(spin: Spin, angle: f64) -> f64 {
146    use std::f64::consts::PI;
147
148    let j = spin.value();
149    let two_j = spin.two_j;
150    let dim = 2.0 * j + 1.0;
151
152    // Find which multiple of 2π we're nearest to
153    // θ ≈ 2nπ has a singularity where sin(θ/2) = sin(nπ) ≈ 0
154    let n = (angle / (2.0 * PI)).round();
155    let near_multiple = (angle - n * 2.0 * PI).abs() < 1e-10;
156
157    if near_multiple {
158        // At θ = 2nπ, use the limit formula:
159        // χⱼ(2nπ) = (-1)^{2jn} × (2j+1)
160        //
161        // For SU(2) double cover:
162        // - n even: g = I, χ = dim
163        // - n odd: g = -I, χ = (-1)^{2j} × dim
164        let n_int = n as i64;
165        if n_int % 2 == 0 {
166            return dim;
167        }
168        let phase = if two_j % 2 == 0 { 1.0 } else { -1.0 };
169        return phase * dim;
170    }
171
172    // χⱼ(θ) = sin((2j+1)θ/2) / sin(θ/2)
173    let numerator = ((2.0 * j + 1.0) * angle / 2.0).sin();
174    let denominator = (angle / 2.0).sin();
175
176    // Handle near-zero denominator (numerical safety)
177    if denominator.abs() < 1e-10 {
178        // Use L'Hôpital's rule: limit is (2j+1) × cos((2j+1)θ/2) / cos(θ/2)
179        let num_deriv = (2.0 * j + 1.0) * ((2.0 * j + 1.0) * angle / 2.0).cos();
180        let den_deriv = (angle / 2.0).cos();
181        if den_deriv.abs() < 1e-10 {
182            return dim; // Fallback
183        }
184        return num_deriv / den_deriv;
185    }
186
187    numerator / denominator
188}
189
190/// Character of an SU(2) group element
191///
192/// # Algorithm
193///
194/// 1. Convert SU(2) to quaternion
195/// 2. Extract rotation angle θ
196/// 3. Compute χⱼ(θ) = sin((2j+1)θ/2) / sin(θ/2)
197#[must_use]
198pub fn character_su2(spin: Spin, g: &SU2) -> f64 {
199    // Convert to quaternion and get rotation angle
200    let matrix_array = g.to_matrix_array();
201    let quat = UnitQuaternion::from_matrix(matrix_array);
202    let (_axis, angle) = quat.to_axis_angle();
203
204    character(spin, angle)
205}
206
207/// Clebsch-Gordan decomposition: Vⱼ₁ ⊗ Vⱼ₂ = ⨁ₖ Vₖ
208///
209/// # Mathematical Formula
210///
211/// The tensor product of representations j₁ and j₂ decomposes as:
212/// ```text
213/// j₁ ⊗ j₂ = ⨁ₖ k
214/// ```
215/// where k ranges from |j₁ - j₂| to j₁ + j₂ in integer steps.
216///
217/// # Examples
218///
219/// - 1/2 ⊗ 1/2 = 0 ⊕ 1 (spinor × spinor = scalar + vector)
220/// - 1/2 ⊗ 1 = 1/2 ⊕ 3/2 (spinor × vector)
221/// - 1 ⊗ 1 = 0 ⊕ 1 ⊕ 2 (vector × vector)
222///
223/// # Physical Meaning
224///
225/// This is how angular momenta combine in quantum mechanics:
226/// - Two spin-1/2 particles combine to give spin-0 (singlet) or spin-1 (triplet)
227/// - This explains atomic spectra and chemical bonding
228#[must_use]
229pub fn clebsch_gordan_decomposition(j1: Spin, j2: Spin) -> Vec<Spin> {
230    let min_k = (j1.two_j as i32 - j2.two_j as i32).unsigned_abs();
231    let max_k = j1.two_j + j2.two_j;
232
233    let mut result = Vec::new();
234
235    // k ranges from |j₁ - j₂| to j₁ + j₂ in steps of 2
236    // (steps of 2 because two_j representation)
237    let mut k = min_k;
238    while k <= max_k {
239        result.push(Spin::from_half_integer(k));
240        k += 2;
241    }
242
243    result
244}
245
246/// Character orthogonality relation (analytical)
247///
248/// # Mathematical Statement
249///
250/// Characters of different irreps are orthogonal:
251/// ```text
252/// ∫_{SU(2)} χⱼ(g) χₖ(g)* dg = δⱼₖ
253/// ```
254///
255/// For SU(2), the Haar measure integral gives:
256/// ```text
257/// ∫₀^{2π} χⱼ(θ) χₖ(θ) sin²(θ/2) dθ / π = δⱼₖ
258/// ```
259///
260/// # Peter-Weyl Theorem
261///
262/// This orthogonality is fundamental to the Peter-Weyl theorem,
263/// which states that irreps form a complete orthonormal basis for L²(G).
264#[must_use]
265pub fn character_orthogonality_delta(j1: Spin, j2: Spin) -> bool {
266    // Characters are orthogonal: different spins give 0, same spin gives 1
267    // This is an analytical fact from representation theory
268    j1 == j2
269}
270
271/// Dimension formula: dim(Vⱼ) = 2j + 1
272///
273/// This is the number of basis states in the spin-j representation.
274///
275/// # Examples
276///
277/// - j = 0: 1 state (scalar)
278/// - j = 1/2: 2 states (|↑⟩, |↓⟩)
279/// - j = 1: 3 states (|+1⟩, |0⟩, |-1⟩)
280#[must_use]
281pub fn representation_dimension(spin: Spin) -> usize {
282    spin.dimension()
283}
284
285#[cfg(test)]
286mod tests {
287    use super::*;
288    use std::f64::consts::PI;
289
290    #[test]
291    fn test_spin_values() {
292        assert_eq!(Spin::ZERO.value(), 0.0);
293        assert_eq!(Spin::HALF.value(), 0.5);
294        assert_eq!(Spin::ONE.value(), 1.0);
295        assert_eq!(Spin::from_half_integer(3).value(), 1.5);
296    }
297
298    #[test]
299    fn test_dimension_formula() {
300        // dim(j) = 2j + 1
301        assert_eq!(Spin::ZERO.dimension(), 1); // Scalar
302        assert_eq!(Spin::HALF.dimension(), 2); // Spinor
303        assert_eq!(Spin::ONE.dimension(), 3); // Vector
304        assert_eq!(Spin::from_half_integer(3).dimension(), 4); // j=3/2
305    }
306
307    #[test]
308    fn test_integer_vs_half_integer() {
309        // Integer spins (bosons): j = 0, 1, 2, ...
310        assert!(Spin::ZERO.is_integer());
311        assert!(Spin::ONE.is_integer());
312        assert!(Spin::from_integer(2).is_integer());
313
314        // Half-integer spins (fermions): j = 1/2, 3/2, 5/2, ...
315        assert!(Spin::HALF.is_half_integer());
316        assert!(Spin::from_half_integer(3).is_half_integer()); // j = 3/2
317        assert!(Spin::from_half_integer(5).is_half_integer()); // j = 5/2
318    }
319
320    #[test]
321    fn test_character_at_identity() {
322        // χⱼ(e) = 2j + 1
323        assert_eq!(character(Spin::ZERO, 0.0), 1.0);
324        assert_eq!(character(Spin::HALF, 0.0), 2.0);
325        assert_eq!(character(Spin::ONE, 0.0), 3.0);
326    }
327
328    #[test]
329    fn test_character_at_2pi() {
330        // χⱼ(2π) = (-1)^{2j}
331        let angle = 2.0 * PI;
332
333        // j = 0: (-1)^0 = 1
334        assert!((character(Spin::ZERO, angle) - 1.0).abs() < 1e-10);
335
336        // j = 1/2: (-1)^1 = -1
337        assert!((character(Spin::HALF, angle) - (-2.0)).abs() < 1e-10);
338
339        // j = 1: (-1)^2 = 1
340        assert!((character(Spin::ONE, angle) - 3.0).abs() < 1e-10);
341    }
342
343    #[test]
344    fn test_clebsch_gordan_spinor_times_spinor() {
345        // 1/2 ⊗ 1/2 = 0 ⊕ 1
346        let decomp = clebsch_gordan_decomposition(Spin::HALF, Spin::HALF);
347        assert_eq!(decomp.len(), 2);
348        assert_eq!(decomp[0], Spin::ZERO);
349        assert_eq!(decomp[1], Spin::ONE);
350    }
351
352    #[test]
353    fn test_clebsch_gordan_spinor_times_vector() {
354        // 1/2 ⊗ 1 = 1/2 ⊕ 3/2
355        let decomp = clebsch_gordan_decomposition(Spin::HALF, Spin::ONE);
356        assert_eq!(decomp.len(), 2);
357        assert_eq!(decomp[0], Spin::HALF);
358        assert_eq!(decomp[1], Spin::from_half_integer(3));
359    }
360
361    #[test]
362    fn test_clebsch_gordan_vector_times_vector() {
363        // 1 ⊗ 1 = 0 ⊕ 1 ⊕ 2
364        let decomp = clebsch_gordan_decomposition(Spin::ONE, Spin::ONE);
365        assert_eq!(decomp.len(), 3);
366        assert_eq!(decomp[0], Spin::ZERO);
367        assert_eq!(decomp[1], Spin::ONE);
368        assert_eq!(decomp[2], Spin::from_integer(2));
369    }
370
371    #[test]
372    fn test_character_su2_identity() {
373        let identity = SU2::identity();
374        let chi = character_su2(Spin::ONE, &identity);
375
376        // At identity, χⱼ = 2j + 1 = 3 for j=1
377        assert!((chi - 3.0).abs() < 1e-10);
378    }
379
380    #[test]
381    fn test_dimension_counts() {
382        // Verify dim = 2j + 1 for several spins
383        assert_eq!(representation_dimension(Spin::ZERO), 1);
384        assert_eq!(representation_dimension(Spin::HALF), 2);
385        assert_eq!(representation_dimension(Spin::ONE), 3);
386        assert_eq!(representation_dimension(Spin::from_half_integer(3)), 4);
387        assert_eq!(representation_dimension(Spin::from_integer(2)), 5);
388    }
389
390    #[test]
391    fn test_character_formula_j_equals_half() {
392        // For j = 1/2: χ(θ) = 2cos(θ/2)
393        let angles = [0.0, PI / 4.0, PI / 2.0, PI];
394
395        for &angle in &angles {
396            let chi = character(Spin::HALF, angle);
397            let expected = 2.0 * (angle / 2.0).cos();
398            assert!(
399                (chi - expected).abs() < 1e-10,
400                "j=1/2: χ({angle}) = {chi}, expected {expected}"
401            );
402        }
403    }
404
405    #[test]
406    fn test_character_formula_j_equals_one() {
407        // For j = 1: χ(θ) = 1 + 2cos(θ)
408        let angles = [0.0, PI / 3.0, PI / 2.0, PI];
409
410        for &angle in &angles {
411            let chi = character(Spin::ONE, angle);
412            let expected = 1.0 + 2.0 * angle.cos();
413            assert!(
414                (chi - expected).abs() < 1e-10,
415                "j=1: χ({angle}) = {chi}, expected {expected}"
416            );
417        }
418    }
419}