use ndarray::Array2;
use num_complex::Complex64;
use std::collections::HashSet;
use std::fmt::{self, Write};
#[derive(Debug, Clone, PartialEq)]
pub struct Root {
pub(crate) coords: Vec<f64>,
}
impl Root {
#[must_use]
pub fn new(coords: Vec<f64>) -> Self {
Self { coords }
}
#[must_use]
pub fn coords(&self) -> &[f64] {
&self.coords
}
#[must_use]
pub fn rank(&self) -> usize {
self.coords.len()
}
#[must_use]
pub fn inner_product(&self, other: &Root) -> f64 {
assert_eq!(self.rank(), other.rank());
self.coords
.iter()
.zip(&other.coords)
.map(|(a, b)| a * b)
.sum()
}
#[must_use]
pub fn norm_squared(&self) -> f64 {
self.inner_product(self)
}
#[must_use]
pub fn reflect(&self, beta: &Root) -> Root {
let factor = 2.0 * self.inner_product(beta) / self.norm_squared();
Root::new(
beta.coords
.iter()
.zip(&self.coords)
.map(|(b, a)| b - factor * a)
.collect(),
)
}
#[inline]
#[must_use]
pub fn cartan_integer(&self, beta: &Root) -> i32 {
let value = 2.0 * self.inner_product(beta) / self.norm_squared();
value.round() as i32
}
#[must_use]
pub fn is_positive(&self) -> bool {
for &c in &self.coords {
if c.abs() > 1e-10 {
return c > 0.0;
}
}
false }
#[must_use]
pub fn negate(&self) -> Root {
Root::new(self.coords.iter().map(|c| -c).collect())
}
}
impl fmt::Display for Root {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "(")?;
for (i, c) in self.coords.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{:.2}", c)?;
}
write!(f, ")")
}
}
#[derive(Debug, Clone)]
pub struct RootSystem {
rank: usize,
roots: Vec<Root>,
simple_roots: Vec<Root>,
cartan_matrix: Vec<Vec<i32>>,
}
impl RootSystem {
#[must_use]
pub fn type_a(n: usize) -> Self {
assert!(n >= 1, "Type A_n requires n >= 1");
let rank = n;
let mut simple_roots = Vec::new();
for i in 0..n {
let mut coords = vec![0.0; n + 1];
coords[i] = 1.0;
coords[i + 1] = -1.0;
simple_roots.push(Root::new(coords));
}
let mut positive_roots = Vec::new();
for i in 0..=n {
for j in (i + 1)..=n {
let mut coords = vec![0.0; n + 1];
coords[i] = 1.0;
coords[j] = -1.0;
positive_roots.push(Root::new(coords));
}
}
let mut roots = positive_roots.clone();
for root in &positive_roots {
roots.push(root.negate());
}
let cartan_matrix = simple_roots
.iter()
.map(|alpha_i| {
simple_roots
.iter()
.map(|alpha_j| alpha_i.cartan_integer(alpha_j))
.collect()
})
.collect();
Self {
rank,
roots,
simple_roots,
cartan_matrix,
}
}
#[must_use]
pub fn rank(&self) -> usize {
self.rank
}
#[must_use]
pub fn roots(&self) -> &[Root] {
&self.roots
}
#[must_use]
pub fn simple_roots(&self) -> &[Root] {
&self.simple_roots
}
#[must_use]
pub fn cartan_matrix(&self) -> &[Vec<i32>] {
&self.cartan_matrix
}
#[must_use]
pub fn num_roots(&self) -> usize {
self.roots.len()
}
#[must_use]
pub fn num_positive_roots(&self) -> usize {
self.roots.iter().filter(|r| r.is_positive()).count()
}
#[must_use]
pub fn positive_roots(&self) -> Vec<Root> {
self.roots
.iter()
.filter(|r| r.is_positive())
.cloned()
.collect()
}
#[must_use]
pub fn highest_root(&self) -> Root {
let n = self.rank;
let mut coords = vec![0.0; n + 1];
for simple_root in &self.simple_roots {
for (i, &coord) in simple_root.coords.iter().enumerate() {
coords[i] += coord;
}
}
Root::new(coords)
}
#[must_use]
pub fn contains_root(&self, root: &Root) -> bool {
self.roots.iter().any(|r| {
r.coords.len() == root.coords.len()
&& r.coords
.iter()
.zip(root.coords.iter())
.all(|(a, b)| (a - b).abs() < 1e-10)
})
}
#[must_use]
pub fn weyl_reflection(&self, alpha: &Root, beta: &Root) -> Root {
alpha.reflect(beta)
}
#[must_use]
pub fn weyl_orbit(&self, weight: &Root) -> Vec<Root> {
let mut orbit = vec![weight.clone()];
let mut seen = HashSet::new();
seen.insert(format!("{:?}", weight.coords));
let mut queue = vec![weight.clone()];
while let Some(current) = queue.pop() {
for simple_root in &self.simple_roots {
let reflected = simple_root.reflect(¤t);
let key = format!("{:?}", reflected.coords);
if !seen.contains(&key) {
seen.insert(key);
orbit.push(reflected.clone());
queue.push(reflected);
}
}
}
orbit
}
#[must_use]
pub fn dimension(&self) -> usize {
self.rank + self.num_roots()
}
#[must_use]
pub fn is_dominant_weight(&self, weight: &Root) -> bool {
self.simple_roots
.iter()
.all(|alpha| weight.inner_product(alpha) >= -1e-10)
}
pub fn simple_root_expansion(&self, root: &Root) -> Option<Vec<i32>> {
if !self.contains_root(root) {
return None;
}
let coords = &root.coords;
let mut pos_idx: Option<usize> = None;
let mut neg_idx: Option<usize> = None;
for (idx, &val) in coords.iter().enumerate() {
if (val - 1.0).abs() < 1e-10 {
if pos_idx.is_some() {
return None;
}
pos_idx = Some(idx);
} else if (val + 1.0).abs() < 1e-10 {
if neg_idx.is_some() {
return None;
}
neg_idx = Some(idx);
} else if val.abs() > 1e-10 {
return None;
}
}
let (Some(i), Some(j)) = (pos_idx, neg_idx) else {
return None;
};
let mut coeffs = vec![0i32; self.rank];
let (min_idx, max_idx) = if i < j { (i, j) } else { (j, i) };
let sign = if i < j { 1 } else { -1 };
for k in min_idx..max_idx {
if k < self.rank {
coeffs[k] = sign;
}
}
Some(coeffs)
}
}
pub struct WeightLattice {
root_system: RootSystem,
fundamental_weights: Vec<Root>,
}
impl WeightLattice {
#[must_use]
pub fn from_root_system(root_system: RootSystem) -> Self {
let fundamental_weights = Self::compute_fundamental_weights(&root_system);
Self {
root_system,
fundamental_weights,
}
}
fn compute_fundamental_weights(rs: &RootSystem) -> Vec<Root> {
let n = rs.rank();
let dim = n + 1; let mut weights = Vec::new();
for i in 0..n {
let mut coords = vec![0.0; dim];
for j in 0..=i {
coords[j] = 1.0;
}
let sum: f64 = coords.iter().sum();
let mean = sum / (dim as f64);
for coord in &mut coords {
*coord -= mean;
}
weights.push(Root::new(coords));
}
weights
}
#[must_use]
pub fn fundamental_weights(&self) -> &[Root] {
&self.fundamental_weights
}
#[must_use]
pub fn dynkin_to_weight(&self, dynkin_labels: &[usize]) -> Root {
assert_eq!(dynkin_labels.len(), self.root_system.rank());
let mut weight_coords = vec![0.0; self.root_system.rank() + 1];
for (i, &m) in dynkin_labels.iter().enumerate() {
for (j, &w) in self.fundamental_weights[i].coords.iter().enumerate() {
weight_coords[j] += (m as f64) * w;
}
}
Root::new(weight_coords)
}
#[must_use]
pub fn root_system(&self) -> &RootSystem {
&self.root_system
}
#[must_use]
pub fn rho(&self) -> Root {
let mut rho_coords = vec![0.0; self.root_system.rank() + 1];
for omega in &self.fundamental_weights {
for (i, &coord) in omega.coords.iter().enumerate() {
rho_coords[i] += coord;
}
}
Root::new(rho_coords)
}
fn kostant_partition_function(&self, gamma: &Root) -> usize {
use std::collections::HashMap;
if gamma.coords.iter().all(|&x| x.abs() < 1e-10) {
return 1;
}
let mut memo: HashMap<(String, usize), usize> = HashMap::new();
let positive_roots: Vec<Root> = self.root_system.positive_roots();
Self::partition_helper_ordered(gamma, &positive_roots, 0, &mut memo)
}
fn partition_helper_ordered(
gamma: &Root,
positive_roots: &[Root],
start_idx: usize,
memo: &mut std::collections::HashMap<(String, usize), usize>,
) -> usize {
if gamma.coords.iter().all(|&x| x.abs() < 1e-10) {
return 1;
}
if start_idx >= positive_roots.len() {
return 0;
}
let key = (WeightLattice::weight_key(&gamma.coords), start_idx);
if let Some(&result) = memo.get(&key) {
return result;
}
let mut has_large_negative = false;
for &coord in &gamma.coords {
if coord < -100.0 {
has_large_negative = true;
break;
}
}
if has_large_negative {
memo.insert(key, 0);
return 0;
}
let mut count = 0;
let alpha = &positive_roots[start_idx];
if memo.len() < 100_000 {
count += Self::partition_helper_ordered(gamma, positive_roots, start_idx + 1, memo);
}
let mut gamma_minus_k_alpha = gamma.clone();
for (i, &a) in alpha.coords.iter().enumerate() {
gamma_minus_k_alpha.coords[i] -= a;
}
let can_use = gamma_minus_k_alpha.coords.iter().all(|&x| x > -100.0);
if can_use && memo.len() < 100_000 {
count += Self::partition_helper_ordered(
&gamma_minus_k_alpha,
positive_roots,
start_idx,
memo,
);
}
memo.insert(key, count);
count
}
fn weyl_group_type_a(&self) -> Vec<(Vec<usize>, i32)> {
let n_plus_1 = self.root_system.rank() + 1;
let mut perms = Vec::new();
let mut current: Vec<usize> = (0..n_plus_1).collect();
Self::generate_permutations(&mut current, n_plus_1, &mut perms);
perms
}
fn generate_permutations(arr: &mut [usize], size: usize, result: &mut Vec<(Vec<usize>, i32)>) {
if size == 1 {
let perm = arr.to_vec();
let sign = Self::permutation_sign(&perm);
result.push((perm, sign));
return;
}
for i in 0..size {
Self::generate_permutations(arr, size - 1, result);
if size % 2 == 0 {
arr.swap(i, size - 1);
} else {
arr.swap(0, size - 1);
}
}
}
fn permutation_sign(perm: &[usize]) -> i32 {
let n = perm.len();
let mut sign = 1i32;
for i in 0..n {
for j in (i + 1)..n {
if perm[i] > perm[j] {
sign *= -1;
}
}
}
sign
}
fn weyl_action(&self, weight: &Root, permutation: &[usize]) -> Root {
let mut new_coords = vec![0.0; weight.coords.len()];
for (i, &pi) in permutation.iter().enumerate() {
new_coords[i] = weight.coords[pi];
}
Root::new(new_coords)
}
#[must_use]
pub fn kostant_multiplicity(&self, highest_weight: &Root, weight: &Root) -> usize {
let rho = self.rho();
let mut lambda_plus_rho = highest_weight.clone();
for (i, &r) in rho.coords.iter().enumerate() {
lambda_plus_rho.coords[i] += r;
}
let mut mu_plus_rho = weight.clone();
for (i, &r) in rho.coords.iter().enumerate() {
mu_plus_rho.coords[i] += r;
}
let weyl_group = self.weyl_group_type_a();
let mut multiplicity = 0i32;
for (perm, sign) in weyl_group {
let w_lambda_plus_rho = self.weyl_action(&lambda_plus_rho, &perm);
let mut gamma = w_lambda_plus_rho.clone();
for (i, &mu) in mu_plus_rho.coords.iter().enumerate() {
gamma.coords[i] -= mu;
}
let p_gamma = self.kostant_partition_function(&gamma);
multiplicity += sign * (p_gamma as i32);
}
assert!(
multiplicity >= 0,
"Kostant multiplicity must be non-negative, got {}",
multiplicity
);
multiplicity as usize
}
#[must_use]
pub fn weyl_dimension(&self, highest_weight: &Root) -> usize {
let rho = self.rho();
let mut lambda_plus_rho = highest_weight.clone();
for (i, &r) in rho.coords.iter().enumerate() {
lambda_plus_rho.coords[i] += r;
}
let mut numerator = 1.0;
let mut denominator = 1.0;
for alpha in self.root_system.positive_roots() {
let num = lambda_plus_rho.inner_product(&alpha);
let denom = rho.inner_product(&alpha);
numerator *= num;
denominator *= denom;
}
(numerator / denominator).round() as usize
}
fn weight_key(coords: &[f64]) -> String {
let rounded: Vec<String> = coords.iter().map(|&x| format!("{:.10}", x)).collect();
format!("[{}]", rounded.join(", "))
}
#[must_use]
pub fn weights_of_irrep(&self, highest_weight: &Root) -> Vec<(Root, usize)> {
use std::collections::{HashMap, VecDeque};
let mut candidates: HashMap<String, Root> = HashMap::new();
let mut queue: VecDeque<Root> = VecDeque::new();
let hw_key = Self::weight_key(&highest_weight.coords);
candidates.insert(hw_key, highest_weight.clone());
queue.push_back(highest_weight.clone());
while let Some(weight) = queue.pop_front() {
for pos_root in self.root_system.positive_roots() {
let mut new_weight = weight.clone();
for (i, &a) in pos_root.coords.iter().enumerate() {
new_weight.coords[i] -= a;
}
let new_key = Self::weight_key(&new_weight.coords);
if candidates.contains_key(&new_key) {
continue;
}
let norm = new_weight.norm_squared();
let hw_norm = highest_weight.norm_squared();
if norm > 3.0 * hw_norm + 10.0 {
continue;
}
candidates.insert(new_key, new_weight.clone());
queue.push_back(new_weight);
if candidates.len() > 1000 {
break;
}
}
if candidates.len() > 1000 {
break;
}
}
let weyl_orbit = self.root_system.weyl_orbit(highest_weight);
for w in weyl_orbit {
let key = Self::weight_key(&w.coords);
candidates.insert(key, w);
}
let candidate_list: Vec<Root> = candidates.into_values().collect();
let mut result = Vec::new();
for weight in candidate_list.iter() {
let mult = self.kostant_multiplicity(highest_weight, weight);
if mult > 0 {
result.push((weight.clone(), mult));
}
}
result
}
#[must_use]
pub fn weight_diagram_string(&self, highest_weight: &Root) -> String {
if self.root_system.rank() != 2 {
return "Weight diagrams only implemented for rank 2".to_string();
}
let weights = self.weights_of_irrep(highest_weight);
let mut output = String::new();
writeln!(
&mut output,
"Weight diagram for highest weight {:?}",
highest_weight.coords
)
.expect("String formatting should not fail");
writeln!(&mut output, "Dimension: {}", weights.len())
.expect("String formatting should not fail");
output.push_str("Weights (multiplicity):\n");
for (weight, mult) in &weights {
writeln!(&mut output, " {} (×{})", weight, mult)
.expect("String formatting should not fail");
}
output
}
}
pub struct CartanSubalgebra {
dimension: usize,
basis_matrices: Vec<Array2<Complex64>>,
matrix_size: usize,
}
impl CartanSubalgebra {
#[must_use]
pub fn type_a(n: usize) -> Self {
assert!(n >= 1, "Type A_n requires n >= 1");
let matrix_size = n + 1;
let dimension = n;
let mut basis_matrices = Vec::new();
for i in 0..n {
let mut h = Array2::<Complex64>::zeros((matrix_size, matrix_size));
h[(i, i)] = Complex64::new(1.0, 0.0);
h[(i + 1, i + 1)] = Complex64::new(-1.0, 0.0);
basis_matrices.push(h);
}
Self {
dimension,
basis_matrices,
matrix_size,
}
}
#[must_use]
pub fn dimension(&self) -> usize {
self.dimension
}
#[must_use]
pub fn basis_matrices(&self) -> &[Array2<Complex64>] {
&self.basis_matrices
}
#[must_use]
pub fn matrix_size(&self) -> usize {
self.matrix_size
}
#[must_use]
pub fn evaluate_root(&self, root: &Root, coefficients: &[f64]) -> Complex64 {
assert_eq!(
coefficients.len(),
self.dimension,
"Coefficient vector must match Cartan dimension"
);
assert_eq!(
root.rank(),
self.matrix_size,
"Root dimension must match matrix size"
);
let mut h_diagonal = vec![Complex64::new(0.0, 0.0); self.matrix_size];
for (i, &c) in coefficients.iter().enumerate() {
h_diagonal[i] += Complex64::new(c, 0.0);
h_diagonal[i + 1] -= Complex64::new(c, 0.0);
}
root.coords
.iter()
.zip(&h_diagonal)
.map(|(alpha_i, h_i)| h_i * alpha_i)
.sum()
}
#[must_use]
pub fn project_matrix(&self, matrix: &Array2<Complex64>) -> Option<Vec<f64>> {
assert_eq!(
matrix.shape(),
&[self.matrix_size, self.matrix_size],
"Matrix must be {}×{}",
self.matrix_size,
self.matrix_size
);
let n = self.dimension;
let mut gram = vec![vec![0.0; n]; n];
let mut rhs = vec![0.0; n];
for i in 0..n {
for j in 0..n {
let inner: Complex64 = self.basis_matrices[i]
.iter()
.zip(self.basis_matrices[j].iter())
.map(|(h_i, h_j)| h_i.conj() * h_j)
.sum();
gram[i][j] = inner.re;
}
let inner: Complex64 = matrix
.iter()
.zip(self.basis_matrices[i].iter())
.map(|(m_ij, h_ij)| m_ij.conj() * h_ij)
.sum();
rhs[i] = inner.re;
}
let mut coefficients = vec![0.0; n];
match n {
1 => {
coefficients[0] = rhs[0] / gram[0][0];
Some(coefficients)
}
2 => {
let det = gram[0][0] * gram[1][1] - gram[0][1] * gram[1][0];
coefficients[0] = (rhs[0] * gram[1][1] - rhs[1] * gram[0][1]) / det;
coefficients[1] = (gram[0][0] * rhs[1] - gram[1][0] * rhs[0]) / det;
Some(coefficients)
}
_ => {
None
}
}
}
#[must_use]
pub fn from_coefficients(&self, coefficients: &[f64]) -> Array2<Complex64> {
assert_eq!(
coefficients.len(),
self.dimension,
"Must provide {} coefficients",
self.dimension
);
let mut result = Array2::<Complex64>::zeros((self.matrix_size, self.matrix_size));
for (&c_i, h_i) in coefficients.iter().zip(&self.basis_matrices) {
result = result + h_i * c_i;
}
result
}
#[must_use]
pub fn contains(&self, matrix: &Array2<Complex64>, tolerance: f64) -> bool {
assert_eq!(
matrix.shape(),
&[self.matrix_size, self.matrix_size],
"Matrix must be {}×{}",
self.matrix_size,
self.matrix_size
);
for i in 0..self.matrix_size {
for j in 0..self.matrix_size {
if i != j && matrix[(i, j)].norm() > tolerance {
return false;
}
}
}
let trace: Complex64 = (0..self.matrix_size).map(|i| matrix[(i, i)]).sum();
trace.norm() < tolerance
}
#[must_use]
pub fn killing_form(&self, coeffs1: &[f64], coeffs2: &[f64]) -> f64 {
assert_eq!(coeffs1.len(), self.dimension);
assert_eq!(coeffs2.len(), self.dimension);
let n_plus_1 = self.matrix_size as f64;
coeffs1
.iter()
.zip(coeffs2)
.map(|(c1, c2)| 2.0 * n_plus_1 * 2.0 * c1 * c2)
.sum()
}
#[must_use]
pub fn dual_basis(&self) -> Vec<Root> {
let mut dual_roots = Vec::new();
for i in 0..self.dimension {
let mut coords = vec![0.0; self.matrix_size];
coords[i] = 1.0;
coords[i + 1] = -1.0;
dual_roots.push(Root::new(coords));
}
dual_roots
}
}
#[derive(Debug, Clone)]
pub struct WeylChamber {
root_system: RootSystem,
simple_roots: Vec<Root>,
}
impl WeylChamber {
#[must_use]
pub fn fundamental(root_system: &RootSystem) -> Self {
Self {
root_system: root_system.clone(),
simple_roots: root_system.simple_roots.clone(),
}
}
#[must_use]
pub fn contains(&self, weight: &Root, strict: bool) -> bool {
assert_eq!(weight.rank(), self.root_system.rank + 1);
for alpha in &self.simple_roots {
let pairing = weight.inner_product(alpha);
if strict {
if pairing <= 1e-10 {
return false;
}
} else if pairing < -1e-10 {
return false;
}
}
true
}
#[must_use]
pub fn project(&self, weight: &Root) -> Root {
let mut current = weight.clone();
let max_iterations = 100;
let mut iterations = 0;
while iterations < max_iterations {
let mut found_violation = false;
for alpha in &self.simple_roots {
let pairing = current.inner_product(alpha);
if pairing < -1e-10 {
current = alpha.reflect(¤t);
found_violation = true;
}
}
if !found_violation {
break;
}
iterations += 1;
}
if iterations >= max_iterations {
eprintln!(
"Warning: WeylChamber::project did not converge in {} iterations",
max_iterations
);
}
current
}
#[must_use]
pub fn simple_roots(&self) -> &[Root] {
&self.simple_roots
}
}
#[derive(Debug, Clone)]
pub struct Alcove {
root_system: RootSystem,
simple_roots: Vec<Root>,
highest_root: Root,
level: f64,
}
impl Alcove {
#[must_use]
pub fn fundamental(root_system: &RootSystem) -> Self {
let simple_roots = root_system.simple_roots.clone();
let highest_root = root_system.highest_root();
Self {
root_system: root_system.clone(),
simple_roots,
highest_root,
level: 1.0,
}
}
#[must_use]
pub fn at_level(root_system: &RootSystem, level: f64) -> Self {
assert!(level > 0.0, "Level must be positive");
let simple_roots = root_system.simple_roots.clone();
let highest_root = root_system.highest_root();
Self {
root_system: root_system.clone(),
simple_roots,
highest_root,
level,
}
}
#[must_use]
pub fn contains(&self, weight: &Root, strict: bool) -> bool {
assert_eq!(weight.rank(), self.root_system.rank + 1);
for alpha in &self.simple_roots {
let pairing = weight.inner_product(alpha);
if strict {
if pairing <= 1e-10 {
return false;
}
} else if pairing < -1e-10 {
return false;
}
}
let pairing_highest = weight.inner_product(&self.highest_root);
if strict {
if pairing_highest >= self.level - 1e-10 {
return false;
}
} else if pairing_highest > self.level + 1e-10 {
return false;
}
true
}
#[must_use]
pub fn level(&self) -> f64 {
self.level
}
#[must_use]
pub fn highest_root(&self) -> &Root {
&self.highest_root
}
#[must_use]
pub fn vertices(&self) -> Vec<Root> {
if self.level != 1.0 {
return vec![];
}
let mut vertices = vec![Root::new(vec![0.0; self.root_system.rank + 1])];
let n = self.root_system.rank;
for i in 1..=n {
let mut coords = vec![0.0; n + 1];
for j in 0..i {
coords[j] = (n + 1 - i) as f64 / (n + 1) as f64;
}
for j in i..=n {
coords[j] = -(i as f64) / (n + 1) as f64;
}
vertices.push(Root::new(coords));
}
vertices
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_root_operations() {
let alpha = Root::new(vec![1.0, -1.0, 0.0]);
let beta = Root::new(vec![0.0, 1.0, -1.0]);
assert_eq!(alpha.rank(), 3);
assert!((alpha.norm_squared() - 2.0).abs() < 1e-10);
assert!((alpha.inner_product(&beta) + 1.0).abs() < 1e-10);
assert!(alpha.is_positive());
assert!(!alpha.negate().is_positive());
}
#[test]
fn test_weyl_reflection() {
let alpha = Root::new(vec![1.0, -1.0]);
let beta = Root::new(vec![0.5, 0.5]);
let reflected = alpha.reflect(&beta);
assert!((reflected.coords[0] - 0.5).abs() < 1e-10);
assert!((reflected.coords[1] - 0.5).abs() < 1e-10);
}
#[test]
fn test_cartan_integer() {
let alpha = Root::new(vec![1.0, -1.0, 0.0]);
let beta = Root::new(vec![0.0, 1.0, -1.0]);
assert_eq!(alpha.cartan_integer(&beta), -1);
assert_eq!(beta.cartan_integer(&alpha), -1);
}
#[test]
fn test_type_a1_su2() {
let rs = RootSystem::type_a(1);
assert_eq!(rs.rank(), 1);
assert_eq!(rs.num_roots(), 2); assert_eq!(rs.num_positive_roots(), 1);
assert_eq!(rs.dimension(), 3);
let cartan = rs.cartan_matrix();
assert_eq!(cartan.len(), 1);
assert_eq!(cartan[0][0], 2); }
#[test]
fn test_type_a2_su3() {
let rs = RootSystem::type_a(2);
assert_eq!(rs.rank(), 2);
assert_eq!(rs.num_roots(), 6); assert_eq!(rs.num_positive_roots(), 3);
assert_eq!(rs.dimension(), 8);
let cartan = rs.cartan_matrix();
assert_eq!(cartan.len(), 2);
assert_eq!(cartan[0][0], 2);
assert_eq!(cartan[1][1], 2);
assert_eq!(cartan[0][1], -1); assert_eq!(cartan[1][0], -1);
}
#[test]
fn test_simple_roots_su3() {
let rs = RootSystem::type_a(2);
let simple = rs.simple_roots();
assert_eq!(simple.len(), 2);
assert!((simple[0].coords[0] - 1.0).abs() < 1e-10);
assert!((simple[0].coords[1] + 1.0).abs() < 1e-10);
assert!((simple[0].coords[2]).abs() < 1e-10);
assert!((simple[1].coords[0]).abs() < 1e-10);
assert!((simple[1].coords[1] - 1.0).abs() < 1e-10);
assert!((simple[1].coords[2] + 1.0).abs() < 1e-10);
}
#[test]
fn test_positive_roots_su3() {
let rs = RootSystem::type_a(2);
let positive = rs.positive_roots();
assert_eq!(positive.len(), 3);
}
#[test]
fn test_dominant_weight() {
let rs = RootSystem::type_a(2);
let weight1 = Root::new(vec![1.0, 0.0, 0.0]);
assert!(rs.is_dominant_weight(&weight1));
let weight2 = Root::new(vec![-1.0, 0.0, 0.0]);
assert!(!rs.is_dominant_weight(&weight2));
}
#[test]
fn test_weight_lattice_su2() {
let rs = RootSystem::type_a(1);
let wl = WeightLattice::from_root_system(rs);
assert_eq!(wl.fundamental_weights().len(), 1);
let weight = wl.dynkin_to_weight(&[2]);
assert_eq!(weight.rank(), 2);
}
#[test]
fn test_weight_lattice_su3() {
let rs = RootSystem::type_a(2);
let wl = WeightLattice::from_root_system(rs);
assert_eq!(wl.fundamental_weights().len(), 2);
let fund = wl.dynkin_to_weight(&[1, 0]);
assert_eq!(fund.rank(), 3);
let adj = wl.dynkin_to_weight(&[1, 1]);
assert_eq!(adj.rank(), 3);
}
#[test]
fn test_weyl_orbit_su2() {
let rs = RootSystem::type_a(1);
let weight = Root::new(vec![1.0, 0.0]);
let orbit = rs.weyl_orbit(&weight);
assert_eq!(orbit.len(), 2); }
#[test]
fn test_weyl_dimension_formula() {
let rs = RootSystem::type_a(2); let wl = WeightLattice::from_root_system(rs);
let fund = wl.dynkin_to_weight(&[1, 0]);
assert_eq!(wl.weyl_dimension(&fund), 3);
let antifund = wl.dynkin_to_weight(&[0, 1]);
assert_eq!(wl.weyl_dimension(&antifund), 3);
let adj = wl.dynkin_to_weight(&[1, 1]);
assert_eq!(wl.weyl_dimension(&adj), 8);
let sym2 = wl.dynkin_to_weight(&[2, 0]);
assert_eq!(wl.weyl_dimension(&sym2), 6);
let sym2_bar = wl.dynkin_to_weight(&[0, 2]);
assert_eq!(wl.weyl_dimension(&sym2_bar), 6);
let rs2 = RootSystem::type_a(1); let wl2 = WeightLattice::from_root_system(rs2);
let trivial = wl2.dynkin_to_weight(&[0]);
assert_eq!(wl2.weyl_dimension(&trivial), 1);
let spin1 = wl2.dynkin_to_weight(&[2]);
assert_eq!(wl2.weyl_dimension(&spin1), 3);
}
#[test]
fn test_weights_of_irrep_su3_fundamental() {
let rs = RootSystem::type_a(2);
let wl = WeightLattice::from_root_system(rs);
let highest = wl.dynkin_to_weight(&[1, 0]);
let weights = wl.weights_of_irrep(&highest);
let expected_dim = wl.weyl_dimension(&highest);
assert_eq!(expected_dim, 3, "Fundamental rep has dimension 3");
assert_eq!(
weights.len(),
3,
"Should find exactly 3 weights, found {}",
weights.len()
);
}
#[test]
fn test_weights_of_irrep_su3_adjoint() {
let rs = RootSystem::type_a(2);
let wl = WeightLattice::from_root_system(rs);
let highest = wl.dynkin_to_weight(&[1, 1]);
let weights = wl.weights_of_irrep(&highest);
let dim = wl.weyl_dimension(&highest);
assert_eq!(dim, 8, "Adjoint rep has dimension 8");
let total_dim: usize = weights.iter().map(|(_, m)| m).sum();
assert_eq!(
total_dim, 8,
"Total dimension (sum of multiplicities) should be 8"
);
}
#[test]
fn test_partition_function_basics() {
let rs = RootSystem::type_a(1); let wl = WeightLattice::from_root_system(rs);
let zero = Root::new(vec![0.0, 0.0]);
assert_eq!(wl.kostant_partition_function(&zero), 1, "P(0) = 1");
let alpha = Root::new(vec![1.0, -1.0]);
assert_eq!(wl.kostant_partition_function(&alpha), 1, "P(α) = 1");
let two_alpha = Root::new(vec![2.0, -2.0]);
assert_eq!(wl.kostant_partition_function(&two_alpha), 1, "P(2α) = 1");
let neg_alpha = Root::new(vec![-1.0, 1.0]);
assert_eq!(wl.kostant_partition_function(&neg_alpha), 0, "P(-α) = 0");
}
#[test]
fn test_partition_function_su3() {
let rs = RootSystem::type_a(2);
let wl = WeightLattice::from_root_system(rs);
let zero = Root::new(vec![0.0, 0.0, 0.0]);
assert_eq!(wl.kostant_partition_function(&zero), 1, "P(0) = 1");
let alpha1 = Root::new(vec![1.0, -1.0, 0.0]);
let p_alpha1 = wl.kostant_partition_function(&alpha1);
eprintln!("P(α₁) = {}", p_alpha1);
assert_eq!(p_alpha1, 1, "P(α₁) = 1");
let alpha1_plus_alpha2 = Root::new(vec![1.0, 0.0, -1.0]);
let p_sum = wl.kostant_partition_function(&alpha1_plus_alpha2);
eprintln!("P(α₁+α₂) = {}", p_sum);
}
#[test]
fn test_kostant_dimension_invariant() {
let rs = RootSystem::type_a(2); let wl = WeightLattice::from_root_system(rs);
let fund = wl.dynkin_to_weight(&[1, 0]);
let fund_weights = wl.weights_of_irrep(&fund);
eprintln!("Fundamental [1,0] weights:");
for (w, m) in &fund_weights {
eprintln!(" {:?} mult {}", w.coords, m);
}
let fund_dim: usize = fund_weights.iter().map(|(_, m)| m).sum();
eprintln!("Total dimension: {} (expected 3)", fund_dim);
assert_eq!(fund_dim, 3, "Fundamental rep dimension invariant");
let adj = wl.dynkin_to_weight(&[1, 1]);
let adj_weights = wl.weights_of_irrep(&adj);
eprintln!("\nAdjoint [1,1] weights:");
for (w, m) in &adj_weights {
eprintln!(" {:?} mult {}", w.coords, m);
}
let adj_dim: usize = adj_weights.iter().map(|(_, m)| m).sum();
eprintln!("Total dimension: {} (expected 8)", adj_dim);
assert_eq!(adj_dim, 8, "Adjoint rep dimension invariant");
let anti = wl.dynkin_to_weight(&[0, 1]);
let anti_weights = wl.weights_of_irrep(&anti);
let anti_dim: usize = anti_weights.iter().map(|(_, m)| m).sum();
assert_eq!(anti_dim, 3, "Antifundamental rep dimension invariant");
}
#[test]
fn test_weight_diagram_string_su3() {
let rs = RootSystem::type_a(2);
let wl = WeightLattice::from_root_system(rs);
let highest = wl.dynkin_to_weight(&[1, 0]);
let diagram = wl.weight_diagram_string(&highest);
assert!(diagram.contains("Weight diagram"));
assert!(diagram.contains("Dimension"));
assert!(diagram.contains("Weights"));
}
#[test]
fn test_weight_diagram_rank1_not_supported() {
let rs = RootSystem::type_a(1);
let wl = WeightLattice::from_root_system(rs);
let highest = wl.dynkin_to_weight(&[2]);
let diagram = wl.weight_diagram_string(&highest);
assert!(diagram.contains("only implemented for rank 2"));
}
#[test]
fn test_cartan_subalgebra_su2() {
let cartan = CartanSubalgebra::type_a(1);
assert_eq!(cartan.dimension(), 1);
assert_eq!(cartan.matrix_size(), 2);
assert_eq!(cartan.basis_matrices().len(), 1);
let h1 = &cartan.basis_matrices()[0];
assert!((h1[(0, 0)].re - 1.0).abs() < 1e-10);
assert!((h1[(1, 1)].re + 1.0).abs() < 1e-10);
assert!(h1[(0, 1)].norm() < 1e-10);
assert!(h1[(1, 0)].norm() < 1e-10);
}
#[test]
fn test_cartan_subalgebra_su3() {
let cartan = CartanSubalgebra::type_a(2);
assert_eq!(cartan.dimension(), 2);
assert_eq!(cartan.matrix_size(), 3);
assert_eq!(cartan.basis_matrices().len(), 2);
let h1 = &cartan.basis_matrices()[0];
assert!((h1[(0, 0)].re - 1.0).abs() < 1e-10);
assert!((h1[(1, 1)].re + 1.0).abs() < 1e-10);
assert!(h1[(2, 2)].norm() < 1e-10);
let h2 = &cartan.basis_matrices()[1];
assert!(h2[(0, 0)].norm() < 1e-10);
assert!((h2[(1, 1)].re - 1.0).abs() < 1e-10);
assert!((h2[(2, 2)].re + 1.0).abs() < 1e-10);
}
#[test]
fn test_cartan_basis_commutes() {
let cartan = CartanSubalgebra::type_a(2);
let h1 = &cartan.basis_matrices()[0];
let h2 = &cartan.basis_matrices()[1];
let commutator = h1.dot(h2) - h2.dot(h1);
for val in commutator.iter() {
assert!(val.norm() < 1e-10, "Cartan basis elements must commute");
}
}
#[test]
fn test_cartan_dual_basis() {
let cartan = CartanSubalgebra::type_a(2);
let dual = cartan.dual_basis();
assert_eq!(dual.len(), 2);
assert!((dual[0].coords[0] - 1.0).abs() < 1e-10);
assert!((dual[0].coords[1] + 1.0).abs() < 1e-10);
assert!(dual[0].coords[2].abs() < 1e-10);
assert!(dual[1].coords[0].abs() < 1e-10);
assert!((dual[1].coords[1] - 1.0).abs() < 1e-10);
assert!((dual[1].coords[2] + 1.0).abs() < 1e-10);
let rs = RootSystem::type_a(2);
let simple_roots = rs.simple_roots();
for (d, s) in dual.iter().zip(simple_roots) {
for (dc, sc) in d.coords.iter().zip(&s.coords) {
assert!((dc - sc).abs() < 1e-10);
}
}
}
#[test]
fn test_cartan_evaluate_root() {
let cartan = CartanSubalgebra::type_a(1);
let root = Root::new(vec![1.0, -1.0]);
let result = cartan.evaluate_root(&root, &[1.0]);
assert!((result.re - 2.0).abs() < 1e-10);
assert!(result.im.abs() < 1e-10);
let result2 = cartan.evaluate_root(&root, &[0.5]);
assert!((result2.re - 1.0).abs() < 1e-10);
}
#[test]
fn test_cartan_from_coefficients() {
let cartan = CartanSubalgebra::type_a(2);
let h = cartan.from_coefficients(&[2.0, 3.0]);
assert!((h[(0, 0)].re - 2.0).abs() < 1e-10);
assert!((h[(1, 1)].re - 1.0).abs() < 1e-10);
assert!((h[(2, 2)].re + 3.0).abs() < 1e-10);
let trace: Complex64 = (0..3).map(|i| h[(i, i)]).sum();
assert!(trace.norm() < 1e-10);
}
#[test]
fn test_cartan_project_matrix() {
let cartan = CartanSubalgebra::type_a(2);
let mut mat = Array2::<Complex64>::zeros((3, 3));
mat[(0, 0)] = Complex64::new(1.0, 0.0);
mat[(1, 1)] = Complex64::new(-0.5, 0.0);
mat[(2, 2)] = Complex64::new(-0.5, 0.0);
let coeffs = cartan
.project_matrix(&mat)
.expect("Rank 2 should be supported");
assert_eq!(coeffs.len(), 2);
let reconstructed = cartan.from_coefficients(&coeffs);
for i in 0..3 {
let diff = (mat[(i, i)] - reconstructed[(i, i)]).norm();
assert!(
diff < 1e-10,
"Diagonal projection should be exact: diff at ({},{}) = {}",
i,
i,
diff
);
}
}
#[test]
fn test_cartan_contains() {
let cartan = CartanSubalgebra::type_a(2);
let mut h = Array2::<Complex64>::zeros((3, 3));
h[(0, 0)] = Complex64::new(1.0, 0.0);
h[(1, 1)] = Complex64::new(-0.5, 0.0);
h[(2, 2)] = Complex64::new(-0.5, 0.0);
assert!(cartan.contains(&h, 1e-6));
let mut not_h = Array2::<Complex64>::zeros((3, 3));
not_h[(0, 1)] = Complex64::new(1.0, 0.0);
assert!(!cartan.contains(¬_h, 1e-6));
let mut not_traceless = Array2::<Complex64>::zeros((3, 3));
not_traceless[(0, 0)] = Complex64::new(1.0, 0.0);
assert!(!cartan.contains(¬_traceless, 1e-6));
}
#[test]
fn test_cartan_killing_form() {
let cartan = CartanSubalgebra::type_a(1);
let killing = cartan.killing_form(&[1.0], &[1.0]);
assert!((killing - 8.0).abs() < 1e-10);
let killing_zero = cartan.killing_form(&[1.0], &[0.0]);
assert!(killing_zero.abs() < 1e-10);
let k12 = cartan.killing_form(&[1.0], &[2.0]);
let k21 = cartan.killing_form(&[2.0], &[1.0]);
assert!((k12 - k21).abs() < 1e-10);
}
#[test]
fn test_cartan_killing_form_su3() {
let cartan = CartanSubalgebra::type_a(2);
let k11 = cartan.killing_form(&[1.0, 0.0], &[1.0, 0.0]);
assert!((k11 - 12.0).abs() < 1e-10);
let k22 = cartan.killing_form(&[0.0, 1.0], &[0.0, 1.0]);
assert!((k22 - 12.0).abs() < 1e-10);
let k12 = cartan.killing_form(&[1.0, 0.0], &[0.0, 1.0]);
assert!(k12.abs() < 1e-10);
}
#[test]
fn test_weyl_chamber_contains_su2() {
let rs = RootSystem::type_a(1); let chamber = WeylChamber::fundamental(&rs);
let dominant = Root::new(vec![1.0, 0.0]);
assert!(chamber.contains(&dominant, false));
assert!(chamber.contains(&dominant, true));
let non_dominant = Root::new(vec![-1.0, 0.0]);
assert!(!chamber.contains(&non_dominant, false));
let boundary = Root::new(vec![0.0, 0.0]);
assert!(chamber.contains(&boundary, false)); assert!(!chamber.contains(&boundary, true)); }
#[test]
fn test_weyl_chamber_contains_su3() {
let rs = RootSystem::type_a(2); let chamber = WeylChamber::fundamental(&rs);
let omega1 = Root::new(vec![2.0 / 3.0, -1.0 / 3.0, -1.0 / 3.0]);
assert!(chamber.contains(&omega1, false));
let neg = Root::new(vec![-1.0, 0.0, 1.0]);
assert!(!chamber.contains(&neg, false));
let origin = Root::new(vec![0.0, 0.0, 0.0]);
assert!(chamber.contains(&origin, false));
assert!(!chamber.contains(&origin, true));
}
#[test]
fn test_weyl_chamber_project() {
let rs = RootSystem::type_a(1); let chamber = WeylChamber::fundamental(&rs);
let dominant = Root::new(vec![1.0, -1.0]);
let projected = chamber.project(&dominant);
assert!((projected.coords[0] - dominant.coords[0]).abs() < 1e-10);
assert!((projected.coords[1] - dominant.coords[1]).abs() < 1e-10);
let non_dominant = Root::new(vec![-1.0, 1.0]);
let projected = chamber.project(&non_dominant);
assert!(chamber.contains(&projected, false));
assert!((projected.coords[0] - 1.0).abs() < 1e-10);
assert!((projected.coords[1] + 1.0).abs() < 1e-10);
}
#[test]
fn test_weyl_chamber_project_su3() {
let rs = RootSystem::type_a(2); let chamber = WeylChamber::fundamental(&rs);
let lambda = Root::new(vec![-1.0, 2.0, -1.0]);
let projected = chamber.project(&lambda);
assert!(chamber.contains(&projected, false));
let norm_before = lambda.norm_squared();
let norm_after = projected.norm_squared();
assert!((norm_before - norm_after).abs() < 1e-8);
}
#[test]
fn test_alcove_contains_su2() {
let rs = RootSystem::type_a(1); let alcove = Alcove::fundamental(&rs);
let inside = Root::new(vec![0.4, -0.4]);
assert!(alcove.contains(&inside, false));
let outside = Root::new(vec![1.0, -1.0]);
assert!(!alcove.contains(&outside, false));
let origin = Root::new(vec![0.0, 0.0]);
assert!(alcove.contains(&origin, false));
assert!(!alcove.contains(&origin, true)); }
#[test]
fn test_alcove_vertices_su2() {
let rs = RootSystem::type_a(1); let alcove = Alcove::fundamental(&rs);
let vertices = alcove.vertices();
assert_eq!(vertices.len(), 2);
assert!(vertices[0].coords[0].abs() < 1e-10);
assert!(vertices[0].coords[1].abs() < 1e-10);
assert!((vertices[1].coords[0] - 0.5).abs() < 1e-10);
assert!((vertices[1].coords[1] + 0.5).abs() < 1e-10);
}
#[test]
fn test_alcove_vertices_su3() {
let rs = RootSystem::type_a(2); let alcove = Alcove::fundamental(&rs);
let vertices = alcove.vertices();
assert_eq!(vertices.len(), 3);
for &coord in &vertices[0].coords {
assert!(coord.abs() < 1e-10);
}
for vertex in &vertices {
assert!(alcove.contains(vertex, false));
}
}
#[test]
fn test_alcove_at_level() {
let rs = RootSystem::type_a(1); let alcove_k2 = Alcove::at_level(&rs, 2.0);
assert_eq!(alcove_k2.level(), 2.0);
let inside = Root::new(vec![0.8, -0.8]);
assert!(alcove_k2.contains(&inside, false));
let outside = Root::new(vec![1.5, -1.5]);
assert!(!alcove_k2.contains(&outside, false));
}
#[test]
fn test_alcove_highest_root() {
let rs = RootSystem::type_a(2); let alcove = Alcove::fundamental(&rs);
let theta = alcove.highest_root();
assert!((theta.coords[0] - 1.0).abs() < 1e-10);
assert!(theta.coords[1].abs() < 1e-10);
assert!((theta.coords[2] + 1.0).abs() < 1e-10);
let theta_norm = theta.norm_squared();
for root in rs.positive_roots() {
assert!(root.norm_squared() <= theta_norm + 1e-10);
}
}
#[test]
fn test_simple_root_expansion_su2() {
let rs = RootSystem::type_a(1);
let alpha1 = Root::new(vec![1.0, -1.0]);
let coeffs = rs.simple_root_expansion(&alpha1);
assert_eq!(coeffs, Some(vec![1]));
let neg_alpha1 = Root::new(vec![-1.0, 1.0]);
let coeffs_neg = rs.simple_root_expansion(&neg_alpha1);
assert_eq!(coeffs_neg, Some(vec![-1]));
}
#[test]
fn test_simple_root_expansion_su3() {
let rs = RootSystem::type_a(2);
let alpha1 = Root::new(vec![1.0, -1.0, 0.0]);
assert_eq!(rs.simple_root_expansion(&alpha1), Some(vec![1, 0]));
let alpha2 = Root::new(vec![0.0, 1.0, -1.0]);
assert_eq!(rs.simple_root_expansion(&alpha2), Some(vec![0, 1]));
let alpha1_plus_alpha2 = Root::new(vec![1.0, 0.0, -1.0]);
assert_eq!(
rs.simple_root_expansion(&alpha1_plus_alpha2),
Some(vec![1, 1])
);
let neg_alpha1 = Root::new(vec![-1.0, 1.0, 0.0]);
assert_eq!(rs.simple_root_expansion(&neg_alpha1), Some(vec![-1, 0]));
let neg_highest = Root::new(vec![-1.0, 0.0, 1.0]);
assert_eq!(rs.simple_root_expansion(&neg_highest), Some(vec![-1, -1]));
}
#[test]
fn test_simple_root_expansion_su4() {
let rs = RootSystem::type_a(3);
let alpha1 = Root::new(vec![1.0, -1.0, 0.0, 0.0]);
assert_eq!(rs.simple_root_expansion(&alpha1), Some(vec![1, 0, 0]));
let alpha2 = Root::new(vec![0.0, 1.0, -1.0, 0.0]);
assert_eq!(rs.simple_root_expansion(&alpha2), Some(vec![0, 1, 0]));
let alpha3 = Root::new(vec![0.0, 0.0, 1.0, -1.0]);
assert_eq!(rs.simple_root_expansion(&alpha3), Some(vec![0, 0, 1]));
let e0_minus_e2 = Root::new(vec![1.0, 0.0, -1.0, 0.0]);
assert_eq!(rs.simple_root_expansion(&e0_minus_e2), Some(vec![1, 1, 0]));
let e1_minus_e3 = Root::new(vec![0.0, 1.0, 0.0, -1.0]);
assert_eq!(rs.simple_root_expansion(&e1_minus_e3), Some(vec![0, 1, 1]));
let highest = Root::new(vec![1.0, 0.0, 0.0, -1.0]);
assert_eq!(rs.simple_root_expansion(&highest), Some(vec![1, 1, 1]));
let neg_highest = Root::new(vec![-1.0, 0.0, 0.0, 1.0]);
assert_eq!(
rs.simple_root_expansion(&neg_highest),
Some(vec![-1, -1, -1])
);
}
#[test]
fn test_simple_root_expansion_not_a_root() {
let rs = RootSystem::type_a(2);
let not_a_root = Root::new(vec![1.0, 1.0, -2.0]);
assert_eq!(rs.simple_root_expansion(¬_a_root), None);
let zero = Root::new(vec![0.0, 0.0, 0.0]);
assert_eq!(rs.simple_root_expansion(&zero), None);
}
#[test]
fn test_simple_root_expansion_roundtrip() {
let rs = RootSystem::type_a(2);
for root in rs.roots() {
let coeffs = rs.simple_root_expansion(root);
assert!(coeffs.is_some(), "All roots should have expansions");
let coeffs = coeffs.unwrap();
let simple = rs.simple_roots();
let mut reconstructed = vec![0.0; root.coords.len()];
for (i, &c) in coeffs.iter().enumerate() {
for (j, &coord) in simple[i].coords.iter().enumerate() {
reconstructed[j] += (c as f64) * coord;
}
}
for (orig, recon) in root.coords.iter().zip(&reconstructed) {
assert!(
(orig - recon).abs() < 1e-10,
"Reconstruction failed for root {:?}: expected {:?}, got {:?}",
root.coords,
root.coords,
reconstructed
);
}
}
}
}