Skip to main content

logicaffeine_proof/
two_type.rs

1//! A genuinely **2-truncated** ∞-groupoid, had as an object: the minimal `K(A, 2)`.
2//!
3//! `kan_complex` built the nerve `BG = K(G, 1)` and proved it 1-truncated (inner horns fill *uniquely*).
4//! This is the first object *beyond* that — a Kan complex with real `π₂ ≠ 0` living inside it, not merely
5//! admitted by a crossed module or glimpsed as homology. It is the Eilenberg–MacLane space `K(A, 2)`:
6//! `π₂ = A`, every other `πₙ = 0`.
7//!
8//! The model is Dold–Kan `Γ(A[2])` for the chain complex with `A` in degree 2. Its `n`-simplices are the
9//! `A`-linear combinations of order-preserving **surjections `[n] ↠ [2]`** (since the complex is
10//! concentrated in degree 2, a face that fails to stay surjective dies — `C₁ = C₃ = 0`). Concretely an
11//! `n`-simplex is an `A`-labeling of `S_n = {surjections [n] ↠ [2]}`, and `dᵢ` pulls back along the
12//! `i`-th coface, keeping only the still-surjective terms. It is a simplicial *abelian group*, hence
13//! automatically a Kan complex — and we verify, by enumeration:
14//!
15//! - **`π₂ ≠ 0`**: the inner horn `Λ²₁` has `|A|` fillers (the `2`-cells), *not* the unique filler of a
16//!   1-type. This is the structure `K(G,1)` provably lacked.
17//! - **Kan**: every horn fills (the face-tuple map onto the compatible-horn object is surjective), checked
18//!   in degrees 2, 3, 4.
19//! - **exactly 2-truncated**: degree-4 inner horns fill *uniquely* again (no `π₃`), so the higher homotopy
20//!   stops at level 2.
21
22use std::collections::{HashMap, HashSet};
23
24/// All order-preserving surjections `[n] ↠ [2]`, as nondecreasing length-`(n+1)` value vectors hitting
25/// `0, 1, 2`. `S_n` — the basis of the degree-`n` part. Empty for `n < 2` (so `Γ₀ = Γ₁ = 0`).
26fn surjections(n: usize) -> Vec<Vec<u8>> {
27    fn rec(len: usize, pos: usize, last: u8, cur: &mut Vec<u8>, out: &mut Vec<Vec<u8>>) {
28        if pos == len {
29            if cur.contains(&0) && cur.contains(&1) && cur.contains(&2) {
30                out.push(cur.clone());
31            }
32            return;
33        }
34        for v in last..=2 {
35            cur.push(v);
36            rec(len, pos + 1, v, cur, out);
37            cur.pop();
38        }
39    }
40    let mut out = Vec::new();
41    rec(n + 1, 0, 0, &mut Vec::new(), &mut out);
42    out
43}
44
45fn is_surjective(v: &[u8]) -> bool {
46    v.contains(&0) && v.contains(&1) && v.contains(&2)
47}
48
49/// `σ ∘ δᵢ` — precompose a surjection with the `i`-th coface, i.e. drop coordinate `i`.
50fn drop_coord(v: &[u8], i: usize) -> Vec<u8> {
51    v.iter().enumerate().filter(|&(t, _)| t != i).map(|(_, &x)| x).collect()
52}
53
54/// The face map `dᵢ : Γ_n → Γ_{n-1}` over `A = Z/modulus`. A degree-`n` simplex is an `A`-labeling of
55/// `S_n`; `dᵢ` sends basis `σ` to `σ∘δᵢ` when that stays surjective, else to `0`, summing collisions.
56fn face(modulus: usize, n: usize, i: usize, x: &[usize]) -> Vec<usize> {
57    let sn = surjections(n);
58    let sn1 = surjections(n - 1);
59    let idx: HashMap<Vec<u8>, usize> = sn1.iter().cloned().enumerate().map(|(k, v)| (v, k)).collect();
60    let mut out = vec![0usize; sn1.len()];
61    for (k, sigma) in sn.iter().enumerate() {
62        let d = drop_coord(sigma, i);
63        if is_surjective(&d) {
64            let t = idx[&d];
65            out[t] = (out[t] + x[k]) % modulus;
66        }
67    }
68    out
69}
70
71/// Every degree-`n` simplex — all `A`-labelings of `S_n` (`|A|^{|S_n|}` of them).
72fn gamma(modulus: usize, n: usize) -> Vec<Vec<usize>> {
73    let size = surjections(n).len();
74    let mut out: Vec<Vec<usize>> = vec![vec![]];
75    for _ in 0..size {
76        let mut next = Vec::with_capacity(out.len() * modulus);
77        for t in &out {
78            for e in 0..modulus {
79                let mut u = t.clone();
80                u.push(e);
81                next.push(u);
82            }
83        }
84        out = next;
85    }
86    out
87}
88
89fn positions(n: usize, k: usize) -> Vec<usize> {
90    (0..=n).filter(|&i| i != k).collect()
91}
92
93/// The number of degree-`n` simplices filling the horn `Λⁿ_k` whose given faces are all the chosen
94/// `horn[i]` (`i ≠ k`).
95fn filler_count(modulus: usize, n: usize, k: usize, horn: &HashMap<usize, Vec<usize>>) -> usize {
96    gamma(modulus, n)
97        .iter()
98        .filter(|y| positions(n, k).iter().all(|&p| &face(modulus, n, p, y) == &horn[&p]))
99        .count()
100}
101
102/// `|ker hₖ|` — degree-`n` simplices whose faces `i ≠ k` are all zero. Equals the number of fillers of
103/// the *trivial* horn; `> 1` means a horn fills non-uniquely (genuine higher cells in that degree).
104fn kernel_size(modulus: usize, n: usize, k: usize) -> usize {
105    gamma(modulus, n)
106        .iter()
107        .filter(|y| positions(n, k).iter().all(|&p| face(modulus, n, p, y).iter().all(|&c| c == 0)))
108        .count()
109}
110
111/// `|im hₖ|` — the number of distinct face-tuples `(dᵢ y)_{i≠k}` realized by some degree-`n` simplex.
112fn image_size(modulus: usize, n: usize, k: usize) -> usize {
113    let mut images: HashSet<Vec<Vec<usize>>> = HashSet::new();
114    for y in gamma(modulus, n) {
115        images.insert(positions(n, k).iter().map(|&p| face(modulus, n, p, &y)).collect());
116    }
117    images.len()
118}
119
120/// The number of **compatible horns** `Λⁿ_k`: tuples `(x_i)_{i≠k}` of degree-`(n-1)` simplices satisfying
121/// the simplicial identities `d_a x_b = d_{b-1} x_a` (`a < b`). The horn maps `hₖ` is onto these iff Kan.
122fn compatible_horn_count(modulus: usize, n: usize, k: usize) -> usize {
123    let pos = positions(n, k);
124    let lower = gamma(modulus, n - 1);
125    let base = lower.len();
126    let total: u128 = (base as u128).pow(pos.len() as u32);
127    let mut count = 0;
128    for idx in 0..total {
129        let mut x = idx;
130        let horn: HashMap<usize, Vec<usize>> = pos
131            .iter()
132            .map(|&p| {
133                let a = (x % base as u128) as usize;
134                x /= base as u128;
135                (p, lower[a].clone())
136            })
137            .collect();
138        // simplicial identity d_a x_b = d_{b-1} x_a for every pair a < b of given positions
139        let ok = pos.iter().enumerate().all(|(ai, &a)| {
140            pos[ai + 1..].iter().all(|&b| face(modulus, n - 1, a, &horn[&b]) == face(modulus, n - 1, b - 1, &horn[&a]))
141        });
142        if ok {
143            count += 1;
144        }
145    }
146    count
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152
153    fn trivial_horn(n: usize, k: usize, modulus: usize) -> HashMap<usize, Vec<usize>> {
154        // the horn whose given faces are all zero (lives in Γ_{n-1})
155        let zero = vec![0usize; surjections(n - 1).len()];
156        positions(n, k).iter().map(|&p| (p, zero.clone())).collect()
157    }
158
159    #[test]
160    fn the_2_type_has_a_nonunique_inner_filler_so_pi2_is_nonzero() {
161        // π₂ ≠ 0, INSIDE THE OBJECT. The inner horn Λ²₁ has |A| fillers — the 2-cells — not the unique
162        // filler a 1-type forces. This is precisely the structure kan_complex proved K(G,1) lacks:
163        // there every inner horn filled uniquely; here a whole copy of A fills the same horn.
164        for modulus in [2usize, 3, 4] {
165            let fillers = filler_count(modulus, 2, 1, &trivial_horn(2, 1, modulus));
166            assert_eq!(fillers, modulus, "Λ²₁ has |A| = {modulus} fillers — π₂ = A, genuinely 2-dimensional");
167            assert!(fillers > 1, "non-unique inner filler ⇒ NOT 1-truncated (this is what K(G,1) cannot do)");
168        }
169    }
170
171    #[test]
172    fn the_2_type_is_a_kan_complex_every_horn_fills() {
173        // It IS a Kan complex (an ∞-groupoid): the face-tuple map hₖ is SURJECTIVE onto the compatible
174        // horns in every degree we check (2, 3, 4) and position — so every horn fills. A simplicial
175        // abelian group is Kan; we confirm it by counting, not by appeal.
176        let modulus = 2;
177        for (n, ks) in [(2usize, vec![0, 1, 2]), (3, vec![0, 1, 2, 3])] {
178            for k in ks {
179                assert_eq!(
180                    image_size(modulus, n, k),
181                    compatible_horn_count(modulus, n, k),
182                    "Λ^{n}_{k} fills: image of hₖ = all compatible horns"
183                );
184            }
185        }
186        // degree 4, inner horns: still surjective (Kan)
187        for k in 1..4 {
188            assert_eq!(image_size(modulus, 4, k), compatible_horn_count(modulus, 4, k), "Λ⁴_{k} fills (Kan)");
189        }
190    }
191
192    #[test]
193    fn the_2_type_realizes_the_crossed_modules_admitted_pi2_admit_equals_have() {
194        // SYMMETRY BREAKING, one level up — and the campaign's loop CLOSES. two_group's crossed module
195        // A → 1 (g = trivial, h = A, ∂ = 0) ADMITS π₂ = ker∂ = A, algebraically. This K(A,2) Kan complex
196        // HAS π₂ = A geometrically: the inner horn Λ²₁ has exactly |A| fillers. They are the SAME A — the
197        // 2-group that admits the higher symmetry is realized by the object that carries it: admit = have.
198        // And the |A| fillers form an A-TORSOR: breaking that symmetry (choosing the canonical filler) is
199        // the very π₀ orbit-collapse this whole campaign began with, now acting on 2-cells. Symmetry
200        // breaking runs from π₀ all the way up the tower.
201        use crate::two_group::{CrossedModule, FiniteGroup};
202        for m in [2usize, 3, 4] {
203            let cm = CrossedModule { g: FiniteGroup::trivial(), h: FiniteGroup::cyclic(m), partial: vec![0; m] };
204            let admitted = cm.pi_two().len(); // algebra: ker ∂ = A
205            let had = filler_count(m, 2, 1, &trivial_horn(2, 1, m)); // geometry: # fillers of Λ²₁
206            assert_eq!(admitted, had, "crossed module ADMITS π₂ = A ⟺ K(A,2) HAS π₂ = A (|A| fillers)");
207            assert_eq!(admitted, m, "and both equal |A| = {m}");
208        }
209    }
210
211    #[test]
212    fn the_2_type_is_exactly_two_truncated() {
213        // EXACTLY 2-truncated. At degree 2 the inner horn fills NON-uniquely (π₂ lives there: kernel = A).
214        // By degree 4 the inner horns fill UNIQUELY again (kernel trivial) — there is no π₃, the higher
215        // homotopy stops at level 2. So this object is a genuine 2-type: more than K(G,1), and no more.
216        let modulus = 2;
217        assert_eq!(kernel_size(modulus, 2, 1), modulus, "degree 2 inner: |ker| = |A| ⇒ π₂ = A");
218        for k in 1..4 {
219            assert_eq!(kernel_size(modulus, 4, k), 1, "degree 4 inner Λ⁴_{k}: unique filler ⇒ no π₃ (2-truncated)");
220        }
221    }
222}