Skip to main content

logicaffeine_proof/
trace_determinism.rs

1//! The concurrency tie made **literal**: *determinism = contractibility*, on a real machine.
2//!
3//! `proof_rewrite` showed that a refutation's independent steps commute, and that the space of
4//! reorderings is a contractible `CAT(0)` cube complex. That is not an analogy to concurrency — it is
5//! the *same* object. Here we run an actual tiny shared-memory machine and prove the identification on
6//! the nose:
7//!
8//! - Two operations' **commutation 2-cell** exists (the square commutes in the cube complex) **iff**
9//!   running them in both orders yields the same state — and the structural test for that is exactly
10//!   **Bernstein's conditions** (neither writes what the other reads or writes), the independence
11//!   relation of the trace monoid.
12//! - A set of pairwise-independent operations is **deterministic** — every one of the `n!` schedules
13//!   produces byte-identical state (we run them all) — and that is **exactly the contractibility**
14//!   (`χ = 1`, the cube condition) of the same [`ProofPoset`] the proof tower uses.
15//! - **Cooperative ≡ work-stealing**: two schedulers over independent tasks agree byte-for-byte because
16//!   both are linear extensions of one contractible trace complex, joined by commutation 2-cells. This
17//!   is the repo's `diff_cooperative_eq_workstealing` as a homotopy — the certificate of agreement *is*
18//!   the 2-cell path.
19//! - A **data race** is a *missing* 2-cell: a write-write conflict makes the ops dependent (no square),
20//!   and the two orders genuinely diverge. Race detection falls out of the homotopy.
21//!
22//! So the homotopy theory of proofs and the determinism theory of concurrency are one theory, and this
23//! module is the bridge with the machine actually running underneath it.
24
25use crate::proof_rewrite::{permutations, ProofPoset};
26
27/// A shared-memory operation: read some cells, then write one cell with a pure function of the reads
28/// (in `reads` order). The minimal unit that can race.
29#[derive(Clone)]
30pub struct Op {
31    pub reads: Vec<usize>,
32    pub write: usize,
33    pub f: fn(&[i64]) -> i64,
34}
35
36impl Op {
37    /// Apply the operation to mutable shared state.
38    pub fn apply(&self, state: &mut [i64]) {
39        let args: Vec<i64> = self.reads.iter().map(|&r| state[r]).collect();
40        state[self.write] = (self.f)(&args);
41    }
42}
43
44/// **Bernstein's conditions** — `a` and `b` commute (are independent) iff neither writes a cell the
45/// other reads or writes. This is the independence relation of the trace monoid: precisely the pairs
46/// whose commutation square is a 2-cell in [`ProofPoset`]. (Read–read sharing is fine; reads commute.)
47pub fn bernstein_independent(a: &Op, b: &Op) -> bool {
48    a.write != b.write && !b.reads.contains(&a.write) && !a.reads.contains(&b.write)
49}
50
51/// Run a schedule (a sequence of op indices) from an initial state, returning the final state.
52pub fn run(ops: &[Op], schedule: &[usize], init: &[i64]) -> Vec<i64> {
53    let mut state = init.to_vec();
54    for &i in schedule {
55        ops[i].apply(&mut state);
56    }
57    state
58}
59
60/// The commutation poset of a set of concurrent operations: independent pairs (Bernstein) are left
61/// unordered — their commutation is a 2-cell — and conflicting pairs get a program-order edge (lower
62/// index first), which is the only way to keep a deterministic execution. The trace's cube complex.
63pub fn trace_poset(ops: &[Op]) -> ProofPoset {
64    let mut edges = Vec::new();
65    for i in 0..ops.len() {
66        for j in (i + 1)..ops.len() {
67            if !bernstein_independent(&ops[i], &ops[j]) {
68                edges.push((i, j));
69            }
70        }
71    }
72    ProofPoset::new(ops.len(), &edges)
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78
79    #[test]
80    fn the_commutation_2cell_is_literally_state_agreement() {
81        // THE BRIDGE. A pair's commutation square commutes in the cube complex IFF running them in both
82        // orders yields the same state. Bernstein-independent ops agree on every state (the 2-cell is
83        // present); a write-write race diverges (the 2-cell is absent). The homotopy 2-cell = real
84        // determinism of that step.
85        let p = Op { reads: vec![0], write: 1, f: |a| a[0] + 1 };
86        let q = Op { reads: vec![2], write: 3, f: |a| a[0] * 2 };
87        assert!(bernstein_independent(&p, &q), "disjoint footprints ⇒ independent");
88        let ops = vec![p, q];
89        for init in [[0, 0, 0, 0], [3, 9, 5, 7], [-1, 2, 4, -8]] {
90            assert_eq!(
91                run(&ops, &[0, 1], &init),
92                run(&ops, &[1, 0], &init),
93                "independent ops: both orders agree — the 2-cell commutes"
94            );
95        }
96
97        // a genuine write-write race: both write cell 1
98        let r = Op { reads: vec![0], write: 1, f: |a| a[0] + 1 };
99        let s = Op { reads: vec![2], write: 1, f: |a| a[0] + 10 };
100        assert!(!bernstein_independent(&r, &s), "write-write race ⇒ dependent");
101        let race = vec![r, s];
102        let init = [5, 0, 7, 0];
103        assert_ne!(
104            run(&race, &[0, 1], &init),
105            run(&race, &[1, 0], &init),
106            "the race diverges — the 2-cell is genuinely absent"
107        );
108    }
109
110    #[test]
111    fn determinism_is_contractibility_run_every_schedule() {
112        // GLOBAL IDENTIFICATION. A set of pairwise-independent ops (disjoint footprints) is
113        // DETERMINISTIC — every one of the n! schedules yields byte-identical final state (we run them
114        // all) — and this is EXACTLY the contractibility of the trace cube complex (χ = 1, cube
115        // condition), the same complex the proof tower uses. Computed two ways, reconciled on the nose.
116        let inc = |a: &[i64]| a[0] + 1;
117        let ops = vec![
118            Op { reads: vec![0], write: 1, f: inc },
119            Op { reads: vec![2], write: 3, f: inc },
120            Op { reads: vec![4], write: 5, f: inc },
121            Op { reads: vec![6], write: 7, f: inc },
122        ];
123        let init = [10, 0, 20, 0, 30, 0, 40, 0];
124        let canonical = run(&ops, &[0, 1, 2, 3], &init);
125        let mut schedules = 0;
126        for perm in permutations(4) {
127            assert_eq!(run(&ops, &perm, &init), canonical, "schedule {perm:?} agrees — determinism");
128            schedules += 1;
129        }
130        assert_eq!(schedules, 24);
131
132        let poset = trace_poset(&ops);
133        assert_eq!(poset.euler_characteristic(), 1, "deterministic execution ⇒ contractible trace complex");
134        assert!(poset.satisfies_cube_condition(), "all interleavings fill the cubes (CAT(0))");
135        assert_eq!(poset.linear_extensions().len(), 24, "the 24 schedules ARE the linear extensions");
136    }
137
138    #[test]
139    fn cooperative_equals_work_stealing_byte_identical() {
140        // THE REPO'S SHAPE, abstracted. Two schedulers — cooperative round-robin [0,1,2,3] and
141        // work-stealing LIFO [3,2,1,0] — over independent tasks give BYTE-IDENTICAL state, because both
142        // are linear extensions of one contractible trace complex, connected by commutation 2-cells.
143        // diff_cooperative_eq_workstealing as a homotopy: the certificate of agreement IS the 2-cell path.
144        let ops = vec![
145            Op { reads: vec![0], write: 1, f: |a| a[0] * 2 },
146            Op { reads: vec![2], write: 3, f: |a| a[0] + 7 },
147            Op { reads: vec![4], write: 5, f: |a| a[0] - 1 },
148            Op { reads: vec![6], write: 7, f: |a| a[0] * a[0] },
149        ];
150        let init = [3, 0, 4, 0, 5, 0, 6, 0];
151        let cooperative = run(&ops, &[0, 1, 2, 3], &init);
152        let work_stealing = run(&ops, &[3, 2, 1, 0], &init);
153        assert_eq!(cooperative, work_stealing, "cooperative ≡ work-stealing, byte-identical");
154
155        let poset = trace_poset(&ops);
156        assert!(poset.extensions_connected_by_commutation(), "the two schedules are joined by 2-cells");
157        assert_eq!(poset.euler_characteristic(), 1, "their agreement is the contractibility of the trace");
158    }
159
160    #[test]
161    fn seeded_replay_is_symmetry_breaking_the_scheduler_and_its_sound() {
162        // KEEP SYMMETRY BREAKING — now on the SCHEDULER. The freedom to order independent enabled tasks
163        // is a symmetry of the trace complex: its linear extensions form ONE commutation orbit. Picking a
164        // single canonical interleaving (the lex-least schedule — or, in the engine, a seed-chosen one)
165        // is SYMMETRY BREAKING: one representative per orbit, the very π₀ move the assignment tower makes,
166        // now applied to schedules. And it is SOUND precisely because the trace is CONTRACTIBLE: every
167        // schedule yields the same final state, so collapsing to the canonical one loses nothing.
168        // Determinism = contractibility is the certificate that breaking the scheduler symmetry is safe.
169        let ops = vec![
170            Op { reads: vec![0], write: 1, f: |a| a[0] + 5 },
171            Op { reads: vec![2], write: 3, f: |a| a[0] * 3 },
172            Op { reads: vec![4], write: 5, f: |a| a[0] - 2 },
173            Op { reads: vec![6], write: 7, f: |a| a[0] + a[0] },
174        ];
175        let init = [1, 0, 2, 0, 3, 0, 4, 0];
176        let poset = trace_poset(&ops);
177
178        // the scheduler symmetry: all schedules are one commutation orbit (π₀ = 1)
179        assert!(poset.extensions_connected_by_commutation(), "all schedules = one commutation orbit");
180
181        // SYMMETRY BREAKING: pick the canonical (lex-least) representative — one schedule per trace
182        let canonical = poset.canonical_extension();
183        assert_eq!(canonical, vec![0, 1, 2, 3], "the canonical (lex-least) schedule is the orbit rep");
184
185        // SOUNDNESS of the break: the canonical schedule's result = EVERY schedule's result
186        let canonical_state = run(&ops, &canonical, &init);
187        for perm in permutations(4) {
188            assert_eq!(
189                run(&ops, &perm, &init),
190                canonical_state,
191                "breaking the scheduler symmetry to {canonical:?} loses nothing — contractibility certifies it"
192            );
193        }
194    }
195
196    #[test]
197    fn a_data_race_is_a_missing_2cell_nondeterminism_detected() {
198        // RACE DETECTION VIA HOMOTOPY. A write-write conflict (two ops writing cell 1) makes the trace
199        // poset ORDER them — the 2-cell is absent — and running the two orders diverges. The missing
200        // 2-cell IS the race; nondeterminism is the failure of the square to commute.
201        let ops = vec![
202            Op { reads: vec![0], write: 1, f: |a| a[0] + 1 },
203            Op { reads: vec![2], write: 1, f: |a| a[0] + 100 },
204        ];
205        assert!(!bernstein_independent(&ops[0], &ops[1]), "write-write conflict on cell 1");
206        let poset = trace_poset(&ops);
207        assert!(!poset.independent(0, 1), "the racing ops are ordered — the 2-cell is absent");
208
209        let init = [5, 0, 7, 0];
210        assert_ne!(
211            run(&ops, &[0, 1], &init),
212            run(&ops, &[1, 0], &init),
213            "the race is genuinely nondeterministic — the missing 2-cell, observed"
214        );
215    }
216}