Skip to main content

lie_groups/
root_systems.rs

1//! Root Systems for Semisimple Lie Algebras
2//!
3//! Implements the theory of root systems, which classify semisimple Lie algebras
4//! via Cartan-Killing classification. Root systems encode the structure of the
5//! Lie bracket via the adjoint representation on the Cartan subalgebra.
6//!
7//! # Mathematical Background
8//!
9//! For a semisimple Lie algebra 𝔤, choose a Cartan subalgebra 𝔥 (maximal abelian).
10//! The adjoint representation ad: 𝔤 → End(𝔤) diagonalizes on 𝔥, giving eigenvalues
11//! called **roots**:
12//!
13//! ```text
14//! [H, X_α] = α(H) X_α    for all H ∈ 𝔥
15//! ```
16//!
17//! The set of roots Φ ⊂ 𝔥* forms a **root system**, satisfying:
18//! 1. Φ is finite, spans 𝔥*, and 0 ∉ Φ
19//! 2. If α ∈ Φ, then -α ∈ Φ and no other scalar multiples
20//! 3. Φ is closed under Weyl reflections: `s_α(β)` = β - ⟨β,α⟩α
21//! 4. ⟨β,α⟩ ∈ ℤ for all α,β ∈ Φ (Cartan integers)
22//!
23//! # Cartan Classification
24//!
25//! | Type | Rank | Group | Dimension | Example |
26//! |------|------|-------|-----------|---------|
27//! | Aₙ   | n    | SU(n+1) | n(n+2)  | A₁ = SU(2) |
28//! | Bₙ   | n    | SO(2n+1) | n(2n+1) | B₂ = SO(5) |
29//! | Cₙ   | n    | Sp(2n)  | n(2n+1) | C₂ = Sp(4) |
30//! | Dₙ   | n    | SO(2n)  | n(2n-1) | D₃ = SO(6) |
31//! | E₆   | 6    | E₆      | 78      | Exceptional |
32//! | E₇   | 7    | E₇      | 133     | Exceptional |
33//! | E₈   | 8    | E₈      | 248     | Exceptional |
34//! | F₄   | 4    | F₄      | 52      | Exceptional |
35//! | G₂   | 2    | G₂      | 14      | Exceptional |
36//!
37//! # References
38//!
39//! - Hall, *Lie Groups, Lie Algebras, and Representations*, Chapter 7
40//! - Humphreys, *Introduction to Lie Algebras and Representation Theory*, Ch. II
41//! - Fulton & Harris, *Representation Theory*, Lecture 21
42
43use ndarray::Array2;
44use num_complex::Complex64;
45use std::collections::HashSet;
46use std::fmt::{self, Write};
47
48/// A root in the dual space of a Cartan subalgebra.
49///
50/// Mathematically: α ∈ 𝔥* represented as a vector in ℝⁿ (rank n).
51/// For SU(n+1) (type Aₙ), roots are differences of standard basis vectors: eᵢ - eⱼ.
52///
53/// # Example
54///
55/// ```
56/// use lie_groups::Root;
57///
58/// // SU(3) root: e₁ - e₂
59/// let alpha = Root::new(vec![1.0, -1.0, 0.0]);
60/// assert_eq!(alpha.rank(), 3);
61/// assert!((alpha.norm_squared() - 2.0).abs() < 1e-10);
62/// ```
63#[derive(Debug, Clone, PartialEq)]
64pub struct Root {
65    /// Coordinates in ℝⁿ (n = rank of Lie algebra)
66    pub coords: Vec<f64>,
67}
68
69impl Root {
70    /// Create a new root from coordinates.
71    pub fn new(coords: Vec<f64>) -> Self {
72        Self { coords }
73    }
74
75    /// Rank of the Lie algebra (dimension of Cartan subalgebra).
76    pub fn rank(&self) -> usize {
77        self.coords.len()
78    }
79
80    /// Inner product ⟨α, β⟩ (standard Euclidean).
81    pub fn inner_product(&self, other: &Root) -> f64 {
82        assert_eq!(self.rank(), other.rank());
83        self.coords
84            .iter()
85            .zip(&other.coords)
86            .map(|(a, b)| a * b)
87            .sum()
88    }
89
90    /// Squared norm ⟨α, α⟩.
91    #[must_use]
92    pub fn norm_squared(&self) -> f64 {
93        self.inner_product(self)
94    }
95
96    /// Weyl reflection `s_α(β)` = β - 2⟨β,α⟩/⟨α,α⟩ · α.
97    #[must_use]
98    pub fn reflect(&self, beta: &Root) -> Root {
99        let factor = 2.0 * self.inner_product(beta) / self.norm_squared();
100        Root::new(
101            beta.coords
102                .iter()
103                .zip(&self.coords)
104                .map(|(b, a)| b - factor * a)
105                .collect(),
106        )
107    }
108
109    /// Cartan integer ⟨β, α^∨⟩ = 2⟨β,α⟩/⟨α,α⟩.
110    #[must_use]
111    pub fn cartan_integer(&self, beta: &Root) -> i32 {
112        let value = 2.0 * self.inner_product(beta) / self.norm_squared();
113        value.round() as i32
114    }
115
116    /// Check if this root is positive (first nonzero coordinate is positive).
117    #[must_use]
118    pub fn is_positive(&self) -> bool {
119        for &c in &self.coords {
120            if c.abs() > 1e-10 {
121                return c > 0.0;
122            }
123        }
124        false // Zero root (shouldn't happen)
125    }
126
127    /// Negate this root.
128    #[must_use]
129    pub fn negate(&self) -> Root {
130        Root::new(self.coords.iter().map(|c| -c).collect())
131    }
132}
133
134impl fmt::Display for Root {
135    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136        write!(f, "(")?;
137        for (i, c) in self.coords.iter().enumerate() {
138            if i > 0 {
139                write!(f, ", ")?;
140            }
141            write!(f, "{:.2}", c)?;
142        }
143        write!(f, ")")
144    }
145}
146
147/// A root system for a semisimple Lie algebra.
148///
149/// Encodes the structure of the Lie bracket through roots and Weyl reflections.
150///
151/// # Example
152///
153/// ```
154/// use lie_groups::RootSystem;
155///
156/// // SU(3) = type A₂
157/// let su3 = RootSystem::type_a(2);
158/// assert_eq!(su3.rank(), 2);
159/// assert_eq!(su3.num_roots(), 6); // 3² - 1 = 8, but we store 6 roots
160/// assert_eq!(su3.num_positive_roots(), 3);
161/// ```
162#[derive(Debug, Clone)]
163pub struct RootSystem {
164    /// Rank of the Lie algebra (dimension of Cartan subalgebra)
165    rank: usize,
166
167    /// All roots (positive and negative)
168    roots: Vec<Root>,
169
170    /// Simple roots (basis for positive roots)
171    simple_roots: Vec<Root>,
172
173    /// Cartan matrix `A_ij` = ⟨`α_j`, `α_i`^∨⟩
174    cartan_matrix: Vec<Vec<i32>>,
175}
176
177impl RootSystem {
178    /// Create a type Aₙ root system (SU(n+1)).
179    ///
180    /// For SU(n+1), the rank is n, and roots are differences eᵢ - eⱼ for i ≠ j.
181    /// Simple roots: αᵢ = eᵢ - eᵢ₊₁ for i = 1, ..., n.
182    ///
183    /// # Example
184    ///
185    /// ```
186    /// use lie_groups::RootSystem;
187    ///
188    /// // SU(2) = A₁
189    /// let su2 = RootSystem::type_a(1);
190    /// assert_eq!(su2.rank(), 1);
191    /// assert_eq!(su2.num_roots(), 2); // ±α
192    ///
193    /// // SU(3) = A₂
194    /// let su3 = RootSystem::type_a(2);
195    /// assert_eq!(su3.rank(), 2);
196    /// assert_eq!(su3.simple_roots().len(), 2);
197    /// ```
198    pub fn type_a(n: usize) -> Self {
199        assert!(n >= 1, "Type A_n requires n >= 1");
200
201        let rank = n;
202
203        // Simple roots: αᵢ = eᵢ - eᵢ₊₁ for i = 1, ..., n
204        // Embedded in ℝⁿ⁺¹ with constraint Σxᵢ = 0
205        let mut simple_roots = Vec::new();
206        for i in 0..n {
207            let mut coords = vec![0.0; n + 1];
208            coords[i] = 1.0;
209            coords[i + 1] = -1.0;
210            simple_roots.push(Root::new(coords));
211        }
212
213        // Generate all positive roots
214        let mut positive_roots = Vec::new();
215        for i in 0..=n {
216            for j in (i + 1)..=n {
217                let mut coords = vec![0.0; n + 1];
218                coords[i] = 1.0;
219                coords[j] = -1.0;
220                positive_roots.push(Root::new(coords));
221            }
222        }
223
224        // All roots = positive + negative
225        let mut roots = positive_roots.clone();
226        for root in &positive_roots {
227            roots.push(root.negate());
228        }
229
230        // Compute Cartan matrix: A_ij = ⟨α_j, α_i^∨⟩
231        let cartan_matrix = simple_roots
232            .iter()
233            .map(|alpha_i| {
234                simple_roots
235                    .iter()
236                    .map(|alpha_j| alpha_i.cartan_integer(alpha_j))
237                    .collect()
238            })
239            .collect();
240
241        Self {
242            rank,
243            roots,
244            simple_roots,
245            cartan_matrix,
246        }
247    }
248
249    /// Rank of the Lie algebra.
250    pub fn rank(&self) -> usize {
251        self.rank
252    }
253
254    /// All roots (positive and negative).
255    pub fn roots(&self) -> &[Root] {
256        &self.roots
257    }
258
259    /// Simple roots (basis for root system).
260    pub fn simple_roots(&self) -> &[Root] {
261        &self.simple_roots
262    }
263
264    /// Cartan matrix `A_ij` = ⟨`α_j`, `α_i`^∨⟩.
265    pub fn cartan_matrix(&self) -> &[Vec<i32>] {
266        &self.cartan_matrix
267    }
268
269    /// Number of roots.
270    pub fn num_roots(&self) -> usize {
271        self.roots.len()
272    }
273
274    /// Number of positive roots.
275    pub fn num_positive_roots(&self) -> usize {
276        self.roots.iter().filter(|r| r.is_positive()).count()
277    }
278
279    /// Get all positive roots.
280    pub fn positive_roots(&self) -> Vec<Root> {
281        self.roots
282            .iter()
283            .filter(|r| r.is_positive())
284            .cloned()
285            .collect()
286    }
287
288    /// Get the highest root (longest root in the positive system).
289    ///
290    /// The highest root θ is the unique positive root with maximal height
291    /// (sum of coefficients when expanded in simple roots). It's also the
292    /// longest root in the root system for simply-laced types like Aₙ.
293    ///
294    /// For type `A_n` (SU(n+1)), the highest root is θ = α₁ + α₂ + ... + αₙ.
295    ///
296    /// # Example
297    ///
298    /// ```
299    /// use lie_groups::RootSystem;
300    ///
301    /// let su3 = RootSystem::type_a(2);
302    /// let theta = su3.highest_root();
303    ///
304    /// // For SU(3), θ = α₁ + α₂ = (1, 0, -1)
305    /// ```
306    pub fn highest_root(&self) -> Root {
307        // For type A_n, highest root is α₁ + α₂ + ... + αₙ
308        // In coordinates: e₁ - eₙ₊₁ = (1, 0, ..., 0, -1)
309
310        let n = self.rank;
311
312        // Sum of simple roots
313        let mut coords = vec![0.0; n + 1];
314
315        for simple_root in &self.simple_roots {
316            for (i, &coord) in simple_root.coords.iter().enumerate() {
317                coords[i] += coord;
318            }
319        }
320
321        Root::new(coords)
322    }
323
324    /// Check if a root is in the system.
325    pub fn contains_root(&self, root: &Root) -> bool {
326        self.roots.iter().any(|r| {
327            r.coords.len() == root.coords.len()
328                && r.coords
329                    .iter()
330                    .zip(root.coords.iter())
331                    .all(|(a, b)| (a - b).abs() < 1e-10)
332        })
333    }
334
335    /// Weyl reflection `s_α` for a root α.
336    pub fn weyl_reflection(&self, alpha: &Root, beta: &Root) -> Root {
337        alpha.reflect(beta)
338    }
339
340    /// Generate the Weyl group orbit of a weight under simple reflections.
341    ///
342    /// The Weyl group is generated by reflections in simple roots.
343    /// For type Aₙ, |W| = (n+1)!
344    pub fn weyl_orbit(&self, weight: &Root) -> Vec<Root> {
345        let mut orbit = vec![weight.clone()];
346        let mut seen = HashSet::new();
347        seen.insert(format!("{:?}", weight.coords));
348
349        let mut queue = vec![weight.clone()];
350
351        while let Some(current) = queue.pop() {
352            for simple_root in &self.simple_roots {
353                let reflected = simple_root.reflect(&current);
354                let key = format!("{:?}", reflected.coords);
355
356                if !seen.contains(&key) {
357                    seen.insert(key);
358                    orbit.push(reflected.clone());
359                    queue.push(reflected);
360                }
361            }
362        }
363
364        orbit
365    }
366
367    /// Dimension of the Lie algebra: rank + `num_roots`.
368    ///
369    /// For type Aₙ: dim = n + n(n+1) = n(n+2)
370    #[must_use]
371    pub fn dimension(&self) -> usize {
372        self.rank + self.num_roots()
373    }
374
375    /// Dominant weight chamber: λ such that ⟨λ, α⟩ ≥ 0 for all simple roots α.
376    #[must_use]
377    pub fn is_dominant_weight(&self, weight: &Root) -> bool {
378        self.simple_roots
379            .iter()
380            .all(|alpha| weight.inner_product(alpha) >= -1e-10)
381    }
382
383    /// Express a root as a linear combination of simple roots.
384    ///
385    /// Returns coefficients [c₁, c₂, ..., cₙ] such that β = Σ cᵢ αᵢ.
386    /// For roots in the system, coefficients are integers (positive for positive roots).
387    ///
388    /// Returns `None` if the root is not in this system, or if the expansion
389    /// is not yet implemented for general root systems.
390    ///
391    /// # Supported Systems
392    ///
393    /// - **Type A** (SU(n+1)): Fully implemented. Roots `e_i - e_j` expand as
394    ///   sums of consecutive simple roots.
395    /// - **Other types**: Returns `None`. General expansion requires Cartan
396    ///   matrix inversion, which is not yet implemented.
397    pub fn simple_root_expansion(&self, root: &Root) -> Option<Vec<i32>> {
398        if !self.contains_root(root) {
399            return None;
400        }
401
402        // For type A_n (SU(n+1)): roots are e_i - e_j in ℝⁿ⁺¹
403        // Simple roots are α_k = e_k - e_{k+1} for k = 0, ..., n-1
404        // A root e_i - e_j (i < j) = α_i + α_{i+1} + ... + α_{j-1}
405        //
406        // Detect type A by checking if root has exactly one +1 and one -1 coordinate
407        let coords = &root.coords;
408        let mut pos_idx: Option<usize> = None;
409        let mut neg_idx: Option<usize> = None;
410
411        for (idx, &val) in coords.iter().enumerate() {
412            if (val - 1.0).abs() < 1e-10 {
413                if pos_idx.is_some() {
414                    // Not type A format - fall through to general case
415                    return None;
416                }
417                pos_idx = Some(idx);
418            } else if (val + 1.0).abs() < 1e-10 {
419                if neg_idx.is_some() {
420                    return None;
421                }
422                neg_idx = Some(idx);
423            } else if val.abs() > 1e-10 {
424                // Non-zero entry that's not ±1 - not type A
425                return None;
426            }
427        }
428
429        let (Some(i), Some(j)) = (pos_idx, neg_idx) else {
430            return None;
431        };
432
433        // Build coefficients: for e_i - e_j, coefficient of α_k is:
434        // +1 if min(i,j) <= k < max(i,j) and i < j (positive root)
435        // -1 if min(i,j) <= k < max(i,j) and i > j (negative root)
436        let mut coeffs = vec![0i32; self.rank];
437        let (min_idx, max_idx) = if i < j { (i, j) } else { (j, i) };
438        let sign = if i < j { 1 } else { -1 };
439
440        for k in min_idx..max_idx {
441            if k < self.rank {
442                coeffs[k] = sign;
443            }
444        }
445
446        Some(coeffs)
447    }
448}
449
450/// Weight lattice for a root system.
451///
452/// Weights are elements λ ∈ 𝔥* such that ⟨λ, α^∨⟩ ∈ ℤ for all roots α.
453/// The weight lattice contains the root lattice as a sublattice.
454///
455/// # Fundamental Weights
456///
457/// For simple roots α₁, ..., αₙ, the fundamental weights ω₁, ..., ωₙ satisfy:
458/// ```text
459/// ⟨ωᵢ, αⱼ^∨⟩ = δᵢⱼ
460/// ```
461///
462/// Every dominant weight can be written λ = Σ mᵢ ωᵢ with mᵢ ∈ ℤ≥₀.
463pub struct WeightLattice {
464    root_system: RootSystem,
465    fundamental_weights: Vec<Root>,
466}
467
468impl WeightLattice {
469    /// Create weight lattice from root system.
470    pub fn from_root_system(root_system: RootSystem) -> Self {
471        let fundamental_weights = Self::compute_fundamental_weights(&root_system);
472        Self {
473            root_system,
474            fundamental_weights,
475        }
476    }
477
478    /// Compute fundamental weights ωᵢ such that ⟨ωᵢ, αⱼ^∨⟩ = δᵢⱼ.
479    fn compute_fundamental_weights(rs: &RootSystem) -> Vec<Root> {
480        // For type A_n (SU(n+1)):
481        // In GL(n+1) basis: ωᵢ = (1, 1, ..., 1, 0, ..., 0) with i ones
482        // In SU(n+1) traceless basis: project to hyperplane Σxᵢ = 0
483        //
484        // Projection: x_traceless = x - (Σxᵢ)/(n+1) · (1,1,...,1)
485
486        let n = rs.rank();
487        let dim = n + 1; // Dimension of fundamental representation
488        let mut weights = Vec::new();
489
490        for i in 0..n {
491            // Start with GL(n+1) fundamental weight
492            let mut coords = vec![0.0; dim];
493            for j in 0..=i {
494                coords[j] = 1.0;
495            }
496
497            // Project to traceless subspace: subtract mean
498            let sum: f64 = coords.iter().sum();
499            let mean = sum / (dim as f64);
500            for coord in &mut coords {
501                *coord -= mean;
502            }
503
504            weights.push(Root::new(coords));
505        }
506
507        weights
508    }
509
510    /// Fundamental weights.
511    pub fn fundamental_weights(&self) -> &[Root] {
512        &self.fundamental_weights
513    }
514
515    /// Convert Dynkin labels (m₁, ..., mₙ) to weight λ = Σ mᵢ ωᵢ.
516    pub fn dynkin_to_weight(&self, dynkin_labels: &[usize]) -> Root {
517        assert_eq!(dynkin_labels.len(), self.root_system.rank());
518
519        let mut weight_coords = vec![0.0; self.root_system.rank() + 1];
520        for (i, &m) in dynkin_labels.iter().enumerate() {
521            for (j, &w) in self.fundamental_weights[i].coords.iter().enumerate() {
522                weight_coords[j] += (m as f64) * w;
523            }
524        }
525
526        Root::new(weight_coords)
527    }
528
529    /// Get the root system.
530    pub fn root_system(&self) -> &RootSystem {
531        &self.root_system
532    }
533
534    /// Compute ρ (half-sum of positive roots) = sum of fundamental weights.
535    ///
536    /// For type Aₙ: ρ = ω₁ + ω₂ + ... + ωₙ
537    pub fn rho(&self) -> Root {
538        let mut rho_coords = vec![0.0; self.root_system.rank() + 1];
539        for omega in &self.fundamental_weights {
540            for (i, &coord) in omega.coords.iter().enumerate() {
541                rho_coords[i] += coord;
542            }
543        }
544        Root::new(rho_coords)
545    }
546
547    /// Kostant's partition function P(γ): number of ways to write γ as a sum of positive roots.
548    ///
549    /// Counts solutions to: γ = Σ_{α ∈ Φ⁺} `n_α` · α where `n_α` ≥ 0 are non-negative integers.
550    ///
551    /// This is computed via dynamic programming with ORDERED roots to avoid overcounting.
552    ///
553    /// # Returns
554    /// Number of distinct ways (counting multiplicities) to express γ as a sum of positive roots.
555    /// Returns 0 if γ cannot be expressed as such a sum.
556    ///
557    /// # Algorithm
558    /// Uses DP with memoization and ROOT ORDERING to count unordered partitions.
559    /// For each root `α_i`, we only consider using roots `α_j` where j ≥ i.
560    /// This ensures we count combinations, not permutations.
561    ///
562    /// # Performance
563    /// O(|Φ⁺| × V) where V is the size of the reachable region.
564    /// For typical representations, this is manageable.
565    fn kostant_partition_function(&self, gamma: &Root) -> usize {
566        use std::collections::HashMap;
567
568        // Base case: zero vector has exactly one representation (empty sum)
569        if gamma.coords.iter().all(|&x| x.abs() < 1e-10) {
570            return 1;
571        }
572
573        // Use memoization to avoid recomputation
574        // Key: (gamma, starting_index) to track which roots we can still use
575        let mut memo: HashMap<(String, usize), usize> = HashMap::new();
576        let positive_roots: Vec<Root> = self.root_system.positive_roots();
577
578        Self::partition_helper_ordered(gamma, &positive_roots, 0, &mut memo)
579    }
580
581    /// Recursive helper for partition function with ROOT ORDERING to avoid overcounting.
582    ///
583    /// Only considers roots starting from index `start_idx` onwards.
584    /// This ensures we generate combinations (not permutations) of roots.
585    fn partition_helper_ordered(
586        gamma: &Root,
587        positive_roots: &[Root],
588        start_idx: usize,
589        memo: &mut std::collections::HashMap<(String, usize), usize>,
590    ) -> usize {
591        // Base case: zero
592        if gamma.coords.iter().all(|&x| x.abs() < 1e-10) {
593            return 1;
594        }
595
596        // Base case: no more roots to try
597        if start_idx >= positive_roots.len() {
598            return 0;
599        }
600
601        // Check memo
602        let key = (WeightLattice::weight_key(&gamma.coords), start_idx);
603        if let Some(&result) = memo.get(&key) {
604            return result;
605        }
606
607        // Early termination: if γ has large negative coordinates, it's not expressible
608        let mut has_large_negative = false;
609        for &coord in &gamma.coords {
610            if coord < -100.0 {
611                has_large_negative = true;
612                break;
613            }
614        }
615
616        if has_large_negative {
617            memo.insert(key, 0);
618            return 0;
619        }
620
621        // Recursive case: for current root α at start_idx, we have two choices:
622        // 1. Don't use this root at all: move to next root
623        // 2. Use this root k times (k ≥ 1): subtract k·α and stay at same index
624        let mut count = 0;
625        let alpha = &positive_roots[start_idx];
626
627        // Choice 1: Skip this root entirely
628        if memo.len() < 100_000 {
629            count += Self::partition_helper_ordered(gamma, positive_roots, start_idx + 1, memo);
630        }
631
632        // Choice 2: Use this root at least once
633        let mut gamma_minus_k_alpha = gamma.clone();
634        for (i, &a) in alpha.coords.iter().enumerate() {
635            gamma_minus_k_alpha.coords[i] -= a;
636        }
637
638        // Can we use this root? Check if γ - α is "valid" (not too negative)
639        let can_use = gamma_minus_k_alpha.coords.iter().all(|&x| x > -100.0);
640
641        if can_use && memo.len() < 100_000 {
642            // Recurse with γ - α, staying at same root index (can use it multiple times)
643            count += Self::partition_helper_ordered(
644                &gamma_minus_k_alpha,
645                positive_roots,
646                start_idx,
647                memo,
648            );
649        }
650
651        memo.insert(key, count);
652        count
653    }
654
655    /// Generate Weyl group elements for type `A_n` (symmetric group S_{n+1}).
656    ///
657    /// The Weyl group of type `A_n` is isomorphic to S_{n+1}, the symmetric group
658    /// of permutations of {1, 2, ..., n+1}.
659    ///
660    /// Each permutation σ acts on weights λ = (λ₁, ..., λ_{n+1}) by permuting coordinates.
661    ///
662    /// # Returns
663    /// Vector of (permutation, sign) pairs where:
664    /// - permutation[i] = j means coordinate i maps to coordinate j
665    /// - sign = +1 for even permutations, -1 for odd
666    ///
667    /// # Performance
668    /// Generates all (n+1)! permutations. For SU(3) this is 6, for SU(4) this is 24, etc.
669    fn weyl_group_type_a(&self) -> Vec<(Vec<usize>, i32)> {
670        let n_plus_1 = self.root_system.rank() + 1;
671        let mut perms = Vec::new();
672
673        // Generate all permutations via Heap's algorithm
674        let mut current: Vec<usize> = (0..n_plus_1).collect();
675        Self::generate_permutations(&mut current, n_plus_1, &mut perms);
676
677        perms
678    }
679
680    /// Generate all permutations using Heap's algorithm.
681    fn generate_permutations(arr: &mut [usize], size: usize, result: &mut Vec<(Vec<usize>, i32)>) {
682        if size == 1 {
683            let perm = arr.to_vec();
684            let sign = Self::permutation_sign(&perm);
685            result.push((perm, sign));
686            return;
687        }
688
689        for i in 0..size {
690            Self::generate_permutations(arr, size - 1, result);
691
692            if size % 2 == 0 {
693                arr.swap(i, size - 1);
694            } else {
695                arr.swap(0, size - 1);
696            }
697        }
698    }
699
700    /// Compute the sign of a permutation (+1 for even, -1 for odd).
701    fn permutation_sign(perm: &[usize]) -> i32 {
702        let n = perm.len();
703        let mut sign = 1i32;
704
705        for i in 0..n {
706            for j in (i + 1)..n {
707                if perm[i] > perm[j] {
708                    sign *= -1;
709                }
710            }
711        }
712
713        sign
714    }
715
716    /// Apply Weyl group element (permutation) to a weight.
717    fn weyl_action(&self, weight: &Root, permutation: &[usize]) -> Root {
718        let mut new_coords = vec![0.0; weight.coords.len()];
719        for (i, &pi) in permutation.iter().enumerate() {
720            new_coords[i] = weight.coords[pi];
721        }
722        Root::new(new_coords)
723    }
724
725    /// Compute weight multiplicity using Kostant's formula.
726    ///
727    /// Kostant's formula (exact, not recursive like Freudenthal):
728    /// ```text
729    /// m_Λ(λ) = Σ_{w ∈ W} ε(w) · P(w·(Λ+ρ) - (λ+ρ))
730    /// ```
731    ///
732    /// where:
733    /// - Λ = highest weight of the representation
734    /// - λ = weight whose multiplicity we compute
735    /// - W = Weyl group
736    /// - ε(w) = sign of w (+1 for even, -1 for odd)
737    /// - P(γ) = partition function counting ways to write γ as sum of positive roots
738    ///
739    /// # Mathematical Background
740    ///
741    /// This is the most general multiplicity formula, working for all weights in all
742    /// representations. Unlike Freudenthal (which requires dominance), Kostant works
743    /// directly.
744    ///
745    /// # Performance
746    /// O(|W| × `P_cost`) where |W| = (n+1)! for type `A_n` and `P_cost` is partition function cost.
747    /// For SU(3): 6 Weyl group elements.
748    /// For SU(4): 24 Weyl group elements.
749    ///
750    /// # References
751    /// - Humphreys, "Introduction to Lie Algebras and Representation Theory", §24.3
752    /// - Kostant (1959), "A Formula for the Multiplicity of a Weight"
753    pub fn kostant_multiplicity(&self, highest_weight: &Root, weight: &Root) -> usize {
754        let rho = self.rho();
755
756        // Compute Λ + ρ
757        let mut lambda_plus_rho = highest_weight.clone();
758        for (i, &r) in rho.coords.iter().enumerate() {
759            lambda_plus_rho.coords[i] += r;
760        }
761
762        // Compute λ + ρ
763        let mut mu_plus_rho = weight.clone();
764        for (i, &r) in rho.coords.iter().enumerate() {
765            mu_plus_rho.coords[i] += r;
766        }
767
768        // Sum over Weyl group
769        let weyl_group = self.weyl_group_type_a();
770        let mut multiplicity = 0i32; // Use signed arithmetic for alternating sum
771
772        for (perm, sign) in weyl_group {
773            // Compute w·(Λ+ρ)
774            let w_lambda_plus_rho = self.weyl_action(&lambda_plus_rho, &perm);
775
776            // Compute γ = w·(Λ+ρ) - (λ+ρ)
777            let mut gamma = w_lambda_plus_rho.clone();
778            for (i, &mu) in mu_plus_rho.coords.iter().enumerate() {
779                gamma.coords[i] -= mu;
780            }
781
782            // Compute P(γ)
783            let p_gamma = self.kostant_partition_function(&gamma);
784
785            // Add signed contribution
786            multiplicity += sign * (p_gamma as i32);
787        }
788
789        // Result must be non-negative
790        assert!(
791            multiplicity >= 0,
792            "Kostant multiplicity must be non-negative, got {}",
793            multiplicity
794        );
795        multiplicity as usize
796    }
797
798    /// Compute dimension of irreducible representation using Weyl dimension formula.
799    ///
800    /// For type Aₙ with highest weight λ:
801    /// ```text
802    /// dim(λ) = ∏_{α>0} ⟨λ + ρ, α⟩ / ⟨ρ, α⟩
803    /// ```
804    ///
805    /// This is the EXACT dimension - no approximation.
806    pub fn weyl_dimension(&self, highest_weight: &Root) -> usize {
807        let rho = self.rho();
808
809        // λ + ρ
810        let mut lambda_plus_rho = highest_weight.clone();
811        for (i, &r) in rho.coords.iter().enumerate() {
812            lambda_plus_rho.coords[i] += r;
813        }
814
815        let mut numerator = 1.0;
816        let mut denominator = 1.0;
817
818        for alpha in self.root_system.positive_roots() {
819            let num = lambda_plus_rho.inner_product(&alpha);
820            let denom = rho.inner_product(&alpha);
821
822            numerator *= num;
823            denominator *= denom;
824        }
825
826        (numerator / denominator).round() as usize
827    }
828
829    /// Helper function to create a stable `HashMap` key from weight coordinates.
830    ///
831    /// Rounds each coordinate to 10 decimal places to avoid floating-point precision issues
832    /// when using coordinates as `HashMap` keys.
833    fn weight_key(coords: &[f64]) -> String {
834        let rounded: Vec<String> = coords.iter().map(|&x| format!("{:.10}", x)).collect();
835        format!("[{}]", rounded.join(", "))
836    }
837
838    /// Generate all weights in an irreducible representation.
839    ///
840    /// Uses the CORRECT, GENERAL algorithm:
841    /// 1. Generate candidate weights by exploring from highest weight with ALL positive roots
842    /// 2. Add Weyl orbit of highest weight to ensure completeness
843    /// 3. Use Kostant's formula to compute accurate multiplicities
844    /// 4. Return only weights with multiplicity > 0
845    ///
846    /// This works for ALL representations, including adjoint where negative roots appear.
847    pub fn weights_of_irrep(&self, highest_weight: &Root) -> Vec<(Root, usize)> {
848        use std::collections::{HashMap, VecDeque};
849
850        let mut candidates: HashMap<String, Root> = HashMap::new();
851        let mut queue: VecDeque<Root> = VecDeque::new();
852
853        // Start with highest weight
854        let hw_key = Self::weight_key(&highest_weight.coords);
855        candidates.insert(hw_key, highest_weight.clone());
856        queue.push_back(highest_weight.clone());
857
858        // BFS: Subtract ALL positive roots (not just simple roots!)
859        // This ensures we reach all weights, including negative roots in adjoint rep
860        while let Some(weight) = queue.pop_front() {
861            for pos_root in self.root_system.positive_roots() {
862                let mut new_weight = weight.clone();
863                for (i, &a) in pos_root.coords.iter().enumerate() {
864                    new_weight.coords[i] -= a;
865                }
866
867                let new_key = Self::weight_key(&new_weight.coords);
868
869                // Skip if already seen
870                if candidates.contains_key(&new_key) {
871                    continue;
872                }
873
874                // Check if in reasonable bounds (heuristic to avoid infinite exploration)
875                let norm = new_weight.norm_squared();
876                let hw_norm = highest_weight.norm_squared();
877                if norm > 3.0 * hw_norm + 10.0 {
878                    continue;
879                }
880
881                candidates.insert(new_key, new_weight.clone());
882                queue.push_back(new_weight);
883
884                // Safety limit
885                if candidates.len() > 1000 {
886                    break;
887                }
888            }
889
890            if candidates.len() > 1000 {
891                break;
892            }
893        }
894
895        // CRITICAL: Also add the full Weyl orbit of the highest weight
896        // This ensures we don't miss any extremal weights
897        let weyl_orbit = self.root_system.weyl_orbit(highest_weight);
898        for w in weyl_orbit {
899            let key = Self::weight_key(&w.coords);
900            candidates.insert(key, w);
901        }
902
903        // Compute accurate multiplicities using Kostant's formula
904        let candidate_list: Vec<Root> = candidates.into_values().collect();
905        let mut result = Vec::new();
906
907        for weight in candidate_list.iter() {
908            let mult = self.kostant_multiplicity(highest_weight, weight);
909            if mult > 0 {
910                result.push((weight.clone(), mult));
911            }
912        }
913
914        result
915    }
916
917    /// Generate a string representation of a weight diagram for rank 2.
918    ///
919    /// For SU(3) representations, this creates a 2D triangular lattice
920    /// showing all weights with their multiplicities.
921    ///
922    /// # Example
923    ///
924    /// ```
925    /// use lie_groups::{RootSystem, WeightLattice};
926    ///
927    /// let rs = RootSystem::type_a(2);
928    /// let wl = WeightLattice::from_root_system(rs);
929    /// let highest = wl.dynkin_to_weight(&[1, 1]); // Adjoint rep
930    ///
931    /// let diagram = wl.weight_diagram_string(&highest);
932    /// // Should show 8 weights (8-dimensional rep)
933    /// ```
934    pub fn weight_diagram_string(&self, highest_weight: &Root) -> String {
935        if self.root_system.rank() != 2 {
936            return "Weight diagrams only implemented for rank 2".to_string();
937        }
938
939        let weights = self.weights_of_irrep(highest_weight);
940
941        let mut output = String::new();
942        writeln!(
943            &mut output,
944            "Weight diagram for highest weight {:?}",
945            highest_weight.coords
946        )
947        .expect("String formatting should not fail");
948        writeln!(&mut output, "Dimension: {}", weights.len())
949            .expect("String formatting should not fail");
950        output.push_str("Weights (multiplicity):\n");
951
952        for (weight, mult) in &weights {
953            writeln!(&mut output, "  {} (×{})", weight, mult)
954                .expect("String formatting should not fail");
955        }
956
957        output
958    }
959}
960
961/// Cartan subalgebra of a semisimple Lie algebra.
962///
963/// The Cartan subalgebra 𝔥 is the maximal abelian subalgebra consisting of
964/// simultaneously diagonalizable elements. For SU(n+1) (type Aₙ), it consists
965/// of traceless diagonal matrices.
966///
967/// # Mathematical Background
968///
969/// - Dimension: rank of the Lie algebra
970/// - Basis: {H₁, ..., Hₙ} where [Hᵢ, Hⱼ] = 0 (all commute)
971/// - For SU(n+1): Hᵢ = Eᵢᵢ - Eᵢ₊₁,ᵢ₊₁ (diagonal matrices)
972/// - Roots are functionals α: 𝔥 → ℝ
973///
974/// # Example
975///
976/// ```
977/// use lie_groups::root_systems::CartanSubalgebra;
978///
979/// // SU(3) Cartan subalgebra (2-dimensional)
980/// let cartan = CartanSubalgebra::type_a(2);
981/// assert_eq!(cartan.dimension(), 2);
982///
983/// let basis = cartan.basis_matrices();
984/// assert_eq!(basis.len(), 2);
985/// ```
986pub struct CartanSubalgebra {
987    /// Dimension of the Cartan subalgebra (rank of Lie algebra)
988    dimension: usize,
989
990    /// Basis matrices as complex arrays
991    /// For SU(n+1), these are n diagonal traceless matrices
992    basis_matrices: Vec<Array2<Complex64>>,
993
994    /// Matrix dimension (N for SU(N))
995    matrix_size: usize,
996}
997
998impl CartanSubalgebra {
999    /// Create Cartan subalgebra for type Aₙ (SU(n+1)).
1000    ///
1001    /// The Cartan subalgebra consists of traceless diagonal matrices.
1002    /// Basis: Hᵢ = Eᵢᵢ - Eᵢ₊₁,ᵢ₊₁ for i = 1, ..., n
1003    ///
1004    /// # Example
1005    ///
1006    /// ```
1007    /// use lie_groups::root_systems::CartanSubalgebra;
1008    ///
1009    /// // SU(2) Cartan subalgebra: 1-dimensional
1010    /// let h = CartanSubalgebra::type_a(1);
1011    /// assert_eq!(h.dimension(), 1);
1012    ///
1013    /// // First basis element: diag(1, -1)
1014    /// let h1 = &h.basis_matrices()[0];
1015    /// assert!((h1[(0,0)].re - 1.0).abs() < 1e-10);
1016    /// assert!((h1[(1,1)].re + 1.0).abs() < 1e-10);
1017    /// ```
1018    pub fn type_a(n: usize) -> Self {
1019        assert!(n >= 1, "Type A_n requires n >= 1");
1020
1021        let matrix_size = n + 1;
1022        let dimension = n;
1023
1024        let mut basis_matrices = Vec::new();
1025
1026        // Generate Hᵢ = Eᵢᵢ - Eᵢ₊₁,ᵢ₊₁ for i = 0, ..., n-1
1027        for i in 0..n {
1028            let mut h = Array2::<Complex64>::zeros((matrix_size, matrix_size));
1029            h[(i, i)] = Complex64::new(1.0, 0.0);
1030            h[(i + 1, i + 1)] = Complex64::new(-1.0, 0.0);
1031            basis_matrices.push(h);
1032        }
1033
1034        Self {
1035            dimension,
1036            basis_matrices,
1037            matrix_size,
1038        }
1039    }
1040
1041    /// Dimension of the Cartan subalgebra (rank).
1042    pub fn dimension(&self) -> usize {
1043        self.dimension
1044    }
1045
1046    /// Basis matrices for the Cartan subalgebra.
1047    pub fn basis_matrices(&self) -> &[Array2<Complex64>] {
1048        &self.basis_matrices
1049    }
1050
1051    /// Matrix size (N for SU(N)).
1052    pub fn matrix_size(&self) -> usize {
1053        self.matrix_size
1054    }
1055
1056    /// Evaluate a root functional on a Cartan element.
1057    ///
1058    /// For a root α and Cartan element H = Σ cᵢ Hᵢ, computes α(H) = Σ cᵢ α(Hᵢ).
1059    ///
1060    /// # Arguments
1061    ///
1062    /// * `root` - Root as a vector of coordinates
1063    /// * `coefficients` - Coefficients [c₁, ..., cₙ] in basis {H₁, ..., Hₙ}
1064    ///
1065    /// # Returns
1066    ///
1067    /// The value α(H) ∈ ℂ
1068    pub fn evaluate_root(&self, root: &Root, coefficients: &[f64]) -> Complex64 {
1069        assert_eq!(
1070            coefficients.len(),
1071            self.dimension,
1072            "Coefficient vector must match Cartan dimension"
1073        );
1074        assert_eq!(
1075            root.rank(),
1076            self.matrix_size,
1077            "Root dimension must match matrix size"
1078        );
1079
1080        // For type A_n, α(H) is computed via the standard pairing
1081        // If H = diag(h₁, ..., hₙ₊₁) with Σhᵢ = 0, and α = (α₁, ..., αₙ₊₁)
1082        // then α(H) = Σ αᵢ hᵢ
1083
1084        // Reconstruct diagonal H from basis coefficients
1085        let mut h_diagonal = vec![Complex64::new(0.0, 0.0); self.matrix_size];
1086        for (i, &c) in coefficients.iter().enumerate() {
1087            h_diagonal[i] += Complex64::new(c, 0.0);
1088            h_diagonal[i + 1] -= Complex64::new(c, 0.0);
1089        }
1090
1091        // Compute α(H) = Σ αᵢ hᵢ
1092        root.coords
1093            .iter()
1094            .zip(&h_diagonal)
1095            .map(|(alpha_i, h_i)| h_i * alpha_i)
1096            .sum()
1097    }
1098
1099    /// Project a matrix onto the Cartan subalgebra.
1100    ///
1101    /// For a matrix M, finds coefficients [c₁, ..., cₙ] such that
1102    /// Σ cᵢ Hᵢ best approximates M in the Frobenius norm.
1103    ///
1104    /// Since the basis {H₁, ..., Hₙ} is not necessarily orthogonal,
1105    /// we solve the linear system: G c = g where Gᵢⱼ = ⟨Hᵢ, Hⱼ⟩.
1106    ///
1107    /// # Arguments
1108    ///
1109    /// * `matrix` - Matrix to project
1110    ///
1111    /// # Returns
1112    ///
1113    /// `Some(coefficients)` in the Cartan basis for rank ≤ 2,
1114    /// `None` for rank > 2 (Gaussian elimination not yet implemented).
1115    ///
1116    /// # Limitations
1117    ///
1118    /// Currently only supports rank 1 (SU(2)) and rank 2 (SU(3)) systems.
1119    /// Higher rank systems require general Gaussian elimination.
1120    pub fn project_matrix(&self, matrix: &Array2<Complex64>) -> Option<Vec<f64>> {
1121        assert_eq!(
1122            matrix.shape(),
1123            &[self.matrix_size, self.matrix_size],
1124            "Matrix must be {}×{}",
1125            self.matrix_size,
1126            self.matrix_size
1127        );
1128
1129        // Compute Gram matrix G and right-hand side g
1130        let n = self.dimension;
1131        let mut gram = vec![vec![0.0; n]; n];
1132        let mut rhs = vec![0.0; n];
1133
1134        for i in 0..n {
1135            for j in 0..n {
1136                // Gᵢⱼ = ⟨Hᵢ, Hⱼ⟩ = Tr(Hᵢ† Hⱼ)
1137                let inner: Complex64 = self.basis_matrices[i]
1138                    .iter()
1139                    .zip(self.basis_matrices[j].iter())
1140                    .map(|(h_i, h_j)| h_i.conj() * h_j)
1141                    .sum();
1142                gram[i][j] = inner.re;
1143            }
1144
1145            // gᵢ = ⟨M, Hᵢ⟩ = Tr(M† Hᵢ)
1146            let inner: Complex64 = matrix
1147                .iter()
1148                .zip(self.basis_matrices[i].iter())
1149                .map(|(m_ij, h_ij)| m_ij.conj() * h_ij)
1150                .sum();
1151            rhs[i] = inner.re;
1152        }
1153
1154        // Solve G c = g using direct formulas (small n only)
1155        let mut coefficients = vec![0.0; n];
1156
1157        match n {
1158            1 => {
1159                coefficients[0] = rhs[0] / gram[0][0];
1160                Some(coefficients)
1161            }
1162            2 => {
1163                // Solve 2×2 system directly
1164                let det = gram[0][0] * gram[1][1] - gram[0][1] * gram[1][0];
1165                coefficients[0] = (rhs[0] * gram[1][1] - rhs[1] * gram[0][1]) / det;
1166                coefficients[1] = (gram[0][0] * rhs[1] - gram[1][0] * rhs[0]) / det;
1167                Some(coefficients)
1168            }
1169            _ => {
1170                // Rank > 2: Gaussian elimination not yet implemented
1171                None
1172            }
1173        }
1174    }
1175
1176    /// Construct a Cartan element from coefficients.
1177    ///
1178    /// Given coefficients [c₁, ..., cₙ], returns H = Σ cᵢ Hᵢ.
1179    pub fn from_coefficients(&self, coefficients: &[f64]) -> Array2<Complex64> {
1180        assert_eq!(
1181            coefficients.len(),
1182            self.dimension,
1183            "Must provide {} coefficients",
1184            self.dimension
1185        );
1186
1187        let mut result = Array2::<Complex64>::zeros((self.matrix_size, self.matrix_size));
1188
1189        for (&c_i, h_i) in coefficients.iter().zip(&self.basis_matrices) {
1190            result = result + h_i * c_i;
1191        }
1192
1193        result
1194    }
1195
1196    /// Check if a matrix is in the Cartan subalgebra.
1197    ///
1198    /// Returns true if the matrix is diagonal (or nearly diagonal within tolerance).
1199    pub fn contains(&self, matrix: &Array2<Complex64>, tolerance: f64) -> bool {
1200        assert_eq!(
1201            matrix.shape(),
1202            &[self.matrix_size, self.matrix_size],
1203            "Matrix must be {}×{}",
1204            self.matrix_size,
1205            self.matrix_size
1206        );
1207
1208        // Check if matrix is diagonal
1209        for i in 0..self.matrix_size {
1210            for j in 0..self.matrix_size {
1211                if i != j && matrix[(i, j)].norm() > tolerance {
1212                    return false;
1213                }
1214            }
1215        }
1216
1217        // Check if diagonal entries sum to zero (traceless)
1218        let trace: Complex64 = (0..self.matrix_size).map(|i| matrix[(i, i)]).sum();
1219
1220        trace.norm() < tolerance
1221    }
1222
1223    /// Killing form restricted to Cartan subalgebra.
1224    ///
1225    /// For H, H' ∈ 𝔥, the Killing form is κ(H, H') = Tr(`ad_H` ∘ `ad_H`').
1226    /// For type `A_n`, this simplifies significantly.
1227    ///
1228    /// # Arguments
1229    ///
1230    /// * `coeffs1` - First Cartan element as coefficients
1231    /// * `coeffs2` - Second Cartan element as coefficients
1232    ///
1233    /// # Returns
1234    ///
1235    /// The value κ(H₁, H₂)
1236    pub fn killing_form(&self, coeffs1: &[f64], coeffs2: &[f64]) -> f64 {
1237        assert_eq!(coeffs1.len(), self.dimension);
1238        assert_eq!(coeffs2.len(), self.dimension);
1239
1240        // For SU(n+1), the Killing form on the Cartan is
1241        // κ(H, H') = 2(n+1) Tr(HH')
1242        //
1243        // For our basis Hᵢ = Eᵢᵢ - Eᵢ₊₁,ᵢ₊₁:
1244        // Tr(Hᵢ Hⱼ) = 2δᵢⱼ
1245
1246        let n_plus_1 = self.matrix_size as f64;
1247
1248        coeffs1
1249            .iter()
1250            .zip(coeffs2)
1251            .map(|(c1, c2)| 2.0 * n_plus_1 * 2.0 * c1 * c2)
1252            .sum()
1253    }
1254
1255    /// Dual basis in 𝔥* (root space).
1256    ///
1257    /// Returns roots {α₁, ..., αₙ} such that αᵢ(Hⱼ) = δᵢⱼ.
1258    /// For type `A_n`, these correspond to the simple roots.
1259    pub fn dual_basis(&self) -> Vec<Root> {
1260        let mut dual_roots = Vec::new();
1261
1262        for i in 0..self.dimension {
1263            // For Hᵢ = Eᵢᵢ - Eᵢ₊₁,ᵢ₊₁, the dual is αᵢ = eᵢ - eᵢ₊₁
1264            let mut coords = vec![0.0; self.matrix_size];
1265            coords[i] = 1.0;
1266            coords[i + 1] = -1.0;
1267            dual_roots.push(Root::new(coords));
1268        }
1269
1270        dual_roots
1271    }
1272}
1273
1274/// Weyl chamber for a root system.
1275///
1276/// A Weyl chamber is a connected component of the complement of the reflecting
1277/// hyperplanes in the dual Cartan space 𝔥*. The **fundamental Weyl chamber**
1278/// (or **dominant chamber**) is defined by the simple roots:
1279///
1280/// ```text
1281/// C = {λ ∈ 𝔥* : ⟨λ, αᵢ⟩ ≥ 0 for all simple roots αᵢ}
1282/// ```
1283///
1284/// # Mathematical Background
1285///
1286/// - The Weyl group W acts on 𝔥* by reflections through root hyperplanes
1287/// - W partitions 𝔥* into finitely many Weyl chambers
1288/// - All chambers are isometric under W
1289/// - The fundamental chamber parametrizes dominant weights (highest weights
1290///   of irreducible representations)
1291///
1292/// # Example
1293///
1294/// ```
1295/// use lie_groups::root_systems::{RootSystem, WeylChamber, Root};
1296///
1297/// let root_system = RootSystem::type_a(2); // SU(3)
1298/// let chamber = WeylChamber::fundamental(&root_system);
1299///
1300/// // Fundamental weight ω₁ = (2/3, -1/3, -1/3) is dominant (traceless for SU(3))
1301/// let omega1 = Root::new(vec![2.0/3.0, -1.0/3.0, -1.0/3.0]);
1302/// assert!(chamber.contains(&omega1, false)); // Non-strict since on boundary
1303///
1304/// // Negative weight is not dominant
1305/// let neg = Root::new(vec![-1.0, 0.0, 1.0]);
1306/// assert!(!chamber.contains(&neg, false));
1307/// ```
1308///
1309/// # References
1310///
1311/// - Humphreys, *Introduction to Lie Algebras*, §10.2
1312/// - Hall, *Lie Groups*, §8.4
1313#[derive(Debug, Clone)]
1314pub struct WeylChamber {
1315    /// The root system defining the chamber
1316    root_system: RootSystem,
1317
1318    /// Simple roots defining the chamber walls
1319    simple_roots: Vec<Root>,
1320}
1321
1322impl WeylChamber {
1323    /// Construct the fundamental Weyl chamber for a root system.
1324    ///
1325    /// The fundamental chamber is bounded by hyperplanes perpendicular to
1326    /// the simple roots:
1327    ///
1328    /// ```text
1329    /// C = {λ : ⟨λ, αᵢ⟩ ≥ 0 for all simple αᵢ}
1330    /// ```
1331    pub fn fundamental(root_system: &RootSystem) -> Self {
1332        Self {
1333            root_system: root_system.clone(),
1334            simple_roots: root_system.simple_roots.clone(),
1335        }
1336    }
1337
1338    /// Check if a weight is in the Weyl chamber.
1339    ///
1340    /// # Arguments
1341    ///
1342    /// * `weight` - Weight λ ∈ 𝔥* to test
1343    /// * `strict` - If true, use strict inequality (⟨λ, αᵢ⟩ > 0); else non-strict (≥ 0)
1344    ///
1345    /// # Returns
1346    ///
1347    /// True if λ is in the chamber (dominant weight)
1348    pub fn contains(&self, weight: &Root, strict: bool) -> bool {
1349        // For type A_n, roots have n+1 coordinates
1350        assert_eq!(weight.rank(), self.root_system.rank + 1);
1351
1352        for alpha in &self.simple_roots {
1353            let pairing = weight.inner_product(alpha);
1354
1355            if strict {
1356                if pairing <= 1e-10 {
1357                    return false;
1358                }
1359            } else if pairing < -1e-10 {
1360                return false;
1361            }
1362        }
1363
1364        true
1365    }
1366
1367    /// Project a weight onto the closure of the fundamental Weyl chamber.
1368    ///
1369    /// Uses the Weyl group to map an arbitrary weight to its unique dominant
1370    /// representative (closest point in the chamber).
1371    ///
1372    /// # Algorithm
1373    ///
1374    /// Iteratively reflect through violated hyperplanes until all simple root
1375    /// pairings are non-negative.
1376    pub fn project(&self, weight: &Root) -> Root {
1377        let mut current = weight.clone();
1378
1379        // Maximum iterations to prevent infinite loops
1380        let max_iterations = 100;
1381        let mut iterations = 0;
1382
1383        while iterations < max_iterations {
1384            let mut found_violation = false;
1385
1386            for alpha in &self.simple_roots {
1387                let pairing = current.inner_product(alpha);
1388
1389                if pairing < -1e-10 {
1390                    // Reflect through α to increase ⟨current, α⟩
1391                    current = alpha.reflect(&current);
1392                    found_violation = true;
1393                }
1394            }
1395
1396            if !found_violation {
1397                break;
1398            }
1399
1400            iterations += 1;
1401        }
1402
1403        if iterations >= max_iterations {
1404            eprintln!(
1405                "Warning: WeylChamber::project did not converge in {} iterations",
1406                max_iterations
1407            );
1408        }
1409
1410        current
1411    }
1412
1413    /// Get the simple roots defining the chamber walls.
1414    pub fn simple_roots(&self) -> &[Root] {
1415        &self.simple_roots
1416    }
1417}
1418
1419/// Fundamental alcove for an affine root system.
1420///
1421/// An **alcove** is a bounded region in the Cartan space defined by affine
1422/// hyperplanes (root hyperplanes shifted by integer levels). The fundamental
1423/// alcove is:
1424///
1425/// ```text
1426/// A = {λ ∈ 𝔥* : ⟨λ, αᵢ⟩ ≥ 0 for all simple αᵢ, and ⟨λ, θ⟩ ≤ 1}
1427/// ```
1428///
1429/// where θ is the **highest root** (longest root in the positive system).
1430///
1431/// # Mathematical Background
1432///
1433/// - Alcoves tile 𝔥* under the affine Weyl group Wₐff
1434/// - The fundamental alcove parametrizes integrable highest-weight modules
1435///   at level k (conformal field theory, loop groups)
1436/// - Vertices of the alcove are fundamental weights
1437///
1438/// # Example
1439///
1440/// ```
1441/// use lie_groups::root_systems::{RootSystem, Alcove, Root};
1442///
1443/// let root_system = RootSystem::type_a(2); // SU(3)
1444/// let alcove = Alcove::fundamental(&root_system);
1445///
1446/// // Weight inside alcove
1447/// let lambda = Root::new(vec![0.3, 0.3, -0.6]);
1448/// // (need to verify ⟨λ, αᵢ⟩ ≥ 0 and ⟨λ, θ⟩ ≤ 1)
1449/// ```
1450///
1451/// # References
1452///
1453/// - Kac, *Infinite Dimensional Lie Algebras*, §6.2
1454/// - Humphreys, *Reflection Groups and Coxeter Groups*, §4.4
1455#[derive(Debug, Clone)]
1456pub struct Alcove {
1457    /// The root system defining the alcove
1458    root_system: RootSystem,
1459
1460    /// Simple roots (defining lower walls)
1461    simple_roots: Vec<Root>,
1462
1463    /// Highest root θ (defining upper wall ⟨λ, θ⟩ ≤ level)
1464    highest_root: Root,
1465
1466    /// Level k (affine level; typically 1 for fundamental alcove)
1467    level: f64,
1468}
1469
1470impl Alcove {
1471    /// Construct the fundamental alcove at level k = 1.
1472    ///
1473    /// The fundamental alcove is:
1474    /// ```text
1475    /// A = {λ : ⟨λ, αᵢ⟩ ≥ 0, ⟨λ, θ⟩ ≤ 1}
1476    /// ```
1477    pub fn fundamental(root_system: &RootSystem) -> Self {
1478        let simple_roots = root_system.simple_roots.clone();
1479        let highest_root = root_system.highest_root();
1480
1481        Self {
1482            root_system: root_system.clone(),
1483            simple_roots,
1484            highest_root,
1485            level: 1.0,
1486        }
1487    }
1488
1489    /// Construct an alcove at a specified level k.
1490    ///
1491    /// # Arguments
1492    ///
1493    /// * `root_system` - The root system
1494    /// * `level` - Affine level k (positive integer for integrable modules)
1495    pub fn at_level(root_system: &RootSystem, level: f64) -> Self {
1496        assert!(level > 0.0, "Level must be positive");
1497
1498        let simple_roots = root_system.simple_roots.clone();
1499        let highest_root = root_system.highest_root();
1500
1501        Self {
1502            root_system: root_system.clone(),
1503            simple_roots,
1504            highest_root,
1505            level,
1506        }
1507    }
1508
1509    /// Check if a weight is in the alcove.
1510    ///
1511    /// # Arguments
1512    ///
1513    /// * `weight` - Weight λ ∈ 𝔥* to test
1514    /// * `strict` - If true, use strict inequalities
1515    ///
1516    /// # Returns
1517    ///
1518    /// True if λ is in the alcove
1519    pub fn contains(&self, weight: &Root, strict: bool) -> bool {
1520        // For type A_n, roots have n+1 coordinates
1521        assert_eq!(weight.rank(), self.root_system.rank + 1);
1522
1523        // Check lower walls: ⟨λ, αᵢ⟩ ≥ 0
1524        for alpha in &self.simple_roots {
1525            let pairing = weight.inner_product(alpha);
1526
1527            if strict {
1528                if pairing <= 1e-10 {
1529                    return false;
1530                }
1531            } else if pairing < -1e-10 {
1532                return false;
1533            }
1534        }
1535
1536        // Check upper wall: ⟨λ, θ⟩ ≤ level
1537        let pairing_highest = weight.inner_product(&self.highest_root);
1538
1539        if strict {
1540            if pairing_highest >= self.level - 1e-10 {
1541                return false;
1542            }
1543        } else if pairing_highest > self.level + 1e-10 {
1544            return false;
1545        }
1546
1547        true
1548    }
1549
1550    /// Get the level of the alcove.
1551    pub fn level(&self) -> f64 {
1552        self.level
1553    }
1554
1555    /// Get the highest root defining the upper wall.
1556    pub fn highest_root(&self) -> &Root {
1557        &self.highest_root
1558    }
1559
1560    /// Compute vertices of the fundamental alcove.
1561    ///
1562    /// For type `A_n`, the fundamental alcove is a simplex with n+1 vertices:
1563    /// the origin and the fundamental weights {ω₁, ..., ωₙ}.
1564    ///
1565    /// # Returns
1566    ///
1567    /// Vector of vertices (as Roots in 𝔥*)
1568    pub fn vertices(&self) -> Vec<Root> {
1569        if self.level != 1.0 {
1570            // For level k ≠ 1, vertices are scaled: k·ωᵢ/(k + h∨)
1571            // where h∨ is the dual Coxeter number
1572            // Not implemented for now
1573            return vec![];
1574        }
1575
1576        // For fundamental alcove at level 1:
1577        // Vertices are 0 and fundamental weights
1578
1579        let mut vertices = vec![Root::new(vec![0.0; self.root_system.rank + 1])];
1580
1581        // Add fundamental weights (computed from simple roots)
1582        // For type A_n, ωᵢ = (1/n+1) * (n+1-i, ..., n+1-i, -i, ..., -i)
1583        //                                 └── i times ──┘  └─ n+1-i ──┘
1584
1585        let n = self.root_system.rank;
1586
1587        for i in 1..=n {
1588            let mut coords = vec![0.0; n + 1];
1589
1590            // ωᵢ has i coordinates equal to (n+1-i)/(n+1) and (n+1-i) equal to -i/(n+1)
1591            for j in 0..i {
1592                coords[j] = (n + 1 - i) as f64 / (n + 1) as f64;
1593            }
1594            for j in i..=n {
1595                coords[j] = -(i as f64) / (n + 1) as f64;
1596            }
1597
1598            vertices.push(Root::new(coords));
1599        }
1600
1601        vertices
1602    }
1603}
1604
1605#[cfg(test)]
1606mod tests {
1607    use super::*;
1608
1609    #[test]
1610    fn test_root_operations() {
1611        let alpha = Root::new(vec![1.0, -1.0, 0.0]);
1612        let beta = Root::new(vec![0.0, 1.0, -1.0]);
1613
1614        assert_eq!(alpha.rank(), 3);
1615        assert!((alpha.norm_squared() - 2.0).abs() < 1e-10);
1616        assert!((alpha.inner_product(&beta) + 1.0).abs() < 1e-10);
1617        assert!(alpha.is_positive());
1618        assert!(!alpha.negate().is_positive());
1619    }
1620
1621    #[test]
1622    fn test_weyl_reflection() {
1623        let alpha = Root::new(vec![1.0, -1.0]);
1624        let beta = Root::new(vec![0.5, 0.5]);
1625
1626        let reflected = alpha.reflect(&beta);
1627        // s_α(β) = β - 2⟨β,α⟩/⟨α,α⟩ · α
1628        // ⟨β,α⟩ = 0.5 - 0.5 = 0, so s_α(β) = β
1629        assert!((reflected.coords[0] - 0.5).abs() < 1e-10);
1630        assert!((reflected.coords[1] - 0.5).abs() < 1e-10);
1631    }
1632
1633    #[test]
1634    fn test_cartan_integer() {
1635        let alpha = Root::new(vec![1.0, -1.0, 0.0]);
1636        let beta = Root::new(vec![0.0, 1.0, -1.0]);
1637
1638        // ⟨β, α^∨⟩ = 2⟨β,α⟩/⟨α,α⟩ = 2(-1)/2 = -1
1639        assert_eq!(alpha.cartan_integer(&beta), -1);
1640        assert_eq!(beta.cartan_integer(&alpha), -1);
1641    }
1642
1643    #[test]
1644    fn test_type_a1_su2() {
1645        let rs = RootSystem::type_a(1);
1646
1647        assert_eq!(rs.rank(), 1);
1648        assert_eq!(rs.num_roots(), 2); // ±α
1649        assert_eq!(rs.num_positive_roots(), 1);
1650        assert_eq!(rs.dimension(), 3); // SU(2) dim = 1 + 2 = 3
1651
1652        let cartan = rs.cartan_matrix();
1653        assert_eq!(cartan.len(), 1);
1654        assert_eq!(cartan[0][0], 2); // ⟨α, α^∨⟩ = 2
1655    }
1656
1657    #[test]
1658    fn test_type_a2_su3() {
1659        let rs = RootSystem::type_a(2);
1660
1661        assert_eq!(rs.rank(), 2);
1662        assert_eq!(rs.num_roots(), 6); // 3² - 1 = 8 total, 6 nonzero differences
1663        assert_eq!(rs.num_positive_roots(), 3);
1664        assert_eq!(rs.dimension(), 8); // SU(3) dim = 2 + 6 = 8
1665
1666        let cartan = rs.cartan_matrix();
1667        assert_eq!(cartan.len(), 2);
1668        assert_eq!(cartan[0][0], 2);
1669        assert_eq!(cartan[1][1], 2);
1670        assert_eq!(cartan[0][1], -1); // Adjacent simple roots
1671        assert_eq!(cartan[1][0], -1);
1672    }
1673
1674    #[test]
1675    fn test_simple_roots_su3() {
1676        let rs = RootSystem::type_a(2);
1677        let simple = rs.simple_roots();
1678
1679        assert_eq!(simple.len(), 2);
1680
1681        // α₁ = e₁ - e₂ = (1, -1, 0)
1682        assert!((simple[0].coords[0] - 1.0).abs() < 1e-10);
1683        assert!((simple[0].coords[1] + 1.0).abs() < 1e-10);
1684        assert!((simple[0].coords[2]).abs() < 1e-10);
1685
1686        // α₂ = e₂ - e₃ = (0, 1, -1)
1687        assert!((simple[1].coords[0]).abs() < 1e-10);
1688        assert!((simple[1].coords[1] - 1.0).abs() < 1e-10);
1689        assert!((simple[1].coords[2] + 1.0).abs() < 1e-10);
1690    }
1691
1692    #[test]
1693    fn test_positive_roots_su3() {
1694        let rs = RootSystem::type_a(2);
1695        let positive = rs.positive_roots();
1696
1697        assert_eq!(positive.len(), 3);
1698        // Should have: e₁-e₂, e₂-e₃, e₁-e₃
1699    }
1700
1701    #[test]
1702    fn test_dominant_weight() {
1703        let rs = RootSystem::type_a(2);
1704
1705        // (1, 0, 0) should be dominant
1706        let weight1 = Root::new(vec![1.0, 0.0, 0.0]);
1707        assert!(rs.is_dominant_weight(&weight1));
1708
1709        // (-1, 0, 0) should not be dominant
1710        let weight2 = Root::new(vec![-1.0, 0.0, 0.0]);
1711        assert!(!rs.is_dominant_weight(&weight2));
1712    }
1713
1714    #[test]
1715    fn test_weight_lattice_su2() {
1716        let rs = RootSystem::type_a(1);
1717        let wl = WeightLattice::from_root_system(rs);
1718
1719        assert_eq!(wl.fundamental_weights().len(), 1);
1720
1721        // Spin-1 representation: Dynkin label [2]
1722        let weight = wl.dynkin_to_weight(&[2]);
1723        assert_eq!(weight.rank(), 2);
1724    }
1725
1726    #[test]
1727    fn test_weight_lattice_su3() {
1728        let rs = RootSystem::type_a(2);
1729        let wl = WeightLattice::from_root_system(rs);
1730
1731        assert_eq!(wl.fundamental_weights().len(), 2);
1732
1733        // Fundamental representation: Dynkin labels [1, 0]
1734        let fund = wl.dynkin_to_weight(&[1, 0]);
1735        assert_eq!(fund.rank(), 3);
1736
1737        // Adjoint representation: Dynkin labels [1, 1]
1738        let adj = wl.dynkin_to_weight(&[1, 1]);
1739        assert_eq!(adj.rank(), 3);
1740    }
1741
1742    #[test]
1743    fn test_weyl_orbit_su2() {
1744        let rs = RootSystem::type_a(1);
1745        let weight = Root::new(vec![1.0, 0.0]);
1746
1747        let orbit = rs.weyl_orbit(&weight);
1748        assert_eq!(orbit.len(), 2); // Weyl group S₂ has 2 elements
1749    }
1750
1751    #[test]
1752    fn test_weyl_dimension_formula() {
1753        let rs = RootSystem::type_a(2); // SU(3)
1754        let wl = WeightLattice::from_root_system(rs);
1755
1756        // Test known dimensions for SU(3)
1757        // Fundamental [1,0]: dim = 3
1758        let fund = wl.dynkin_to_weight(&[1, 0]);
1759        assert_eq!(wl.weyl_dimension(&fund), 3);
1760
1761        // Anti-fundamental [0,1]: dim = 3
1762        let antifund = wl.dynkin_to_weight(&[0, 1]);
1763        assert_eq!(wl.weyl_dimension(&antifund), 3);
1764
1765        // Adjoint [1,1]: dim = 8
1766        let adj = wl.dynkin_to_weight(&[1, 1]);
1767        assert_eq!(wl.weyl_dimension(&adj), 8);
1768
1769        // [2,0]: dim = 6
1770        let sym2 = wl.dynkin_to_weight(&[2, 0]);
1771        assert_eq!(wl.weyl_dimension(&sym2), 6);
1772
1773        // [0,2]: dim = 6
1774        let sym2_bar = wl.dynkin_to_weight(&[0, 2]);
1775        assert_eq!(wl.weyl_dimension(&sym2_bar), 6);
1776
1777        // [1,0,0,...] for SU(2) - trivial rep has dim = 1
1778        let rs2 = RootSystem::type_a(1); // SU(2)
1779        let wl2 = WeightLattice::from_root_system(rs2);
1780        let trivial = wl2.dynkin_to_weight(&[0]);
1781        assert_eq!(wl2.weyl_dimension(&trivial), 1);
1782
1783        // [2] for SU(2) - spin-1 has dim = 3
1784        let spin1 = wl2.dynkin_to_weight(&[2]);
1785        assert_eq!(wl2.weyl_dimension(&spin1), 3);
1786    }
1787
1788    #[test]
1789    fn test_weights_of_irrep_su3_fundamental() {
1790        let rs = RootSystem::type_a(2);
1791        let wl = WeightLattice::from_root_system(rs);
1792
1793        // Fundamental representation [1, 0] should have dimension 3
1794        let highest = wl.dynkin_to_weight(&[1, 0]);
1795        let weights = wl.weights_of_irrep(&highest);
1796
1797        // Check exact dimension (Weyl dimension formula)
1798        let expected_dim = wl.weyl_dimension(&highest);
1799        assert_eq!(expected_dim, 3, "Fundamental rep has dimension 3");
1800        assert_eq!(
1801            weights.len(),
1802            3,
1803            "Should find exactly 3 weights, found {}",
1804            weights.len()
1805        );
1806    }
1807
1808    #[test]
1809    fn test_weights_of_irrep_su3_adjoint() {
1810        let rs = RootSystem::type_a(2);
1811        let wl = WeightLattice::from_root_system(rs);
1812
1813        // Adjoint representation [1, 1] should have dimension 8
1814        let highest = wl.dynkin_to_weight(&[1, 1]);
1815        let weights = wl.weights_of_irrep(&highest);
1816
1817        // Check dimension (Weyl dimension formula)
1818        let dim = wl.weyl_dimension(&highest);
1819        assert_eq!(dim, 8, "Adjoint rep has dimension 8");
1820
1821        // With Kostant's formula, we now compute proper multiplicities
1822        // For SU(3) adjoint: 6 roots (mult 1) + 1 zero weight (mult 2) = dimension 8
1823        let total_dim: usize = weights.iter().map(|(_, m)| m).sum();
1824        assert_eq!(
1825            total_dim, 8,
1826            "Total dimension (sum of multiplicities) should be 8"
1827        );
1828    }
1829
1830    #[test]
1831    fn test_partition_function_basics() {
1832        // Test partition function on simple cases we can verify by hand
1833        let rs = RootSystem::type_a(1); // SU(2)
1834        let wl = WeightLattice::from_root_system(rs);
1835
1836        // SU(2) has one positive root: α = [1, -1]
1837        // Test P(0) = 1 (empty sum)
1838        let zero = Root::new(vec![0.0, 0.0]);
1839        assert_eq!(wl.kostant_partition_function(&zero), 1, "P(0) = 1");
1840
1841        // Test P(α) = 1 (just α itself)
1842        let alpha = Root::new(vec![1.0, -1.0]);
1843        assert_eq!(wl.kostant_partition_function(&alpha), 1, "P(α) = 1");
1844
1845        // Test P(2α) = 1 (just 2·α)
1846        let two_alpha = Root::new(vec![2.0, -2.0]);
1847        assert_eq!(wl.kostant_partition_function(&two_alpha), 1, "P(2α) = 1");
1848
1849        // Test P(-α) = 0 (negative, impossible)
1850        let neg_alpha = Root::new(vec![-1.0, 1.0]);
1851        assert_eq!(wl.kostant_partition_function(&neg_alpha), 0, "P(-α) = 0");
1852    }
1853
1854    #[test]
1855    fn test_partition_function_su3() {
1856        // SU(3) has 3 positive roots: α₁=[1,-1,0], α₂=[0,1,-1], α₁+α₂=[1,0,-1]
1857        let rs = RootSystem::type_a(2);
1858        let wl = WeightLattice::from_root_system(rs);
1859
1860        // Test P(0) = 1
1861        let zero = Root::new(vec![0.0, 0.0, 0.0]);
1862        assert_eq!(wl.kostant_partition_function(&zero), 1, "P(0) = 1");
1863
1864        // Test P(α₁) = 1
1865        let alpha1 = Root::new(vec![1.0, -1.0, 0.0]);
1866        let p_alpha1 = wl.kostant_partition_function(&alpha1);
1867        eprintln!("P(α₁) = {}", p_alpha1);
1868        assert_eq!(p_alpha1, 1, "P(α₁) = 1");
1869
1870        // Test P(α₁+α₂) = 2 (can write as α₁+α₂ OR as the root [1,0,-1])
1871        // Wait, [1,0,-1] IS α₁+α₂, so P(α₁+α₂) = 1
1872        let alpha1_plus_alpha2 = Root::new(vec![1.0, 0.0, -1.0]);
1873        let p_sum = wl.kostant_partition_function(&alpha1_plus_alpha2);
1874        eprintln!("P(α₁+α₂) = {}", p_sum);
1875        // This should be 1, not 2! Because [1,0,-1] is itself a positive root.
1876    }
1877
1878    #[test]
1879    fn test_kostant_dimension_invariant() {
1880        // Verify that Kostant's formula satisfies the dimension invariant:
1881        // Σ_λ m(λ) = dim(V_Λ) for any representation
1882        let rs = RootSystem::type_a(2); // SU(3)
1883        let wl = WeightLattice::from_root_system(rs);
1884
1885        // Test fundamental [1,0]: dimension 3
1886        let fund = wl.dynkin_to_weight(&[1, 0]);
1887        let fund_weights = wl.weights_of_irrep(&fund);
1888        eprintln!("Fundamental [1,0] weights:");
1889        for (w, m) in &fund_weights {
1890            eprintln!("  {:?} mult {}", w.coords, m);
1891        }
1892        let fund_dim: usize = fund_weights.iter().map(|(_, m)| m).sum();
1893        eprintln!("Total dimension: {} (expected 3)", fund_dim);
1894        assert_eq!(fund_dim, 3, "Fundamental rep dimension invariant");
1895
1896        // Test adjoint [1,1]: dimension 8
1897        let adj = wl.dynkin_to_weight(&[1, 1]);
1898        let adj_weights = wl.weights_of_irrep(&adj);
1899        eprintln!("\nAdjoint [1,1] weights:");
1900        for (w, m) in &adj_weights {
1901            eprintln!("  {:?} mult {}", w.coords, m);
1902        }
1903        let adj_dim: usize = adj_weights.iter().map(|(_, m)| m).sum();
1904        eprintln!("Total dimension: {} (expected 8)", adj_dim);
1905        assert_eq!(adj_dim, 8, "Adjoint rep dimension invariant");
1906
1907        // Test antifundamental [0,1]: dimension 3
1908        let anti = wl.dynkin_to_weight(&[0, 1]);
1909        let anti_weights = wl.weights_of_irrep(&anti);
1910        let anti_dim: usize = anti_weights.iter().map(|(_, m)| m).sum();
1911        assert_eq!(anti_dim, 3, "Antifundamental rep dimension invariant");
1912    }
1913
1914    #[test]
1915    fn test_weight_diagram_string_su3() {
1916        let rs = RootSystem::type_a(2);
1917        let wl = WeightLattice::from_root_system(rs);
1918
1919        let highest = wl.dynkin_to_weight(&[1, 0]);
1920        let diagram = wl.weight_diagram_string(&highest);
1921
1922        // Check that diagram contains expected elements
1923        assert!(diagram.contains("Weight diagram"));
1924        assert!(diagram.contains("Dimension"));
1925        assert!(diagram.contains("Weights"));
1926    }
1927
1928    #[test]
1929    fn test_weight_diagram_rank1_not_supported() {
1930        let rs = RootSystem::type_a(1);
1931        let wl = WeightLattice::from_root_system(rs);
1932
1933        let highest = wl.dynkin_to_weight(&[2]);
1934        let diagram = wl.weight_diagram_string(&highest);
1935
1936        assert!(diagram.contains("only implemented for rank 2"));
1937    }
1938
1939    // --- Cartan Subalgebra Tests ---
1940
1941    #[test]
1942    fn test_cartan_subalgebra_su2() {
1943        let cartan = CartanSubalgebra::type_a(1);
1944
1945        assert_eq!(cartan.dimension(), 1);
1946        assert_eq!(cartan.matrix_size(), 2);
1947        assert_eq!(cartan.basis_matrices().len(), 1);
1948
1949        // H₁ = diag(1, -1)
1950        let h1 = &cartan.basis_matrices()[0];
1951        assert!((h1[(0, 0)].re - 1.0).abs() < 1e-10);
1952        assert!((h1[(1, 1)].re + 1.0).abs() < 1e-10);
1953        assert!(h1[(0, 1)].norm() < 1e-10);
1954        assert!(h1[(1, 0)].norm() < 1e-10);
1955    }
1956
1957    #[test]
1958    fn test_cartan_subalgebra_su3() {
1959        let cartan = CartanSubalgebra::type_a(2);
1960
1961        assert_eq!(cartan.dimension(), 2);
1962        assert_eq!(cartan.matrix_size(), 3);
1963        assert_eq!(cartan.basis_matrices().len(), 2);
1964
1965        // H₁ = diag(1, -1, 0)
1966        let h1 = &cartan.basis_matrices()[0];
1967        assert!((h1[(0, 0)].re - 1.0).abs() < 1e-10);
1968        assert!((h1[(1, 1)].re + 1.0).abs() < 1e-10);
1969        assert!(h1[(2, 2)].norm() < 1e-10);
1970
1971        // H₂ = diag(0, 1, -1)
1972        let h2 = &cartan.basis_matrices()[1];
1973        assert!(h2[(0, 0)].norm() < 1e-10);
1974        assert!((h2[(1, 1)].re - 1.0).abs() < 1e-10);
1975        assert!((h2[(2, 2)].re + 1.0).abs() < 1e-10);
1976    }
1977
1978    #[test]
1979    fn test_cartan_basis_commutes() {
1980        let cartan = CartanSubalgebra::type_a(2);
1981
1982        // [H₁, H₂] = 0 (diagonal matrices commute)
1983        let h1 = &cartan.basis_matrices()[0];
1984        let h2 = &cartan.basis_matrices()[1];
1985
1986        let commutator = h1.dot(h2) - h2.dot(h1);
1987
1988        for val in commutator.iter() {
1989            assert!(val.norm() < 1e-10, "Cartan basis elements must commute");
1990        }
1991    }
1992
1993    #[test]
1994    fn test_cartan_dual_basis() {
1995        let cartan = CartanSubalgebra::type_a(2);
1996        let dual = cartan.dual_basis();
1997
1998        assert_eq!(dual.len(), 2);
1999
2000        // α₁ = e₁ - e₂
2001        assert!((dual[0].coords[0] - 1.0).abs() < 1e-10);
2002        assert!((dual[0].coords[1] + 1.0).abs() < 1e-10);
2003        assert!(dual[0].coords[2].abs() < 1e-10);
2004
2005        // α₂ = e₂ - e₃
2006        assert!(dual[1].coords[0].abs() < 1e-10);
2007        assert!((dual[1].coords[1] - 1.0).abs() < 1e-10);
2008        assert!((dual[1].coords[2] + 1.0).abs() < 1e-10);
2009
2010        // These should be the simple roots
2011        let rs = RootSystem::type_a(2);
2012        let simple_roots = rs.simple_roots();
2013
2014        for (d, s) in dual.iter().zip(simple_roots) {
2015            for (dc, sc) in d.coords.iter().zip(&s.coords) {
2016                assert!((dc - sc).abs() < 1e-10);
2017            }
2018        }
2019    }
2020
2021    #[test]
2022    fn test_cartan_evaluate_root() {
2023        let cartan = CartanSubalgebra::type_a(1);
2024
2025        // For SU(2), H = c₁ · diag(1, -1)
2026        // Root α = (1, -1) (the simple root)
2027        let root = Root::new(vec![1.0, -1.0]);
2028
2029        // α(H) = 1·c₁ + (-1)·(-c₁) = 2c₁
2030        let result = cartan.evaluate_root(&root, &[1.0]);
2031        assert!((result.re - 2.0).abs() < 1e-10);
2032        assert!(result.im.abs() < 1e-10);
2033
2034        let result2 = cartan.evaluate_root(&root, &[0.5]);
2035        assert!((result2.re - 1.0).abs() < 1e-10);
2036    }
2037
2038    #[test]
2039    fn test_cartan_from_coefficients() {
2040        let cartan = CartanSubalgebra::type_a(2);
2041
2042        // H = 2H₁ + 3H₂
2043        let h = cartan.from_coefficients(&[2.0, 3.0]);
2044
2045        // Should be diag(2, -2+3, -3) = diag(2, 1, -3)
2046        assert!((h[(0, 0)].re - 2.0).abs() < 1e-10);
2047        assert!((h[(1, 1)].re - 1.0).abs() < 1e-10);
2048        assert!((h[(2, 2)].re + 3.0).abs() < 1e-10);
2049
2050        // Check traceless
2051        let trace: Complex64 = (0..3).map(|i| h[(i, i)]).sum();
2052        assert!(trace.norm() < 1e-10);
2053    }
2054
2055    #[test]
2056    fn test_cartan_project_matrix() {
2057        let cartan = CartanSubalgebra::type_a(2);
2058
2059        // Create a diagonal matrix
2060        let mut mat = Array2::<Complex64>::zeros((3, 3));
2061        mat[(0, 0)] = Complex64::new(1.0, 0.0);
2062        mat[(1, 1)] = Complex64::new(-0.5, 0.0);
2063        mat[(2, 2)] = Complex64::new(-0.5, 0.0);
2064
2065        let coeffs = cartan
2066            .project_matrix(&mat)
2067            .expect("Rank 2 should be supported");
2068        assert_eq!(coeffs.len(), 2);
2069
2070        // Reconstruct and check
2071        let reconstructed = cartan.from_coefficients(&coeffs);
2072
2073        // Should reconstruct exactly for diagonal traceless matrices
2074        for i in 0..3 {
2075            let diff = (mat[(i, i)] - reconstructed[(i, i)]).norm();
2076            assert!(
2077                diff < 1e-10,
2078                "Diagonal projection should be exact: diff at ({},{}) = {}",
2079                i,
2080                i,
2081                diff
2082            );
2083        }
2084    }
2085
2086    #[test]
2087    fn test_cartan_contains() {
2088        let cartan = CartanSubalgebra::type_a(2);
2089
2090        // Diagonal traceless matrix - should be in Cartan
2091        let mut h = Array2::<Complex64>::zeros((3, 3));
2092        h[(0, 0)] = Complex64::new(1.0, 0.0);
2093        h[(1, 1)] = Complex64::new(-0.5, 0.0);
2094        h[(2, 2)] = Complex64::new(-0.5, 0.0);
2095
2096        assert!(cartan.contains(&h, 1e-6));
2097
2098        // Non-diagonal matrix - should not be in Cartan
2099        let mut not_h = Array2::<Complex64>::zeros((3, 3));
2100        not_h[(0, 1)] = Complex64::new(1.0, 0.0);
2101
2102        assert!(!cartan.contains(&not_h, 1e-6));
2103
2104        // Diagonal but not traceless - should not be in Cartan
2105        let mut not_traceless = Array2::<Complex64>::zeros((3, 3));
2106        not_traceless[(0, 0)] = Complex64::new(1.0, 0.0);
2107
2108        assert!(!cartan.contains(&not_traceless, 1e-6));
2109    }
2110
2111    #[test]
2112    fn test_cartan_killing_form() {
2113        let cartan = CartanSubalgebra::type_a(1);
2114
2115        // For SU(2), κ(H, H) = 2(2)·2 = 8 for H = H₁
2116        let killing = cartan.killing_form(&[1.0], &[1.0]);
2117        assert!((killing - 8.0).abs() < 1e-10);
2118
2119        // κ(H, 0) = 0
2120        let killing_zero = cartan.killing_form(&[1.0], &[0.0]);
2121        assert!(killing_zero.abs() < 1e-10);
2122
2123        // κ is symmetric
2124        let k12 = cartan.killing_form(&[1.0], &[2.0]);
2125        let k21 = cartan.killing_form(&[2.0], &[1.0]);
2126        assert!((k12 - k21).abs() < 1e-10);
2127    }
2128
2129    #[test]
2130    fn test_cartan_killing_form_su3() {
2131        let cartan = CartanSubalgebra::type_a(2);
2132
2133        // For SU(3), κ(Hᵢ, Hⱼ) = 2(3)·2δᵢⱼ = 12δᵢⱼ
2134        let k11 = cartan.killing_form(&[1.0, 0.0], &[1.0, 0.0]);
2135        assert!((k11 - 12.0).abs() < 1e-10);
2136
2137        let k22 = cartan.killing_form(&[0.0, 1.0], &[0.0, 1.0]);
2138        assert!((k22 - 12.0).abs() < 1e-10);
2139
2140        // Off-diagonal should be zero for orthogonal basis
2141        let k12 = cartan.killing_form(&[1.0, 0.0], &[0.0, 1.0]);
2142        assert!(k12.abs() < 1e-10);
2143    }
2144
2145    #[test]
2146    fn test_weyl_chamber_contains_su2() {
2147        let rs = RootSystem::type_a(1); // SU(2)
2148        let chamber = WeylChamber::fundamental(&rs);
2149
2150        // Simple root α = (1, -1)
2151        // Dominant weights satisfy ⟨λ, α⟩ ≥ 0
2152
2153        // Dominant weight: (1, 0)
2154        let dominant = Root::new(vec![1.0, 0.0]);
2155        assert!(chamber.contains(&dominant, false));
2156        assert!(chamber.contains(&dominant, true));
2157
2158        // Non-dominant: (-1, 0)
2159        let non_dominant = Root::new(vec![-1.0, 0.0]);
2160        assert!(!chamber.contains(&non_dominant, false));
2161
2162        // Boundary: (0, 0)
2163        let boundary = Root::new(vec![0.0, 0.0]);
2164        assert!(chamber.contains(&boundary, false)); // Non-strict allows boundary
2165        assert!(!chamber.contains(&boundary, true)); // Strict excludes boundary
2166    }
2167
2168    #[test]
2169    fn test_weyl_chamber_contains_su3() {
2170        let rs = RootSystem::type_a(2); // SU(3)
2171        let chamber = WeylChamber::fundamental(&rs);
2172
2173        // Simple roots: α₁ = (1, -1, 0), α₂ = (0, 1, -1)
2174        // Dominant: ⟨λ, α₁⟩ ≥ 0 and ⟨λ, α₂⟩ ≥ 0
2175
2176        // Fundamental weight ω₁ = (1, 0, 0) - 1/3 for traceless
2177        let omega1 = Root::new(vec![2.0 / 3.0, -1.0 / 3.0, -1.0 / 3.0]);
2178        assert!(chamber.contains(&omega1, false));
2179
2180        // Negative weight
2181        let neg = Root::new(vec![-1.0, 0.0, 1.0]);
2182        assert!(!chamber.contains(&neg, false));
2183
2184        // Origin
2185        let origin = Root::new(vec![0.0, 0.0, 0.0]);
2186        assert!(chamber.contains(&origin, false));
2187        assert!(!chamber.contains(&origin, true));
2188    }
2189
2190    #[test]
2191    fn test_weyl_chamber_project() {
2192        let rs = RootSystem::type_a(1); // SU(2)
2193        let chamber = WeylChamber::fundamental(&rs);
2194
2195        // Already dominant
2196        let dominant = Root::new(vec![1.0, -1.0]);
2197        let projected = chamber.project(&dominant);
2198        assert!((projected.coords[0] - dominant.coords[0]).abs() < 1e-10);
2199        assert!((projected.coords[1] - dominant.coords[1]).abs() < 1e-10);
2200
2201        // Non-dominant: should reflect to dominant
2202        let non_dominant = Root::new(vec![-1.0, 1.0]);
2203        let projected = chamber.project(&non_dominant);
2204
2205        // After reflection by α = (1, -1), should get (1, -1)
2206        assert!(chamber.contains(&projected, false));
2207        assert!((projected.coords[0] - 1.0).abs() < 1e-10);
2208        assert!((projected.coords[1] + 1.0).abs() < 1e-10);
2209    }
2210
2211    #[test]
2212    fn test_weyl_chamber_project_su3() {
2213        let rs = RootSystem::type_a(2); // SU(3)
2214        let chamber = WeylChamber::fundamental(&rs);
2215
2216        // Non-dominant weight
2217        let lambda = Root::new(vec![-1.0, 2.0, -1.0]);
2218        let projected = chamber.project(&lambda);
2219
2220        // Projected weight should be dominant
2221        assert!(chamber.contains(&projected, false));
2222
2223        // Check that projection preserves norm (Weyl group is orthogonal)
2224        let norm_before = lambda.norm_squared();
2225        let norm_after = projected.norm_squared();
2226        assert!((norm_before - norm_after).abs() < 1e-8);
2227    }
2228
2229    #[test]
2230    fn test_alcove_contains_su2() {
2231        let rs = RootSystem::type_a(1); // SU(2)
2232        let alcove = Alcove::fundamental(&rs);
2233
2234        // Highest root for SU(2): θ = (1, -1) (same as simple root)
2235        // Alcove: ⟨λ, α⟩ ≥ 0 and ⟨λ, θ⟩ ≤ 1
2236
2237        // Inside alcove: (0.4, -0.4)
2238        let inside = Root::new(vec![0.4, -0.4]);
2239        // ⟨inside, α⟩ = 0.4·1 + (-0.4)·(-1) = 0.8 ≥ 0 ✓
2240        // ⟨inside, θ⟩ = 0.4·1 + (-0.4)·(-1) = 0.8 ≤ 1 ✓
2241        assert!(alcove.contains(&inside, false));
2242
2243        // Outside alcove (too large): (1, -1)
2244        let outside = Root::new(vec![1.0, -1.0]);
2245        // ⟨outside, θ⟩ = 2 > 1
2246        assert!(!alcove.contains(&outside, false));
2247
2248        // Boundary: origin
2249        let origin = Root::new(vec![0.0, 0.0]);
2250        assert!(alcove.contains(&origin, false));
2251        assert!(!alcove.contains(&origin, true)); // Strict excludes boundary
2252    }
2253
2254    #[test]
2255    fn test_alcove_vertices_su2() {
2256        let rs = RootSystem::type_a(1); // SU(2)
2257        let alcove = Alcove::fundamental(&rs);
2258
2259        let vertices = alcove.vertices();
2260
2261        // For SU(2), fundamental alcove has 2 vertices: 0 and ω₁
2262        assert_eq!(vertices.len(), 2);
2263
2264        // First vertex: origin
2265        assert!(vertices[0].coords[0].abs() < 1e-10);
2266        assert!(vertices[0].coords[1].abs() < 1e-10);
2267
2268        // Second vertex: fundamental weight ω₁ = (1/2, -1/2)
2269        assert!((vertices[1].coords[0] - 0.5).abs() < 1e-10);
2270        assert!((vertices[1].coords[1] + 0.5).abs() < 1e-10);
2271    }
2272
2273    #[test]
2274    fn test_alcove_vertices_su3() {
2275        let rs = RootSystem::type_a(2); // SU(3)
2276        let alcove = Alcove::fundamental(&rs);
2277
2278        let vertices = alcove.vertices();
2279
2280        // For SU(3), fundamental alcove has 3 vertices: 0, ω₁, ω₂
2281        assert_eq!(vertices.len(), 3);
2282
2283        // First vertex: origin
2284        for &coord in &vertices[0].coords {
2285            assert!(coord.abs() < 1e-10);
2286        }
2287
2288        // Verify all vertices are in the alcove
2289        for vertex in &vertices {
2290            assert!(alcove.contains(vertex, false));
2291        }
2292    }
2293
2294    #[test]
2295    fn test_alcove_at_level() {
2296        let rs = RootSystem::type_a(1); // SU(2)
2297        let alcove_k2 = Alcove::at_level(&rs, 2.0);
2298
2299        assert_eq!(alcove_k2.level(), 2.0);
2300
2301        // At level 2, the upper bound is ⟨λ, θ⟩ ≤ 2
2302        let inside = Root::new(vec![0.8, -0.8]);
2303        // ⟨inside, θ⟩ = 1.6 ≤ 2 ✓
2304        assert!(alcove_k2.contains(&inside, false));
2305
2306        let outside = Root::new(vec![1.5, -1.5]);
2307        // ⟨outside, θ⟩ = 3 > 2
2308        assert!(!alcove_k2.contains(&outside, false));
2309    }
2310
2311    #[test]
2312    fn test_alcove_highest_root() {
2313        let rs = RootSystem::type_a(2); // SU(3)
2314        let alcove = Alcove::fundamental(&rs);
2315
2316        let theta = alcove.highest_root();
2317
2318        // For SU(3), highest root is θ = α₁ + α₂ = (1, 0, -1)
2319        assert!((theta.coords[0] - 1.0).abs() < 1e-10);
2320        assert!(theta.coords[1].abs() < 1e-10);
2321        assert!((theta.coords[2] + 1.0).abs() < 1e-10);
2322
2323        // Verify it's the longest root
2324        let theta_norm = theta.norm_squared();
2325        for root in rs.positive_roots() {
2326            assert!(root.norm_squared() <= theta_norm + 1e-10);
2327        }
2328    }
2329
2330    // --- Simple Root Expansion Tests ---
2331
2332    #[test]
2333    fn test_simple_root_expansion_su2() {
2334        let rs = RootSystem::type_a(1); // SU(2)
2335
2336        // SU(2) has one simple root: α₁ = e₀ - e₁ = (1, -1)
2337        // The only positive root is α₁ itself
2338
2339        // Test the simple root itself: α₁ = 1·α₁
2340        let alpha1 = Root::new(vec![1.0, -1.0]);
2341        let coeffs = rs.simple_root_expansion(&alpha1);
2342        assert_eq!(coeffs, Some(vec![1]));
2343
2344        // Test the negative root: -α₁ = (-1)·α₁
2345        let neg_alpha1 = Root::new(vec![-1.0, 1.0]);
2346        let coeffs_neg = rs.simple_root_expansion(&neg_alpha1);
2347        assert_eq!(coeffs_neg, Some(vec![-1]));
2348    }
2349
2350    #[test]
2351    fn test_simple_root_expansion_su3() {
2352        let rs = RootSystem::type_a(2); // SU(3)
2353
2354        // SU(3) has 2 simple roots:
2355        // α₁ = e₀ - e₁ = (1, -1, 0)
2356        // α₂ = e₁ - e₂ = (0, 1, -1)
2357        // And a third positive root: α₁ + α₂ = e₀ - e₂ = (1, 0, -1)
2358
2359        // Test α₁ = (1, -1, 0): coefficients [1, 0]
2360        let alpha1 = Root::new(vec![1.0, -1.0, 0.0]);
2361        assert_eq!(rs.simple_root_expansion(&alpha1), Some(vec![1, 0]));
2362
2363        // Test α₂ = (0, 1, -1): coefficients [0, 1]
2364        let alpha2 = Root::new(vec![0.0, 1.0, -1.0]);
2365        assert_eq!(rs.simple_root_expansion(&alpha2), Some(vec![0, 1]));
2366
2367        // Test α₁ + α₂ = (1, 0, -1): coefficients [1, 1]
2368        let alpha1_plus_alpha2 = Root::new(vec![1.0, 0.0, -1.0]);
2369        assert_eq!(
2370            rs.simple_root_expansion(&alpha1_plus_alpha2),
2371            Some(vec![1, 1])
2372        );
2373
2374        // Test negative roots
2375        let neg_alpha1 = Root::new(vec![-1.0, 1.0, 0.0]);
2376        assert_eq!(rs.simple_root_expansion(&neg_alpha1), Some(vec![-1, 0]));
2377
2378        let neg_highest = Root::new(vec![-1.0, 0.0, 1.0]);
2379        assert_eq!(rs.simple_root_expansion(&neg_highest), Some(vec![-1, -1]));
2380    }
2381
2382    #[test]
2383    fn test_simple_root_expansion_su4() {
2384        let rs = RootSystem::type_a(3); // SU(4)
2385
2386        // SU(4) has 3 simple roots:
2387        // α₁ = e₀ - e₁ = (1, -1, 0, 0)
2388        // α₂ = e₁ - e₂ = (0, 1, -1, 0)
2389        // α₃ = e₂ - e₃ = (0, 0, 1, -1)
2390
2391        // Test simple roots
2392        let alpha1 = Root::new(vec![1.0, -1.0, 0.0, 0.0]);
2393        assert_eq!(rs.simple_root_expansion(&alpha1), Some(vec![1, 0, 0]));
2394
2395        let alpha2 = Root::new(vec![0.0, 1.0, -1.0, 0.0]);
2396        assert_eq!(rs.simple_root_expansion(&alpha2), Some(vec![0, 1, 0]));
2397
2398        let alpha3 = Root::new(vec![0.0, 0.0, 1.0, -1.0]);
2399        assert_eq!(rs.simple_root_expansion(&alpha3), Some(vec![0, 0, 1]));
2400
2401        // Test e₀ - e₂ = α₁ + α₂: coefficients [1, 1, 0]
2402        let e0_minus_e2 = Root::new(vec![1.0, 0.0, -1.0, 0.0]);
2403        assert_eq!(rs.simple_root_expansion(&e0_minus_e2), Some(vec![1, 1, 0]));
2404
2405        // Test e₁ - e₃ = α₂ + α₃: coefficients [0, 1, 1]
2406        let e1_minus_e3 = Root::new(vec![0.0, 1.0, 0.0, -1.0]);
2407        assert_eq!(rs.simple_root_expansion(&e1_minus_e3), Some(vec![0, 1, 1]));
2408
2409        // Test highest root e₀ - e₃ = α₁ + α₂ + α₃: coefficients [1, 1, 1]
2410        let highest = Root::new(vec![1.0, 0.0, 0.0, -1.0]);
2411        assert_eq!(rs.simple_root_expansion(&highest), Some(vec![1, 1, 1]));
2412
2413        // Test negative of highest root: coefficients [-1, -1, -1]
2414        let neg_highest = Root::new(vec![-1.0, 0.0, 0.0, 1.0]);
2415        assert_eq!(
2416            rs.simple_root_expansion(&neg_highest),
2417            Some(vec![-1, -1, -1])
2418        );
2419    }
2420
2421    #[test]
2422    fn test_simple_root_expansion_not_a_root() {
2423        let rs = RootSystem::type_a(2); // SU(3)
2424
2425        // Test a vector that is not a root
2426        let not_a_root = Root::new(vec![1.0, 1.0, -2.0]);
2427        assert_eq!(rs.simple_root_expansion(&not_a_root), None);
2428
2429        // Test zero vector
2430        let zero = Root::new(vec![0.0, 0.0, 0.0]);
2431        assert_eq!(rs.simple_root_expansion(&zero), None);
2432    }
2433
2434    #[test]
2435    fn test_simple_root_expansion_roundtrip() {
2436        // Verify that we can reconstruct roots from their expansions
2437        let rs = RootSystem::type_a(2); // SU(3)
2438
2439        for root in rs.roots() {
2440            let coeffs = rs.simple_root_expansion(root);
2441            assert!(coeffs.is_some(), "All roots should have expansions");
2442
2443            let coeffs = coeffs.unwrap();
2444            let simple = rs.simple_roots();
2445
2446            // Reconstruct: Σ cᵢ αᵢ
2447            let mut reconstructed = vec![0.0; root.coords.len()];
2448            for (i, &c) in coeffs.iter().enumerate() {
2449                for (j, &coord) in simple[i].coords.iter().enumerate() {
2450                    reconstructed[j] += (c as f64) * coord;
2451                }
2452            }
2453
2454            // Compare with original
2455            for (orig, recon) in root.coords.iter().zip(&reconstructed) {
2456                assert!(
2457                    (orig - recon).abs() < 1e-10,
2458                    "Reconstruction failed for root {:?}: expected {:?}, got {:?}",
2459                    root.coords,
2460                    root.coords,
2461                    reconstructed
2462                );
2463            }
2464        }
2465    }
2466}