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}