"""naming.py -- which primes divide the Euler characteristic of a set. QUESTION. Take a finite set S of primes, N = the product of S, m = |S|, and the ring Z/N read through its m channels. Its HAMMING GRAPH joins two residues that differ in exactly one channel: the Cartesian product of the complete graphs K_p, p in S. Fill in every clique and call the result X(S), the CLIQUE COMPLEX. What is its homotopy type, what is its Euler characteristic chi, and which primes s outside S divide -chi (s NAMES S)? THE ARGUMENT (written before this script). (1) THE COMPLEX. Sizes n_1..n_m >= 2, not necessarily prime. If u, v differ in coordinate i and v, w in coordinate j != i, then u, w differ in both and are not adjacent; so a clique lies in a LINE (all coordinates but one fixed). The maximal cliques are the L = sum_i N/n_i lines, two lines meet in at most one vertex, and every vertex lies on m lines. Replace each line (a simplex) by the cone on its vertices: both are contractible and glued along the same vertex set, so X is homotopy equivalent to the bipartite VERTEX-LINE GRAPH B, which has N + L vertices and mN edges and is connected. So X is a wedge of circles with b1 = mN - N - L + 1 = 1 + N (m - 1 - sum_i 1/n_i), b_j = 0 for j >= 2, and -chi = b1 - 1. b1 = N exactly when sum 1/n_i = m - 2 + 1/N. Sizes >= 2 give sum 1/n_i <= m/2, so m <= 3; m = 1 and m = 2 are impossible (m = 2 would need n_1 + n_2 = 1); at m = 3 the sum exceeds 1, so the least size is 2, and then (n_2 - 2)(n_3 - 2) = 3: the sizes are {2, 3, 5} and nothing else. (2) THE INTEGER. For primes, -chi(S) = N (g - 1) with g = sum over S of (1 - 1/p). Modulo a member p every term of N (m - 1) - sum over r in S of N/r vanishes but -N/p, which is prime to p: no member divides -chi. Modulo 2 with every member odd, -chi is (m - 1) - m = -1: -chi is odd, so 2 never names. g is the mean Hamming distance from any point of Z/N, since a uniform residue differs from a fixed one at p with chance 1 - 1/p; so -chi = sum over x of (d(0, x) - 1), d the Hamming distance, the number of channels where two residues differ. Both sides count mN - N - L: the reading is a recount, not a second fact. (3) THE WEIGHT LAW. For a prime s not in S, N is invertible mod s, so s | -chi iff g = 1 in Z/s, that is iff the WEIGHTS w_s(p) = 1 - p^(-1) mod s over S sum to 1. Naming therefore depends only on S's residues mod s; a prime p = 1 mod s has weight 0 and is invisible; mod 3 the weights are 0 (p = 1) and 2 (p = 2), so 3 names S iff #{p in S : p = 2 mod 3} = 2 mod 3. A pool of primes has a set that s names iff 1 is a subset sum of the weights; the empty sum is 0 and a singleton's -chi is -1, so nothing smaller cheats. By Cauchy-Davenport, adding a nonzero weight to a subset-sum set A of Z/s gives at least min(s, |A| + 1) sums; so s - 1 primes of nonzero weight reach every residue, and every prime s >= 3 names a set once the rung holds s - 1 primes other than s that are not 1 mod s; Dirichlet's theorem supplies them, so such a rung exists. (4) THE BASELINE. Over a pool P of primes not containing s, #{S in P : s names S} = (1/s) sum_j e(-j/s) prod_p (1 + e(j w_p / s)), and |1 + e(t)| = 2|cos(pi t)|, so the count differs from 2^|P|/s by at most ((s - 1)/s) 2^|P| max_{j != 0} prod_p |cos(pi j w_p / s)|, each factor with w_p != 0 at most cos(pi / s) < 1. (5) THE SLICES. For T inside S, fixing the coordinates of S \\ T cuts X(S)'s vertices into c = N_S / N_T SLICES, each an induced copy of X(T). In B each slice is a subgraph (its vertices and its T-lines), the slices are disjoint, and a subgraph's cycle space is a subspace of the graph's; so the slices' first homology injects jointly, rank c b1(T). The count identity b1(S) - 1 = c (b1(T) - 1) + N_S sum over S \\ T of (1 - 1/p) is (2)'s algebra, not a finding. (6) NO SYMMETRY. For distinct prime sizes, Aut(X(S)) is the product of the Sym(p) acting coordinatewise (the Hamming graph's automorphisms, its factors being Cartesian-prime and pairwise non-isomorphic). An automorphism of prime order s not in S has each component of order 1 or s, with p - s k fixed points, k its number of s-cycles, never 0 since s does not divide p; so it fixes a vertex. s | b1 - 1 says X is homotopy equivalent to an s-fold cover of a graph, and no simplicial Z/s action on X supplies it. DESIGN. Sections of checks printing PASS or FAIL. X the complex. For twenty size tuples (primes, repeats, non-primes, m = 1..4): the Hamming graph's cliques are enumerated from the adjacency alone (common neighbours), never from lines; the GF(2) Betti numbers b0..b3 come from boundary ranks. Bron-Kerbosch lists the maximal cliques to check they are the lines. Then an exhaustive search of (1)'s closed form for b1 = N over size multisets: m = 1, 2, 3 with sizes to 60, m = 4 to 30, m = 5 to 12. POSITIVE CONTROL first: the Betti engine (the clique search has none of its own; X1 and X2 guard it) on a hollow hexagon (1, 1, 0, 0), two filled triangles (2, 0, 0, 0), a tetrahedron's boundary (1, 0, 1, 0) and a 4-simplex's boundary (1, 0, 0, 1), the last two proving the b2 and b3 columns can print a nonzero. I the integer, over every nonempty S in the first nine primes: -chi odd, prime to every member; and, by brute force over Z/N for every S in the first five primes, -chi = sum (d(0, x) - 1), d the Hamming distance, the number of channels where two residues differ. W the weight law over every (S, s), S nonempty in the first nine primes, s prime <= 200 outside S; the mod-3 count over every S in the first twelve primes without 3. POSITIVE CONTROL: the law with weight p^(-1) in place of 1 - p^(-1) must fail. Then, for every prime s < 200, the first rung at which s names a set, read two ways -- by -chi over every S in the first twelve primes, and by a subset-sum walk of the weights to rung 60 -- against the Cauchy-Davenport rung. B the baseline: s in {3, 5, 7, 11, 13}, P the first 6 and the first 9 primes other than s; the count, 2^|P|/s and the bound. An all-invisible pool (the primes 1 mod 3 below 50, s = 3) must count exactly 0. S the slices: every pair T inside S, both nonempty and T != S, S inside {2, 3, 5, 7} of two or more primes. The rank of the slices' cycles in H1(X(S)) over GF(2) is rank(D + Z1(slices)) - rank(D), D the triangle boundaries. POSITIVE CONTROL first: the cycles of one line's edge graph (a boundary, since the line is a simplex) must map to rank 0. A automorphisms: |Aut| of the Hamming graph by brute force over all vertex permutations at sizes (2, 3), after a POSITIVE CONTROL at (2, 2), where equal sizes let the factors swap. Then, for three (S, s) with s outside S, every element of order s in the product of the Sym(p) fixes a vertex, after a CONTROL s = 5 inside S = {2, 5, 7}, where a vertex-free one exists. PREDICTIONS, fixed before the run. X0 the three control complexes read (1, 1, 0), (2, 0, 0), (1, 0, 1). X1 b1 = 1 + N (m - 1 - sum 1/n) on all twenty tuples: 2, 4, 8, 24 at (2,3), (2,5), (3,5), (5,7); 1, 4, 0, 3, 15 at (2,2), (3,3), (4), (2,4), (4,6); 5, 23 at (2,2,2), (2,3,4); 30, 44, 82, 140, 74 at (2,3,5), (2,3,7), (2,5,7), (3,5,7), (3,4,5); 49, 17, 384, 89 at (2,2,3,3), (2,2,2,2), (2,3,5,7), (2,2,3,5). b0 = 1, b2 = b3 = 0 everywhere. X2 the maximal cliques are L lines, N/n_i of size n_i, meeting pairwise in at most one vertex. X3 the only size multiset with b1 = N is {2, 3, 5}. I1 0 exceptions over 511 sets; I2 0 exceptions over 31 sets. W1 0 violations over 21,202 pairs; W2 0 violations; W0 the control shows violations. W3 the two first-naming rungs agree wherever both are defined; 2 names no set; 41 first names one at rung 8 ({17, 19}, whose -chi is 287 = 7 * 41); every odd prime below 200 names one by rung 11; every first rung sits at or below the Cauchy-Davenport rung. B1 each count within its bound, in all ten cells; B2 exactly 0. S1 image rank = c b1(T) at all 50 pairs; S0 the control reads 0. A1 |Aut| = 12 = 2! 3!; A0 |Aut| = 8 at (2, 2), not 2! 2! = 4. A2 no vertex-free element of order s; A3 the control finds some. FINDINGS. Every prediction landed, 22/22 checks PASS, the controls first; X0's 4-simplex and the W3m split came after the slate froze. X0 the controls read (1, 1, 0, 0), (2, 0, 0, 0), (1, 0, 1, 0) and (1, 0, 0, 1). X1 X2 all twenty tuples print b = (1, b1, 0, 0) with b1 the formula's, from 2 at (2, 3) to 384 at (2, 3, 5, 7); the maximal cliques are the lines and meet in at most one vertex. X3 the search finds (2, 3, 5) alone. (X2 was widened after the first run to test that each maximal clique varies in exactly one coordinate; it passes.) I1 511 sets, 0 exceptions; I2 31 sets, 0 exceptions. W0 the wrong weight shows 962 violations; W1 21,202 pairs, 0 violations; W2 2,047 sets, 0 violations. W3 the scan and the walk agree at every prime below 200; 2 names no set; 41 first at rung 8, through {17, 19}, whose -chi is 287 = 7 * 41; the first rungs run 3 to 9, the largest prime first naming a set at rung 9 being 167, every one at or below its Cauchy-Davenport rung and meeting it only at s = 3 (the meeting asserted after the slate froze). B1 all ten cells inside the bound (at s = 13, |P| = 6 the bound, 9.80, exceeds 2^6/13 itself: six primes do not yet force the count); B2 0. S1 50 pairs, image rank c b1(T) at every one, 22 of them with T of two or more primes and so a nonzero rank; S0 six cycles, image 0. A1 12; A0 8. A2 7,370, 24,127 and 504 elements of order s, none vertex-free; A3 12,120 of 12,624 elements of order 5 are vertex-free. Tiers: the complex is a theorem, proved in (1), b1 = N at {2, 3, 5} alone with it; (2) is a property; the weight law a criterion, and every odd prime eventually naming a set a theorem; the baseline, the slices and the fixed vertex are theorems. Every check is a witness in range, never the proof. Run record: 22/22 PASS, 0.2 s, peak commit 10.7 MB under a memory guard. Standard library only. python naming.py """ from itertools import combinations, combinations_with_replacement, \ permutations, product from math import cos, pi, prod CHECKS = [] def check(name, ok, detail=""): CHECKS.append(bool(ok)) print(f" [{'PASS' if ok else 'FAIL'}] {name}" + (f" -- {detail}" if detail else "")) def is_prime(n): return n > 1 and all(n % d for d in range(2, int(n ** 0.5) + 1)) PRIMES = [p for p in range(2, 2000) if is_prime(p)] def neg_chi(S): N = prod(S) return N * (len(S) - 1) - sum(N // p for p in S) # ---- homology over GF(2) ---------------------------------------------- def rank(rows): pivots, r = {}, 0 for v in rows: while v: h = v.bit_length() - 1 if h in pivots: v ^= pivots[h] else: pivots[h] = v r += 1 break return r def boundary_rows(upper, lower): index = {s: i for i, s in enumerate(lower)} rows = [] for s in upper: v = 0 for face in combinations(s, len(s) - 1): v |= 1 << index[face] rows.append(v) return rows def betti(simplices, top=3): """simplices[d] = sorted tuples of dimension d; b_0..b_top.""" ranks = [0] for d in range(1, top + 2): upper = simplices[d] if d < len(simplices) else [] ranks.append(rank(boundary_rows(upper, simplices[d - 1])) if upper else 0) counts = [len(simplices[d]) if d < len(simplices) else 0 for d in range(top + 1)] return [counts[d] - ranks[d] - ranks[d + 1] for d in range(top + 1)] def closure(maximal): faces = set() for s in maximal: for r in range(1, len(s) + 1): faces.update(combinations(sorted(s), r)) top = max(len(f) for f in faces) return [sorted(f for f in faces if len(f) == d + 1) for d in range(top)] # ---- the Hamming graph and its cliques -------------------------------- def hamming(sizes): verts = list(product(*[range(n) for n in sizes])) adj = [set() for _ in verts] for i, u in enumerate(verts): for j in range(i + 1, len(verts)): if sum(a != b for a, b in zip(u, verts[j])) == 1: adj[i].add(j) adj[j].add(i) return verts, adj def cliques(adj, top): """All cliques with up to top + 1 vertices, from adjacency alone.""" levels = [[(v,) for v in range(len(adj))]] while len(levels) <= top: nxt = [] for c in levels[-1]: common = set.intersection(*(adj[v] for v in c)) nxt.extend(c + (w,) for w in sorted(common) if w > c[-1]) if not nxt: break levels.append(nxt) return levels def maximal_cliques(adj): out = [] def bk(r, p, x): if not p and not x: out.append(frozenset(r)) return for v in list(p): bk(r | {v}, p & adj[v], x & adj[v]) p = p - {v} x = x | {v} bk(set(), set(range(len(adj))), set()) return out def b1_formula(sizes): N = prod(sizes) return 1 + N * (len(sizes) - 1) - sum(N // n for n in sizes) def section_x(): print("X the complex is a wedge of circles") controls = { "hexagon": ([(i, (i + 1) % 6) for i in range(6)], [1, 1, 0, 0]), "two triangles": ([(0, 1, 2), (3, 4, 5)], [2, 0, 0, 0]), "sphere": (list(combinations(range(4), 3)), [1, 0, 1, 0]), "3-sphere": (list(combinations(range(5), 4)), [1, 0, 0, 1]), } got = {k: betti(closure(v), top=3) for k, (v, _) in controls.items()} check("X0 control complexes", all(got[k] == v for k, (_, v) in controls.items()), str(got)) cases = [(2, 3), (2, 5), (3, 5), (5, 7), (2, 2), (3, 3), (4,), (2, 4), (4, 6), (2, 2, 2), (2, 3, 4), (2, 3, 5), (2, 3, 7), (2, 5, 7), (3, 5, 7), (3, 4, 5), (2, 2, 3, 3), (2, 2, 2, 2), (2, 3, 5, 7), (2, 2, 3, 5)] bad_b, bad_l = [], [] for sizes in cases: verts, adj = hamming(sizes) levels = cliques(adj, 4) b = betti(levels, top=3) want = [1, b1_formula(sizes), 0, 0] print(f" {str(sizes):14s} N={len(verts):4d} b={b}") if b != want: bad_b.append(sizes) mc = maximal_cliques(adj) N = prod(sizes) want_sizes = sorted(n for n in sizes for _ in range(N // n)) meets = max((len(c1 & c2) for c1, c2 in combinations(mc, 2)), default=0) lines = all(sum(len({verts[v][i] for v in c}) > 1 for i in range(len(sizes))) == 1 for c in mc) if (sorted(len(c) for c in mc) != want_sizes or meets > 1 or not lines): bad_l.append(sizes) check("X1 b1 = 1 + N(m - 1 - sum 1/n), b0 = 1, b2 = b3 = 0", not bad_b, f"{len(cases)} tuples, failures {bad_b}") check("X2 maximal cliques are the lines, meeting in <= 1 vertex", not bad_l, f"failures {bad_l}") hits = [] for m, top in ((1, 60), (2, 60), (3, 60), (4, 30), (5, 12)): for sizes in combinations_with_replacement(range(2, top + 1), m): if b1_formula(sizes) == prod(sizes): hits.append(sizes) check("X3 b1 = N in the search only at sizes {2, 3, 5}", hits == [(2, 3, 5)], f"hits {hits}") def section_i(): print("I the integer -chi = N(g - 1)") bad = 0 sets = [S for r in range(1, 10) for S in combinations(PRIMES[:9], r)] for S in sets: c = neg_chi(S) if c % 2 == 0 or any(c % p == 0 for p in S): bad += 1 check("I1 -chi odd and prime to every member", bad == 0, f"{len(sets)} sets, {bad} exceptions") bad = 0 sets = [S for r in range(1, 6) for S in combinations(PRIMES[:5], r)] for S in sets: total = sum(sum(x % p != 0 for p in S) - 1 for x in range(prod(S))) bad += total != neg_chi(S) check("I2 -chi = sum over Z/N of (d(0, x) - 1)", bad == 0, f"{len(sets)} sets, {bad} exceptions") # ---- the weight law --------------------------------------------------- def weight(p, s): return (1 - pow(p, -1, s)) % s def first_rung_walk(s, kmax=60): """Least k with 1 a subset sum of the weights of p_1..p_k, p != s.""" reach = {0} for k, p in enumerate(PRIMES[:kmax], start=1): if p != s: w = weight(p, s) reach |= {(a + w) % s for a in reach} if 1 in reach: return k return None def cauchy_davenport_rung(s): count = 0 for k, p in enumerate(PRIMES, start=1): if p != s and p % s != 1: count += 1 if count == s - 1: return k def section_w(): print("W the weight law") pool = PRIMES[:9] small = [s for s in PRIMES if s <= 200] pairs = viol = ctrl = 0 for r in range(1, 10): for S in combinations(pool, r): c = neg_chi(S) for s in small: if s in S: continue pairs += 1 named = c % s == 0 viol += named != (sum(weight(p, s) for p in S) % s == 1) ctrl += named != (sum(pow(p, -1, s) for p in S) % s == 1) check("W0 control: weight 1/p in place of 1 - 1/p fails", ctrl > 0, f"{ctrl} violations") check("W1 s names S iff the weights sum to 1 mod s", viol == 0, f"{pairs} pairs, {viol} violations") pool3 = [p for p in PRIMES[:12] if p != 3] sets3 = [S for r in range(1, len(pool3) + 1) for S in combinations(pool3, r)] bad = sum((neg_chi(S) % 3 == 0) != (sum(p % 3 == 2 for p in S) % 3 == 2) for S in sets3) check("W2 3 names S iff #{p = 2 mod 3} = 2 mod 3", bad == 0, f"{len(sets3)} sets, {bad} violations") chis = {} for r in range(1, 13): for S in combinations(range(12), r): chis[S] = neg_chi([PRIMES[i] for i in S]) first, agree, cd_ok, meet = {}, True, True, [] for s in small: direct = min((max(S) + 1 for S, c in chis.items() if c % s == 0 and s not in [PRIMES[i] for i in S]), default=None) walk = first_rung_walk(s) first[s] = walk if direct != (walk if walk is not None and walk <= 12 else None): agree = False if walk is None and s != 2: agree = False if s > 2 and (walk is None or walk > cauchy_davenport_rung(s)): cd_ok = False if s > 2 and walk == cauchy_davenport_rung(s): meet.append(s) latest = max((k, s) for s, k in first.items() if k is not None) check("W3 first naming rung: -chi scan = subset-sum walk", agree, f"latest odd s < 200: {latest[1]} at rung {latest[0]}") c = neg_chi((17, 19)) check("W3 2 names no set; 41 first names one at rung 8, {17, 19}", first[2] is None and first[41] == 8 and c == 287 == 7 * 41, f"41 -> {first[41]}, -chi(17, 19) = {c}") rungs = [first[s] for s in small if s > 2] check("W3 every odd prime < 200 names a set by rung 11", all(k is not None and k <= 11 for k in rungs), f"rungs {sorted(set(k for k in rungs if k is not None))}") check("W3 at or below the Cauchy-Davenport rung", cd_ok) check("W3m meeting the Cauchy-Davenport rung only at s = 3", meet == [3], f"meets at {meet}") def section_b(): print("B the baseline") ok, cells = True, 0 for s in (3, 5, 7, 11, 13): for size in (6, 9): cells += 1 P = [p for p in PRIMES if p != s][:size] w = [weight(p, s) for p in P] count = sum(sum(w[i] for i in S) % s == 1 for r in range(size + 1) for S in combinations(range(size), r)) base = 2 ** size / s bound = (s - 1) / s * 2 ** size * max( prod(abs(cos(pi * j * x / s)) for x in w) for j in range(1, s)) ok &= abs(count - base) <= bound + 1e-9 print(f" s={s:2d} |P|={size} count {count:4d} " f"2^|P|/s {base:7.2f} bound {bound:7.2f}") check("B1 |count - 2^|P|/s| within the character bound", ok, f"{cells} cells") P = [p for p in PRIMES if p < 50 and p % 3 == 1] zero = sum(neg_chi(S) % 3 == 0 for r in range(1, len(P) + 1) for S in combinations(P, r)) check("B2 an all-invisible pool names nothing", zero == 0, f"s = 3 over {P}") # ---- the slices ------------------------------------------------------- def complex_edges(sizes): verts, adj = hamming(sizes) levels = cliques(adj, 2) return verts, levels[1], levels[2] def cycle_basis(edges, nverts, eindex): """Fundamental cycles of a graph, as bitmasks over eindex.""" nbr = [[] for _ in range(nverts)] for (u, v) in edges: nbr[u].append(v) nbr[v].append(u) to_root, seen, tree = {}, set(), set() for root in range(nverts): if root in seen or not nbr[root]: continue seen.add(root) to_root[root] = 0 stack = [root] while stack: u = stack.pop() for v in nbr[u]: if v not in seen: seen.add(v) e = (min(u, v), max(u, v)) tree.add(e) to_root[v] = to_root[u] ^ (1 << eindex[e]) stack.append(v) return [(1 << eindex[e]) ^ to_root[e[0]] ^ to_root[e[1]] for e in edges if e not in tree] def section_s(): print("S the slices inject jointly") verts, edges, tris = complex_edges((2, 5)) eindex = {e: i for i, e in enumerate(edges)} line = [e for e in edges if verts[e[0]][0] == verts[e[1]][0] == 0] z = cycle_basis(line, len(verts), eindex) bd = boundary_rows(tris, edges) image = rank(bd + z) - rank(bd) check("S0 control: one line's cycles map to rank 0", image == 0 and len(z) == 6, f"{len(z)} cycles, image {image}") base = (2, 3, 5, 7) ok, npairs, nonzero = True, 0, 0 for r in range(2, 5): for S in combinations(base, r): verts, edges, tris = complex_edges(S) eindex = {e: i for i, e in enumerate(edges)} bd = boundary_rows(tris, edges) rb = rank(bd) for t in range(1, r): for T in combinations(S, t): keep = [i for i, p in enumerate(S) if p not in T] inside = [e for e in edges if all( verts[e[0]][i] == verts[e[1]][i] for i in keep)] z = cycle_basis(inside, len(verts), eindex) image = rank(bd + z) - rb want = prod(S) // prod(T) * b1_formula(T) ok &= image == want npairs += 1 nonzero += want > 0 check("S1 slice cycles have rank c * b1(T) in H1(X(S))", ok, f"{npairs} pairs, {nonzero} with nonzero rank") # ---- automorphisms ---------------------------------------------------- def automorphisms(sizes): verts, adj = hamming(sizes) edges = [(u, v) for u in range(len(verts)) for v in adj[u] if u < v] return sum(all(g[v] in adj[g[u]] for u, v in edges) for g in permutations(range(len(verts)))) def order_s_elements(p, s): ident = tuple(range(p)) out = [] for g in permutations(range(p)): h = ident for _ in range(s): h = tuple(g[i] for i in h) if h == ident: out.append(sum(g[i] == i for i in range(p))) return out def free_elements(S, s): """Elements of order s in prod Sym(p) fixing no vertex.""" fixes = [order_s_elements(p, s) for p in S] total = free = 0 for combo in product(*fixes): if all(f == p for f, p in zip(combo, S)): continue total += 1 free += any(f == 0 for f in combo) return total, free def section_a(): print("A no vertex-free symmetry") a = automorphisms((2, 2)) check("A0 control: |Aut| at (2, 2) = 8, factors swap", a == 8, f"{a}") a = automorphisms((2, 3)) check("A1 |Aut| at (2, 3) = 2! 3!", a == 12, f"{a}") total, free = free_elements((2, 5, 7), 5) check("A3 control: s = 5 inside S = {2, 5, 7} has vertex-free elements", free > 0, f"{free} of {total}") ok = True for S, s in (((2, 5, 7), 3), ((3, 5, 7), 2), ((2, 3, 7), 5)): total, free = free_elements(S, s) print(f" S={S} s={s}: {total} elements of order s, " f"{free} vertex-free") ok &= total > 0 and free == 0 check("A2 every element of order s outside S fixes a vertex", ok) def main(): section_x() section_i() section_w() section_b() section_s() section_a() print(f"{sum(CHECKS)}/{len(CHECKS)} checks passed") raise SystemExit(0 if all(CHECKS) else 1) if __name__ == "__main__": main()