use super::{IntegralForm, NiemeierComponentKind};
use crate::clifford::{determinant, versor_grade_parity, CliffordAlgebra, LinearMap, Multivector};
use crate::scalar::{Rational, Scalar};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct WeylVersorInvariants {
pub kind: NiemeierComponentKind,
pub rank: usize,
pub weyl_group_order: u128,
pub coxeter_number: u128,
pub simple_reflections_match_cartan: bool,
pub simple_reflection_determinants_are_minus_one: bool,
pub coxeter_versor_order: u128,
pub coxeter_order_matches: bool,
pub coxeter_versor_grade_parity: Option<u128>,
}
fn r(n: i128) -> Rational {
Rational::from_int(n)
}
fn rational_clifford(lattice: &IntegralForm) -> CliffordAlgebra<Rational> {
let metric = lattice.clifford_metric();
CliffordAlgebra::new(metric.q.len(), metric)
}
pub fn weyl_simple_root_versors(lattice: &IntegralForm) -> Vec<Multivector<Rational>> {
let alg = rational_clifford(lattice);
(0..lattice.dim()).map(|i| alg.e(i)).collect()
}
pub fn weyl_simple_reflection_map(lattice: &IntegralForm, i: usize) -> Option<LinearMap<Rational>> {
let n = lattice.dim();
if i >= n || lattice.gram()[i][i] != 2 {
return None;
}
let mut cols = Vec::with_capacity(n);
for j in 0..n {
let mut col = vec![Rational::zero(); n];
col[j] = Rational::one();
col[i] = col[i].sub(&r(lattice.gram()[j][i]));
cols.push(col);
}
Some(LinearMap::from_columns(cols))
}
pub fn weyl_simple_reflection_maps(lattice: &IntegralForm) -> Option<Vec<LinearMap<Rational>>> {
(0..lattice.dim())
.map(|i| weyl_simple_reflection_map(lattice, i))
.collect()
}
fn grade1_coords(
alg: &CliffordAlgebra<Rational>,
mv: &Multivector<Rational>,
) -> Option<Vec<Rational>> {
let mut out = vec![Rational::zero(); alg.dim()];
for (&mask, coeff) in mv.terms() {
if mask == 0 || mask.count_ones() != 1 {
return None;
}
let i = mask.trailing_zeros() as usize;
if i >= alg.dim() {
return None;
}
out[i] = coeff.clone();
}
Some(out)
}
pub fn weyl_versor_action_map(
lattice: &IntegralForm,
versor: &Multivector<Rational>,
) -> Option<LinearMap<Rational>> {
let alg = rational_clifford(lattice);
let mut cols = Vec::with_capacity(alg.dim());
for i in 0..alg.dim() {
let image = alg.twisted_sandwich(versor, &alg.e(i))?;
cols.push(grade1_coords(&alg, &image)?);
}
Some(LinearMap::from_columns(cols))
}
fn simple_reflections_match_cartan(lattice: &IntegralForm) -> bool {
let alg = rational_clifford(lattice);
for i in 0..lattice.dim() {
let Some(map) = weyl_simple_reflection_map(lattice, i) else {
return false;
};
let versor = alg.e(i);
for j in 0..lattice.dim() {
let Some(image) = alg.reflect(&versor, &alg.e(j)) else {
return false;
};
if image != map.image(&alg, j) {
return false;
}
}
}
true
}
fn simple_reflection_determinants_are_minus_one(lattice: &IntegralForm) -> bool {
let alg = rational_clifford(lattice);
let Some(maps) = weyl_simple_reflection_maps(lattice) else {
return false;
};
maps.iter().all(|f| determinant(&alg, f) == r(-1))
}
pub fn weyl_coxeter_versor(lattice: &IntegralForm) -> Option<Multivector<Rational>> {
if lattice
.gram()
.iter()
.enumerate()
.any(|(i, row)| row[i] != 2)
{
return None;
}
let alg = rational_clifford(lattice);
let mut out = alg.scalar(Rational::one());
for i in 0..lattice.dim() {
out = alg.mul(&out, &alg.e(i));
}
Some(out)
}
fn linear_map_order(map: &LinearMap<Rational>, max_order: u128) -> Option<u128> {
let id = LinearMap::identity(map.n());
let mut cur = id.clone();
for k in 1..=max_order {
cur = map.compose(&cur);
if cur == id {
return Some(k);
}
}
None
}
pub fn weyl_coxeter_action_order(lattice: &IntegralForm, max_order: u128) -> Option<u128> {
let c = weyl_coxeter_versor(lattice)?;
let action = weyl_versor_action_map(lattice, &c)?;
linear_map_order(&action, max_order)
}
pub fn weyl_versor_report(kind: NiemeierComponentKind) -> Option<WeylVersorInvariants> {
let lattice = kind.root_lattice()?;
let coxeter_number = kind.coxeter_number()?;
let coxeter_versor = weyl_coxeter_versor(&lattice)?;
let coxeter_order = weyl_coxeter_action_order(&lattice, coxeter_number)?;
Some(WeylVersorInvariants {
kind,
rank: kind.rank(),
weyl_group_order: kind.weyl_group_order()?,
coxeter_number,
simple_reflections_match_cartan: simple_reflections_match_cartan(&lattice),
simple_reflection_determinants_are_minus_one: simple_reflection_determinants_are_minus_one(
&lattice,
),
coxeter_versor_order: coxeter_order,
coxeter_order_matches: coxeter_order == coxeter_number,
coxeter_versor_grade_parity: versor_grade_parity(&coxeter_versor),
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::forms::E8_WEYL_GROUP_ORDER;
#[test]
fn a2_simple_roots_act_as_cartan_reflections() {
let report = weyl_versor_report(NiemeierComponentKind::A(2)).unwrap();
assert_eq!(report.rank, 2);
assert_eq!(report.weyl_group_order, 6);
assert_eq!(report.coxeter_number, 3);
assert!(report.simple_reflections_match_cartan);
assert!(report.simple_reflection_determinants_are_minus_one);
assert_eq!(report.coxeter_versor_order, 3);
assert!(report.coxeter_order_matches);
assert_eq!(report.coxeter_versor_grade_parity, Some(0));
}
#[test]
fn d4_report_uses_weyl_order_not_full_diagram_automorphisms() {
let report = weyl_versor_report(NiemeierComponentKind::D(4)).unwrap();
assert_eq!(report.weyl_group_order, 192);
assert_eq!(report.coxeter_number, 6);
assert_eq!(report.coxeter_versor_order, 6);
assert!(report.simple_reflections_match_cartan);
}
#[test]
fn e8_coxeter_versor_has_order_30() {
let report = weyl_versor_report(NiemeierComponentKind::E8).unwrap();
assert_eq!(report.weyl_group_order, E8_WEYL_GROUP_ORDER);
assert_eq!(report.coxeter_number, 30);
assert_eq!(report.coxeter_versor_order, 30);
assert!(report.coxeter_order_matches);
assert!(report.simple_reflection_determinants_are_minus_one);
}
#[test]
fn e6_e7_and_more_ade_ranks_match_standard_coxeter_numbers() {
let e6 = weyl_versor_report(NiemeierComponentKind::E6).unwrap();
assert_eq!(e6.coxeter_number, 12);
assert_eq!(e6.coxeter_versor_order, 12);
assert!(e6.coxeter_order_matches);
assert!(e6.simple_reflections_match_cartan);
assert!(e6.simple_reflection_determinants_are_minus_one);
let e7 = weyl_versor_report(NiemeierComponentKind::E7).unwrap();
assert_eq!(e7.coxeter_number, 18);
assert_eq!(e7.coxeter_versor_order, 18);
assert!(e7.coxeter_order_matches);
assert!(e7.simple_reflections_match_cartan);
assert!(e7.simple_reflection_determinants_are_minus_one);
for n in [3usize, 5] {
let report = weyl_versor_report(NiemeierComponentKind::A(n)).unwrap();
let h = n as u128 + 1;
assert_eq!(report.coxeter_number, h, "A_{n} Coxeter number");
assert_eq!(report.coxeter_versor_order, h);
assert!(report.coxeter_order_matches);
assert!(report.simple_reflections_match_cartan);
assert!(report.simple_reflection_determinants_are_minus_one);
}
for n in [5usize, 6] {
let report = weyl_versor_report(NiemeierComponentKind::D(n)).unwrap();
let h = 2 * (n as u128 - 1);
assert_eq!(report.coxeter_number, h, "D_{n} Coxeter number");
assert_eq!(report.coxeter_versor_order, h);
assert!(report.coxeter_order_matches);
assert!(report.simple_reflections_match_cartan);
}
}
}