Skip to main content

logicaffeine_proof/
two_group.rs

1//! The **2-group frontier** — climbing past `K(G,1)` to genuine 2-types, honestly.
2//!
3//! The tower named its 1-truncation: a *discrete* symmetry group `G` gives the ∞-groupoid
4//! `BG = K(G,1)` — `π₁ = G`, `πₙ = 0` for `n ≥ 2`. To populate `π₂` you need a **2-group** (a symmetry
5//! whose symmetries themselves have symmetries), modeled by a **crossed module** `∂: H → G`:
6//!
7//! - `π₁ = coker(∂) = G / ∂(H)` — the symmetries that survive modulo the higher cells, and
8//! - `π₂ = ker(∂)` — the genuinely higher homotopy.
9//!
10//! This module builds that machinery and is honest about where our SAT structure sits in it: our
11//! symmetry is the *degenerate* crossed module `1 → G` (so `π₂ = 0`, `BG = K(G,1)`), and a genuine
12//! 2-group `Z/m → 1` gives `π₂ = Z/m ≠ 0` — a `K(Z/m, 2)`. The machinery reads off both; our case is
13//! the 1-type. Climbing further (`π₃, …`) is the same construction one categorical level up — the
14//! frontier, named, not claimed.
15
16/// A finite group by its (0-indexed) multiplication table.
17#[derive(Clone)]
18pub struct FiniteGroup {
19    /// `mul[a][b] = a · b`.
20    pub mul: Vec<Vec<usize>>,
21    /// The identity element.
22    pub id: usize,
23}
24
25impl FiniteGroup {
26    pub fn order(&self) -> usize {
27        self.mul.len()
28    }
29
30    pub fn inverse(&self, a: usize) -> usize {
31        (0..self.order()).find(|&b| self.mul[a][b] == self.id).expect("a group element has an inverse")
32    }
33
34    /// The trivial group `1`.
35    pub fn trivial() -> FiniteGroup {
36        FiniteGroup { mul: vec![vec![0]], id: 0 }
37    }
38
39    /// The cyclic group `Z/m` (elements `0..m`, addition mod `m`).
40    pub fn cyclic(m: usize) -> FiniteGroup {
41        let mul = (0..m).map(|a| (0..m).map(|b| (a + b) % m).collect()).collect();
42        FiniteGroup { mul, id: 0 }
43    }
44
45    /// The symmetric group `S_n` (all `n!` permutations, indexed; multiplication = composition). This
46    /// is the shape of a pigeonhole symmetry group (pigeon/hole permutations).
47    pub fn symmetric(n: usize) -> FiniteGroup {
48        // enumerate permutations of 0..n (Heap-free: lexicographic via Steinhaus–Johnson or simple
49        // recursive build)
50        let mut perms: Vec<Vec<usize>> = Vec::new();
51        let mut cur: Vec<usize> = (0..n).collect();
52        permute(&mut cur, 0, &mut perms);
53        let index: std::collections::HashMap<Vec<usize>, usize> =
54            perms.iter().cloned().enumerate().map(|(i, p)| (p, i)).collect();
55        let compose = |p: &[usize], q: &[usize]| -> Vec<usize> { (0..n).map(|i| p[q[i]]).collect() };
56        let mul: Vec<Vec<usize>> = perms
57            .iter()
58            .map(|p| perms.iter().map(|q| index[&compose(p, q)]).collect())
59            .collect();
60        let id = index[&(0..n).collect::<Vec<_>>()];
61        FiniteGroup { mul, id }
62    }
63}
64
65fn permute(arr: &mut Vec<usize>, k: usize, out: &mut Vec<Vec<usize>>) {
66    if k == arr.len() {
67        out.push(arr.clone());
68        return;
69    }
70    for i in k..arr.len() {
71        arr.swap(k, i);
72        permute(arr, k + 1, out);
73        arr.swap(k, i);
74    }
75}
76
77/// A **crossed module** `∂: H → G` — the algebraic model of a 2-group. `partial[h]` is `∂(h) ∈ G`,
78/// a group homomorphism. Its homotopy groups are `π₁ = coker(∂)` and `π₂ = ker(∂)`.
79pub struct CrossedModule {
80    pub g: FiniteGroup,
81    pub h: FiniteGroup,
82    /// `partial[x] = ∂(x)` for `x ∈ H`.
83    pub partial: Vec<usize>,
84}
85
86impl CrossedModule {
87    /// `∂` must be a group homomorphism: `∂(x · y) = ∂(x) · ∂(y)`.
88    pub fn is_homomorphism(&self) -> bool {
89        (0..self.h.order()).all(|x| {
90            (0..self.h.order()).all(|y| {
91                self.partial[self.h.mul[x][y]] == self.g.mul[self.partial[x]][self.partial[y]]
92            })
93        })
94    }
95
96    /// `π₂ = ker(∂)` — the elements of `H` mapping to the identity of `G`. The genuine higher homotopy.
97    pub fn pi_two(&self) -> Vec<usize> {
98        (0..self.h.order()).filter(|&x| self.partial[x] == self.g.id).collect()
99    }
100
101    /// `|im(∂)|` — the size of `∂`'s image in `G`.
102    pub fn image_size(&self) -> usize {
103        let mut img: Vec<usize> = (0..self.h.order()).map(|x| self.partial[x]).collect();
104        img.sort_unstable();
105        img.dedup();
106        img.len()
107    }
108
109    /// `|π₁| = |coker(∂)| = |G| / |im(∂)|` (valid when `im(∂)` is normal, which holds for our examples).
110    pub fn pi_one_order(&self) -> usize {
111        self.g.order() / self.image_size()
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    #[test]
120    fn finite_groups_are_well_formed() {
121        for g in [FiniteGroup::trivial(), FiniteGroup::cyclic(5), FiniteGroup::symmetric(4)] {
122            let n = g.order();
123            // identity acts as identity, and every element has an inverse
124            for a in 0..n {
125                assert_eq!(g.mul[g.id][a], a);
126                assert_eq!(g.mul[a][g.id], a);
127                let _ = g.inverse(a);
128            }
129        }
130        assert_eq!(FiniteGroup::symmetric(4).order(), 24, "|S_4| = 24");
131    }
132
133    #[test]
134    fn discrete_symmetry_is_a_one_type_pi_two_vanishes() {
135        // Our SAT symmetry is DISCRETE: the degenerate crossed module 1 → G. π₂ = ker(∂) = 1, π₁ = G.
136        // So the ∞-groupoid is BG = K(G,1), a 1-TYPE — exactly the tower's named top, now via the
137        // 2-group machinery. (G = S_4, the pigeon symmetry of PHP(5).)
138        let g = FiniteGroup::symmetric(4);
139        let cm = CrossedModule { g, h: FiniteGroup::trivial(), partial: vec![0] };
140        assert!(cm.is_homomorphism(), "∂ : 1 → G is a homomorphism");
141        assert_eq!(cm.pi_two().len(), 1, "π₂ = ker(∂) = trivial ⇒ a 1-type (discrete symmetry)");
142        assert_eq!(cm.pi_one_order(), 24, "π₁ = G = S_4");
143    }
144
145    #[test]
146    fn a_genuine_2_group_populates_pi_two() {
147        // A GENUINE 2-group (the frontier): the crossed module Z/m → 1 with ∂ = 0. Now π₂ = ker(∂) =
148        // Z/m ≠ 0 — a K(Z/m, 2), a genuine 2-TYPE. This is what a symmetry-with-internal-symmetry
149        // would contribute; our SAT structure does not provide it, but the machinery reads it off.
150        for m in [2usize, 3, 5] {
151            let cm = CrossedModule { g: FiniteGroup::trivial(), h: FiniteGroup::cyclic(m), partial: vec![0; m] };
152            assert!(cm.is_homomorphism(), "∂ = 0 : Z/m → 1 is a homomorphism");
153            assert_eq!(cm.pi_two().len(), m, "π₂ = ker(∂) = Z/{m} — genuine higher homotopy");
154            assert_eq!(cm.pi_one_order(), 1, "π₁ = coker = trivial");
155        }
156    }
157
158    #[test]
159    fn an_isomorphism_crossed_module_is_contractible() {
160        // ∂ = id : Z/m → Z/m kills both homotopy groups (π₁ = coker = 1, π₂ = ker = 1) — a contractible
161        // 2-group. The machinery spans the range: from contractible, through K(G,1), to K(Z/m,2).
162        let m = 6;
163        let cm = CrossedModule {
164            g: FiniteGroup::cyclic(m),
165            h: FiniteGroup::cyclic(m),
166            partial: (0..m).collect(), // ∂ = identity
167        };
168        assert!(cm.is_homomorphism());
169        assert_eq!(cm.pi_two().len(), 1, "π₂ = ker(id) = trivial");
170        assert_eq!(cm.pi_one_order(), 1, "π₁ = coker(id) = trivial");
171    }
172}