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}