1use crate::progress_complex::gf2_rank_wide;
15use std::collections::{BTreeSet, HashMap};
16
17#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
20pub struct Cube {
21 pub corner: Vec<usize>,
22 pub dirs: Vec<usize>,
23}
24
25impl Cube {
26 pub fn dim(&self) -> usize {
27 self.dirs.len()
28 }
29
30 pub fn boundary(&self) -> Vec<Cube> {
33 let mut faces = Vec::with_capacity(2 * self.dirs.len());
34 for (idx, &a) in self.dirs.iter().enumerate() {
35 let mut sub = self.dirs.clone();
36 sub.remove(idx);
37 faces.push(Cube { corner: self.corner.clone(), dirs: sub.clone() });
38 let mut up = self.corner.clone();
39 up[a] += 1;
40 faces.push(Cube { corner: up, dirs: sub });
41 }
42 faces
43 }
44}
45
46pub struct CubicalComplex {
48 cells: Vec<BTreeSet<Cube>>,
49 dim: usize,
50}
51
52impl CubicalComplex {
53 pub fn from_top_cells(top: Vec<Cube>) -> Self {
55 let dim = top.iter().map(Cube::dim).max().unwrap_or(0);
56 let mut cells: Vec<BTreeSet<Cube>> = vec![BTreeSet::new(); dim + 1];
57 for c in top {
58 let d = c.dim();
59 cells[d].insert(c);
60 }
61 for k in (1..=dim).rev() {
62 let current: Vec<Cube> = cells[k].iter().cloned().collect();
63 for c in current {
64 for f in c.boundary() {
65 cells[f.dim()].insert(f);
66 }
67 }
68 }
69 CubicalComplex { cells, dim }
70 }
71
72 pub fn progress(lengths: &[usize], forbidden: &[Vec<usize>]) -> Self {
75 let d = lengths.len();
76 let forbidden: BTreeSet<Vec<usize>> = forbidden.iter().cloned().collect();
77 let mut top = Vec::new();
78 let mut idx = vec![0usize; d];
79 loop {
80 if !forbidden.contains(&idx) {
81 top.push(Cube { corner: idx.clone(), dirs: (0..d).collect() });
82 }
83 let mut a = 0;
84 while a < d {
85 idx[a] += 1;
86 if idx[a] < lengths[a] {
87 break;
88 }
89 idx[a] = 0;
90 a += 1;
91 }
92 if a == d {
93 break;
94 }
95 }
96 CubicalComplex::from_top_cells(top)
97 }
98
99 pub fn dim(&self) -> usize {
100 self.dim
101 }
102
103 pub fn num_cells(&self, k: usize) -> usize {
104 self.cells.get(k).map_or(0, BTreeSet::len)
105 }
106
107 fn boundary_rank(&self, k: usize) -> usize {
109 if k == 0 || k > self.dim {
110 return 0;
111 }
112 let lower: Vec<Cube> = self.cells[k - 1].iter().cloned().collect();
113 let ncols = lower.len();
114 if ncols == 0 {
115 return 0;
116 }
117 let lidx: HashMap<Cube, usize> = lower.into_iter().enumerate().map(|(i, c)| (c, i)).collect();
118 let rows: Vec<Vec<u64>> = self.cells[k]
119 .iter()
120 .map(|c| {
121 let mut row = vec![0u64; ncols.div_ceil(64)];
122 for f in c.boundary() {
123 let idx = lidx[&f];
124 row[idx / 64] ^= 1u64 << (idx % 64);
125 }
126 row
127 })
128 .collect();
129 gf2_rank_wide(rows, ncols)
130 }
131
132 pub fn betti(&self) -> Vec<usize> {
134 let mut ranks = vec![0usize; self.dim + 2];
135 for k in 1..=self.dim {
136 ranks[k] = self.boundary_rank(k);
137 }
138 (0..=self.dim).map(|k| self.cells[k].len() - ranks[k] - ranks[k + 1]).collect()
139 }
140
141 pub fn euler(&self) -> i64 {
143 (0..=self.dim)
144 .map(|k| {
145 let c = self.cells[k].len() as i64;
146 if k % 2 == 0 {
147 c
148 } else {
149 -c
150 }
151 })
152 .sum()
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 fn center(d: usize) -> Vec<Vec<usize>> {
161 vec![vec![1usize; d]]
162 }
163
164 #[test]
165 fn the_general_engine_reproduces_the_hand_built_2d_and_3d_complexes() {
166 let two = CubicalComplex::progress(&[3, 3], ¢er(2));
169 assert_eq!(two.betti(), vec![1, 1, 0], "2D mutex hole: β₁ = 1 (π₁ = Z)");
170 let three = CubicalComplex::progress(&[3, 3, 3], ¢er(3));
171 assert_eq!(three.betti(), vec![1, 0, 1, 0], "3D forbidden core: β₂ = 1 (π₂ = Z)");
172 }
173
174 #[test]
175 fn solid_d_cubes_are_contractible_in_every_dimension() {
176 for d in 1..=4 {
178 let lengths = vec![3usize; d];
179 let pc = CubicalComplex::progress(&lengths, &[]);
180 let beta = pc.betti();
181 assert_eq!(beta[0], 1, "connected in dimension {d}");
182 assert!(beta[1..].iter().all(|&b| b == 0), "solid d-cube is contractible (d={d})");
183 assert_eq!(pc.euler(), 1, "χ = 1 for a contractible complex (d={d})");
184 }
185 }
186
187 #[test]
188 fn four_processes_produce_pi_three_a_3_sphere_void() {
189 let pc = CubicalComplex::progress(&[3, 3, 3, 3], ¢er(4));
194 let beta = pc.betti();
195 assert_eq!(beta, vec![1, 0, 0, 1, 0], "4 processes, forbidden core ⇒ β₃ = 1: a 3-sphere void, π₃ = Z");
196 let alt: i64 = beta.iter().enumerate().map(|(k, &b)| if k % 2 == 0 { b as i64 } else { -(b as i64) }).sum();
198 assert_eq!(alt, pc.euler(), "Euler–Poincaré holds: Σ(−1)^k β_k = χ");
199 assert_eq!(pc.euler(), 0, "χ = 0, the signature of a 3-sphere shell");
200 }
201
202 #[test]
203 fn infinity_is_a_finite_law_checkable_at_every_rung_not_an_object_we_hold() {
204 let pc = CubicalComplex::progress(&[3, 3, 3, 3, 3], ¢er(5));
211 let mut expected = vec![0usize; 6];
212 expected[0] = 1;
213 expected[4] = 1;
214 assert_eq!(pc.betti(), expected, "5 processes ⇒ β₄ = 1: a 4-sphere void, π₄ = Z — the next rung");
215
216 for d in 2..=5 {
217 let beta = CubicalComplex::progress(&vec![3usize; d], ¢er(d)).betti();
218 assert_eq!(beta[d - 1], 1, "rung d={d}: π_{{d-1}} is realized");
219 assert_eq!(beta.iter().sum::<usize>(), 2, "exactly β₀ and β_{{d-1}} fire — a clean (d−1)-sphere");
220 }
221 }
222
223 #[test]
224 fn homology_is_not_all_the_invariants_pi3_of_the_2_sphere_is_invisible() {
225 let s2 = CubicalComplex::from_top_cells(vec![
233 Cube { corner: vec![0, 0, 0], dirs: vec![1, 2] },
234 Cube { corner: vec![1, 0, 0], dirs: vec![1, 2] },
235 Cube { corner: vec![0, 0, 0], dirs: vec![0, 2] },
236 Cube { corner: vec![0, 1, 0], dirs: vec![0, 2] },
237 Cube { corner: vec![0, 0, 0], dirs: vec![0, 1] },
238 Cube { corner: vec![0, 0, 1], dirs: vec![0, 1] },
239 ]);
240 assert_eq!(s2.betti(), vec![1, 0, 1], "the cube's surface is S²: β = [1,0,1]");
241 assert_eq!(s2.dim(), 2, "a 2-dimensional complex — β has no degree-3 term at all, yet π₃ ≠ 0");
243 }
244
245 #[test]
246 fn the_ladder_one_more_process_is_one_more_homotopy_dimension() {
247 for d in 2..=4 {
252 let lengths = vec![3usize; d];
253 let beta = CubicalComplex::progress(&lengths, ¢er(d)).betti();
254 assert_eq!(beta.len(), d + 1);
255 assert_eq!(beta[0], 1, "connected (d={d})");
256 for k in 1..=d {
257 let expected = usize::from(k == d - 1);
258 assert_eq!(beta[k], expected, "d={d}: β_{k} should be {expected} (only π_{{d-1}} ≠ 0)");
259 }
260 }
261 }
262}