Skip to main content

logicaffeine_proof/
kan_complex.rs

1//! Actually **having** an ∞-groupoid — not its invariants, the object itself, with its defining
2//! property machine-verified.
3//!
4//! Everything in `progress_complex`/`cubical` computed *invariants* (Betti numbers) of *spaces*. That is
5//! not the same as possessing the ∞-groupoid: by the homotopy hypothesis the simplicial model of an
6//! ∞-groupoid is a **Kan complex** — a simplicial set in which every *horn* (a simplex with one face
7//! missing) can be filled. Here we build such an object directly: the **nerve of a group**, whose
8//! degree-`n` simplices are `Gⁿ` (chains of composable arrows), and we *verify the Kan condition* —
9//! every horn fills — in degrees 2 and 3. The nerve of `Aut(F)` is exactly `BG = K(Aut(F),1)`, the
10//! classifying space the symmetry tower named, so this is that ∞-groupoid, now had as an object.
11//!
12//! It is honestly the **1-truncated** one: inner horns fill *uniquely*, the hallmark of the nerve of a
13//! 1-groupoid (no genuine higher cells). A non-truncated ∞-groupoid — nontrivial `π_{≥2}`, inner horns
14//! with *several* fillers, the homology ladder's higher `πₙ` assembled into one coherent complex with
15//! its k-invariants — is the frontier this does NOT yet reach, and we say so in the test that proves the
16//! uniqueness.
17
18use crate::two_group::FiniteGroup;
19
20/// The `i`-th face `d_i` of a degree-`n` simplex `(g_1, …, g_n) ∈ Gⁿ` of the nerve: drop the first arrow
21/// (`i=0`), drop the last (`i=n`), or compose the adjacent pair (`0 < i < n`).
22fn face(g: &FiniteGroup, s: &[usize], i: usize) -> Vec<usize> {
23    let n = s.len();
24    if i == 0 {
25        return s[1..].to_vec();
26    }
27    if i == n {
28        return s[..n - 1].to_vec();
29    }
30    let mut out = s.to_vec();
31    out[i - 1] = g.mul[s[i - 1]][s[i]];
32    out.remove(i);
33    out
34}
35
36/// Every degree-`n` simplex of the nerve — all of `Gⁿ`.
37fn all_simplices(g: &FiniteGroup, n: usize) -> Vec<Vec<usize>> {
38    let mut out: Vec<Vec<usize>> = vec![vec![]];
39    for _ in 0..n {
40        let mut next = Vec::with_capacity(out.len() * g.order());
41        for s in &out {
42            for e in 0..g.order() {
43                let mut t = s.clone();
44                t.push(e);
45                next.push(t);
46            }
47        }
48        out = next;
49    }
50    out
51}
52
53/// Enumerate every **compatible horn** `Λⁿ_k` — an assignment of a degree-`(n-1)` simplex to each face
54/// position `i ≠ k` satisfying the simplicial identities `d_i x_j = d_{j-1} x_i` (`i < j`) — and call
55/// `each(filler_count)` with how many degree-`n` simplices fill it.
56fn for_each_horn(g: &FiniteGroup, n: usize, k: usize, mut each: impl FnMut(usize)) {
57    let lower = all_simplices(g, n - 1);
58    let tops = all_simplices(g, n);
59    let positions: Vec<usize> = (0..=n).filter(|&i| i != k).collect();
60    let base = lower.len();
61    let total: u128 = (base as u128).pow(positions.len() as u32);
62    for idx in 0..total {
63        // decode the assignment of a lower simplex to each position
64        let mut x = idx;
65        let horn: Vec<(usize, &Vec<usize>)> = positions
66            .iter()
67            .map(|&p| {
68                let a = (x % base as u128) as usize;
69                x /= base as u128;
70                (p, &lower[a])
71            })
72            .collect();
73        // compatibility: d_i x_j = d_{j-1} x_i for i < j
74        let compatible = horn.iter().enumerate().all(|(a, &(i, xi))| {
75            horn[a + 1..].iter().all(|&(j, xj)| face(g, xj, i) == face(g, xi, j - 1))
76        });
77        if !compatible {
78            continue;
79        }
80        let fillers = tops.iter().filter(|y| horn.iter().all(|&(p, xp)| &face(g, y, p) == xp)).count();
81        each(fillers);
82    }
83}
84
85/// Does the nerve satisfy the **Kan condition** for horns `Λⁿ_k` — does every compatible horn fill?
86pub fn kan_fills(g: &FiniteGroup, n: usize, k: usize) -> bool {
87    let mut ok = true;
88    for_each_horn(g, n, k, |fillers| ok &= fillers >= 1);
89    ok
90}
91
92/// Does every compatible **inner** horn (`0 < k < n`) fill *uniquely*? Unique inner fillers are the
93/// signature of a 1-truncated nerve — the ∞-groupoid is exactly `K(G,1) = BG`, no genuine higher cells.
94pub fn inner_horns_fill_uniquely(g: &FiniteGroup, n: usize, k: usize) -> bool {
95    assert!(0 < k && k < n, "inner horns only");
96    let mut unique = true;
97    for_each_horn(g, n, k, |fillers| unique &= fillers == 1);
98    unique
99}
100
101/// All `A`-valued cochains on `BG_k` — functions `BG_k = Gᵏ → A = Z/modulus`.
102fn nerve_cochains(g: &FiniteGroup, modulus: usize, k: usize) -> Vec<Vec<usize>> {
103    let size = all_simplices(g, k).len();
104    let mut out: Vec<Vec<usize>> = vec![vec![]];
105    for _ in 0..size {
106        let mut next = Vec::with_capacity(out.len() * modulus);
107        for t in &out {
108            for v in 0..modulus {
109                let mut u = t.clone();
110                u.push(v);
111                next.push(u);
112            }
113        }
114        out = next;
115    }
116    out
117}
118
119/// Simplicial coboundary `δ : Cᵏ(BG; A) → Cᵏ⁺¹` from the **nerve's own faces**:
120/// `(δφ)(σ) = Σᵢ (−1)ⁱ φ(dᵢ σ)`.
121fn nerve_coboundary(g: &FiniteGroup, modulus: usize, k: usize, phi: &[usize]) -> Vec<usize> {
122    let sk = all_simplices(g, k);
123    let idx: std::collections::HashMap<Vec<usize>, usize> =
124        sk.iter().cloned().enumerate().map(|(i, s)| (s, i)).collect();
125    let skp1 = all_simplices(g, k + 1);
126    let mut out = vec![0usize; skp1.len()];
127    for (j, sigma) in skp1.iter().enumerate() {
128        let mut val = 0i64;
129        for i in 0..=(k + 1) {
130            let f = face(g, sigma, i);
131            let s = if i % 2 == 0 { 1 } else { -1 };
132            val += s * phi[idx[&f]] as i64;
133        }
134        out[j] = val.rem_euclid(modulus as i64) as usize;
135    }
136    out
137}
138
139/// The simplicial cohomology `|Hⁿ(BG; A)|` of the nerve `BG = K(G,1)`, computed from the nerve's own
140/// face maps. By the Eilenberg–MacLane representing property `Hⁿ(BG; A) = [BG, K(A,n)]`; we confirm it
141/// equals the algebraic **group cohomology** `Hⁿ(G; A)`, so the spectrum represents — on the symmetry's
142/// classifying space — exactly group cohomology, the home of the Postnikov k-invariants.
143pub fn nerve_cohomology_size(g: &FiniteGroup, modulus: usize, n: usize) -> usize {
144    let cocycles = nerve_cochains(g, modulus, n)
145        .into_iter()
146        .filter(|phi| nerve_coboundary(g, modulus, n, phi).iter().all(|&x| x == 0))
147        .count();
148    let mut coboundaries: std::collections::HashSet<Vec<usize>> = std::collections::HashSet::new();
149    for psi in nerve_cochains(g, modulus, n - 1) {
150        coboundaries.insert(nerve_coboundary(g, modulus, n - 1, &psi));
151    }
152    cocycles / coboundaries.len()
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158
159    #[test]
160    fn the_spectrum_represents_cohomology_nerve_cohomology_is_group_cohomology() {
161        // K(A,n) REPRESENTS cohomology: Hⁿ(X;A) = [X, K(A,n)]. Instantiated on the symmetry's classifying
162        // space X = BG = K(G,1): Hⁿ(BG;A) = [BG, K(A,n)]. We compute Hⁿ(BG;A) from the NERVE's own face
163        // maps and confirm it equals the algebraic group cohomology Hⁿ(G;A) — so what the Eilenberg–
164        // MacLane spectrum represents on BG is exactly group cohomology, the home of the k-invariants.
165        for n in 2..=3 {
166            assert_eq!(
167                nerve_cohomology_size(&FiniteGroup::cyclic(2), 2, n),
168                crate::postnikov::cohomology_size(&FiniteGroup::cyclic(2), 2, n),
169                "Hⁿ(BG; Z/2) from the nerve = group cohomology Hⁿ(Z/2; Z/2)"
170            );
171        }
172        // a second group: H²(BZ/3; Z/2) from the nerve = group cohomology H²(Z/3; Z/2)
173        assert_eq!(
174            nerve_cohomology_size(&FiniteGroup::cyclic(3), 2, 2),
175            crate::postnikov::cohomology_size(&FiniteGroup::cyclic(3), 2, 2),
176            "the representing property holds for BZ/3 too"
177        );
178    }
179
180    #[test]
181    fn the_nerve_is_a_kan_complex_so_we_actually_have_an_infinity_groupoid() {
182        // We stop computing invariants and build the ∞-GROUPOID OBJECT: the nerve of a group, a
183        // simplicial set, and VERIFY its defining property — the Kan horn-filling condition — in degrees
184        // 2 and 3, for EVERY horn (inner and outer). A Kan complex IS an ∞-groupoid (homotopy
185        // hypothesis). The nerve of Aut(F) is BG = K(Aut(F),1), so this is the very ∞-groupoid the
186        // symmetry tower named — now possessed as an object, not merely measured by its Betti numbers.
187        for g in [FiniteGroup::cyclic(2), FiniteGroup::cyclic(3), FiniteGroup::symmetric(3)] {
188            for n in 2..=3 {
189                for k in 0..=n {
190                    assert!(kan_fills(&g, n, k), "the nerve fills every horn Λ^n_k — it is a Kan complex");
191                }
192            }
193        }
194    }
195
196    #[test]
197    fn it_is_exactly_one_truncated_so_honestly_its_only_K_G_1_for_now() {
198        // HONEST SCOPE — the test that keeps us truthful. The ∞-groupoid we now have is precisely the
199        // 1-TRUNCATED one: every inner horn fills UNIQUELY, the hallmark of the nerve of a 1-groupoid
200        // (no genuine higher cells, π_{≥2} = 0). A non-truncated ∞-groupoid would have inner horns with
201        // MULTIPLE fillers. Assembling the homology ladder's higher πₙ into one coherent Kan complex with
202        // its k-invariants — a genuinely 2+-truncated object — is the frontier we do NOT yet hold.
203        for g in [FiniteGroup::cyclic(2), FiniteGroup::cyclic(3), FiniteGroup::symmetric(3)] {
204            for n in 2..=3 {
205                for k in 1..n {
206                    assert!(inner_horns_fill_uniquely(&g, n, k), "inner horns unique ⇒ 1-truncated = K(G,1)");
207                }
208            }
209        }
210    }
211}