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}