""" fiber.py — Fiber decomposition of the Claude's Cycles problem. KEY INSIGHT: The map f(i,j,k) = (i+j+k) mod m stratifies the digraph into m "fiber" layers F_0, …, F_{m-1}, each of size m². Every arc goes from F_s to F_{s+1 mod m}. In fiber coordinates (i,j) with k = (s-i-j) mod m, the 3 arc types become: arc 0: (i,j) in F_s → (i+1, j) in F_{s+1} [shift (1,0)] arc 1: (i,j) in F_s → (i, j+1) in F_{s+1} [shift (0,1)] arc 2: (i,j) in F_s → (i, j) in F_{s+1} [shift (0,0) — identity] A "column-uniform" sigma depends only on (s, j) — not on i. At each level s, column j gets a fixed permutation: perm[j] = [arc→cycle]. The COMPOSED permutation after all m levels: Q_c(i,j) = (i + b_c(j), j + r_c) mod m where r_c = total j-increment for cycle c, b_c(j) = total i-increment. Single m²-cycle condition: gcd(r_c, m) = 1 AND gcd(Σ b_c(j), m) = 1 """ from __future__ import annotations from typing import Dict, List, Optional, Tuple, Callable from math import gcd from itertools import permutations as _iperms # 2D arc shifts in fiber space FIBER_SHIFTS: Tuple[Tuple[int,int],...] = ( (1, 0), # arc 0: incr i (0, 1), # arc 1: incr j (0, 0), # arc 2: identity ) FiberPos = Tuple[int, int] # (i, j) in fiber space LevelTable = Dict[int, List[int]] # j → perm (for one fiber level) SigmaTable = List[LevelTable] # indexed by s=0..m-1 QFunc = Dict[FiberPos, FiberPos] # composed permutation # --------------------------------------------------------------------------- # # Level table validity # --------------------------------------------------------------------------- # def is_bijective_level(level: LevelTable, m: int) -> bool: """ Check that at level s, each cycle c induces a bijection on Z_m². For cycle c: the set of targets {(i+di, j+dj) : j in Z_m, i in Z_m} must be exactly Z_m² (all m² positions hit). """ for c in range(3): targets = set() for j in range(m): arc_type = level[j].index(c) di, dj = FIBER_SHIFTS[arc_type] for i in range(m): targets.add(((i+di) % m, (j+dj) % m)) if len(targets) != m * m: return False return True def all_valid_levels(m: int) -> List[LevelTable]: """Enumerate all column-uniform level assignments that are bijective.""" result = [] for combo in _product(_ALL_PERMS, repeat=m): level = {j: list(combo[j]) for j in range(m)} if is_bijective_level(level, m): result.append(level) return result # Lazy import helper from itertools import product as _product _ALL_PERMS = list(_iperms(range(3))) # --------------------------------------------------------------------------- # # Q composition # --------------------------------------------------------------------------- # def compose_levels(sigma_table: SigmaTable, m: int) -> List[QFunc]: """ Compose all m fiber-level functions to get Q_0, Q_1, Q_2. Returns 3 permutations on Z_m² (as dicts). """ Qs: List[QFunc] = [{} for _ in range(3)] for i0 in range(m): for j0 in range(m): pos = [[i0, j0], [i0, j0], [i0, j0]] # pos[c] = current (i,j) for s in range(m): level = sigma_table[s] for c in range(3): cj = pos[c][1] perm = level[cj] arc_type = perm.index(c) di, dj = FIBER_SHIFTS[arc_type] pos[c][0] = (pos[c][0] + di) % m pos[c][1] = (pos[c][1] + dj) % m for c in range(3): Qs[c][(i0, j0)] = (pos[c][0], pos[c][1]) return Qs def is_single_q_cycle(Q: QFunc, m: int) -> bool: """Check that permutation Q on Z_m² is a single m²-cycle.""" n = m * m visited: set = set() cur: FiberPos = (0, 0) while cur not in visited: visited.add(cur) cur = Q[cur] return len(visited) == n and cur == (0, 0) # --------------------------------------------------------------------------- # # Lift sigma_table → SigmaFn (3D) # --------------------------------------------------------------------------- # def table_to_sigma_fn(sigma_table: SigmaTable, m: int): """ Convert a SigmaTable (indexed by [s][j]) into a 3D sigma function sigma(i, j, k) that can be used with core.verify_sigma. The key: depends only on s=(i+j+k)%m and j. """ def sigma(i: int, j: int, k: int) -> List[int]: s = (i + j + k) % m return list(sigma_table[s][j]) return sigma # --------------------------------------------------------------------------- # # Q structure analysis # --------------------------------------------------------------------------- # def analyze_Q_structure(Qs: List[QFunc], m: int) -> dict: """ Analyze whether Q_c has the twisted translation form: Q_c(i,j) = (i + b_c(j), j + r_c) mod m Returns a dict with r_c, b_c, is_twisted, single_cycle per cycle. """ result = {"cycles": [], "all_twisted": True, "all_single": True} for c in range(3): Q = Qs[c] # Detect r_c: j-increment should be constant across all starting positions r_c_vals = set((Q[(i,j)][1] - j) % m for i in range(m) for j in range(m)) is_uniform_r = (len(r_c_vals) == 1) r_c = r_c_vals.pop() if is_uniform_r else None # Detect b_c(j): i-offset at fixed starting i=0 if is_uniform_r: b_c = [(Q[(0,j)][0] - 0) % m for j in range(m)] # Verify b_c is shift-invariant: Q(i,j)[0] = i + b_c(j) mod m is_twisted = all(Q[(i,j)][0] == (i + b_c[j]) % m for i in range(m) for j in range(m)) else: b_c = None is_twisted = False single = is_single_q_cycle(Q, m) cycle_info = { "cycle": c, "r_c": r_c, "b_c": b_c, "is_twisted": is_twisted, "is_single_cycle": single, "sum_b": (sum(b_c) % m) if b_c else None, "gcd_r_m": gcd(r_c, m) if r_c is not None else None, "gcd_sumb_m": gcd(sum(b_c) % m, m) if b_c else None, } result["cycles"].append(cycle_info) if not is_twisted: result["all_twisted"] = False if not single: result["all_single"] = False if result["all_twisted"]: r_vals = [info["r_c"] for info in result["cycles"]] result["sum_r"] = sum(r_vals) % m result["r_values"] = r_vals return result # --------------------------------------------------------------------------- # # Theorem verification helpers # --------------------------------------------------------------------------- # def verify_single_cycle_conditions(r_c: int, b_c: List[int], m: int) -> dict: """ Verify the two necessary and sufficient conditions for Q_c to be a single m²-Hamiltonian cycle. """ s_b = sum(b_c) % m return { "gcd_r_m": gcd(r_c, m), "gcd_sumb_m": gcd(s_b, m), "condition_a": gcd(r_c, m) == 1, "condition_b": gcd(s_b, m) == 1, "both_satisfied": gcd(r_c, m) == 1 and gcd(s_b, m) == 1, } def even_m_impossibility_check(m: int) -> dict: """ Verify the impossibility theorem for even m: No (r_0,r_1,r_2) with gcd(r_c,m)=1 can sum to m when m is even. """ if m % 2 == 0: # Coprime to even m means ODD # Sum of 3 odd numbers is ODD ≠ EVEN = m example_coprime = [r for r in range(m) if gcd(r, m) == 1] min_sum = sum(sorted(example_coprime)[:3]) return { "m": m, "m_is_even": True, "coprime_elements": example_coprime, "all_coprime_are_odd": all(r % 2 == 1 for r in example_coprime), "three_odds_sum_is_odd": True, "needed_sum": m, "impossibility_proved": True, "proof": "All r coprime to even m are odd. Sum of 3 odds is odd ≠ m (even).", } else: # For odd m: show valid (r_0,r_1,r_2) exists valid = [(r0,r1,r2) for r0 in range(m) for r1 in range(m) for r2 in range(m) if gcd(r0,m)==1 and gcd(r1,m)==1 and gcd(r2,m)==1 and r0+r1+r2==m] return { "m": m, "m_is_even": False, "impossibility_proved": False, "valid_r_triples_count": len(valid), "example": valid[0] if valid else None, }