Skip to main content

logicaffeine_proof/
progress_complex.rs

1//! **Genuine higher homotopy, at last** — `π₁ ≠ 0` from real concurrency, the hole *is* the deadlock.
2//!
3//! Everything below `proof_rewrite` was a 1-type or contractible: a *discrete* symmetry gives `K(G,1)`,
4//! a *deterministic* trace gives a contractible cube complex. Genuine non-trivial homotopy needs a
5//! **forbidden region** — and concurrency with shared resources is exactly where one appears
6//! (Fajstrup–Goubault–Raussen, *directed algebraic topology*).
7//!
8//! Model two processes of lengths `n` and `m` by their **progress complex**: a grid of states `(i, j)`
9//! ("`P` has run `i` steps, `Q` has run `j` steps"), unit edges for single steps, and a filled square
10//! at every cell where the two steps commute — *except* cells in the **forbidden region** (both
11//! processes inside their critical section at once, which mutual exclusion forbids). Removing those
12//! cells leaves a **hole**, and the hole is not decoration: it is the synchronization obstruction,
13//! the deadlock, made into topology.
14//!
15//! We compute the homology rigorously — `β₀` by union-find, `β₁ = #E − #V + β₀ − rank ∂₂` and
16//! `β₂ = #C − rank ∂₂` with the boundary rank taken over `GF(2)` by Gaussian elimination — and we
17//! cross-check every case against the Euler–Poincaré identity `β₀ − β₁ + β₂ = χ`. No contention gives
18//! a contractible square (`β₁ = 0`, determinism). One mutex gives `β₁ = 1`: the execution space is a
19//! circle, and the two directed ways around the hole (`P`-first vs `Q`-first) are genuinely
20//! inequivalent schedules — **the hole is precisely where the scheduler symmetry can no longer be
21//! broken to one canonical class.** Higher homotopy = the obstruction to symmetry breaking.
22
23use std::collections::{BTreeSet, HashMap};
24
25/// `GF(2)` rank of a set of rows (each a bitset of column indices) by Gaussian elimination.
26fn 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
46/// `GF(2)` rank for wide bitsets (more than 128 columns), each row a `Vec<u64>` of `words` limbs.
47pub(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
74/// The progress complex of two processes of lengths `n`, `m` (an `n × m` grid of cells), with a set of
75/// **forbidden cells** (the mutual-exclusion region) left unfilled.
76pub 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    /// The four boundary edges of cell `(i, j)`, each as an ordered vertex-id pair.
92    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)),     // bottom
97            order(v(i, j + 1), v(i + 1, j + 1)), // top
98            order(v(i, j), v(i, j + 1)),     // left
99            order(v(i + 1, j), v(i + 1, j + 1)), // right
100        ]
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    /// The Betti numbers `(β₀, β₁, β₂)` of the **closure of the allowed cells** — vertices and edges are
116    /// included exactly when they bound some allowed cell, so removing a forbidden region opens a real
117    /// hole. Homology over `GF(2)`, fully computed (no shortcuts).
118    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        // β₀ — connected components of the 1-skeleton by union-find.
139        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        // ∂₂ over GF(2): each allowed cell ↦ the bitset of its four boundary edges.
162        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        // β₁ = dim Z₁ − dim B₁ = (#E − rank ∂₁) − rank ∂₂, with rank ∂₁ = #V − β₀.
170        let b1 = ne + b0 - nv - rank2;
171        (b0, b1, b2)
172    }
173
174    /// `χ = #V − #E + #C` over the closure of the allowed cells.
175    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
190/// A 3D coordinate (vertex) of the three-process progress complex.
191type V3 = (usize, usize, usize);
192
193/// The progress complex of **three** processes of lengths `n`, `m`, `p` — a 3D grid of states, filled
194/// with solid 3-cells (cubes) where all three steps commute, *except* forbidden cells. Climbing one
195/// homotopy dimension: a forbidden core now leaves a hollow **2-sphere**, so `β₂ = π₂ ≠ 0` — the first
196/// genuine `π₂` the tower *produces* (not just admits). Adding a process climbs a rung; the limit of
197/// "one more process, one more dimension" is the `∞`-groupoid the ladder points at.
198pub 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    /// The eight corners of cell `(i, j, l)`.
211    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    /// The twelve edges of a cell — corner pairs differing in exactly one coordinate.
225    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    /// The six faces of a cell, each the four corners sharing one fixed coordinate (axis, value), sorted.
243    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    /// The four boundary edges of a face (its four corners, adjacent pairs).
261    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    /// Betti numbers `(β₀, β₁, β₂, β₃)` of the closure of the allowed 3-cells — full `GF(2)` homology
292    /// through `∂₃`. `β₂` is the genuine `π₂` (2-voids); `β₃` detects enclosed 3-voids (none here).
293    pub fn betti(&self) -> (usize, usize, usize, usize) {
294        let cells = self.allowed_cells();
295
296        // collect closure: every vertex/edge/face that bounds an allowed 3-cell
297        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        // β₀ — components of the 1-skeleton.
320        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        // ∂₂ : faces → edges (4 each), ∂₃ : cells → faces (6 each), ranks over GF(2).
343        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        // No shared resource ⇒ no forbidden cells ⇒ the full filled grid, contractible: β₀ = 1, β₁ = 0.
382        // This is the deterministic case — the same contractible execution space as an independent trace.
383        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        // GENUINE HIGHER HOMOTOPY. Both processes run [.., acquire, CS, CS, release, ..]; the forbidden
393        // region is the 2×2 block where both are inside the critical section. Removing it opens ONE
394        // hole: β₁ = 1, so the execution space is homotopy-equivalent to a circle (π₁ = Z). The two
395        // directed paths around the hole are "P finishes its CS before Q" vs "Q before P" — genuinely
396        // inequivalent schedules. The hole IS the synchronization obstruction.
397        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        // Two disjoint forbidden blocks (two contended resources) ⇒ β₁ = 2. The first Betti number
410        // genuinely COUNTS the independent obstructions — the homology is real, not a yes/no flag.
411        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        // No forbidden cell ⇒ a solid 3×3×3 block of cubes, contractible: β = (1,0,0,0). The 3D
421        // determinism baseline, and a check that the ∂₃ homology machinery is calibrated.
422        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        // CLIMBING A RUNG: genuine π₂. Forbid the single center cell of a 3×3×3 grid — the state where
429        // all three processes are jointly in the forbidden core. Its six faces remain (they bound the
430        // surrounding allowed cells), forming a 2-CYCLE that no longer bounds anything: a hollow
431        // 2-SPHERE. So β₂ = 1 — the execution space has π₂ = Z. This is the first π₂ the tower PRODUCES
432        // from a real system, not merely admits via the crossed-module machinery. The 2-void is the
433        // higher-dimensional synchronization obstruction: a sphere of schedules that cannot be contracted.
434        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        // Euler–Poincaré in 3D: β₀ − β₁ + β₂ − β₃ = χ, and χ = 2 for a 2-sphere shell.
441        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        // THE THROUGH-LINE, closed. For a contractible execution (β₁ = 0) the scheduler symmetry breaks
447        // cleanly — one canonical schedule per trace, the π₀ collapse of `trace_determinism`. The instant
448        // a mutex opens a hole (β₁ = 1), that collapse is OBSTRUCTED: there are β₁ independent classes of
449        // directed paths the commutation 2-cells cannot merge, because merging them would cross the
450        // forbidden region. So β₁ measures, exactly, how far the scheduler symmetry FAILS to be breakable.
451        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}