1#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
49pub struct CartanMatrix<const N: usize> {
50 entries: [[i8; N]; N],
51}
52
53impl<const N: usize> CartanMatrix<N> {
54 #[must_use]
67 pub const fn new(entries: [[i8; N]; N]) -> Self {
68 Self { entries }
69 }
70
71 #[must_use]
73 pub const fn rank(&self) -> usize {
74 N
75 }
76
77 #[must_use]
83 pub const fn get(&self, i: usize, j: usize) -> i8 {
84 self.entries[i][j]
85 }
86
87 #[must_use]
89 pub const fn entries(&self) -> &[[i8; N]; N] {
90 &self.entries
91 }
92
93 #[must_use]
100 pub fn is_valid(&self) -> bool {
101 for i in 0..N {
103 if self.entries[i][i] != 2 {
104 return false;
105 }
106 }
107
108 for i in 0..N {
110 for j in 0..N {
111 if i != j {
112 if self.entries[i][j] > 0 {
114 return false;
115 }
116
117 let is_zero_ij = self.entries[i][j] == 0;
119 let is_zero_ji = self.entries[j][i] == 0;
120 if is_zero_ij != is_zero_ji {
121 return false;
122 }
123 }
124 }
125 }
126
127 true
128 }
129
130 #[must_use]
135 pub fn is_simply_laced(&self) -> bool {
136 for i in 0..N {
137 for j in 0..N {
138 if i != j {
139 let entry = self.entries[i][j];
140 if entry != 0 && entry != -1 {
141 return false;
142 }
143 }
144 }
145 }
146 true
147 }
148
149 #[must_use]
153 pub fn is_symmetric(&self) -> bool {
154 for i in 0..N {
155 for j in 0..N {
156 if self.entries[i][j] != self.entries[j][i] {
157 return false;
158 }
159 }
160 }
161 true
162 }
163
164 #[must_use]
169 pub fn determinant(&self) -> i64 {
170 match N {
171 1 => i64::from(self.entries[0][0]),
172 2 => {
173 let a = i64::from(self.entries[0][0]);
174 let b = i64::from(self.entries[0][1]);
175 let c = i64::from(self.entries[1][0]);
176 let d = i64::from(self.entries[1][1]);
177 a * d - b * c
178 },
179 3 => self.determinant_3x3(),
180 4 => self.determinant_4x4(),
181 _ => self.determinant_general(),
182 }
183 }
184
185 fn determinant_3x3(&self) -> i64 {
187 let m = &self.entries;
188 let a = i64::from(m[0][0]);
189 let b = i64::from(m[0][1]);
190 let c = i64::from(m[0][2]);
191 let d = i64::from(m[1][0]);
192 let e = i64::from(m[1][1]);
193 let f = i64::from(m[1][2]);
194 let g = i64::from(m[2][0]);
195 let h = i64::from(m[2][1]);
196 let i = i64::from(m[2][2]);
197
198 a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g)
199 }
200
201 fn determinant_4x4(&self) -> i64 {
203 let m = &self.entries;
204 let mut det = 0_i64;
205
206 for j in 0..4 {
208 let sign = if j % 2 == 0 { 1 } else { -1 };
209 let cofactor = self.minor_3x3(0, j);
210 det += sign * i64::from(m[0][j]) * cofactor;
211 }
212
213 det
214 }
215
216 fn minor_3x3(&self, skip_row: usize, skip_col: usize) -> i64 {
218 let mut minor = [[0_i8; 3]; 3];
219 let mut mi = 0;
220
221 for i in 0..4 {
222 if i == skip_row {
223 continue;
224 }
225 let mut mj = 0;
226 for j in 0..4 {
227 if j == skip_col {
228 continue;
229 }
230 minor[mi][mj] = self.entries[i][j];
231 mj += 1;
232 }
233 mi += 1;
234 }
235
236 let a = i64::from(minor[0][0]);
237 let b = i64::from(minor[0][1]);
238 let c = i64::from(minor[0][2]);
239 let d = i64::from(minor[1][0]);
240 let e = i64::from(minor[1][1]);
241 let f = i64::from(minor[1][2]);
242 let g = i64::from(minor[2][0]);
243 let h = i64::from(minor[2][1]);
244 let i = i64::from(minor[2][2]);
245
246 a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g)
247 }
248
249 fn determinant_general(&self) -> i64 {
253 if N == 0 {
254 return 1;
255 }
256 if N == 1 {
257 return i64::from(self.entries[0][0]);
258 }
259
260 let mut det = 0_i64;
261
262 for j in 0..N {
264 let sign = if j % 2 == 0 { 1 } else { -1 };
265 let cofactor = self.minor_general(0, j);
266 det += sign * i64::from(self.entries[0][j]) * cofactor;
267 }
268
269 det
270 }
271
272 fn minor_general(&self, skip_row: usize, skip_col: usize) -> i64 {
274 if N <= 1 {
275 return 1;
276 }
277
278 let mut minor_entries = vec![vec![0_i8; N - 1]; N - 1];
280 let mut mi = 0;
281
282 for i in 0..N {
283 if i == skip_row {
284 continue;
285 }
286 let mut mj = 0;
287 for j in 0..N {
288 if j == skip_col {
289 continue;
290 }
291 minor_entries[mi][mj] = self.entries[i][j];
292 mj += 1;
293 }
294 mi += 1;
295 }
296
297 Self::determinant_recursive(&minor_entries, N - 1)
299 }
300
301 fn determinant_recursive(matrix: &[Vec<i8>], size: usize) -> i64 {
303 if size == 1 {
304 return i64::from(matrix[0][0]);
305 }
306 if size == 2 {
307 let a = i64::from(matrix[0][0]);
308 let b = i64::from(matrix[0][1]);
309 let c = i64::from(matrix[1][0]);
310 let d = i64::from(matrix[1][1]);
311 return a * d - b * c;
312 }
313
314 let mut det = 0_i64;
315
316 for j in 0..size {
317 let sign = if j % 2 == 0 { 1 } else { -1 };
318
319 let mut minor = vec![vec![0_i8; size - 1]; size - 1];
321 for i in 1..size {
322 let mut mj = 0;
323 for k in 0..size {
324 if k == j {
325 continue;
326 }
327 minor[i - 1][mj] = matrix[i][k];
328 mj += 1;
329 }
330 }
331
332 det += sign * i64::from(matrix[0][j]) * Self::determinant_recursive(&minor, size - 1);
333 }
334
335 det
336 }
337
338 #[must_use]
343 pub fn num_connected_components(&self) -> usize {
344 let mut visited = vec![false; N];
345 let mut num_components = 0;
346
347 for start in 0..N {
348 if !visited[start] {
349 let mut queue = vec![start];
351 visited[start] = true;
352
353 while let Some(i) = queue.pop() {
354 #[allow(clippy::needless_range_loop)]
355 for j in 0..N {
356 if i != j && !visited[j] && self.entries[i][j] != 0 {
357 visited[j] = true;
358 queue.push(j);
359 }
360 }
361 }
362
363 num_components += 1;
364 }
365 }
366
367 num_components
368 }
369
370 #[must_use]
372 pub fn is_connected(&self) -> bool {
373 self.num_connected_components() == 1
374 }
375
376 #[must_use]
397 pub fn to_dynkin_diagram(&self, group_name: &str) -> DynkinDiagram<N> {
398 DynkinDiagram::from_cartan(self, group_name)
399 }
400}
401
402impl CartanMatrix<2> {
404 #[must_use]
411 pub const fn g2() -> Self {
412 Self::new([[2, -3], [-1, 2]])
413 }
414}
415
416impl CartanMatrix<4> {
417 #[must_use]
426 pub const fn f4() -> Self {
427 Self::new([[2, -1, 0, 0], [-1, 2, -2, 0], [0, -1, 2, -1], [0, 0, -1, 2]])
428 }
429}
430
431impl CartanMatrix<6> {
432 #[must_use]
443 pub const fn e6() -> Self {
444 Self::new([
445 [2, -1, 0, 0, 0, 0],
446 [-1, 2, -1, 0, 0, 0],
447 [0, -1, 2, -1, 0, -1],
448 [0, 0, -1, 2, -1, 0],
449 [0, 0, 0, -1, 2, 0],
450 [0, 0, -1, 0, 0, 2],
451 ])
452 }
453}
454
455impl CartanMatrix<7> {
456 #[must_use]
468 pub const fn e7() -> Self {
469 Self::new([
470 [2, -1, 0, 0, 0, 0, 0],
471 [-1, 2, -1, 0, 0, 0, 0],
472 [0, -1, 2, -1, 0, 0, 0],
473 [0, 0, -1, 2, -1, 0, -1],
474 [0, 0, 0, -1, 2, -1, 0],
475 [0, 0, 0, 0, -1, 2, 0],
476 [0, 0, 0, -1, 0, 0, 2],
477 ])
478 }
479}
480
481impl CartanMatrix<8> {
482 #[must_use]
495 pub const fn e8() -> Self {
496 Self::new([
497 [2, -1, 0, 0, 0, 0, 0, 0],
498 [-1, 2, -1, 0, 0, 0, 0, 0],
499 [0, -1, 2, -1, 0, 0, 0, 0],
500 [0, 0, -1, 2, -1, 0, 0, 0],
501 [0, 0, 0, -1, 2, -1, 0, -1],
502 [0, 0, 0, 0, -1, 2, -1, 0],
503 [0, 0, 0, 0, 0, -1, 2, 0],
504 [0, 0, 0, 0, -1, 0, 0, 2],
505 ])
506 }
507}
508
509#[derive(Debug, Clone, PartialEq, Eq)]
520pub struct DynkinDiagram<const N: usize> {
521 group_name: String,
523 rank: usize,
525 cartan: CartanMatrix<N>,
527 bonds: Vec<(usize, usize, u8)>,
530 degrees: Vec<usize>,
532}
533
534impl<const N: usize> DynkinDiagram<N> {
535 #[must_use]
543 pub fn from_cartan(cartan: &CartanMatrix<N>, group_name: &str) -> Self {
544 let mut bonds = Vec::new();
545 let mut degrees = vec![0; N];
546
547 for i in 0..N {
550 for j in (i + 1)..N {
551 let c_ij = cartan.get(i, j);
552 let c_ji = cartan.get(j, i);
553
554 if c_ij != 0 && c_ji != 0 {
556 let product = i32::from(c_ij) * i32::from(c_ji);
558 let multiplicity = u8::try_from(product.unsigned_abs())
559 .expect("multiplicity should fit in u8");
560
561 bonds.push((i, j, multiplicity));
562
563 degrees[i] += 1;
565 degrees[j] += 1;
566 }
567 }
568 }
569
570 Self { group_name: group_name.to_string(), rank: N, cartan: *cartan, bonds, degrees }
571 }
572
573 #[must_use]
575 pub const fn rank(&self) -> usize {
576 self.rank
577 }
578
579 #[must_use]
581 pub fn group_name(&self) -> &str {
582 &self.group_name
583 }
584
585 #[must_use]
587 pub fn bonds(&self) -> &[(usize, usize, u8)] {
588 &self.bonds
589 }
590
591 #[must_use]
593 pub fn degree(&self, node: usize) -> usize {
594 self.degrees[node]
595 }
596
597 #[must_use]
599 pub fn degrees(&self) -> &[usize] {
600 &self.degrees
601 }
602
603 #[must_use]
605 pub const fn cartan_matrix(&self) -> &CartanMatrix<N> {
606 &self.cartan
607 }
608
609 #[must_use]
611 pub fn is_connected(&self) -> bool {
612 if N == 0 {
613 return true;
614 }
615
616 let mut visited = vec![false; N];
618 let mut queue = vec![0];
619 visited[0] = true;
620 let mut count = 1;
621
622 while let Some(node) = queue.pop() {
623 for &(i, j, _) in &self.bonds {
625 let neighbor = if i == node {
626 Some(j)
627 } else if j == node {
628 Some(i)
629 } else {
630 None
631 };
632
633 if let Some(n) = neighbor {
634 if !visited[n] {
635 visited[n] = true;
636 queue.push(n);
637 count += 1;
638 }
639 }
640 }
641 }
642
643 count == N
644 }
645
646 #[must_use]
663 pub fn branch_nodes(&self) -> Vec<usize> {
664 self.degrees
665 .iter()
666 .enumerate()
667 .filter(|(_, °)| deg >= 3)
668 .map(|(i, _)| i)
669 .collect()
670 }
671
672 #[must_use]
689 pub fn endpoints(&self) -> Vec<usize> {
690 self.degrees
691 .iter()
692 .enumerate()
693 .filter(|(_, °)| deg == 1)
694 .map(|(i, _)| i)
695 .collect()
696 }
697
698 #[must_use]
714 pub fn middle_nodes(&self) -> Vec<usize> {
715 self.degrees
716 .iter()
717 .enumerate()
718 .filter(|(_, °)| deg == 2)
719 .map(|(i, _)| i)
720 .collect()
721 }
722
723 #[must_use]
730 pub fn to_ascii(&self) -> String {
731 match self.group_name.as_str() {
734 "G₂" => Self::ascii_g2(),
735 "F₄" => Self::ascii_f4(),
736 "E₆" => Self::ascii_e6(),
737 "E₇" => Self::ascii_e7(),
738 "E₈" => Self::ascii_e8(),
739 _ => self.ascii_generic(),
740 }
741 }
742
743 fn ascii_g2() -> String {
745 "\nG₂ Dynkin Diagram (rank 2):\n\n α₁ o≡≡≡o α₂\n\nTriple bond: |C₁₂ × C₂₁| = 3\n\
746 Short root (α₁) connected to long root (α₂)\n"
747 .to_string()
748 }
749
750 fn ascii_f4() -> String {
752 "\nF₄ Dynkin Diagram (rank 4):\n\n α₁ o---o α₂ ⇒ α₃ o---o α₄\n\n\
753 Double bond (⇒): |C₂₃ × C₃₂| = 2\n\
754 Arrow points from short to long roots\n"
755 .to_string()
756 }
757
758 fn ascii_e6() -> String {
760 "\nE₆ Dynkin Diagram (rank 6):\n\n α₂\n o\n |\n \
761 α₁ o---o α₀ ---o α₃ ---o α₄ ---o α₅\n\n\
762 Branching structure: central node (α₀) has degree 3\n\
763 Simply-laced: all single bonds\n"
764 .to_string()
765 }
766
767 fn ascii_e7() -> String {
769 "\nE₇ Dynkin Diagram (rank 7):\n\n α₆\n o\n |\n \
770 α₀ o---o α₁ ---o α₂ ---o α₃ ---o α₄ ---o α₅\n\n\
771 Extended E₆ structure\n\
772 Simply-laced: all single bonds\n"
773 .to_string()
774 }
775
776 fn ascii_e8() -> String {
778 "\nE₈ Dynkin Diagram (rank 8):\n\n α₇\n o\n |\n \
779 α₀ o---o α₁ ---o α₂ ---o α₃ ---o α₄ ---o α₅ ---o α₆\n\n\
780 Largest exceptional group\n\
781 Simply-laced: all single bonds\n".to_string()
782 }
783
784 fn ascii_generic(&self) -> String {
786 use std::fmt::Write as _;
787 let mut diagram = format!("\n{} Dynkin Diagram (rank {}):\n\n", self.group_name, N);
788
789 diagram.push_str(" ");
791 for i in 0..N {
792 write!(diagram, "α{i}").unwrap();
793 if i < N - 1 {
794 diagram.push_str(" o");
795 let bond = self
797 .bonds
798 .iter()
799 .find(|(a, b, _)| (*a == i && *b == i + 1) || (*a == i + 1 && *b == i));
800 match bond {
801 Some((_, _, 1)) => diagram.push_str("---"),
802 Some((_, _, 2)) => diagram.push_str("==>"),
803 Some((_, _, 3)) => diagram.push_str("≡≡≡"),
804 _ => diagram.push_str(" "),
805 }
806 diagram.push_str("o ");
807 }
808 }
809 diagram.push('\n');
810
811 if !self.bonds.is_empty() {
813 diagram.push_str("\nBonds:\n");
814 for (i, j, mult) in &self.bonds {
815 writeln!(diagram, " α{i} ↔ α{j} (multiplicity {mult})").unwrap();
816 }
817 }
818
819 diagram
820 }
821}
822
823#[cfg(test)]
824mod tests {
825 use super::*;
826
827 #[test]
828 fn test_g2_cartan() {
829 let g2 = CartanMatrix::g2();
830 assert_eq!(g2.rank(), 2);
831 assert!(g2.is_valid());
832 assert!(!g2.is_simply_laced());
833 assert!(!g2.is_symmetric());
834 assert_eq!(g2.determinant(), 1);
835 assert!(g2.is_connected());
836 }
837
838 #[test]
839 fn test_f4_cartan() {
840 let f4 = CartanMatrix::f4();
841 assert_eq!(f4.rank(), 4);
842 assert!(f4.is_valid());
843 assert!(!f4.is_simply_laced());
844 assert!(!f4.is_symmetric());
845 assert_eq!(f4.determinant(), 1);
846 assert!(f4.is_connected());
847 }
848
849 #[test]
850 fn test_e6_cartan() {
851 let e6 = CartanMatrix::e6();
852 assert_eq!(e6.rank(), 6);
853 assert!(e6.is_valid());
854 assert!(e6.is_simply_laced());
855 assert!(e6.is_symmetric());
856 assert_eq!(e6.determinant(), 3);
857 assert!(e6.is_connected());
858 }
859
860 #[test]
861 fn test_e7_cartan() {
862 let e7 = CartanMatrix::e7();
863 assert_eq!(e7.rank(), 7);
864 assert!(e7.is_valid());
865 assert!(e7.is_simply_laced());
866 assert!(e7.is_symmetric());
867 assert_eq!(e7.determinant(), 2);
868 assert!(e7.is_connected());
869 }
870
871 #[test]
872 fn test_e8_cartan() {
873 let e8 = CartanMatrix::e8();
874 assert_eq!(e8.rank(), 8);
875 assert!(e8.is_valid());
876 assert!(e8.is_simply_laced());
877 assert!(e8.is_symmetric());
878 assert_eq!(e8.determinant(), 1);
879 assert!(e8.is_connected());
880 }
881
882 #[test]
883 fn test_cartan_validity() {
884 let invalid = CartanMatrix::new([[1, 0], [0, 2]]);
886 assert!(!invalid.is_valid());
887
888 let invalid2 = CartanMatrix::new([[2, 1], [1, 2]]);
890 assert!(!invalid2.is_valid());
891
892 let invalid3 = CartanMatrix::new([[2, 0], [-1, 2]]);
894 assert!(!invalid3.is_valid());
895 }
896
897 #[test]
898 fn test_simply_laced() {
899 let sl = CartanMatrix::new([[2, -1], [-1, 2]]);
901 assert!(sl.is_simply_laced());
902
903 let nsl = CartanMatrix::new([[2, -2], [-1, 2]]);
905 assert!(!nsl.is_simply_laced());
906
907 let nsl2 = CartanMatrix::new([[2, -3], [-1, 2]]);
909 assert!(!nsl2.is_simply_laced());
910 }
911
912 #[test]
913 fn test_determinant_2x2() {
914 let m = CartanMatrix::new([[2, -1], [-1, 2]]);
915 assert_eq!(m.determinant(), 3);
916 }
917
918 #[test]
919 fn test_connected_components() {
920 let disconnected = CartanMatrix::new([[2, 0], [0, 2]]);
922 assert_eq!(disconnected.num_connected_components(), 2);
923 assert!(!disconnected.is_connected());
924
925 let connected = CartanMatrix::new([[2, -1], [-1, 2]]);
927 assert_eq!(connected.num_connected_components(), 1);
928 assert!(connected.is_connected());
929 }
930
931 #[test]
936 fn test_dynkin_g2_triple_bond() {
937 let g2 = CartanMatrix::g2();
938 let dynkin = g2.to_dynkin_diagram("G₂");
939
940 assert_eq!(dynkin.rank(), 2);
941 assert_eq!(dynkin.group_name(), "G₂");
942 assert!(dynkin.is_connected());
943
944 let bonds = dynkin.bonds();
946 assert_eq!(bonds.len(), 1, "G₂ has 1 bond");
947 assert_eq!(bonds[0].2, 3, "G₂ triple bond: |C₁₂ × C₂₁| = |-3 × -1| = 3");
948
949 assert_eq!(dynkin.degree(0), 1);
951 assert_eq!(dynkin.degree(1), 1);
952
953 let ascii = dynkin.to_ascii();
955 assert!(ascii.contains("G₂"));
956 assert!(ascii.contains("Triple bond"));
957 }
958
959 #[test]
960 fn test_dynkin_f4_double_bond() {
961 let f4 = CartanMatrix::f4();
962 let dynkin = f4.to_dynkin_diagram("F₄");
963
964 assert_eq!(dynkin.rank(), 4);
965 assert_eq!(dynkin.group_name(), "F₄");
966 assert!(dynkin.is_connected());
967
968 let bonds = dynkin.bonds();
970 assert_eq!(bonds.len(), 3, "F₄ has 3 bonds");
971
972 let double_bond = bonds.iter().find(|(_, _, m)| *m == 2);
974 assert!(double_bond.is_some(), "F₄ has a double bond");
975
976 let double = double_bond.unwrap();
977 assert_eq!(double.2, 2, "F₄ double bond: |C₂₃ × C₃₂| = |-2 × -1| = 2");
978
979 assert_eq!(dynkin.degree(0), 1, "Endpoint");
981 assert_eq!(dynkin.degree(1), 2, "Middle");
982 assert_eq!(dynkin.degree(2), 2, "Middle");
983 assert_eq!(dynkin.degree(3), 1, "Endpoint");
984
985 let ascii = dynkin.to_ascii();
987 assert!(ascii.contains("F₄"));
988 assert!(ascii.contains("Double bond"));
989 }
990
991 #[test]
992 fn test_dynkin_e6_simply_laced() {
993 let e6 = CartanMatrix::e6();
994 let dynkin = e6.to_dynkin_diagram("E₆");
995
996 assert_eq!(dynkin.rank(), 6);
997 assert_eq!(dynkin.group_name(), "E₆");
998 assert!(dynkin.is_connected());
999
1000 let bonds = dynkin.bonds();
1002 assert_eq!(bonds.len(), 5, "E₆ has 5 bonds (tree with 6 nodes)");
1003
1004 for &(_, _, mult) in bonds {
1006 assert_eq!(mult, 1, "E₆ is simply-laced: all bonds are single");
1007 }
1008
1009 let degrees = dynkin.degrees();
1011 let deg_1_count = degrees.iter().filter(|&&d| d == 1).count();
1012 let deg_2_count = degrees.iter().filter(|&&d| d == 2).count();
1013 let deg_3_count = degrees.iter().filter(|&&d| d == 3).count();
1014
1015 assert_eq!(deg_1_count, 3, "E₆ has 3 endpoints");
1016 assert_eq!(deg_2_count, 2, "E₆ has 2 middle nodes");
1017 assert_eq!(deg_3_count, 1, "E₆ has 1 branch node");
1018
1019 let ascii = dynkin.to_ascii();
1021 assert!(ascii.contains("E₆"));
1022 assert!(ascii.contains("Branching structure"));
1023 }
1024
1025 #[test]
1026 fn test_dynkin_e7_simply_laced() {
1027 let e7 = CartanMatrix::e7();
1028 let dynkin = e7.to_dynkin_diagram("E₇");
1029
1030 assert_eq!(dynkin.rank(), 7);
1031 assert!(dynkin.is_connected());
1032
1033 let bonds = dynkin.bonds();
1035 assert_eq!(bonds.len(), 6, "E₇ has 6 bonds (tree with 7 nodes)");
1036
1037 for &(_, _, mult) in bonds {
1038 assert_eq!(mult, 1, "E₇ is simply-laced");
1039 }
1040
1041 let ascii = dynkin.to_ascii();
1043 assert!(ascii.contains("E₇"));
1044 }
1045
1046 #[test]
1047 fn test_dynkin_e8_simply_laced() {
1048 let e8 = CartanMatrix::e8();
1049 let dynkin = e8.to_dynkin_diagram("E₈");
1050
1051 assert_eq!(dynkin.rank(), 8);
1052 assert!(dynkin.is_connected());
1053
1054 let bonds = dynkin.bonds();
1056 assert_eq!(bonds.len(), 7, "E₈ has 7 bonds (tree with 8 nodes)");
1057
1058 for &(_, _, mult) in bonds {
1059 assert_eq!(mult, 1, "E₈ is simply-laced");
1060 }
1061
1062 let degrees = dynkin.degrees();
1064 let deg_1_count = degrees.iter().filter(|&&d| d == 1).count();
1065 let deg_2_count = degrees.iter().filter(|&&d| d == 2).count();
1066 let deg_3_count = degrees.iter().filter(|&&d| d == 3).count();
1067
1068 assert_eq!(deg_1_count, 3, "E₈ has 3 endpoints");
1069 assert_eq!(deg_2_count, 4, "E₈ has 4 middle nodes");
1070 assert_eq!(deg_3_count, 1, "E₈ has 1 branch node");
1071
1072 let ascii = dynkin.to_ascii();
1074 assert!(ascii.contains("E₈"));
1075 }
1076
1077 #[test]
1078 fn test_dynkin_bond_extraction_exact() {
1079 let g2 = CartanMatrix::g2();
1083 let g2_diagram = g2.to_dynkin_diagram("G₂");
1084 assert_eq!(g2_diagram.bonds()[0].2, 3);
1085
1086 let f4 = CartanMatrix::f4();
1088 let f4_diagram = f4.to_dynkin_diagram("F₄");
1089 let double = f4_diagram.bonds().iter().find(|(_, _, m)| *m == 2).unwrap();
1090 assert_eq!(double.2, 2);
1091
1092 let e6 = CartanMatrix::e6();
1094 let e6_diagram = e6.to_dynkin_diagram("E₆");
1095 for &(i, j, mult) in e6_diagram.bonds() {
1096 let c_ij = e6.get(i, j);
1097 let c_ji = e6.get(j, i);
1098 let expected = u8::try_from((i32::from(c_ij) * i32::from(c_ji)).unsigned_abs())
1099 .expect("multiplicity should fit in u8");
1100 assert_eq!(mult, expected, "Bond ({i},{j}) multiplicity");
1101 }
1102 }
1103
1104 #[test]
1105 fn test_dynkin_degrees_from_bonds() {
1106 let a2 = CartanMatrix::new([[2, -1], [-1, 2]]);
1110 let dynkin = a2.to_dynkin_diagram("A₂");
1111 assert_eq!(dynkin.degree(0), 1);
1112 assert_eq!(dynkin.degree(1), 1);
1113
1114 let a3 = CartanMatrix::new([[2, -1, 0], [-1, 2, -1], [0, -1, 2]]);
1116 let dynkin = a3.to_dynkin_diagram("A₃");
1117 assert_eq!(dynkin.degree(0), 1);
1118 assert_eq!(dynkin.degree(1), 2);
1119 assert_eq!(dynkin.degree(2), 1);
1120 }
1121
1122 #[test]
1123 fn test_dynkin_connectivity() {
1124 assert!(CartanMatrix::g2().to_dynkin_diagram("G₂").is_connected());
1126 assert!(CartanMatrix::f4().to_dynkin_diagram("F₄").is_connected());
1127 assert!(CartanMatrix::e6().to_dynkin_diagram("E₆").is_connected());
1128 assert!(CartanMatrix::e7().to_dynkin_diagram("E₇").is_connected());
1129 assert!(CartanMatrix::e8().to_dynkin_diagram("E₈").is_connected());
1130
1131 let disconnected = CartanMatrix::new([[2, 0], [0, 2]]);
1133 let dynkin = disconnected.to_dynkin_diagram("A₁×A₁");
1134 assert!(!dynkin.is_connected(), "Disconnected Dynkin diagram");
1135 }
1136
1137 #[test]
1138 fn test_dynkin_helper_methods() {
1139 let e6 = CartanMatrix::e6().to_dynkin_diagram("E₆");
1141 assert_eq!(e6.branch_nodes().len(), 1, "E₆ has 1 branch node");
1142 assert_eq!(e6.endpoints().len(), 3, "E₆ has 3 endpoints");
1143 assert_eq!(e6.middle_nodes().len(), 2, "E₆ has 2 middle nodes");
1144
1145 let e7 = CartanMatrix::e7().to_dynkin_diagram("E₇");
1147 assert_eq!(e7.branch_nodes().len(), 1, "E₇ has 1 branch node");
1148 assert_eq!(e7.endpoints().len(), 3, "E₇ has 3 endpoints");
1149 assert_eq!(e7.middle_nodes().len(), 3, "E₇ has 3 middle nodes");
1150
1151 let e8 = CartanMatrix::e8().to_dynkin_diagram("E₈");
1153 assert_eq!(e8.branch_nodes().len(), 1, "E₈ has 1 branch node");
1154 assert_eq!(e8.endpoints().len(), 3, "E₈ has 3 endpoints");
1155 assert_eq!(e8.middle_nodes().len(), 4, "E₈ has 4 middle nodes");
1156
1157 let g2 = CartanMatrix::g2().to_dynkin_diagram("G₂");
1159 assert_eq!(g2.branch_nodes().len(), 0, "G₂ has no branch nodes");
1160 assert_eq!(g2.endpoints().len(), 2, "G₂ has 2 endpoints");
1161
1162 let f4 = CartanMatrix::f4().to_dynkin_diagram("F₄");
1164 assert_eq!(f4.branch_nodes().len(), 0, "F₄ has no branch nodes");
1165 assert_eq!(f4.endpoints().len(), 2, "F₄ has 2 endpoints");
1166 assert_eq!(f4.middle_nodes().len(), 2, "F₄ has 2 middle nodes");
1167 }
1168}