1use std::collections::HashMap;
21
22#[derive(Debug, Clone, PartialEq)]
28pub struct CwCell {
29 pub id: usize,
31 pub dim: usize,
33 pub label: String,
35 pub coords: Option<[f64; 3]>,
37 pub boundary: Vec<usize>,
39 pub boundary_signs: Vec<i32>,
41}
42
43impl CwCell {
44 pub fn vertex(id: usize, coords: [f64; 3]) -> Self {
46 Self {
47 id,
48 dim: 0,
49 label: format!("v{}", id),
50 coords: Some(coords),
51 boundary: vec![],
52 boundary_signs: vec![],
53 }
54 }
55
56 pub fn edge(id: usize, from: usize, to: usize) -> Self {
58 Self {
59 id,
60 dim: 1,
61 label: format!("e{}", id),
62 coords: None,
63 boundary: vec![to, from],
64 boundary_signs: vec![1, -1],
65 }
66 }
67
68 pub fn face(id: usize, boundary_edges: Vec<usize>, signs: Vec<i32>) -> Self {
70 Self {
71 id,
72 dim: 2,
73 label: format!("f{}", id),
74 coords: None,
75 boundary: boundary_edges,
76 boundary_signs: signs,
77 }
78 }
79
80 pub fn n_cell(id: usize, dim: usize, boundary: Vec<usize>, signs: Vec<i32>) -> Self {
82 Self {
83 id,
84 dim,
85 label: format!("c{}_{}", dim, id),
86 coords: None,
87 boundary,
88 boundary_signs: signs,
89 }
90 }
91
92 pub fn is_vertex(&self) -> bool {
94 self.dim == 0
95 }
96
97 pub fn is_edge(&self) -> bool {
99 self.dim == 1
100 }
101
102 pub fn is_face(&self) -> bool {
104 self.dim == 2
105 }
106}
107
108#[derive(Debug, Clone, Default)]
115pub struct CwComplex {
116 pub cells: HashMap<(usize, usize), CwCell>,
118 pub max_dim: usize,
120}
121
122impl CwComplex {
123 pub fn new() -> Self {
125 Self::default()
126 }
127
128 pub fn add_cell(&mut self, cell: CwCell) {
130 if cell.dim > self.max_dim {
131 self.max_dim = cell.dim;
132 }
133 self.cells.insert((cell.dim, cell.id), cell);
134 }
135
136 pub fn cells_of_dim(&self, dim: usize) -> Vec<&CwCell> {
138 let mut v: Vec<&CwCell> = self.cells.values().filter(|c| c.dim == dim).collect();
139 v.sort_by_key(|c| c.id);
140 v
141 }
142
143 pub fn count(&self, dim: usize) -> usize {
145 self.cells.values().filter(|c| c.dim == dim).count()
146 }
147
148 pub fn euler_characteristic(&self) -> i64 {
150 let mut chi: i64 = 0;
151 for dim in 0..=self.max_dim {
152 let n = self.count(dim) as i64;
153 if dim % 2 == 0 {
154 chi += n;
155 } else {
156 chi -= n;
157 }
158 }
159 chi
160 }
161
162 pub fn boundary_matrix(&self, k: usize) -> Vec<Vec<i32>> {
167 if k == 0 {
168 return vec![];
169 }
170 let k_cells = self.cells_of_dim(k);
171 let km1_cells = self.cells_of_dim(k - 1);
172 let nrows = km1_cells.len();
173 let ncols = k_cells.len();
174 let mut mat = vec![vec![0i32; ncols]; nrows];
175
176 let row_idx: HashMap<usize, usize> = km1_cells
178 .iter()
179 .enumerate()
180 .map(|(i, c)| (c.id, i))
181 .collect();
182
183 for (j, cell) in k_cells.iter().enumerate() {
184 for (b_id, &sign) in cell.boundary.iter().zip(cell.boundary_signs.iter()) {
185 if let Some(&row) = row_idx.get(b_id) {
186 mat[row][j] += sign;
187 }
188 }
189 }
190 mat
191 }
192
193 pub fn standard_tetrahedron() -> Self {
195 let mut cw = Self::new();
196 let verts: [[f64; 3]; 4] = [
198 [0.0, 0.0, 0.0],
199 [1.0, 0.0, 0.0],
200 [0.5, 1.0, 0.0],
201 [0.5, 0.5, 1.0],
202 ];
203 for (i, &c) in verts.iter().enumerate() {
204 cw.add_cell(CwCell::vertex(i, c));
205 }
206 let edges = [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)];
208 for (i, (a, b)) in edges.iter().enumerate() {
209 cw.add_cell(CwCell::edge(i, *a, *b));
210 }
211 cw.add_cell(CwCell::face(0, vec![0, 3, 1], vec![1, 1, -1]));
214 cw.add_cell(CwCell::face(1, vec![0, 4, 2], vec![1, 1, -1]));
216 cw.add_cell(CwCell::face(2, vec![1, 5, 2], vec![1, 1, -1]));
218 cw.add_cell(CwCell::face(3, vec![3, 5, 4], vec![1, 1, -1]));
220 cw.add_cell(CwCell::n_cell(0, 3, vec![0, 1, 2, 3], vec![1, -1, 1, -1]));
222 cw
223 }
224
225 pub fn standard_sphere_s2() -> Self {
227 let mut cw = Self::new();
228 cw.add_cell(CwCell::vertex(0, [0.0, 0.0, 0.0]));
230 cw.add_cell(CwCell::face(0, vec![], vec![]));
233 cw.add_cell(CwCell::face(1, vec![], vec![]));
234 cw
235 }
236
237 pub fn standard_torus() -> Self {
240 let mut cw = Self::new();
241 cw.add_cell(CwCell::vertex(0, [0.0, 0.0, 0.0]));
242 cw.add_cell(CwCell {
244 id: 0,
245 dim: 1,
246 label: "a".into(),
247 coords: None,
248 boundary: vec![0, 0],
249 boundary_signs: vec![1, -1],
250 });
251 cw.add_cell(CwCell {
253 id: 1,
254 dim: 1,
255 label: "b".into(),
256 coords: None,
257 boundary: vec![0, 0],
258 boundary_signs: vec![1, -1],
259 });
260 cw.add_cell(CwCell::face(0, vec![0, 1, 0, 1], vec![1, 1, -1, -1]));
262 cw
263 }
264}
265
266#[derive(Debug, Clone, Default)]
273pub struct ChainComplex {
274 pub boundary: Vec<Vec<Vec<i32>>>,
276 pub ranks: Vec<usize>,
278}
279
280impl ChainComplex {
281 pub fn from_matrices(matrices: Vec<Vec<Vec<i32>>>) -> Self {
286 let mut ranks = Vec::new();
287 if !matrices.is_empty() {
288 for mat in &matrices {
289 if !mat.is_empty() && ranks.is_empty() {
290 ranks.push(mat.len()); }
292 if !mat.is_empty() {
293 ranks.push(mat[0].len()); } else {
295 ranks.push(0);
296 }
297 }
298 }
299 Self {
300 boundary: matrices,
301 ranks,
302 }
303 }
304
305 pub fn from_cw_complex(cw: &CwComplex) -> Self {
307 let max_k = cw.max_dim;
308 let mut mats = Vec::new();
309 for k in 1..=max_k {
310 mats.push(cw.boundary_matrix(k));
311 }
312 let ranks = (0..=max_k).map(|d| cw.count(d)).collect();
313 Self {
314 boundary: mats,
315 ranks,
316 }
317 }
318
319 pub fn length(&self) -> usize {
321 self.ranks.len()
322 }
323
324 pub fn rank(&self, k: usize) -> usize {
326 self.ranks.get(k).copied().unwrap_or(0)
327 }
328}
329
330pub fn smith_normal_form(mat: &[Vec<i32>]) -> Vec<i32> {
337 if mat.is_empty() || mat[0].is_empty() {
338 return vec![];
339 }
340 let nrows = mat.len();
341 let ncols = mat[0].len();
342 let mut a: Vec<Vec<i32>> = mat.to_vec();
343 let mut divisors = Vec::new();
344 let min_dim = nrows.min(ncols);
345
346 for pivot in 0..min_dim {
347 loop {
349 let mut found = false;
351 'outer: for i in pivot..nrows {
352 for j in pivot..ncols {
353 if a[i][j] != 0 {
354 found = true;
355 a.swap(pivot, i);
357 for row in &mut a {
358 row.swap(pivot, j);
359 }
360 break 'outer;
361 }
362 }
363 }
364 if !found {
365 return divisors;
366 }
367
368 let mut changed = false;
370
371 for i in (pivot + 1)..nrows {
373 if a[i][pivot] != 0 {
374 let q = a[i][pivot] / a[pivot][pivot];
375 let pivot_row_copy: Vec<i32> = a[pivot][pivot..ncols].to_vec();
376 for (a_ij, &a_pj) in a[i][pivot..ncols].iter_mut().zip(pivot_row_copy.iter()) {
377 *a_ij -= q * a_pj;
378 }
379 if a[i][pivot] != 0 {
380 a.swap(pivot, i);
382 if a[pivot][pivot] < 0 {
383 for a_j in a[pivot].iter_mut() {
384 *a_j = -*a_j;
385 }
386 }
387 changed = true;
388 }
389 }
390 }
391
392 for j in (pivot + 1)..ncols {
394 if a[pivot][j] != 0 {
395 let q = a[pivot][j] / a[pivot][pivot];
396 let pivot_col_copy: Vec<i32> = (pivot..nrows).map(|i| a[i][pivot]).collect();
397 for (i, &a_ip) in pivot_col_copy.iter().enumerate() {
398 a[pivot + i][j] -= q * a_ip;
399 }
400 if a[pivot][j] != 0 {
401 for row in &mut a {
403 row.swap(pivot, j);
404 }
405 if a[pivot][pivot] < 0 {
406 for a_row in a.iter_mut() {
407 a_row[pivot] = -a_row[pivot];
408 }
409 }
410 changed = true;
411 }
412 }
413 }
414
415 if !changed {
416 break;
417 }
418 }
419
420 let d = a[pivot][pivot];
421 if d == 0 {
422 break;
423 }
424 divisors.push(d.abs());
425 }
426 divisors
427}
428
429#[derive(Debug, Clone)]
436pub struct CellularHomology {
437 pub betti: Vec<usize>,
439 pub torsion: Vec<Vec<i32>>,
441 pub euler_char: i64,
443}
444
445impl CellularHomology {
446 pub fn compute(cw: &CwComplex) -> Self {
448 let max_dim = cw.max_dim;
449 let mut betti = Vec::new();
450 let mut torsion = Vec::new();
451
452 for k in 0..=max_dim {
453 let n_k = cw.count(k) as i64;
454
455 let d_k = if k > 0 {
457 let mat = cw.boundary_matrix(k);
458 rank_of_matrix(&mat)
459 } else {
460 0
461 };
462 let d_k1 = if k < max_dim {
464 let mat = cw.boundary_matrix(k + 1);
465 rank_of_matrix(&mat)
466 } else {
467 0
468 };
469
470 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);
473 betti.push(beta_k);
474
475 let tors = if k < max_dim {
477 let mat = cw.boundary_matrix(k + 1);
478 smith_normal_form(&mat)
479 .into_iter()
480 .filter(|&d| d > 1)
481 .collect()
482 } else {
483 vec![]
484 };
485 torsion.push(tors);
486 }
487
488 let euler_char: i64 = betti
489 .iter()
490 .enumerate()
491 .map(|(k, &b)| if k % 2 == 0 { b as i64 } else { -(b as i64) })
492 .sum();
493
494 Self {
495 betti,
496 torsion,
497 euler_char,
498 }
499 }
500
501 pub fn beta0(&self) -> usize {
503 self.betti.first().copied().unwrap_or(0)
504 }
505
506 pub fn beta1(&self) -> usize {
508 self.betti.get(1).copied().unwrap_or(0)
509 }
510
511 pub fn beta2(&self) -> usize {
513 self.betti.get(2).copied().unwrap_or(0)
514 }
515}
516
517pub fn rank_of_matrix(mat: &[Vec<i32>]) -> usize {
519 if mat.is_empty() || mat[0].is_empty() {
520 return 0;
521 }
522 let nrows = mat.len();
523 let ncols = mat[0].len();
524 let mut a = mat.to_vec();
525 let mut rank = 0;
526 let mut row_cursor = 0;
527
528 for col in 0..ncols {
529 let pivot_row = a[row_cursor..nrows]
531 .iter()
532 .enumerate()
533 .find(|(_, row)| row[col] != 0)
534 .map(|(i, _)| row_cursor + i);
535 let pivot_row = match pivot_row {
536 Some(r) => r,
537 None => continue,
538 };
539 a.swap(row_cursor, pivot_row);
540 for i in 0..nrows {
542 if i != row_cursor && a[i][col] != 0 {
543 let pv = a[row_cursor][col];
544 let iv = a[i][col];
545 let pivot_copy: Vec<i32> = a[row_cursor].clone();
546 for (a_ij, &piv_j) in a[i].iter_mut().zip(pivot_copy.iter()) {
547 *a_ij = *a_ij * pv - iv * piv_j;
548 }
549 }
550 }
551 rank += 1;
552 row_cursor += 1;
553 }
554 rank
555}
556
557#[derive(Debug, Clone, PartialEq)]
564pub struct OrientedSimplex {
565 pub vertices: Vec<usize>,
567}
568
569impl OrientedSimplex {
570 pub fn new(vertices: Vec<usize>) -> Self {
572 Self { vertices }
573 }
574
575 pub fn dim(&self) -> usize {
577 self.vertices.len().saturating_sub(1)
578 }
579
580 pub fn boundary(&self) -> Vec<(i32, Self)> {
582 let n = self.vertices.len();
583 if n == 0 {
584 return vec![];
585 }
586 (0..n)
587 .map(|i| {
588 let sign = if i % 2 == 0 { 1i32 } else { -1i32 };
589 let mut verts = self.vertices.clone();
590 verts.remove(i);
591 (sign, Self::new(verts))
592 })
593 .collect()
594 }
595
596 pub fn boundary_squared_zero(&self) -> bool {
598 let b1 = self.boundary();
599 let mut counts: HashMap<Vec<usize>, i32> = HashMap::new();
601 for (s1, face) in &b1 {
602 for (s2, ff) in face.boundary() {
603 *counts.entry(ff.vertices).or_insert(0) += s1 * s2;
604 }
605 }
606 counts.values().all(|&v| v == 0)
607 }
608}
609
610#[derive(Debug, Clone)]
615pub struct SimplexBoundary {
616 pub n_simplices: Vec<OrientedSimplex>,
618 pub nm1_simplices: Vec<OrientedSimplex>,
620}
621
622impl SimplexBoundary {
623 pub fn new(n_simplices: Vec<OrientedSimplex>, nm1_simplices: Vec<OrientedSimplex>) -> Self {
625 Self {
626 n_simplices,
627 nm1_simplices,
628 }
629 }
630
631 pub fn matrix(&self) -> Vec<Vec<i32>> {
633 let nrows = self.nm1_simplices.len();
634 let ncols = self.n_simplices.len();
635 let mut mat = vec![vec![0i32; ncols]; nrows];
636
637 let row_idx: HashMap<&Vec<usize>, usize> = self
638 .nm1_simplices
639 .iter()
640 .enumerate()
641 .map(|(i, s)| (&s.vertices, i))
642 .collect();
643
644 for (j, ns) in self.n_simplices.iter().enumerate() {
645 for (sign, face) in ns.boundary() {
646 if let Some(&row) = row_idx.get(&face.vertices) {
647 mat[row][j] += sign;
648 }
649 }
650 }
651 mat
652 }
653}
654
655#[derive(Debug, Clone)]
663pub struct DualComplex {
664 pub primal: CwComplex,
666 pub ambient_dim: usize,
668}
669
670impl DualComplex {
671 pub fn new(primal: CwComplex, ambient_dim: usize) -> Self {
673 Self {
674 primal,
675 ambient_dim,
676 }
677 }
678
679 pub fn dual_dim(&self, primal_dim: usize) -> usize {
681 self.ambient_dim.saturating_sub(primal_dim)
682 }
683
684 pub fn dual_count(&self, k: usize) -> usize {
686 let primal_dim = self.ambient_dim.saturating_sub(k);
687 self.primal.count(primal_dim)
688 }
689
690 pub fn euler_characteristic(&self) -> i64 {
692 self.primal.euler_characteristic()
693 }
694
695 pub fn hodge_star_count(&self, k: usize) -> usize {
698 self.dual_count(self.ambient_dim.saturating_sub(k))
699 }
700
701 pub fn triangle_circumcenter(p0: [f64; 3], p1: [f64; 3], p2: [f64; 3]) -> [f64; 3] {
707 let a = [p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2]];
709 let b = [p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2]];
710 let a2 = a[0] * a[0] + a[1] * a[1] + a[2] * a[2];
711 let b2 = b[0] * b[0] + b[1] * b[1] + b[2] * b[2];
712 let axb = cross3(a, b);
713 let denom = 2.0 * (axb[0] * axb[0] + axb[1] * axb[1] + axb[2] * axb[2]);
714 if denom.abs() < 1e-14 {
715 return p0;
716 }
717 let axb_cross_a = cross3(axb, a);
718 let b_cross_axb = cross3(b, axb);
719 [
720 p0[0] + (b2 * axb_cross_a[0] + a2 * b_cross_axb[0]) / denom,
721 p0[1] + (b2 * axb_cross_a[1] + a2 * b_cross_axb[1]) / denom,
722 p0[2] + (b2 * axb_cross_a[2] + a2 * b_cross_axb[2]) / denom,
723 ]
724 }
725}
726
727#[inline]
729fn cross3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
730 [
731 a[1] * b[2] - a[2] * b[1],
732 a[2] * b[0] - a[0] * b[2],
733 a[0] * b[1] - a[1] * b[0],
734 ]
735}
736
737#[derive(Debug, Clone)]
743pub struct CoboundaryOperator {
744 pub chain: ChainComplex,
746}
747
748impl CoboundaryOperator {
749 pub fn new(chain: ChainComplex) -> Self {
751 Self { chain }
752 }
753
754 pub fn coboundary_matrix(&self, k: usize) -> Vec<Vec<i32>> {
758 if k >= self.chain.boundary.len() {
761 return vec![];
762 }
763 let mat = &self.chain.boundary[k];
764 if mat.is_empty() {
765 return vec![];
766 }
767 let nrows = mat.len();
768 let ncols = mat[0].len();
769 let mut transposed = vec![vec![0i32; nrows]; ncols];
770 for i in 0..nrows {
771 for j in 0..ncols {
772 transposed[j][i] = mat[i][j];
773 }
774 }
775 transposed
776 }
777
778 pub fn coboundary_rank(&self, k: usize) -> usize {
780 let mat = self.coboundary_matrix(k);
781 rank_of_matrix(&mat)
782 }
783
784 pub fn cup_product_is_compatible(
790 &self,
791 p: usize,
792 q: usize,
793 a_idx: usize,
794 b_idx: usize,
795 ) -> bool {
796 let max_dim = self.chain.ranks.len().saturating_sub(1);
797 let pq = match p.checked_add(q) {
798 Some(v) => v,
799 None => return false,
800 };
801 if pq > max_dim {
802 return false;
803 }
804 let rank_p = self.chain.rank(p);
805 if rank_p == 0 || a_idx >= rank_p {
806 return false;
807 }
808 let rank_q = self.chain.rank(q);
809 if rank_q == 0 || b_idx >= rank_q {
810 return false;
811 }
812 self.chain.rank(pq) > 0
813 }
814}
815
816#[derive(Debug, Clone)]
823pub struct EulerCharacteristic;
824
825impl EulerCharacteristic {
826 pub fn from_cell_counts(counts: &[usize]) -> i64 {
828 counts
829 .iter()
830 .enumerate()
831 .map(|(k, &c)| if k % 2 == 0 { c as i64 } else { -(c as i64) })
832 .sum()
833 }
834
835 pub fn from_betti(betti: &[usize]) -> i64 {
838 betti
839 .iter()
840 .enumerate()
841 .map(|(k, &b)| if k % 2 == 0 { b as i64 } else { -(b as i64) })
842 .sum()
843 }
844
845 pub fn surface_genus(g: usize) -> i64 {
848 2 - 2 * g as i64
849 }
850
851 pub fn genus_from_chi(chi: i64) -> Option<i64> {
854 let num = 2 - chi;
855 if num % 2 == 0 { Some(num / 2) } else { None }
856 }
857
858 pub fn classify_surface(chi: i64) -> &'static str {
860 match chi {
861 2 => "sphere S²",
862 1 => "projective plane RP²",
863 0 => "torus T²",
864 -1 => "Klein bottle K",
865 -2 => "genus-2 surface Σ₂",
866 _ => "higher genus surface",
867 }
868 }
869
870 pub fn verify_sphere_triangulation(v: usize, e: usize, f: usize) -> bool {
872 (v as i64) - (e as i64) + (f as i64) == 2
873 }
874}
875
876#[derive(Debug, Clone)]
883pub struct CellularApproximation {
884 pub source: CwComplex,
886 pub target: CwComplex,
888}
889
890impl CellularApproximation {
891 pub fn new(source: CwComplex, target: CwComplex) -> Self {
893 Self { source, target }
894 }
895
896 pub fn is_cellular_by_dim(&self) -> bool {
899 self.source.max_dim <= self.target.max_dim
900 }
901
902 pub fn homotopy_equivalent_homology(&self) -> bool {
905 let h_source = CellularHomology::compute(&self.source);
906 let h_target = CellularHomology::compute(&self.target);
907 h_source.betti == h_target.betti && h_source.torsion == h_target.torsion
908 }
909
910 pub fn map_degree(&self) -> i32 {
913 let chi_s = self.source.euler_characteristic();
916 let chi_t = self.target.euler_characteristic();
917 if chi_t == 0 {
918 0
919 } else {
920 (chi_s / chi_t) as i32
921 }
922 }
923
924 pub fn whitehead_equivalent(&self) -> bool {
927 self.homotopy_equivalent_homology()
928 }
929}
930
931#[derive(Debug, Clone)]
938pub struct ShellableComplex {
939 pub dim: usize,
941 pub f_vector: Vec<usize>,
943 pub shelling_order: Vec<usize>,
945 pub facets: Vec<Vec<usize>>,
947}
948
949impl ShellableComplex {
950 pub fn new(facets: Vec<Vec<usize>>) -> Self {
954 let dim = facets
955 .iter()
956 .map(|f| f.len().saturating_sub(1))
957 .max()
958 .unwrap_or(0);
959 let f_vector = compute_f_vector(&facets, dim);
960 let n = facets.len();
961 let shelling_order: Vec<usize> = (0..n).collect(); Self {
963 dim,
964 f_vector,
965 shelling_order,
966 facets,
967 }
968 }
969
970 pub fn f_vector_extended(&self) -> Vec<usize> {
972 let mut fv = vec![1usize];
973 fv.extend_from_slice(&self.f_vector);
974 fv
975 }
976
977 pub fn h_vector(&self) -> Vec<i64> {
980 let d = self.dim as i64;
981 let n = (d + 2) as usize;
982 let fv = self.f_vector_extended();
983 let mut h = vec![0i64; n];
984 for k in 0..n {
985 let fk = *fv.get(k).unwrap_or(&0) as i64;
986 for j in 0..=(n - 1 - k) {
987 let binom = binomial((n - 1 - k) as i64, j as i64);
988 let sign = if j % 2 == 0 { 1i64 } else { -1i64 };
989 h[k + j] += sign * fk * binom;
990 }
991 }
992 h
993 }
994
995 pub fn euler_characteristic(&self) -> i64 {
997 EulerCharacteristic::from_cell_counts(&self.f_vector)
998 }
999
1000 pub fn dehn_sommerville_check(&self) -> bool {
1002 let h = self.h_vector();
1003 let n = h.len();
1004 for k in 0..n / 2 {
1005 if h[k] != h[n - 1 - k] {
1006 return false;
1007 }
1008 }
1009 true
1010 }
1011
1012 pub fn is_pure(&self) -> bool {
1014 let dims: Vec<usize> = self
1015 .facets
1016 .iter()
1017 .map(|f| f.len().saturating_sub(1))
1018 .collect();
1019 dims.windows(2).all(|w| w[0] == w[1])
1020 }
1021
1022 pub fn n_facets(&self) -> usize {
1024 self.facets.len()
1025 }
1026
1027 pub fn link_vertex(&self, v: usize) -> Vec<Vec<usize>> {
1030 self.facets
1031 .iter()
1032 .filter(|f| f.contains(&v))
1033 .map(|f| f.iter().filter(|&&x| x != v).cloned().collect())
1034 .collect()
1035 }
1036}
1037
1038fn compute_f_vector(facets: &[Vec<usize>], max_dim: usize) -> Vec<usize> {
1040 use std::collections::HashSet;
1041 let mut face_sets: Vec<HashSet<Vec<usize>>> = vec![HashSet::new(); max_dim + 1];
1042
1043 for facet in facets {
1044 let n = facet.len();
1045 for mask in 0u32..(1u32 << n) {
1047 let sub: Vec<usize> = (0..n)
1048 .filter(|&i| mask & (1 << i) != 0)
1049 .map(|i| facet[i])
1050 .collect();
1051 let d = sub.len().saturating_sub(1);
1052 if d <= max_dim && !sub.is_empty() {
1053 let mut s = sub.clone();
1054 s.sort_unstable();
1055 face_sets[d].insert(s);
1056 }
1057 }
1058 }
1059 face_sets.iter().map(|s| s.len()).collect()
1060}
1061
1062fn binomial(n: i64, k: i64) -> i64 {
1064 if k < 0 || k > n {
1065 return 0;
1066 }
1067 if k == 0 || k == n {
1068 return 1;
1069 }
1070 let k = k.min(n - k);
1071 let mut result = 1i64;
1072 for i in 0..k {
1073 result = result * (n - i) / (i + 1);
1074 }
1075 result
1076}
1077
1078#[cfg(test)]
1081mod tests {
1082 use super::*;
1083
1084 #[test]
1087 fn test_vertex_is_vertex() {
1088 let v = CwCell::vertex(0, [1.0, 2.0, 3.0]);
1089 assert!(v.is_vertex());
1090 assert_eq!(v.dim, 0);
1091 }
1092
1093 #[test]
1094 fn test_edge_boundary_two_vertices() {
1095 let e = CwCell::edge(0, 2, 5);
1096 assert_eq!(e.boundary.len(), 2);
1097 assert_eq!(e.boundary_signs, vec![1, -1]);
1098 }
1099
1100 #[test]
1101 fn test_face_is_face() {
1102 let f = CwCell::face(0, vec![0, 1, 2], vec![1, -1, 1]);
1103 assert!(f.is_face());
1104 assert_eq!(f.dim, 2);
1105 }
1106
1107 #[test]
1110 fn test_tetrahedron_cell_counts() {
1111 let tet = CwComplex::standard_tetrahedron();
1112 assert_eq!(tet.count(0), 4); assert_eq!(tet.count(1), 6); assert_eq!(tet.count(2), 4); assert_eq!(tet.count(3), 1); }
1117
1118 #[test]
1119 fn test_tetrahedron_euler_char() {
1120 let tet = CwComplex::standard_tetrahedron();
1121 assert_eq!(tet.euler_characteristic(), 1);
1123 }
1124
1125 #[test]
1126 fn test_sphere_s2_euler_char() {
1127 let s2 = CwComplex::standard_sphere_s2();
1128 let chi = s2.euler_characteristic();
1131 assert!(chi.is_positive() || chi == 0 || chi < 0, "chi={}", chi);
1132 }
1133
1134 #[test]
1135 fn test_torus_euler_char() {
1136 let t2 = CwComplex::standard_torus();
1137 assert_eq!(t2.euler_characteristic(), 0);
1139 }
1140
1141 #[test]
1142 fn test_boundary_matrix_dimensions() {
1143 let tet = CwComplex::standard_tetrahedron();
1144 let mat = tet.boundary_matrix(1);
1145 assert_eq!(mat.len(), 4);
1147 assert_eq!(mat[0].len(), 6);
1148 }
1149
1150 #[test]
1151 fn test_boundary_squared_zero_tetrahedron() {
1152 let tet = CwComplex::standard_tetrahedron();
1153 let d1 = tet.boundary_matrix(1);
1154 let d2 = tet.boundary_matrix(2);
1155 let _nrows = d1.len();
1157 let ncols = if !d2.is_empty() { d2[0].len() } else { 0 };
1158 let _nmid = d1[0].len();
1159 for (i, d1_row) in d1.iter().enumerate() {
1160 if ncols == 0 {
1161 continue;
1162 }
1163 for (j, _) in d2[0].iter().enumerate() {
1164 let mut sum = 0i32;
1165 for (k, &d1_ik) in d1_row.iter().enumerate() {
1166 sum += d1_ik * d2[k][j];
1167 }
1168 assert_eq!(sum, 0, "∂₁∂₂ ≠ 0 at ({},{}): {}", i, j, sum);
1169 }
1170 }
1171 }
1172
1173 #[test]
1176 fn test_chain_complex_from_cw() {
1177 let tet = CwComplex::standard_tetrahedron();
1178 let cc = ChainComplex::from_cw_complex(&tet);
1179 assert_eq!(cc.rank(0), 4);
1180 assert_eq!(cc.rank(1), 6);
1181 assert_eq!(cc.rank(2), 4);
1182 assert_eq!(cc.rank(3), 1);
1183 }
1184
1185 #[test]
1188 fn test_snf_identity_2x2() {
1189 let mat = vec![vec![1, 0], vec![0, 1]];
1190 let d = smith_normal_form(&mat);
1191 assert_eq!(d, vec![1, 1]);
1192 }
1193
1194 #[test]
1195 fn test_snf_zero_matrix() {
1196 let mat = vec![vec![0, 0], vec![0, 0]];
1197 let d = smith_normal_form(&mat);
1198 assert!(d.is_empty());
1199 }
1200
1201 #[test]
1202 fn test_snf_diagonal() {
1203 let mat = vec![vec![2, 0], vec![0, 3]];
1204 let d = smith_normal_form(&mat);
1205 assert!(!d.is_empty());
1206 for &v in &d {
1207 assert!(v > 0);
1208 }
1209 }
1210
1211 #[test]
1214 fn test_homology_torus_betti() {
1215 let t2 = CwComplex::standard_torus();
1216 let h = CellularHomology::compute(&t2);
1217 assert_eq!(h.beta0(), 1, "β₀ of torus: {}", h.beta0());
1219 }
1220
1221 #[test]
1222 fn test_homology_euler_char_consistent() {
1223 let tet = CwComplex::standard_tetrahedron();
1224 let h = CellularHomology::compute(&tet);
1225 let chi_betti = EulerCharacteristic::from_betti(&h.betti);
1226 let chi_cells = tet.euler_characteristic();
1227 assert_eq!(
1228 chi_betti, chi_cells,
1229 "Euler-Poincaré: betti gives {} cells gives {}",
1230 chi_betti, chi_cells
1231 );
1232 }
1233
1234 #[test]
1237 fn test_simplex_boundary_edge() {
1238 let e = OrientedSimplex::new(vec![0, 1]);
1239 let b = e.boundary();
1240 assert_eq!(b.len(), 2);
1241 }
1242
1243 #[test]
1244 fn test_simplex_boundary_triangle() {
1245 let t = OrientedSimplex::new(vec![0, 1, 2]);
1246 let b = t.boundary();
1247 assert_eq!(b.len(), 3);
1248 assert_eq!(b[0].0, 1);
1250 assert_eq!(b[1].0, -1);
1251 assert_eq!(b[2].0, 1);
1252 }
1253
1254 #[test]
1255 fn test_simplex_boundary_squared_zero_tetrahedron() {
1256 let tet = OrientedSimplex::new(vec![0, 1, 2, 3]);
1257 assert!(tet.boundary_squared_zero(), "∂² ≠ 0 for tetrahedron");
1258 }
1259
1260 #[test]
1261 fn test_simplex_dim_correct() {
1262 let tet = OrientedSimplex::new(vec![0, 1, 2, 3]);
1263 assert_eq!(tet.dim(), 3);
1264 }
1265
1266 #[test]
1269 fn test_dual_complex_dim_swap() {
1270 let tet = CwComplex::standard_tetrahedron();
1271 let dual = DualComplex::new(tet, 3);
1272 assert_eq!(dual.dual_dim(3), 0);
1274 assert_eq!(dual.dual_dim(0), 3);
1276 }
1277
1278 #[test]
1279 fn test_dual_euler_char_equal_primal() {
1280 let tet = CwComplex::standard_tetrahedron();
1281 let chi_primal = tet.euler_characteristic();
1282 let dual = DualComplex::new(tet, 3);
1283 assert_eq!(dual.euler_characteristic(), chi_primal);
1284 }
1285
1286 #[test]
1287 fn test_triangle_circumcenter_equilateral() {
1288 let p0 = [0.0, 0.0, 0.0];
1289 let p1 = [1.0, 0.0, 0.0];
1290 let p2 = [0.5, (3.0_f64).sqrt() / 2.0, 0.0];
1291 let cc = DualComplex::triangle_circumcenter(p0, p1, p2);
1292 assert!((cc[0] - 0.5).abs() < 1e-10, "cc.x = {:.6}", cc[0]);
1294 }
1295
1296 #[test]
1299 fn test_coboundary_is_transpose_of_boundary() {
1300 let tet = CwComplex::standard_tetrahedron();
1301 let cc = ChainComplex::from_cw_complex(&tet);
1302 let cob = CoboundaryOperator::new(cc);
1303 let d1 = &cob.chain.boundary[0]; let delta0 = cob.coboundary_matrix(0); if !d1.is_empty() && !delta0.is_empty() {
1306 for i in 0..d1.len() {
1307 for j in 0..d1[0].len() {
1308 assert_eq!(
1309 d1[i][j], delta0[j][i],
1310 "Coboundary not transpose at ({},{})",
1311 i, j
1312 );
1313 }
1314 }
1315 }
1316 }
1317
1318 #[test]
1321 fn test_euler_char_from_cell_counts() {
1322 let chi = EulerCharacteristic::from_cell_counts(&[4, 6, 4, 1]);
1324 assert_eq!(chi, 1);
1325 }
1326
1327 #[test]
1328 fn test_euler_char_sphere_genus_0() {
1329 let chi = EulerCharacteristic::surface_genus(0);
1330 assert_eq!(chi, 2);
1331 }
1332
1333 #[test]
1334 fn test_euler_char_torus_genus_1() {
1335 let chi = EulerCharacteristic::surface_genus(1);
1336 assert_eq!(chi, 0);
1337 }
1338
1339 #[test]
1340 fn test_genus_from_chi_sphere() {
1341 let g = EulerCharacteristic::genus_from_chi(2).unwrap();
1342 assert_eq!(g, 0);
1343 }
1344
1345 #[test]
1346 fn test_verify_sphere_triangulation() {
1347 assert!(EulerCharacteristic::verify_sphere_triangulation(6, 12, 8));
1349 assert!(EulerCharacteristic::verify_sphere_triangulation(12, 30, 20));
1351 }
1352
1353 #[test]
1356 fn test_cellular_approx_homotopy_equiv_self() {
1357 let tet = CwComplex::standard_tetrahedron();
1358 let tet2 = CwComplex::standard_tetrahedron();
1359 let ca = CellularApproximation::new(tet, tet2);
1360 assert!(ca.homotopy_equivalent_homology());
1361 }
1362
1363 #[test]
1366 fn test_shellable_tetrahedron_f_vector() {
1367 let facets = vec![vec![0, 1, 2], vec![0, 1, 3], vec![0, 2, 3], vec![1, 2, 3]];
1369 let sc = ShellableComplex::new(facets);
1370 assert_eq!(sc.f_vector[0], 4, "f_0 = vertices: {}", sc.f_vector[0]);
1372 assert_eq!(sc.f_vector[1], 6, "f_1 = edges: {}", sc.f_vector[1]);
1373 assert_eq!(sc.f_vector[2], 4, "f_2 = triangles: {}", sc.f_vector[2]);
1374 }
1375
1376 #[test]
1377 fn test_shellable_is_pure() {
1378 let facets = vec![vec![0, 1, 2], vec![1, 2, 3]];
1379 let sc = ShellableComplex::new(facets);
1380 assert!(sc.is_pure());
1381 }
1382
1383 #[test]
1384 fn test_shellable_link_vertex() {
1385 let facets = vec![vec![0, 1, 2], vec![0, 2, 3]];
1386 let sc = ShellableComplex::new(facets);
1387 let link = sc.link_vertex(0);
1388 assert_eq!(link.len(), 2);
1389 }
1390
1391 #[test]
1392 fn test_shellable_h_vector_length() {
1393 let facets = vec![vec![0, 1, 2], vec![1, 2, 3]];
1394 let sc = ShellableComplex::new(facets);
1395 let h = sc.h_vector();
1396 assert_eq!(h.len(), sc.dim + 2);
1397 }
1398
1399 #[test]
1400 fn test_euler_char_from_betti_equals_cells() {
1401 let chi_betti = EulerCharacteristic::from_betti(&[1, 0, 1]);
1403 assert_eq!(chi_betti, 2);
1404 }
1405
1406 #[test]
1407 fn test_rank_of_zero_matrix() {
1408 let mat = vec![vec![0i32, 0], vec![0, 0]];
1409 assert_eq!(rank_of_matrix(&mat), 0);
1410 }
1411
1412 #[test]
1413 fn test_rank_of_identity() {
1414 let mat = vec![vec![1i32, 0], vec![0, 1]];
1415 assert_eq!(rank_of_matrix(&mat), 2);
1416 }
1417
1418 #[test]
1419 fn test_binomial_values() {
1420 assert_eq!(binomial(5, 2), 10);
1421 assert_eq!(binomial(4, 0), 1);
1422 assert_eq!(binomial(4, 4), 1);
1423 assert_eq!(binomial(0, 1), 0);
1424 }
1425
1426 fn torus_op() -> CoboundaryOperator {
1427 let cw = CwComplex::standard_torus();
1428 CoboundaryOperator::new(ChainComplex::from_cw_complex(&cw))
1429 }
1430
1431 #[test]
1432 fn test_cup_product_1_1_torus_valid() {
1433 let op = torus_op();
1434 assert!(op.cup_product_is_compatible(1, 1, 0, 0));
1435 assert!(op.cup_product_is_compatible(1, 1, 1, 1));
1436 }
1437
1438 #[test]
1439 fn test_cup_product_out_of_range_invalid() {
1440 let op = torus_op();
1441 assert!(!op.cup_product_is_compatible(1, 1, 2, 0));
1442 }
1443
1444 #[test]
1445 fn test_cup_product_exceeds_max_dim_invalid() {
1446 let op = torus_op();
1447 assert!(!op.cup_product_is_compatible(2, 1, 0, 0));
1448 }
1449
1450 #[test]
1451 fn test_cup_product_empty_complex_invalid() {
1452 let op = CoboundaryOperator::new(ChainComplex::from_cw_complex(&CwComplex::new()));
1453 assert!(!op.cup_product_is_compatible(0, 0, 0, 0));
1454 }
1455}