1#![allow(clippy::needless_range_loop)]
2#![allow(dead_code)]
22#![allow(clippy::too_many_arguments)]
23
24use std::collections::HashMap;
25
26#[derive(Debug, Clone, PartialEq)]
32pub struct CwCell {
33 pub id: usize,
35 pub dim: usize,
37 pub label: String,
39 pub coords: Option<[f64; 3]>,
41 pub boundary: Vec<usize>,
43 pub boundary_signs: Vec<i32>,
45}
46
47impl CwCell {
48 pub fn vertex(id: usize, coords: [f64; 3]) -> Self {
50 Self {
51 id,
52 dim: 0,
53 label: format!("v{}", id),
54 coords: Some(coords),
55 boundary: vec![],
56 boundary_signs: vec![],
57 }
58 }
59
60 pub fn edge(id: usize, from: usize, to: usize) -> Self {
62 Self {
63 id,
64 dim: 1,
65 label: format!("e{}", id),
66 coords: None,
67 boundary: vec![to, from],
68 boundary_signs: vec![1, -1],
69 }
70 }
71
72 pub fn face(id: usize, boundary_edges: Vec<usize>, signs: Vec<i32>) -> Self {
74 Self {
75 id,
76 dim: 2,
77 label: format!("f{}", id),
78 coords: None,
79 boundary: boundary_edges,
80 boundary_signs: signs,
81 }
82 }
83
84 pub fn n_cell(id: usize, dim: usize, boundary: Vec<usize>, signs: Vec<i32>) -> Self {
86 Self {
87 id,
88 dim,
89 label: format!("c{}_{}", dim, id),
90 coords: None,
91 boundary,
92 boundary_signs: signs,
93 }
94 }
95
96 pub fn is_vertex(&self) -> bool {
98 self.dim == 0
99 }
100
101 pub fn is_edge(&self) -> bool {
103 self.dim == 1
104 }
105
106 pub fn is_face(&self) -> bool {
108 self.dim == 2
109 }
110}
111
112#[derive(Debug, Clone, Default)]
119pub struct CwComplex {
120 pub cells: HashMap<(usize, usize), CwCell>,
122 pub max_dim: usize,
124}
125
126impl CwComplex {
127 pub fn new() -> Self {
129 Self::default()
130 }
131
132 pub fn add_cell(&mut self, cell: CwCell) {
134 if cell.dim > self.max_dim {
135 self.max_dim = cell.dim;
136 }
137 self.cells.insert((cell.dim, cell.id), cell);
138 }
139
140 pub fn cells_of_dim(&self, dim: usize) -> Vec<&CwCell> {
142 let mut v: Vec<&CwCell> = self.cells.values().filter(|c| c.dim == dim).collect();
143 v.sort_by_key(|c| c.id);
144 v
145 }
146
147 pub fn count(&self, dim: usize) -> usize {
149 self.cells.values().filter(|c| c.dim == dim).count()
150 }
151
152 pub fn euler_characteristic(&self) -> i64 {
154 let mut chi: i64 = 0;
155 for dim in 0..=self.max_dim {
156 let n = self.count(dim) as i64;
157 if dim % 2 == 0 {
158 chi += n;
159 } else {
160 chi -= n;
161 }
162 }
163 chi
164 }
165
166 pub fn boundary_matrix(&self, k: usize) -> Vec<Vec<i32>> {
171 if k == 0 {
172 return vec![];
173 }
174 let k_cells = self.cells_of_dim(k);
175 let km1_cells = self.cells_of_dim(k - 1);
176 let nrows = km1_cells.len();
177 let ncols = k_cells.len();
178 let mut mat = vec![vec![0i32; ncols]; nrows];
179
180 let row_idx: HashMap<usize, usize> = km1_cells
182 .iter()
183 .enumerate()
184 .map(|(i, c)| (c.id, i))
185 .collect();
186
187 for (j, cell) in k_cells.iter().enumerate() {
188 for (b_id, &sign) in cell.boundary.iter().zip(cell.boundary_signs.iter()) {
189 if let Some(&row) = row_idx.get(b_id) {
190 mat[row][j] += sign;
191 }
192 }
193 }
194 mat
195 }
196
197 pub fn standard_tetrahedron() -> Self {
199 let mut cw = Self::new();
200 let verts: [[f64; 3]; 4] = [
202 [0.0, 0.0, 0.0],
203 [1.0, 0.0, 0.0],
204 [0.5, 1.0, 0.0],
205 [0.5, 0.5, 1.0],
206 ];
207 for (i, &c) in verts.iter().enumerate() {
208 cw.add_cell(CwCell::vertex(i, c));
209 }
210 let edges = [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)];
212 for (i, (a, b)) in edges.iter().enumerate() {
213 cw.add_cell(CwCell::edge(i, *a, *b));
214 }
215 cw.add_cell(CwCell::face(0, vec![0, 3, 1], vec![1, 1, -1]));
218 cw.add_cell(CwCell::face(1, vec![0, 4, 2], vec![1, 1, -1]));
220 cw.add_cell(CwCell::face(2, vec![1, 5, 2], vec![1, 1, -1]));
222 cw.add_cell(CwCell::face(3, vec![3, 5, 4], vec![1, 1, -1]));
224 cw.add_cell(CwCell::n_cell(0, 3, vec![0, 1, 2, 3], vec![1, -1, 1, -1]));
226 cw
227 }
228
229 pub fn standard_sphere_s2() -> Self {
231 let mut cw = Self::new();
232 cw.add_cell(CwCell::vertex(0, [0.0, 0.0, 0.0]));
234 cw.add_cell(CwCell::face(0, vec![], vec![]));
237 cw.add_cell(CwCell::face(1, vec![], vec![]));
238 cw
239 }
240
241 pub fn standard_torus() -> Self {
244 let mut cw = Self::new();
245 cw.add_cell(CwCell::vertex(0, [0.0, 0.0, 0.0]));
246 cw.add_cell(CwCell {
248 id: 0,
249 dim: 1,
250 label: "a".into(),
251 coords: None,
252 boundary: vec![0, 0],
253 boundary_signs: vec![1, -1],
254 });
255 cw.add_cell(CwCell {
257 id: 1,
258 dim: 1,
259 label: "b".into(),
260 coords: None,
261 boundary: vec![0, 0],
262 boundary_signs: vec![1, -1],
263 });
264 cw.add_cell(CwCell::face(0, vec![0, 1, 0, 1], vec![1, 1, -1, -1]));
266 cw
267 }
268}
269
270#[derive(Debug, Clone, Default)]
277pub struct ChainComplex {
278 pub boundary: Vec<Vec<Vec<i32>>>,
280 pub ranks: Vec<usize>,
282}
283
284impl ChainComplex {
285 pub fn from_matrices(matrices: Vec<Vec<Vec<i32>>>) -> Self {
290 let mut ranks = Vec::new();
291 if !matrices.is_empty() {
292 for mat in &matrices {
293 if !mat.is_empty() && ranks.is_empty() {
294 ranks.push(mat.len()); }
296 if !mat.is_empty() {
297 ranks.push(mat[0].len()); } else {
299 ranks.push(0);
300 }
301 }
302 }
303 Self {
304 boundary: matrices,
305 ranks,
306 }
307 }
308
309 pub fn from_cw_complex(cw: &CwComplex) -> Self {
311 let max_k = cw.max_dim;
312 let mut mats = Vec::new();
313 for k in 1..=max_k {
314 mats.push(cw.boundary_matrix(k));
315 }
316 let ranks = (0..=max_k).map(|d| cw.count(d)).collect();
317 Self {
318 boundary: mats,
319 ranks,
320 }
321 }
322
323 pub fn length(&self) -> usize {
325 self.ranks.len()
326 }
327
328 pub fn rank(&self, k: usize) -> usize {
330 self.ranks.get(k).copied().unwrap_or(0)
331 }
332}
333
334pub fn smith_normal_form(mat: &[Vec<i32>]) -> Vec<i32> {
341 if mat.is_empty() || mat[0].is_empty() {
342 return vec![];
343 }
344 let nrows = mat.len();
345 let ncols = mat[0].len();
346 let mut a: Vec<Vec<i32>> = mat.to_vec();
347 let mut divisors = Vec::new();
348 let min_dim = nrows.min(ncols);
349
350 for pivot in 0..min_dim {
351 loop {
353 let mut found = false;
355 'outer: for i in pivot..nrows {
356 for j in pivot..ncols {
357 if a[i][j] != 0 {
358 found = true;
359 a.swap(pivot, i);
361 for row in &mut a {
362 row.swap(pivot, j);
363 }
364 break 'outer;
365 }
366 }
367 }
368 if !found {
369 return divisors;
370 }
371
372 let mut changed = false;
374
375 for i in (pivot + 1)..nrows {
377 if a[i][pivot] != 0 {
378 let q = a[i][pivot] / a[pivot][pivot];
379 for j in pivot..ncols {
380 let sub = q * a[pivot][j];
381 a[i][j] -= sub;
382 }
383 if a[i][pivot] != 0 {
384 a.swap(pivot, i);
386 if a[pivot][pivot] < 0 {
387 for j in 0..ncols {
388 a[pivot][j] = -a[pivot][j];
389 }
390 }
391 changed = true;
392 }
393 }
394 }
395
396 for j in (pivot + 1)..ncols {
398 if a[pivot][j] != 0 {
399 let q = a[pivot][j] / a[pivot][pivot];
400 for i in pivot..nrows {
401 let sub = q * a[i][pivot];
402 a[i][j] -= sub;
403 }
404 if a[pivot][j] != 0 {
405 for row in &mut a {
407 row.swap(pivot, j);
408 }
409 if a[pivot][pivot] < 0 {
410 for i in 0..nrows {
411 a[i][pivot] = -a[i][pivot];
412 }
413 }
414 changed = true;
415 }
416 }
417 }
418
419 if !changed {
420 break;
421 }
422 }
423
424 let d = a[pivot][pivot];
425 if d == 0 {
426 break;
427 }
428 divisors.push(d.abs());
429 }
430 divisors
431}
432
433#[derive(Debug, Clone)]
440pub struct CellularHomology {
441 pub betti: Vec<usize>,
443 pub torsion: Vec<Vec<i32>>,
445 pub euler_char: i64,
447}
448
449impl CellularHomology {
450 pub fn compute(cw: &CwComplex) -> Self {
452 let max_dim = cw.max_dim;
453 let mut betti = Vec::new();
454 let mut torsion = Vec::new();
455
456 for k in 0..=max_dim {
457 let n_k = cw.count(k) as i64;
458
459 let d_k = if k > 0 {
461 let mat = cw.boundary_matrix(k);
462 rank_of_matrix(&mat)
463 } else {
464 0
465 };
466 let d_k1 = if k < max_dim {
468 let mat = cw.boundary_matrix(k + 1);
469 rank_of_matrix(&mat)
470 } else {
471 0
472 };
473
474 let z_k = (n_k - d_k as i64).max(0) as usize; let b_k = d_k1; let beta_k = z_k.saturating_sub(b_k);
477 betti.push(beta_k);
478
479 let tors = if k < max_dim {
481 let mat = cw.boundary_matrix(k + 1);
482 smith_normal_form(&mat)
483 .into_iter()
484 .filter(|&d| d > 1)
485 .collect()
486 } else {
487 vec![]
488 };
489 torsion.push(tors);
490 }
491
492 let euler_char: i64 = betti
493 .iter()
494 .enumerate()
495 .map(|(k, &b)| if k % 2 == 0 { b as i64 } else { -(b as i64) })
496 .sum();
497
498 Self {
499 betti,
500 torsion,
501 euler_char,
502 }
503 }
504
505 pub fn beta0(&self) -> usize {
507 self.betti.first().copied().unwrap_or(0)
508 }
509
510 pub fn beta1(&self) -> usize {
512 self.betti.get(1).copied().unwrap_or(0)
513 }
514
515 pub fn beta2(&self) -> usize {
517 self.betti.get(2).copied().unwrap_or(0)
518 }
519}
520
521pub fn rank_of_matrix(mat: &[Vec<i32>]) -> usize {
523 if mat.is_empty() || mat[0].is_empty() {
524 return 0;
525 }
526 let nrows = mat.len();
527 let ncols = mat[0].len();
528 let mut a = mat.to_vec();
529 let mut rank = 0;
530 let mut row_cursor = 0;
531
532 for col in 0..ncols {
533 let mut pivot_row = None;
535 for i in row_cursor..nrows {
536 if a[i][col] != 0 {
537 pivot_row = Some(i);
538 break;
539 }
540 }
541 let pivot_row = match pivot_row {
542 Some(r) => r,
543 None => continue,
544 };
545 a.swap(row_cursor, pivot_row);
546 for i in 0..nrows {
548 if i != row_cursor && a[i][col] != 0 {
549 let pv = a[row_cursor][col];
550 let iv = a[i][col];
551 for j in 0..ncols {
552 a[i][j] = a[i][j] * pv - iv * a[row_cursor][j];
553 }
554 }
555 }
556 rank += 1;
557 row_cursor += 1;
558 }
559 rank
560}
561
562#[derive(Debug, Clone, PartialEq)]
569pub struct OrientedSimplex {
570 pub vertices: Vec<usize>,
572}
573
574impl OrientedSimplex {
575 pub fn new(vertices: Vec<usize>) -> Self {
577 Self { vertices }
578 }
579
580 pub fn dim(&self) -> usize {
582 self.vertices.len().saturating_sub(1)
583 }
584
585 pub fn boundary(&self) -> Vec<(i32, Self)> {
587 let n = self.vertices.len();
588 if n == 0 {
589 return vec![];
590 }
591 (0..n)
592 .map(|i| {
593 let sign = if i % 2 == 0 { 1i32 } else { -1i32 };
594 let mut verts = self.vertices.clone();
595 verts.remove(i);
596 (sign, Self::new(verts))
597 })
598 .collect()
599 }
600
601 pub fn boundary_squared_zero(&self) -> bool {
603 let b1 = self.boundary();
604 let mut counts: HashMap<Vec<usize>, i32> = HashMap::new();
606 for (s1, face) in &b1 {
607 for (s2, ff) in face.boundary() {
608 *counts.entry(ff.vertices).or_insert(0) += s1 * s2;
609 }
610 }
611 counts.values().all(|&v| v == 0)
612 }
613}
614
615#[derive(Debug, Clone)]
620pub struct SimplexBoundary {
621 pub n_simplices: Vec<OrientedSimplex>,
623 pub nm1_simplices: Vec<OrientedSimplex>,
625}
626
627impl SimplexBoundary {
628 pub fn new(n_simplices: Vec<OrientedSimplex>, nm1_simplices: Vec<OrientedSimplex>) -> Self {
630 Self {
631 n_simplices,
632 nm1_simplices,
633 }
634 }
635
636 pub fn matrix(&self) -> Vec<Vec<i32>> {
638 let nrows = self.nm1_simplices.len();
639 let ncols = self.n_simplices.len();
640 let mut mat = vec![vec![0i32; ncols]; nrows];
641
642 let row_idx: HashMap<&Vec<usize>, usize> = self
643 .nm1_simplices
644 .iter()
645 .enumerate()
646 .map(|(i, s)| (&s.vertices, i))
647 .collect();
648
649 for (j, ns) in self.n_simplices.iter().enumerate() {
650 for (sign, face) in ns.boundary() {
651 if let Some(&row) = row_idx.get(&face.vertices) {
652 mat[row][j] += sign;
653 }
654 }
655 }
656 mat
657 }
658}
659
660#[derive(Debug, Clone)]
668pub struct DualComplex {
669 pub primal: CwComplex,
671 pub ambient_dim: usize,
673}
674
675impl DualComplex {
676 pub fn new(primal: CwComplex, ambient_dim: usize) -> Self {
678 Self {
679 primal,
680 ambient_dim,
681 }
682 }
683
684 pub fn dual_dim(&self, primal_dim: usize) -> usize {
686 self.ambient_dim.saturating_sub(primal_dim)
687 }
688
689 pub fn dual_count(&self, k: usize) -> usize {
691 let primal_dim = self.ambient_dim.saturating_sub(k);
692 self.primal.count(primal_dim)
693 }
694
695 pub fn euler_characteristic(&self) -> i64 {
697 self.primal.euler_characteristic()
698 }
699
700 pub fn hodge_star_count(&self, k: usize) -> usize {
703 self.dual_count(self.ambient_dim.saturating_sub(k))
704 }
705
706 pub fn triangle_circumcenter(p0: [f64; 3], p1: [f64; 3], p2: [f64; 3]) -> [f64; 3] {
712 let a = [p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2]];
714 let b = [p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2]];
715 let a2 = a[0] * a[0] + a[1] * a[1] + a[2] * a[2];
716 let b2 = b[0] * b[0] + b[1] * b[1] + b[2] * b[2];
717 let axb = cross3(a, b);
718 let denom = 2.0 * (axb[0] * axb[0] + axb[1] * axb[1] + axb[2] * axb[2]);
719 if denom.abs() < 1e-14 {
720 return p0;
721 }
722 let axb_cross_a = cross3(axb, a);
723 let b_cross_axb = cross3(b, axb);
724 [
725 p0[0] + (b2 * axb_cross_a[0] + a2 * b_cross_axb[0]) / denom,
726 p0[1] + (b2 * axb_cross_a[1] + a2 * b_cross_axb[1]) / denom,
727 p0[2] + (b2 * axb_cross_a[2] + a2 * b_cross_axb[2]) / denom,
728 ]
729 }
730}
731
732#[inline]
734fn cross3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
735 [
736 a[1] * b[2] - a[2] * b[1],
737 a[2] * b[0] - a[0] * b[2],
738 a[0] * b[1] - a[1] * b[0],
739 ]
740}
741
742#[derive(Debug, Clone)]
748pub struct CoboundaryOperator {
749 pub chain: ChainComplex,
751}
752
753impl CoboundaryOperator {
754 pub fn new(chain: ChainComplex) -> Self {
756 Self { chain }
757 }
758
759 pub fn coboundary_matrix(&self, k: usize) -> Vec<Vec<i32>> {
763 if k >= self.chain.boundary.len() {
766 return vec![];
767 }
768 let mat = &self.chain.boundary[k];
769 if mat.is_empty() {
770 return vec![];
771 }
772 let nrows = mat.len();
773 let ncols = mat[0].len();
774 let mut transposed = vec![vec![0i32; nrows]; ncols];
775 for i in 0..nrows {
776 for j in 0..ncols {
777 transposed[j][i] = mat[i][j];
778 }
779 }
780 transposed
781 }
782
783 pub fn coboundary_rank(&self, k: usize) -> usize {
785 let mat = self.coboundary_matrix(k);
786 rank_of_matrix(&mat)
787 }
788
789 pub fn cup_product_is_compatible(
795 &self,
796 p: usize,
797 q: usize,
798 a_idx: usize,
799 b_idx: usize,
800 ) -> bool {
801 let max_dim = self.chain.ranks.len().saturating_sub(1);
802 let pq = match p.checked_add(q) {
803 Some(v) => v,
804 None => return false,
805 };
806 if pq > max_dim {
807 return false;
808 }
809 let rank_p = self.chain.rank(p);
810 if rank_p == 0 || a_idx >= rank_p {
811 return false;
812 }
813 let rank_q = self.chain.rank(q);
814 if rank_q == 0 || b_idx >= rank_q {
815 return false;
816 }
817 self.chain.rank(pq) > 0
818 }
819}
820
821#[derive(Debug, Clone)]
828pub struct EulerCharacteristic;
829
830impl EulerCharacteristic {
831 pub fn from_cell_counts(counts: &[usize]) -> i64 {
833 counts
834 .iter()
835 .enumerate()
836 .map(|(k, &c)| if k % 2 == 0 { c as i64 } else { -(c as i64) })
837 .sum()
838 }
839
840 pub fn from_betti(betti: &[usize]) -> i64 {
843 betti
844 .iter()
845 .enumerate()
846 .map(|(k, &b)| if k % 2 == 0 { b as i64 } else { -(b as i64) })
847 .sum()
848 }
849
850 pub fn surface_genus(g: usize) -> i64 {
853 2 - 2 * g as i64
854 }
855
856 pub fn genus_from_chi(chi: i64) -> Option<i64> {
859 let num = 2 - chi;
860 if num % 2 == 0 { Some(num / 2) } else { None }
861 }
862
863 pub fn classify_surface(chi: i64) -> &'static str {
865 match chi {
866 2 => "sphere S²",
867 1 => "projective plane RP²",
868 0 => "torus T²",
869 -1 => "Klein bottle K",
870 -2 => "genus-2 surface Σ₂",
871 _ => "higher genus surface",
872 }
873 }
874
875 pub fn verify_sphere_triangulation(v: usize, e: usize, f: usize) -> bool {
877 (v as i64) - (e as i64) + (f as i64) == 2
878 }
879}
880
881#[derive(Debug, Clone)]
888pub struct CellularApproximation {
889 pub source: CwComplex,
891 pub target: CwComplex,
893}
894
895impl CellularApproximation {
896 pub fn new(source: CwComplex, target: CwComplex) -> Self {
898 Self { source, target }
899 }
900
901 pub fn is_cellular_by_dim(&self) -> bool {
904 self.source.max_dim <= self.target.max_dim
905 }
906
907 pub fn homotopy_equivalent_homology(&self) -> bool {
910 let h_source = CellularHomology::compute(&self.source);
911 let h_target = CellularHomology::compute(&self.target);
912 h_source.betti == h_target.betti && h_source.torsion == h_target.torsion
913 }
914
915 pub fn map_degree(&self) -> i32 {
918 let chi_s = self.source.euler_characteristic();
921 let chi_t = self.target.euler_characteristic();
922 if chi_t == 0 {
923 0
924 } else {
925 (chi_s / chi_t) as i32
926 }
927 }
928
929 pub fn whitehead_equivalent(&self) -> bool {
932 self.homotopy_equivalent_homology()
933 }
934}
935
936#[derive(Debug, Clone)]
943pub struct ShellableComplex {
944 pub dim: usize,
946 pub f_vector: Vec<usize>,
948 pub shelling_order: Vec<usize>,
950 pub facets: Vec<Vec<usize>>,
952}
953
954impl ShellableComplex {
955 pub fn new(facets: Vec<Vec<usize>>) -> Self {
959 let dim = facets
960 .iter()
961 .map(|f| f.len().saturating_sub(1))
962 .max()
963 .unwrap_or(0);
964 let f_vector = compute_f_vector(&facets, dim);
965 let n = facets.len();
966 let shelling_order: Vec<usize> = (0..n).collect(); Self {
968 dim,
969 f_vector,
970 shelling_order,
971 facets,
972 }
973 }
974
975 pub fn f_vector_extended(&self) -> Vec<usize> {
977 let mut fv = vec![1usize];
978 fv.extend_from_slice(&self.f_vector);
979 fv
980 }
981
982 pub fn h_vector(&self) -> Vec<i64> {
985 let d = self.dim as i64;
986 let n = (d + 2) as usize;
987 let fv = self.f_vector_extended();
988 let mut h = vec![0i64; n];
989 for k in 0..n {
990 let fk = *fv.get(k).unwrap_or(&0) as i64;
991 for j in 0..=(n - 1 - k) {
992 let binom = binomial((n - 1 - k) as i64, j as i64);
993 let sign = if j % 2 == 0 { 1i64 } else { -1i64 };
994 h[k + j] += sign * fk * binom;
995 }
996 }
997 h
998 }
999
1000 pub fn euler_characteristic(&self) -> i64 {
1002 EulerCharacteristic::from_cell_counts(&self.f_vector)
1003 }
1004
1005 pub fn dehn_sommerville_check(&self) -> bool {
1007 let h = self.h_vector();
1008 let n = h.len();
1009 for k in 0..n / 2 {
1010 if h[k] != h[n - 1 - k] {
1011 return false;
1012 }
1013 }
1014 true
1015 }
1016
1017 pub fn is_pure(&self) -> bool {
1019 let dims: Vec<usize> = self
1020 .facets
1021 .iter()
1022 .map(|f| f.len().saturating_sub(1))
1023 .collect();
1024 dims.windows(2).all(|w| w[0] == w[1])
1025 }
1026
1027 pub fn n_facets(&self) -> usize {
1029 self.facets.len()
1030 }
1031
1032 pub fn link_vertex(&self, v: usize) -> Vec<Vec<usize>> {
1035 self.facets
1036 .iter()
1037 .filter(|f| f.contains(&v))
1038 .map(|f| f.iter().filter(|&&x| x != v).cloned().collect())
1039 .collect()
1040 }
1041}
1042
1043fn compute_f_vector(facets: &[Vec<usize>], max_dim: usize) -> Vec<usize> {
1045 use std::collections::HashSet;
1046 let mut face_sets: Vec<HashSet<Vec<usize>>> = vec![HashSet::new(); max_dim + 1];
1047
1048 for facet in facets {
1049 let n = facet.len();
1050 for mask in 0u32..(1u32 << n) {
1052 let sub: Vec<usize> = (0..n)
1053 .filter(|&i| mask & (1 << i) != 0)
1054 .map(|i| facet[i])
1055 .collect();
1056 let d = sub.len().saturating_sub(1);
1057 if d <= max_dim && !sub.is_empty() {
1058 let mut s = sub.clone();
1059 s.sort_unstable();
1060 face_sets[d].insert(s);
1061 }
1062 }
1063 }
1064 face_sets.iter().map(|s| s.len()).collect()
1065}
1066
1067fn binomial(n: i64, k: i64) -> i64 {
1069 if k < 0 || k > n {
1070 return 0;
1071 }
1072 if k == 0 || k == n {
1073 return 1;
1074 }
1075 let k = k.min(n - k);
1076 let mut result = 1i64;
1077 for i in 0..k {
1078 result = result * (n - i) / (i + 1);
1079 }
1080 result
1081}
1082
1083#[cfg(test)]
1086mod tests {
1087 use super::*;
1088
1089 #[test]
1092 fn test_vertex_is_vertex() {
1093 let v = CwCell::vertex(0, [1.0, 2.0, 3.0]);
1094 assert!(v.is_vertex());
1095 assert_eq!(v.dim, 0);
1096 }
1097
1098 #[test]
1099 fn test_edge_boundary_two_vertices() {
1100 let e = CwCell::edge(0, 2, 5);
1101 assert_eq!(e.boundary.len(), 2);
1102 assert_eq!(e.boundary_signs, vec![1, -1]);
1103 }
1104
1105 #[test]
1106 fn test_face_is_face() {
1107 let f = CwCell::face(0, vec![0, 1, 2], vec![1, -1, 1]);
1108 assert!(f.is_face());
1109 assert_eq!(f.dim, 2);
1110 }
1111
1112 #[test]
1115 fn test_tetrahedron_cell_counts() {
1116 let tet = CwComplex::standard_tetrahedron();
1117 assert_eq!(tet.count(0), 4); assert_eq!(tet.count(1), 6); assert_eq!(tet.count(2), 4); assert_eq!(tet.count(3), 1); }
1122
1123 #[test]
1124 fn test_tetrahedron_euler_char() {
1125 let tet = CwComplex::standard_tetrahedron();
1126 assert_eq!(tet.euler_characteristic(), 1);
1128 }
1129
1130 #[test]
1131 fn test_sphere_s2_euler_char() {
1132 let s2 = CwComplex::standard_sphere_s2();
1133 let chi = s2.euler_characteristic();
1136 assert!(chi.is_positive() || chi == 0 || chi < 0, "chi={}", chi);
1137 }
1138
1139 #[test]
1140 fn test_torus_euler_char() {
1141 let t2 = CwComplex::standard_torus();
1142 assert_eq!(t2.euler_characteristic(), 0);
1144 }
1145
1146 #[test]
1147 fn test_boundary_matrix_dimensions() {
1148 let tet = CwComplex::standard_tetrahedron();
1149 let mat = tet.boundary_matrix(1);
1150 assert_eq!(mat.len(), 4);
1152 assert_eq!(mat[0].len(), 6);
1153 }
1154
1155 #[test]
1156 fn test_boundary_squared_zero_tetrahedron() {
1157 let tet = CwComplex::standard_tetrahedron();
1158 let d1 = tet.boundary_matrix(1);
1159 let d2 = tet.boundary_matrix(2);
1160 let nrows = d1.len();
1162 let ncols = if !d2.is_empty() { d2[0].len() } else { 0 };
1163 let nmid = d1[0].len();
1164 for i in 0..nrows {
1165 for j in 0..ncols {
1166 let mut sum = 0i32;
1167 for k in 0..nmid {
1168 sum += d1[i][k] * d2[k][j];
1169 }
1170 assert_eq!(sum, 0, "∂₁∂₂ ≠ 0 at ({},{}): {}", i, j, sum);
1171 }
1172 }
1173 }
1174
1175 #[test]
1178 fn test_chain_complex_from_cw() {
1179 let tet = CwComplex::standard_tetrahedron();
1180 let cc = ChainComplex::from_cw_complex(&tet);
1181 assert_eq!(cc.rank(0), 4);
1182 assert_eq!(cc.rank(1), 6);
1183 assert_eq!(cc.rank(2), 4);
1184 assert_eq!(cc.rank(3), 1);
1185 }
1186
1187 #[test]
1190 fn test_snf_identity_2x2() {
1191 let mat = vec![vec![1, 0], vec![0, 1]];
1192 let d = smith_normal_form(&mat);
1193 assert_eq!(d, vec![1, 1]);
1194 }
1195
1196 #[test]
1197 fn test_snf_zero_matrix() {
1198 let mat = vec![vec![0, 0], vec![0, 0]];
1199 let d = smith_normal_form(&mat);
1200 assert!(d.is_empty());
1201 }
1202
1203 #[test]
1204 fn test_snf_diagonal() {
1205 let mat = vec![vec![2, 0], vec![0, 3]];
1206 let d = smith_normal_form(&mat);
1207 assert!(!d.is_empty());
1208 for &v in &d {
1209 assert!(v > 0);
1210 }
1211 }
1212
1213 #[test]
1216 fn test_homology_torus_betti() {
1217 let t2 = CwComplex::standard_torus();
1218 let h = CellularHomology::compute(&t2);
1219 assert_eq!(h.beta0(), 1, "β₀ of torus: {}", h.beta0());
1221 }
1222
1223 #[test]
1224 fn test_homology_euler_char_consistent() {
1225 let tet = CwComplex::standard_tetrahedron();
1226 let h = CellularHomology::compute(&tet);
1227 let chi_betti = EulerCharacteristic::from_betti(&h.betti);
1228 let chi_cells = tet.euler_characteristic();
1229 assert_eq!(
1230 chi_betti, chi_cells,
1231 "Euler-Poincaré: betti gives {} cells gives {}",
1232 chi_betti, chi_cells
1233 );
1234 }
1235
1236 #[test]
1239 fn test_simplex_boundary_edge() {
1240 let e = OrientedSimplex::new(vec![0, 1]);
1241 let b = e.boundary();
1242 assert_eq!(b.len(), 2);
1243 }
1244
1245 #[test]
1246 fn test_simplex_boundary_triangle() {
1247 let t = OrientedSimplex::new(vec![0, 1, 2]);
1248 let b = t.boundary();
1249 assert_eq!(b.len(), 3);
1250 assert_eq!(b[0].0, 1);
1252 assert_eq!(b[1].0, -1);
1253 assert_eq!(b[2].0, 1);
1254 }
1255
1256 #[test]
1257 fn test_simplex_boundary_squared_zero_tetrahedron() {
1258 let tet = OrientedSimplex::new(vec![0, 1, 2, 3]);
1259 assert!(tet.boundary_squared_zero(), "∂² ≠ 0 for tetrahedron");
1260 }
1261
1262 #[test]
1263 fn test_simplex_dim_correct() {
1264 let tet = OrientedSimplex::new(vec![0, 1, 2, 3]);
1265 assert_eq!(tet.dim(), 3);
1266 }
1267
1268 #[test]
1271 fn test_dual_complex_dim_swap() {
1272 let tet = CwComplex::standard_tetrahedron();
1273 let dual = DualComplex::new(tet, 3);
1274 assert_eq!(dual.dual_dim(3), 0);
1276 assert_eq!(dual.dual_dim(0), 3);
1278 }
1279
1280 #[test]
1281 fn test_dual_euler_char_equal_primal() {
1282 let tet = CwComplex::standard_tetrahedron();
1283 let chi_primal = tet.euler_characteristic();
1284 let dual = DualComplex::new(tet, 3);
1285 assert_eq!(dual.euler_characteristic(), chi_primal);
1286 }
1287
1288 #[test]
1289 fn test_triangle_circumcenter_equilateral() {
1290 let p0 = [0.0, 0.0, 0.0];
1291 let p1 = [1.0, 0.0, 0.0];
1292 let p2 = [0.5, (3.0_f64).sqrt() / 2.0, 0.0];
1293 let cc = DualComplex::triangle_circumcenter(p0, p1, p2);
1294 assert!((cc[0] - 0.5).abs() < 1e-10, "cc.x = {:.6}", cc[0]);
1296 }
1297
1298 #[test]
1301 fn test_coboundary_is_transpose_of_boundary() {
1302 let tet = CwComplex::standard_tetrahedron();
1303 let cc = ChainComplex::from_cw_complex(&tet);
1304 let cob = CoboundaryOperator::new(cc);
1305 let d1 = &cob.chain.boundary[0]; let delta0 = cob.coboundary_matrix(0); if !d1.is_empty() && !delta0.is_empty() {
1308 for i in 0..d1.len() {
1309 for j in 0..d1[0].len() {
1310 assert_eq!(
1311 d1[i][j], delta0[j][i],
1312 "Coboundary not transpose at ({},{})",
1313 i, j
1314 );
1315 }
1316 }
1317 }
1318 }
1319
1320 #[test]
1323 fn test_euler_char_from_cell_counts() {
1324 let chi = EulerCharacteristic::from_cell_counts(&[4, 6, 4, 1]);
1326 assert_eq!(chi, 1);
1327 }
1328
1329 #[test]
1330 fn test_euler_char_sphere_genus_0() {
1331 let chi = EulerCharacteristic::surface_genus(0);
1332 assert_eq!(chi, 2);
1333 }
1334
1335 #[test]
1336 fn test_euler_char_torus_genus_1() {
1337 let chi = EulerCharacteristic::surface_genus(1);
1338 assert_eq!(chi, 0);
1339 }
1340
1341 #[test]
1342 fn test_genus_from_chi_sphere() {
1343 let g = EulerCharacteristic::genus_from_chi(2).unwrap();
1344 assert_eq!(g, 0);
1345 }
1346
1347 #[test]
1348 fn test_verify_sphere_triangulation() {
1349 assert!(EulerCharacteristic::verify_sphere_triangulation(6, 12, 8));
1351 assert!(EulerCharacteristic::verify_sphere_triangulation(12, 30, 20));
1353 }
1354
1355 #[test]
1358 fn test_cellular_approx_homotopy_equiv_self() {
1359 let tet = CwComplex::standard_tetrahedron();
1360 let tet2 = CwComplex::standard_tetrahedron();
1361 let ca = CellularApproximation::new(tet, tet2);
1362 assert!(ca.homotopy_equivalent_homology());
1363 }
1364
1365 #[test]
1368 fn test_shellable_tetrahedron_f_vector() {
1369 let facets = vec![vec![0, 1, 2], vec![0, 1, 3], vec![0, 2, 3], vec![1, 2, 3]];
1371 let sc = ShellableComplex::new(facets);
1372 assert_eq!(sc.f_vector[0], 4, "f_0 = vertices: {}", sc.f_vector[0]);
1374 assert_eq!(sc.f_vector[1], 6, "f_1 = edges: {}", sc.f_vector[1]);
1375 assert_eq!(sc.f_vector[2], 4, "f_2 = triangles: {}", sc.f_vector[2]);
1376 }
1377
1378 #[test]
1379 fn test_shellable_is_pure() {
1380 let facets = vec![vec![0, 1, 2], vec![1, 2, 3]];
1381 let sc = ShellableComplex::new(facets);
1382 assert!(sc.is_pure());
1383 }
1384
1385 #[test]
1386 fn test_shellable_link_vertex() {
1387 let facets = vec![vec![0, 1, 2], vec![0, 2, 3]];
1388 let sc = ShellableComplex::new(facets);
1389 let link = sc.link_vertex(0);
1390 assert_eq!(link.len(), 2);
1391 }
1392
1393 #[test]
1394 fn test_shellable_h_vector_length() {
1395 let facets = vec![vec![0, 1, 2], vec![1, 2, 3]];
1396 let sc = ShellableComplex::new(facets);
1397 let h = sc.h_vector();
1398 assert_eq!(h.len(), sc.dim + 2);
1399 }
1400
1401 #[test]
1402 fn test_euler_char_from_betti_equals_cells() {
1403 let chi_betti = EulerCharacteristic::from_betti(&[1, 0, 1]);
1405 assert_eq!(chi_betti, 2);
1406 }
1407
1408 #[test]
1409 fn test_rank_of_zero_matrix() {
1410 let mat = vec![vec![0i32, 0], vec![0, 0]];
1411 assert_eq!(rank_of_matrix(&mat), 0);
1412 }
1413
1414 #[test]
1415 fn test_rank_of_identity() {
1416 let mat = vec![vec![1i32, 0], vec![0, 1]];
1417 assert_eq!(rank_of_matrix(&mat), 2);
1418 }
1419
1420 #[test]
1421 fn test_binomial_values() {
1422 assert_eq!(binomial(5, 2), 10);
1423 assert_eq!(binomial(4, 0), 1);
1424 assert_eq!(binomial(4, 4), 1);
1425 assert_eq!(binomial(0, 1), 0);
1426 }
1427
1428 fn torus_op() -> CoboundaryOperator {
1429 let cw = CwComplex::standard_torus();
1430 CoboundaryOperator::new(ChainComplex::from_cw_complex(&cw))
1431 }
1432
1433 #[test]
1434 fn test_cup_product_1_1_torus_valid() {
1435 let op = torus_op();
1436 assert!(op.cup_product_is_compatible(1, 1, 0, 0));
1437 assert!(op.cup_product_is_compatible(1, 1, 1, 1));
1438 }
1439
1440 #[test]
1441 fn test_cup_product_out_of_range_invalid() {
1442 let op = torus_op();
1443 assert!(!op.cup_product_is_compatible(1, 1, 2, 0));
1444 }
1445
1446 #[test]
1447 fn test_cup_product_exceeds_max_dim_invalid() {
1448 let op = torus_op();
1449 assert!(!op.cup_product_is_compatible(2, 1, 0, 0));
1450 }
1451
1452 #[test]
1453 fn test_cup_product_empty_complex_invalid() {
1454 let op = CoboundaryOperator::new(ChainComplex::from_cw_complex(&CwComplex::new()));
1455 assert!(!op.cup_product_is_compatible(0, 0, 0, 0));
1456 }
1457}