1use super::{IntegralForm, NiemeierComponentKind};
18use crate::clifford::{determinant, versor_grade_parity, CliffordAlgebra, LinearMap, Multivector};
19use crate::scalar::{Rational, Scalar};
20use std::fmt;
21
22#[derive(Clone, Debug, PartialEq, Eq)]
24pub struct WeylVersorInvariants {
25 pub kind: NiemeierComponentKind,
27 pub rank: usize,
29 pub weyl_group_order: u128,
31 pub coxeter_number: u128,
33 pub simple_reflections_match_cartan: bool,
35 pub simple_reflection_determinants_are_minus_one: bool,
37 pub coxeter_versor_order: u128,
39 pub coxeter_order_matches: bool,
41 pub coxeter_versor_grade_parity: Option<u128>,
43}
44
45impl WeylVersorInvariants {
46 pub fn display(&self) -> String {
48 self.to_string()
49 }
50}
51
52impl fmt::Display for WeylVersorInvariants {
53 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54 let parity = self
55 .coxeter_versor_grade_parity
56 .map_or_else(|| "none".to_string(), |p| p.to_string());
57 write!(
58 f,
59 "WeylVersorInvariants(kind={}, rank={}, weyl_group_order={}, coxeter_number={}, coxeter_versor_order={}, coxeter_order_matches={}, simple_reflections_match_cartan={}, simple_reflection_determinants_are_minus_one={}, coxeter_versor_grade_parity={})",
60 self.kind,
61 self.rank,
62 self.weyl_group_order,
63 self.coxeter_number,
64 self.coxeter_versor_order,
65 self.coxeter_order_matches,
66 self.simple_reflections_match_cartan,
67 self.simple_reflection_determinants_are_minus_one,
68 parity,
69 )
70 }
71}
72
73fn r(n: i128) -> Rational {
74 Rational::from_int(n)
75}
76
77fn rational_clifford(lattice: &IntegralForm) -> CliffordAlgebra<Rational> {
78 let metric = lattice.clifford_metric();
79 CliffordAlgebra::new(metric.q.len(), metric)
80}
81
82pub fn weyl_simple_root_versors(lattice: &IntegralForm) -> Vec<Multivector<Rational>> {
84 let alg = rational_clifford(lattice);
85 (0..lattice.dim()).map(|i| alg.e(i)).collect()
86}
87
88pub fn weyl_simple_reflection_map(lattice: &IntegralForm, i: usize) -> Option<LinearMap<Rational>> {
91 let n = lattice.dim();
92 if i >= n || lattice.gram()[i][i] != 2 {
93 return None;
94 }
95 let mut cols = Vec::with_capacity(n);
96 for j in 0..n {
97 let mut col = vec![Rational::zero(); n];
98 col[j] = Rational::one();
99 col[i] = col[i].sub(&r(lattice.gram()[j][i]));
100 cols.push(col);
101 }
102 Some(LinearMap::from_columns(cols))
103}
104
105pub fn weyl_simple_reflection_maps(lattice: &IntegralForm) -> Option<Vec<LinearMap<Rational>>> {
107 (0..lattice.dim())
108 .map(|i| weyl_simple_reflection_map(lattice, i))
109 .collect()
110}
111
112fn grade1_coords(
113 alg: &CliffordAlgebra<Rational>,
114 mv: &Multivector<Rational>,
115) -> Option<Vec<Rational>> {
116 let mut out = vec![Rational::zero(); alg.dim()];
117 for (&mask, coeff) in mv.terms() {
118 if mask == 0 || mask.count_ones() != 1 {
119 return None;
120 }
121 let i = mask.trailing_zeros() as usize;
122 if i >= alg.dim() {
123 return None;
124 }
125 out[i] = coeff.clone();
126 }
127 Some(out)
128}
129
130pub fn weyl_versor_action_map(
132 lattice: &IntegralForm,
133 versor: &Multivector<Rational>,
134) -> Option<LinearMap<Rational>> {
135 let alg = rational_clifford(lattice);
136 let mut cols = Vec::with_capacity(alg.dim());
137 for i in 0..alg.dim() {
138 let image = alg.twisted_sandwich(versor, &alg.e(i))?;
139 cols.push(grade1_coords(&alg, &image)?);
140 }
141 Some(LinearMap::from_columns(cols))
142}
143
144fn simple_reflections_match_cartan(lattice: &IntegralForm) -> bool {
145 let alg = rational_clifford(lattice);
146 for i in 0..lattice.dim() {
147 let Some(map) = weyl_simple_reflection_map(lattice, i) else {
148 return false;
149 };
150 let versor = alg.e(i);
151 for j in 0..lattice.dim() {
152 let Some(image) = alg.reflect(&versor, &alg.e(j)) else {
153 return false;
154 };
155 if image != map.image(&alg, j) {
156 return false;
157 }
158 }
159 }
160 true
161}
162
163fn simple_reflection_determinants_are_minus_one(lattice: &IntegralForm) -> bool {
164 let alg = rational_clifford(lattice);
165 let Some(maps) = weyl_simple_reflection_maps(lattice) else {
166 return false;
167 };
168 maps.iter().all(|f| determinant(&alg, f) == r(-1))
169}
170
171pub fn weyl_coxeter_versor(lattice: &IntegralForm) -> Option<Multivector<Rational>> {
174 if lattice
175 .gram()
176 .iter()
177 .enumerate()
178 .any(|(i, row)| row[i] != 2)
179 {
180 return None;
181 }
182 let alg = rational_clifford(lattice);
183 let mut out = alg.scalar(Rational::one());
184 for i in 0..lattice.dim() {
185 out = alg.mul(&out, &alg.e(i));
186 }
187 Some(out)
188}
189
190fn linear_map_order(map: &LinearMap<Rational>, max_order: u128) -> Option<u128> {
191 let id = LinearMap::identity(map.n());
192 let mut cur = id.clone();
193 for k in 1..=max_order {
194 cur = map.compose(&cur);
195 if cur == id {
196 return Some(k);
197 }
198 }
199 None
200}
201
202pub fn weyl_coxeter_action_order(lattice: &IntegralForm, max_order: u128) -> Option<u128> {
204 let c = weyl_coxeter_versor(lattice)?;
205 let action = weyl_versor_action_map(lattice, &c)?;
206 linear_map_order(&action, max_order)
207}
208
209pub fn weyl_versor_report(kind: NiemeierComponentKind) -> Option<WeylVersorInvariants> {
211 let lattice = kind.root_lattice()?;
212 let coxeter_number = kind.coxeter_number()?;
213 let coxeter_versor = weyl_coxeter_versor(&lattice)?;
214 let coxeter_order = weyl_coxeter_action_order(&lattice, coxeter_number)?;
215 Some(WeylVersorInvariants {
216 kind,
217 rank: kind.rank(),
218 weyl_group_order: kind.weyl_group_order()?,
219 coxeter_number,
220 simple_reflections_match_cartan: simple_reflections_match_cartan(&lattice),
221 simple_reflection_determinants_are_minus_one: simple_reflection_determinants_are_minus_one(
222 &lattice,
223 ),
224 coxeter_versor_order: coxeter_order,
225 coxeter_order_matches: coxeter_order == coxeter_number,
226 coxeter_versor_grade_parity: versor_grade_parity(&coxeter_versor),
227 })
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233 use crate::forms::E8_WEYL_GROUP_ORDER;
234
235 #[test]
236 fn a2_simple_roots_act_as_cartan_reflections() {
237 let report = weyl_versor_report(NiemeierComponentKind::A(2)).unwrap();
238 assert_eq!(report.rank, 2);
239 assert_eq!(report.weyl_group_order, 6);
240 assert_eq!(report.coxeter_number, 3);
241 assert!(report.simple_reflections_match_cartan);
242 assert!(report.simple_reflection_determinants_are_minus_one);
243 assert_eq!(report.coxeter_versor_order, 3);
244 assert!(report.coxeter_order_matches);
245 assert_eq!(report.coxeter_versor_grade_parity, Some(0));
246 }
247
248 #[test]
249 fn display_renders_the_full_report() {
250 let report = weyl_versor_report(NiemeierComponentKind::A(2)).unwrap();
251 assert_eq!(
252 report.to_string(),
253 "WeylVersorInvariants(kind=A_2, rank=2, weyl_group_order=6, \
254 coxeter_number=3, coxeter_versor_order=3, coxeter_order_matches=true, \
255 simple_reflections_match_cartan=true, \
256 simple_reflection_determinants_are_minus_one=true, \
257 coxeter_versor_grade_parity=0)"
258 );
259 assert_eq!(report.display(), report.to_string());
260 }
261
262 #[test]
263 fn d4_report_uses_weyl_order_not_full_diagram_automorphisms() {
264 let report = weyl_versor_report(NiemeierComponentKind::D(4)).unwrap();
265 assert_eq!(report.weyl_group_order, 192);
266 assert_eq!(report.coxeter_number, 6);
267 assert_eq!(report.coxeter_versor_order, 6);
268 assert!(report.simple_reflections_match_cartan);
269 }
270
271 #[test]
272 fn e8_coxeter_versor_has_order_30() {
273 let report = weyl_versor_report(NiemeierComponentKind::E8).unwrap();
274 assert_eq!(report.weyl_group_order, E8_WEYL_GROUP_ORDER);
275 assert_eq!(report.coxeter_number, 30);
276 assert_eq!(report.coxeter_versor_order, 30);
277 assert!(report.coxeter_order_matches);
278 assert!(report.simple_reflection_determinants_are_minus_one);
279 }
280
281 #[test]
285 fn e6_e7_and_more_ade_ranks_match_standard_coxeter_numbers() {
286 let e6 = weyl_versor_report(NiemeierComponentKind::E6).unwrap();
287 assert_eq!(e6.coxeter_number, 12);
288 assert_eq!(e6.coxeter_versor_order, 12);
289 assert!(e6.coxeter_order_matches);
290 assert!(e6.simple_reflections_match_cartan);
291 assert!(e6.simple_reflection_determinants_are_minus_one);
292
293 let e7 = weyl_versor_report(NiemeierComponentKind::E7).unwrap();
294 assert_eq!(e7.coxeter_number, 18);
295 assert_eq!(e7.coxeter_versor_order, 18);
296 assert!(e7.coxeter_order_matches);
297 assert!(e7.simple_reflections_match_cartan);
298 assert!(e7.simple_reflection_determinants_are_minus_one);
299
300 for n in [3usize, 5] {
301 let report = weyl_versor_report(NiemeierComponentKind::A(n)).unwrap();
302 let h = n as u128 + 1;
303 assert_eq!(report.coxeter_number, h, "A_{n} Coxeter number");
304 assert_eq!(report.coxeter_versor_order, h);
305 assert!(report.coxeter_order_matches);
306 assert!(report.simple_reflections_match_cartan);
307 assert!(report.simple_reflection_determinants_are_minus_one);
308 }
309
310 for n in [5usize, 6] {
311 let report = weyl_versor_report(NiemeierComponentKind::D(n)).unwrap();
312 let h = 2 * (n as u128 - 1);
313 assert_eq!(report.coxeter_number, h, "D_{n} Coxeter number");
314 assert_eq!(report.coxeter_versor_order, h);
315 assert!(report.coxeter_order_matches);
316 assert!(report.simple_reflections_match_cartan);
317 }
318 }
319}