1use std::collections::{BTreeSet, HashMap};
24
25fn gf2_rank(mut rows: Vec<u128>) -> usize {
27 let mut rank = 0;
28 let mut col = 0;
29 while col < 128 && rank < rows.len() {
30 let bit = 1u128 << col;
31 if let Some(pivot) = (rank..rows.len()).find(|&r| rows[r] & bit != 0) {
32 rows.swap(rank, pivot);
33 let pr = rows[rank];
34 for r in 0..rows.len() {
35 if r != rank && rows[r] & bit != 0 {
36 rows[r] ^= pr;
37 }
38 }
39 rank += 1;
40 }
41 col += 1;
42 }
43 rank
44}
45
46pub(crate) fn gf2_rank_wide(mut rows: Vec<Vec<u64>>, ncols: usize) -> usize {
48 let words = ncols.div_ceil(64);
49 for r in &mut rows {
50 r.resize(words, 0);
51 }
52 let mut rank = 0;
53 for col in 0..ncols {
54 let (w, bit) = (col / 64, 1u64 << (col % 64));
55 if let Some(pivot) = (rank..rows.len()).find(|&r| rows[r][w] & bit != 0) {
56 rows.swap(rank, pivot);
57 let pr = rows[rank].clone();
58 for r in 0..rows.len() {
59 if r != rank && rows[r][w] & bit != 0 {
60 for t in 0..words {
61 rows[r][t] ^= pr[t];
62 }
63 }
64 }
65 rank += 1;
66 if rank == rows.len() {
67 break;
68 }
69 }
70 }
71 rank
72}
73
74pub struct ProgressComplex {
77 pub n: usize,
78 pub m: usize,
79 forbidden: BTreeSet<(usize, usize)>,
80}
81
82impl ProgressComplex {
83 pub fn new(n: usize, m: usize, forbidden: &[(usize, usize)]) -> Self {
84 ProgressComplex { n, m, forbidden: forbidden.iter().copied().collect() }
85 }
86
87 fn vid(&self, i: usize, j: usize) -> usize {
88 i * (self.m + 1) + j
89 }
90
91 fn cell_edges(&self, i: usize, j: usize) -> [(usize, usize); 4] {
93 let v = |a: usize, b: usize| self.vid(a, b);
94 let order = |a: usize, b: usize| (a.min(b), a.max(b));
95 [
96 order(v(i, j), v(i + 1, j)), order(v(i, j + 1), v(i + 1, j + 1)), order(v(i, j), v(i, j + 1)), order(v(i + 1, j), v(i + 1, j + 1)), ]
101 }
102
103 fn allowed_cells(&self) -> Vec<(usize, usize)> {
104 let mut cells = Vec::new();
105 for i in 0..self.n {
106 for j in 0..self.m {
107 if !self.forbidden.contains(&(i, j)) {
108 cells.push((i, j));
109 }
110 }
111 }
112 cells
113 }
114
115 pub fn betti(&self) -> (usize, usize, usize) {
119 let cells = self.allowed_cells();
120
121 let mut edge_set: BTreeSet<(usize, usize)> = BTreeSet::new();
122 let mut vert_set: BTreeSet<usize> = BTreeSet::new();
123 for &(i, j) in &cells {
124 for e in self.cell_edges(i, j) {
125 edge_set.insert(e);
126 vert_set.insert(e.0);
127 vert_set.insert(e.1);
128 }
129 }
130
131 let edges: Vec<(usize, usize)> = edge_set.into_iter().collect();
132 let edge_index: HashMap<(usize, usize), usize> = edges.iter().enumerate().map(|(k, &e)| (e, k)).collect();
133 let verts: Vec<usize> = vert_set.into_iter().collect();
134 let vert_index: HashMap<usize, usize> = verts.iter().enumerate().map(|(k, &v)| (v, k)).collect();
135 let (nv, ne, nc) = (verts.len(), edges.len(), cells.len());
136 assert!(ne <= 128, "GF(2) rows are u128 — keep grids small (#edges = {ne})");
137
138 let mut parent: Vec<usize> = (0..nv).collect();
140 fn find(parent: &mut [usize], x: usize) -> usize {
141 let mut r = x;
142 while parent[r] != r {
143 r = parent[r];
144 }
145 let mut c = x;
146 while parent[c] != r {
147 let nx = parent[c];
148 parent[c] = r;
149 c = nx;
150 }
151 r
152 }
153 for &(a, b) in &edges {
154 let (ra, rb) = (find(&mut parent, vert_index[&a]), find(&mut parent, vert_index[&b]));
155 if ra != rb {
156 parent[ra] = rb;
157 }
158 }
159 let b0 = (0..nv).filter(|&x| find(&mut parent, x) == x).count();
160
161 let d2: Vec<u128> = cells
163 .iter()
164 .map(|&(i, j)| self.cell_edges(i, j).iter().fold(0u128, |row, e| row ^ (1u128 << edge_index[e])))
165 .collect();
166 let rank2 = gf2_rank(d2);
167
168 let b2 = nc - rank2;
169 let b1 = ne + b0 - nv - rank2;
171 (b0, b1, b2)
172 }
173
174 pub fn euler(&self) -> i64 {
176 let cells = self.allowed_cells();
177 let mut edge_set: BTreeSet<(usize, usize)> = BTreeSet::new();
178 let mut vert_set: BTreeSet<usize> = BTreeSet::new();
179 for &(i, j) in &cells {
180 for e in self.cell_edges(i, j) {
181 edge_set.insert(e);
182 vert_set.insert(e.0);
183 vert_set.insert(e.1);
184 }
185 }
186 vert_set.len() as i64 - edge_set.len() as i64 + cells.len() as i64
187 }
188}
189
190type V3 = (usize, usize, usize);
192
193pub struct ProgressComplex3 {
199 pub n: usize,
200 pub m: usize,
201 pub p: usize,
202 forbidden: BTreeSet<V3>,
203}
204
205impl ProgressComplex3 {
206 pub fn new(n: usize, m: usize, p: usize, forbidden: &[V3]) -> Self {
207 ProgressComplex3 { n, m, p, forbidden: forbidden.iter().copied().collect() }
208 }
209
210 fn corners(i: usize, j: usize, l: usize) -> [V3; 8] {
212 let mut c = [(0, 0, 0); 8];
213 for (b, slot) in c.iter_mut().enumerate() {
214 *slot = (i + (b & 1), j + ((b >> 1) & 1), l + ((b >> 2) & 1));
215 }
216 c
217 }
218
219 fn differ_in_one(a: V3, b: V3) -> bool {
220 let d = (a.0 != b.0) as u8 + (a.1 != b.1) as u8 + (a.2 != b.2) as u8;
221 d == 1
222 }
223
224 fn cell_edges(i: usize, j: usize, l: usize) -> Vec<[V3; 2]> {
226 let cs = Self::corners(i, j, l);
227 let mut es = Vec::new();
228 for a in 0..8 {
229 for b in (a + 1)..8 {
230 if Self::differ_in_one(cs[a], cs[b]) {
231 let (mut u, mut v) = (cs[a], cs[b]);
232 if v < u {
233 std::mem::swap(&mut u, &mut v);
234 }
235 es.push([u, v]);
236 }
237 }
238 }
239 es
240 }
241
242 fn cell_faces(i: usize, j: usize, l: usize) -> Vec<[V3; 4]> {
244 let cs = Self::corners(i, j, l);
245 let mut fs = Vec::new();
246 for axis in 0..3 {
247 for val in 0..2 {
248 let mut quad: Vec<V3> = cs
249 .iter()
250 .copied()
251 .filter(|&c| [c.0, c.1, c.2][axis] == [i, j, l][axis] + val)
252 .collect();
253 quad.sort_unstable();
254 fs.push([quad[0], quad[1], quad[2], quad[3]]);
255 }
256 }
257 fs
258 }
259
260 fn face_edges(face: &[V3; 4]) -> Vec<[V3; 2]> {
262 let mut es = Vec::new();
263 for a in 0..4 {
264 for b in (a + 1)..4 {
265 if Self::differ_in_one(face[a], face[b]) {
266 let (mut u, mut v) = (face[a], face[b]);
267 if v < u {
268 std::mem::swap(&mut u, &mut v);
269 }
270 es.push([u, v]);
271 }
272 }
273 }
274 es
275 }
276
277 fn allowed_cells(&self) -> Vec<V3> {
278 let mut cells = Vec::new();
279 for i in 0..self.n {
280 for j in 0..self.m {
281 for l in 0..self.p {
282 if !self.forbidden.contains(&(i, j, l)) {
283 cells.push((i, j, l));
284 }
285 }
286 }
287 }
288 cells
289 }
290
291 pub fn betti(&self) -> (usize, usize, usize, usize) {
294 let cells = self.allowed_cells();
295
296 let mut verts: BTreeSet<V3> = BTreeSet::new();
298 let mut edges: BTreeSet<[V3; 2]> = BTreeSet::new();
299 let mut faces: BTreeSet<[V3; 4]> = BTreeSet::new();
300 for &(i, j, l) in &cells {
301 for c in Self::corners(i, j, l) {
302 verts.insert(c);
303 }
304 for e in Self::cell_edges(i, j, l) {
305 edges.insert(e);
306 }
307 for f in Self::cell_faces(i, j, l) {
308 faces.insert(f);
309 }
310 }
311 let verts: Vec<V3> = verts.into_iter().collect();
312 let edges: Vec<[V3; 2]> = edges.into_iter().collect();
313 let faces: Vec<[V3; 4]> = faces.into_iter().collect();
314 let vidx: HashMap<V3, usize> = verts.iter().enumerate().map(|(k, &v)| (v, k)).collect();
315 let eidx: HashMap<[V3; 2], usize> = edges.iter().enumerate().map(|(k, &e)| (e, k)).collect();
316 let fidx: HashMap<[V3; 4], usize> = faces.iter().enumerate().map(|(k, &f)| (f, k)).collect();
317 let (nv, ne, nf, nc) = (verts.len(), edges.len(), faces.len(), cells.len());
318
319 let mut parent: Vec<usize> = (0..nv).collect();
321 fn find(parent: &mut [usize], x: usize) -> usize {
322 let mut r = x;
323 while parent[r] != r {
324 r = parent[r];
325 }
326 let mut c = x;
327 while parent[c] != r {
328 let nx = parent[c];
329 parent[c] = r;
330 c = nx;
331 }
332 r
333 }
334 for e in &edges {
335 let (ra, rb) = (find(&mut parent, vidx[&e[0]]), find(&mut parent, vidx[&e[1]]));
336 if ra != rb {
337 parent[ra] = rb;
338 }
339 }
340 let b0 = (0..nv).filter(|&x| find(&mut parent, x) == x).count();
341
342 let d2: Vec<Vec<u64>> = faces
344 .iter()
345 .map(|f| {
346 let mut row = vec![0u64; ne.div_ceil(64)];
347 for e in Self::face_edges(f) {
348 let idx = eidx[&e];
349 row[idx / 64] ^= 1u64 << (idx % 64);
350 }
351 row
352 })
353 .collect();
354 let d3: Vec<Vec<u64>> = cells
355 .iter()
356 .map(|&(i, j, l)| {
357 let mut row = vec![0u64; nf.div_ceil(64)];
358 for f in Self::cell_faces(i, j, l) {
359 let idx = fidx[&f];
360 row[idx / 64] ^= 1u64 << (idx % 64);
361 }
362 row
363 })
364 .collect();
365 let rank2 = gf2_rank_wide(d2, ne);
366 let rank3 = gf2_rank_wide(d3, nf);
367
368 let b1 = ne + b0 - nv - rank2;
369 let b2 = nf - rank2 - rank3;
370 let b3 = nc - rank3;
371 (b0, b1, b2, b3)
372 }
373}
374
375#[cfg(test)]
376mod tests {
377 use super::*;
378
379 #[test]
380 fn no_contention_is_a_contractible_square_determinism() {
381 let pc = ProgressComplex::new(4, 4, &[]);
384 let (b0, b1, b2) = pc.betti();
385 assert_eq!((b0, b1, b2), (1, 0, 0), "an uncontended execution space is contractible");
386 assert_eq!(b0 as i64 - b1 as i64 + b2 as i64, pc.euler(), "Euler–Poincaré holds");
387 assert_eq!(pc.euler(), 1);
388 }
389
390 #[test]
391 fn one_mutex_opens_a_hole_pi_one_is_Z_the_deadlock() {
392 let forbidden = [(1, 1), (1, 2), (2, 1), (2, 2)];
398 let pc = ProgressComplex::new(4, 4, &forbidden);
399 let (b0, b1, b2) = pc.betti();
400 assert_eq!(b0, 1, "the allowed region is still connected");
401 assert_eq!(b1, 1, "ONE hole — π₁ = Z — the mutex carves real higher homotopy");
402 assert_eq!(b2, 0, "no enclosed void");
403 assert_eq!(b0 as i64 - b1 as i64 + b2 as i64, pc.euler(), "Euler–Poincaré: β₀−β₁+β₂ = χ");
404 assert_eq!(pc.euler(), 0, "χ = 0, the signature of a single hole");
405 }
406
407 #[test]
408 fn two_critical_sections_give_two_holes_beta_one_counts_them() {
409 let forbidden = [(1, 1), (1, 2), (2, 1), (2, 2), (4, 4), (4, 5), (5, 4), (5, 5)];
412 let pc = ProgressComplex::new(7, 7, &forbidden);
413 let (b0, b1, b2) = pc.betti();
414 assert_eq!((b0, b1, b2), (1, 2, 0), "two critical sections ⇒ two independent holes");
415 assert_eq!(b0 as i64 - b1 as i64 + b2 as i64, pc.euler(), "Euler–Poincaré holds with two holes");
416 }
417
418 #[test]
419 fn three_processes_solid_is_contractible() {
420 let pc = ProgressComplex3::new(3, 3, 3, &[]);
423 assert_eq!(pc.betti(), (1, 0, 0, 0), "a solid 3-process execution is contractible");
424 }
425
426 #[test]
427 fn a_forbidden_core_opens_a_2_sphere_pi_two_is_Z() {
428 let pc = ProgressComplex3::new(3, 3, 3, &[(1, 1, 1)]);
435 let (b0, b1, b2, b3) = pc.betti();
436 assert_eq!(b0, 1, "still connected");
437 assert_eq!(b1, 0, "no 1-holes");
438 assert_eq!(b2, 1, "a hollow 2-sphere — π₂ = Z, genuine higher homotopy");
439 assert_eq!(b3, 0, "no enclosed 3-void");
440 assert_eq!(b0 as i64 - b1 as i64 + b2 as i64 - b3 as i64, 2, "χ = 2, the signature of a 2-sphere");
442 }
443
444 #[test]
445 fn the_hole_is_the_obstruction_to_breaking_the_scheduler_symmetry() {
446 let clean = ProgressComplex::new(4, 4, &[]).betti().1;
452 let contended = ProgressComplex::new(4, 4, &[(1, 1), (1, 2), (2, 1), (2, 2)]).betti().1;
453 assert_eq!(clean, 0, "no hole ⇒ scheduler symmetry fully breakable (determinism)");
454 assert!(contended > clean, "a hole ⇒ symmetry breaking is obstructed — β₁ counts the obstruction");
455 }
456}