Skip to main content

logicaffeine_proof/
postnikov.rs

1//! The **Postnikov `k`-invariant** — the gluing data of a 2-type, and the obstruction to breaking it
2//! into a product.
3//!
4//! `two_type` built `K(A,2)` and `kan_complex` built `K(G,1)`. A general 2-truncated ∞-groupoid is *not*
5//! just a `π₁` and a `π₂` sitting side by side: by Mac Lane–Whitehead / Sinh's theorem it is classified
6//! by `(π₁ = G, π₂ = A, the G-action, k ∈ H³(G; A))`. The class `k` — the **Postnikov invariant** — is
7//! the associator of the corresponding 2-group, and it says how the two levels are *twisted*:
8//!
9//! - `k = 0` ⟺ the 2-type is the **product** `K(G,1) × K(A,2)` — the levels decouple.
10//! - `k ≠ 0` ⟺ a genuinely **entangled** 2-type, provably *not* a product.
11//!
12//! So `k` is "the single object carrying all `πₙ` *with their interactions*" reduced to one checkable
13//! cohomology class. And it is, once more, **a symmetry-breaking obstruction**: splitting the 2-type into
14//! a product is the ultimate symmetry break (decoupling `π₁` from `π₂`), and `k ≠ 0` is exactly the
15//! obstruction to it — the higher sibling of the deadlock `β₁`. We compute group cohomology directly
16//! (`A = Z/modulus`, trivial action) and crush the canonical case `H³(Z/2; Z/2) = Z/2`: the cup-cube
17//! cocycle is a 3-cocycle that is *not* a coboundary, so its 2-type is no product.
18
19use crate::two_group::FiniteGroup;
20
21fn idx(order: usize, t: &[usize]) -> usize {
22    t.iter().fold(0usize, |a, &x| a * order + x)
23}
24
25fn tuples(order: usize, n: usize) -> Vec<Vec<usize>> {
26    let mut out: Vec<Vec<usize>> = vec![vec![]];
27    for _ in 0..n {
28        let mut next = Vec::with_capacity(out.len() * order);
29        for t in &out {
30            for e in 0..order {
31                let mut u = t.clone();
32                u.push(e);
33                next.push(u);
34            }
35        }
36        out = next;
37    }
38    out
39}
40
41/// Every `n`-cochain `Gⁿ → A` (`A = Z/modulus`): `modulus^{order^n}` of them.
42fn all_cochains(order: usize, modulus: usize, n: usize) -> Vec<Vec<usize>> {
43    let len = order.pow(n as u32);
44    let mut out: Vec<Vec<usize>> = vec![vec![]];
45    for _ in 0..len {
46        let mut next = Vec::with_capacity(out.len() * modulus);
47        for t in &out {
48            for v in 0..modulus {
49                let mut u = t.clone();
50                u.push(v);
51                next.push(u);
52            }
53        }
54        out = next;
55    }
56    out
57}
58
59/// The group-cohomology coboundary `δⁿ : Cⁿ(G; A) → Cⁿ⁺¹(G; A)`, with `A = Z/modulus` and **trivial**
60/// `G`-action. `f` is a length-`order^n` table; the result is length `order^{n+1}`.
61pub fn coboundary(g: &FiniteGroup, modulus: usize, n: usize, f: &[usize]) -> Vec<usize> {
62    let order = g.order();
63    let mut out = vec![0usize; order.pow((n + 1) as u32)];
64    for t in tuples(order, n + 1) {
65        // (δf)(g_1,…,g_{n+1}) = f(g_2,…,g_{n+1}) + Σ_{i=1}^n (−1)^i f(…,g_i g_{i+1},…) + (−1)^{n+1} f(g_1,…,g_n)
66        let mut val: i64 = f[idx(order, &t[1..])] as i64;
67        for i in 1..=n {
68            let mut merged = t[..i - 1].to_vec();
69            merged.push(g.mul[t[i - 1]][t[i]]);
70            merged.extend_from_slice(&t[i + 1..]);
71            let sign = if i % 2 == 0 { 1 } else { -1 };
72            val += sign * f[idx(order, &merged)] as i64;
73        }
74        let sign = if (n + 1) % 2 == 0 { 1 } else { -1 };
75        val += sign * f[idx(order, &t[..n])] as i64;
76        out[idx(order, &t)] = val.rem_euclid(modulus as i64) as usize;
77    }
78    out
79}
80
81/// Is `f` an `n`-cocycle? (`δf = 0`)
82pub fn is_cocycle(g: &FiniteGroup, modulus: usize, n: usize, f: &[usize]) -> bool {
83    coboundary(g, modulus, n, f).iter().all(|&x| x == 0)
84}
85
86/// Is `f` an `n`-coboundary? (`f = δβ` for some `(n-1)`-cochain `β`)
87pub fn is_coboundary(g: &FiniteGroup, modulus: usize, n: usize, f: &[usize]) -> bool {
88    all_cochains(g.order(), modulus, n - 1).iter().any(|b| coboundary(g, modulus, n - 1, b) == f)
89}
90
91/// `|Hⁿ(G; A)|` — cocycles modulo coboundaries (both are subgroups of `Cⁿ`, so the order is the index).
92pub fn cohomology_size(g: &FiniteGroup, modulus: usize, n: usize) -> usize {
93    let cocycles = all_cochains(g.order(), modulus, n).into_iter().filter(|f| is_cocycle(g, modulus, n, f)).count();
94    let mut coboundaries: std::collections::HashSet<Vec<usize>> = std::collections::HashSet::new();
95    for b in all_cochains(g.order(), modulus, n - 1) {
96        coboundaries.insert(coboundary(g, modulus, n - 1, &b));
97    }
98    cocycles / coboundaries.len()
99}
100
101/// The **cup product** `Cᵖ × Cᵠ → Cᵖ⁺ᵠ` (Alexander–Whitney; `Z/modulus` coefficients, trivial action,
102/// no signs): `(f ∪ h)(a₁,…,a_{p+q}) = f(a₁,…,a_p) · h(a_{p+1},…,a_{p+q})`. It makes the obstruction
103/// groups a graded ring `H*(G; A)` — the multiplicative algebra binding all the k-invariant levels.
104pub fn cup(g: &FiniteGroup, modulus: usize, p: usize, q: usize, f: &[usize], h: &[usize]) -> Vec<usize> {
105    let order = g.order();
106    let mut out = vec![0usize; order.pow((p + q) as u32)];
107    for t in tuples(order, p + q) {
108        out[idx(order, &t)] = (f[idx(order, &t[..p])] * h[idx(order, &t[p..])]) % modulus;
109    }
110    out
111}
112
113/// The **cup-square** cohomology operation `x ↦ x ∪ x : Hⁿ(−; A) → H²ⁿ(−; A)`. Over `Z/2` it is the TOP
114/// Steenrod square `Sqⁿ` — the first genuine *cohomology operation* (a natural transformation of the
115/// functor `Hⁿ(−;A)`), the secondary structure carried by the cohomology the Eilenberg–MacLane spectrum
116/// represents.
117pub fn cup_square(g: &FiniteGroup, modulus: usize, n: usize, f: &[usize]) -> Vec<usize> {
118    cup(g, modulus, n, n, f, f)
119}
120
121/// The **Bockstein** `β : Hⁿ(−; Z/2) → Hⁿ⁺¹(−; Z/2)` — the connecting homomorphism of the short exact
122/// sequence `0 → Z/2 →(×2) Z/4 → Z/2 → 0`. By a theorem of Steenrod, `β = Sq¹`. Computed honestly: lift
123/// the `Z/2`-cochain to `Z/4`, take `δ` over `Z/4` (which is even on a `Z/2`-cocycle), divide by 2, and
124/// reduce mod 2. The lowest genuine Steenrod square, reached as a connecting map rather than a cup-square.
125pub fn bockstein(g: &FiniteGroup, n: usize, c: &[usize]) -> Vec<usize> {
126    let lifted: Vec<usize> = c.iter().map(|&v| v % 4).collect();
127    let d = coboundary(g, 4, n, &lifted); // δ over Z/4
128    d.iter().map(|&v| (v / 2) % 2).collect()
129}
130
131/// The cup-power `xⁿ` of `Z/2` with `Z/2` coefficients: `α(g_1,…,g_n) = g_1·g_2·⋯·g_n` (1 iff every
132/// argument is 1). As the `n`-fold cup product of the nonzero 1-cocycle, it is an `n`-cocycle and the
133/// generator of `Hⁿ(Z/2; Z/2) = Z/2` — the explicit nonzero obstruction at level `n`.
134pub fn cup_power_z2(n: usize) -> Vec<usize> {
135    let len = 2usize.pow(n as u32);
136    let mut f = vec![0usize; len];
137    f[len - 1] = 1; // only the all-ones tuple (1,1,…,1) has product 1
138    f
139}
140
141/// The canonical nontrivial 3-cocycle (the cup-cube `x³`), generator of `H³(Z/2; Z/2) = Z/2`.
142pub fn cup_cube_z2() -> Vec<usize> {
143    cup_power_z2(3)
144}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149
150    #[test]
151    fn the_cup_square_is_a_cohomology_operation_cocycle_to_cocycle() {
152        // A cohomology OPERATION sends closed forms to closed forms. The cup-square of an n-cocycle is a
153        // 2n-cocycle: δ(x²) = δx∪x ± x∪δx = 0. Checked on the generator x ∈ H¹(BZ/2): x² is a 2-cocycle.
154        let g = FiniteGroup::cyclic(2);
155        let x = cup_power_z2(1);
156        assert!(is_cocycle(&g, 2, 1, &x), "x is a 1-cocycle");
157        let sq = cup_square(&g, 2, 1, &x);
158        assert!(is_cocycle(&g, 2, 2, &sq), "Sq¹(x) = x² is a 2-cocycle — cup-square IS a cohomology operation");
159    }
160
161    #[test]
162    fn the_top_steenrod_square_doubles_the_power_on_bz2() {
163        // On H*(BZ/2; Z/2) = Z/2[x], the top square is Sqⁿ(xᵏ) = x²ᵏ — the cup-square doubles the exponent.
164        let g = FiniteGroup::cyclic(2);
165        for k in 1..=3 {
166            assert_eq!(cup_square(&g, 2, k, &cup_power_z2(k)), cup_power_z2(2 * k), "Sqⁿ doubles the exponent at k={k}");
167        }
168    }
169
170    #[test]
171    fn cup_is_graded_commutative_on_cohomology_giving_steenrod_additivity() {
172        // The deep fact under Sq's ADDITIVITY: the cup product is graded-commutative UP TO a coboundary
173        // — for COCYCLES, `a∪b + b∪a = δ(a∪₁b)` (the cup-1 product; for non-cocycles extra `δa∪₁b` terms
174        // appear, so the identity is genuinely a statement about cohomology classes). Over Z/2 this is
175        // exactly what forces `(a+b)² = a²+b²` on cohomology. We confirm `a∪b + b∪a` is a coboundary for
176        // every pair of 1-COCYCLES on BZ/2 — homotopy-commutativity, the secondary structure made explicit.
177        let g = FiniteGroup::cyclic(2);
178        let cocycles: Vec<Vec<usize>> = all_cochains(2, 2, 1).into_iter().filter(|c| is_cocycle(&g, 2, 1, c)).collect();
179        assert!(cocycles.len() >= 2, "BZ/2 has the cocycles 0 and x in degree 1");
180        for a in &cocycles {
181            for b in &cocycles {
182                let ab = cup(&g, 2, 1, 1, a, b);
183                let ba = cup(&g, 2, 1, 1, b, a);
184                let sum: Vec<usize> = ab.iter().zip(&ba).map(|(x, y)| (x + y) % 2).collect();
185                assert!(
186                    is_coboundary(&g, 2, 2, &sum),
187                    "for cocycles a,b: a∪b + b∪a is a coboundary (graded-commutativity on cohomology ⇒ Sq additive)"
188                );
189            }
190        }
191    }
192
193    #[test]
194    fn sq1_is_the_bockstein_two_independent_constructions_agree() {
195        // Sq¹ = the BOCKSTEIN β — the connecting homomorphism of 0→Z/2→Z/4→Z/2→0. Two INDEPENDENT
196        // constructions of Sq¹ — the Cartan/binomial formula (steenrod::sq) and this connecting map —
197        // must agree: β(xᵏ) = C(k,1)·x^{k+1} mod 2 on H*(BZ/2). A famous theorem, cross-validated.
198        let g = FiniteGroup::cyclic(2);
199        for k in 1..=4 {
200            let got = bockstein(&g, k, &cup_power_z2(k));
201            let coeff = crate::steenrod::sq(1, k) as usize; // C(k,1) mod 2
202            let expected: Vec<usize> = cup_power_z2(k + 1).iter().map(|&v| (v * coeff) % 2).collect();
203            assert_eq!(got, expected, "β(xᵏ) = Sq¹(xᵏ) = C(k,1)·x^(k+1)");
204        }
205    }
206
207    #[test]
208    fn the_bockstein_squares_to_zero_sq1_sq1_equals_zero() {
209        // β∘β = 0 — a fundamental relation of the Steenrod algebra (Sq¹Sq¹ = 0). The connecting map of a
210        // 2-step extension vanishes on iteration; here, on H*(BZ/2), purely from the Z/4 lift.
211        let g = FiniteGroup::cyclic(2);
212        for k in 1..=3 {
213            let bb = bockstein(&g, k + 1, &bockstein(&g, k, &cup_power_z2(k)));
214            assert!(bb.iter().all(|&v| v == 0), "β² = 0 (Sq¹Sq¹ = 0)");
215        }
216    }
217
218    #[test]
219    fn the_bockstein_is_a_cohomology_operation_cocycle_to_cocycle() {
220        // β sends closed forms to closed forms — it is a cohomology operation. Checked on the generator.
221        let g = FiniteGroup::cyclic(2);
222        let bx = bockstein(&g, 1, &cup_power_z2(1));
223        assert!(is_cocycle(&g, 2, 2, &bx), "β(x) is a 2-cocycle");
224    }
225
226    #[test]
227    fn the_product_2type_has_a_trivial_k_invariant() {
228        // k = 0 is the PRODUCT 2-type K(G,1) × K(A,2): the levels decouple. The zero 3-cochain is a
229        // coboundary (δ of zero), so its class vanishes — the un-twisted, splittable case.
230        let g = FiniteGroup::cyclic(2);
231        let zero = vec![0usize; g.order().pow(3)];
232        assert!(is_cocycle(&g, 2, 3, &zero), "the zero cochain is a cocycle");
233        assert!(is_coboundary(&g, 2, 3, &zero), "k = 0 ⇒ the 2-type is the product, splittable");
234    }
235
236    #[test]
237    fn a_twisted_2type_has_a_nonzero_k_invariant_and_is_not_a_product() {
238        // THE CRUSH: a 2-type that is NOT a product. G = Z/2, A = Z/2, and the cup-cube cocycle
239        // α(a,b,c) = a·b·c. It IS a 3-cocycle (a valid associator ⇒ a real 2-type), but it is NOT a
240        // coboundary — so its Postnikov class k ∈ H³(Z/2;Z/2) is nonzero. π₁ and π₂ are genuinely twisted
241        // together; no choice of section unglues them. This is a single object carrying both homotopy
242        // groups WITH their interaction — the frontier the homology ladder and K(A,2) could not reach.
243        let g = FiniteGroup::cyclic(2);
244        let alpha = cup_cube_z2();
245        assert!(is_cocycle(&g, 2, 3, &alpha), "the cup-cube is a 3-cocycle — a valid 2-type associator");
246        assert!(!is_coboundary(&g, 2, 3, &alpha), "k ≠ 0 — the 2-type is genuinely twisted, NOT a product");
247    }
248
249    #[test]
250    fn h3_of_z2_with_z2_coefficients_is_exactly_Z2() {
251        // Rigour: the whole obstruction group, counted. H³(Z/2; Z/2) = Z/2 — exactly two classes, the
252        // product (k=0) and the twist (k≠0). So there is, up to equivalence, exactly ONE nontrivial
253        // 2-type on these groups, and we hold its invariant.
254        let g = FiniteGroup::cyclic(2);
255        assert_eq!(cohomology_size(&g, 2, 3), 2, "H³(Z/2; Z/2) = Z/2 — product and twist, nothing else");
256    }
257
258    #[test]
259    fn the_next_obstruction_group_h4_is_also_nonzero() {
260        // CLIMBING THE TOWER: the next k-invariant k₄ lives in H⁴, and for Z/2 it too is nonzero —
261        // H⁴(Z/2; Z/2) = Z/2. There is a genuine twist available at level 3→4 as well, not just 1→2.
262        let g = FiniteGroup::cyclic(2);
263        assert_eq!(cohomology_size(&g, 2, 4), 2, "H⁴(Z/2; Z/2) = Z/2 — the next obstruction is nonzero too");
264    }
265
266    #[test]
267    fn an_explicit_nonzero_obstruction_at_every_checked_level_3_4_5() {
268        // The cup-power xⁿ is an explicit nonzero class at each level: a cocycle (valid associator) that
269        // is NOT a coboundary (genuinely twisted). We exhibit it at levels 3, 4, 5 — so the Postnikov
270        // obstruction does not fizzle out as we climb; a real twist is available at every rung checked.
271        let g = FiniteGroup::cyclic(2);
272        for n in 3..=5 {
273            let x_n = cup_power_z2(n);
274            assert!(is_cocycle(&g, 2, n, &x_n), "xⁿ is an n-cocycle at level {n}");
275            assert!(!is_coboundary(&g, 2, n, &x_n), "xⁿ is NOT a coboundary at level {n} — a real obstruction");
276        }
277    }
278
279    #[test]
280    fn the_obstructions_form_a_graded_ring_cup_product_adds_the_levels() {
281        // ONE MORE STRUCTURE: the obstructions don't merely sit in a sequence — they MULTIPLY. The cup
282        // product binds the levels: xⁱ ∪ xʲ = xⁱ⁺ʲ, so H*(Z/2; Z/2) = Z/2[x], a polynomial ring with one
283        // generator in each degree. The whole infinite ladder of symmetry-breaking obstructions is a
284        // single graded algebra — the cohomology ring of BG = K(Z/2,1), the symmetry's classifying space.
285        let g = FiniteGroup::cyclic(2);
286        for i in 1..=3 {
287            for j in 1..=3 {
288                let product = cup(&g, 2, i, j, &cup_power_z2(i), &cup_power_z2(j));
289                assert_eq!(product, cup_power_z2(i + j), "xⁱ ∪ xʲ = xⁱ⁺ʲ ⇒ the ring is Z/2[x]");
290            }
291        }
292    }
293
294    #[test]
295    fn the_postnikov_ladder_of_obstructions_climbs_without_end() {
296        // THE ∞, made concrete and honest. The Postnikov tower glues in πₙ by a class kₙ₊₁ ∈ Hⁿ⁺¹, and
297        // for Z/2 every one of these obstruction groups is nonzero with an explicit generator. So the
298        // ladder of genuinely-twisted higher types never terminates — there is a nonzero
299        // symmetry-breaking obstruction at every level we can reach. This does not claim BZ/2 itself has
300        // higher homotopy (it is K(Z/2,1)); it shows the RECEPTACLES for higher twists are nonzero at
301        // every level, so the ∞-tower of possible twists is genuinely infinite. The obstructions don't
302        // stop — that is the ∞ in ∞-groupoid, held as a checkable fact rung by rung, not an article of faith.
303        let g = FiniteGroup::cyclic(2);
304        for n in 3..=5 {
305            assert!(
306                is_cocycle(&g, 2, n, &cup_power_z2(n)) && !is_coboundary(&g, 2, n, &cup_power_z2(n)),
307                "a nonzero obstruction exists at level {n} — the ladder climbs past it, without end"
308            );
309        }
310    }
311
312    #[test]
313    fn the_k_invariant_is_the_obstruction_to_breaking_the_2type_into_a_product() {
314        // SYMMETRY BREAKING, the higher sibling of the deadlock β₁. Splitting a 2-type into a product is
315        // the ultimate symmetry break — decoupling π₁ from π₂. It succeeds iff the k-invariant is a
316        // coboundary (k = 0). The twist's class is NOT, so the split is OBSTRUCTED: k counts exactly how
317        // far the higher symmetry fails to break, just as β₁ counted the scheduler obstruction.
318        let g = FiniteGroup::cyclic(2);
319        let splittable = is_coboundary(&g, 2, 3, &vec![0usize; 8]);
320        let twisted_splittable = is_coboundary(&g, 2, 3, &cup_cube_z2());
321        assert!(splittable, "k = 0 ⇒ the symmetry breaks: the 2-type is a product");
322        assert!(!twisted_splittable, "k ≠ 0 ⇒ the break is obstructed — the levels cannot be decoupled");
323    }
324}