diff --git a/research/c4-semidirect-central-floor/README.md b/research/c4-semidirect-central-floor/README.md new file mode 100644 index 0000000000..a9aea6752b --- /dev/null +++ b/research/c4-semidirect-central-floor/README.md @@ -0,0 +1,185 @@ +# Central bills in the semidirect product of order 16 + +**Finite register-model certificate; no new κ.** The selected result, both +primitive suppliers, and `upstream/` are unchanged. The complex primitive +binds. In this alphabet the proved central bills meet their coordinate +constructions, so register mixing does not undercut them. + +Let \(G\) be the unique nonabelian semidirect product \(C_4\rtimes C_4\), + +\[ +G=\langle a,b\mid a^4=e,\ b^4=e,\ bab^{-1}=a^{-1}\rangle. +\] + +Work in \(M_3(\mathbb F_2[G])\) with exactly three roles and paid calls + +\[ +D_N([g])=I+([g]+[e])N, +\qquad g\in\{a,b\}, +\] + +where \(N\) runs over all 28 rank-one idempotents in \(M_3(\mathbb F_2)\). +Matrix equality is an operator identity on arbitrary dirty states. Elements +are encoded by \(a^ib^j=i+4j\). + +| Target | Coordinate bill | Minimum | +|---|---:|---:| +| \(\operatorname{diag}([a^2],[a^2],[a^2])\) | 6 | 6 | +| \(\operatorname{diag}([b^2],[b^2],[b^2])\) | 6 | 6 | +| \(\operatorname{diag}([a^2],[b^2],[a^2b^2])\) | 8 | 8 | +| \(\operatorname{diag}([a^2b^2],[a^2b^2],[e])\) | 8 | 8 | +| \(\operatorname{diag}([a^2b^2],[a^2b^2],[a^2b^2])\) | 12 | in \(\{8,10,12\}\) | + +The last minimum is not claimed to be sharp. It is absent through length 6. + +The changed hypothesis is the address group. The abelian three-register model +uses arbitrary rational rank-one frames and concludes that nine calls are +necessary for three invertible lower-triangular targets. This alphabet uses +only the two semidirect generators and charges every call. There is no free +scalar mixing outside the 28 idempotents and no fourth role. The group has +exponent 4, so it is not the dihedral, modular, semidihedral, or generalized +quaternion group of order 16. Its center is \(\{e,a^2,b^2,a^2b^2\}\) and its +derived subgroup is \(\{e,a^2\}\). In particular \(b^2\) is not a commutator, +which separates this group from \(Q_8\times C_2\). + +## Priced baseline + +Generator lengths, from the right Cayley graph on \(\{a,b\}\), computed +before the matrix search: + +| Element | Shortest generator word | One-output minimum | Three-role coordinate bill | +|---|---|---:|---:| +| \(a^2\) | \(aa\) | 2 | 6 | +| \(b^2\) | \(bb\) | 2 | 6 | +| \(a^2b^2\) | \(aabb\) | 4 | 12 | + +The one-output column allows every rank-one idempotent. The two idle roles +may be used and must be restored. They do not beat the generator word. +Thus a scratch role inside this three-role alphabet does not change the +coordinate bills above. The word \(abab\) is another expression for \(b^2\), +because \((ab)^2=b^2\), but it is longer than \(bb\). + +The degree-1 relaxation excludes odd lengths for every target in the table. +The augmentation powers of \(\mathbb F_2[G]\) have dimensions +\(15,13,10,6,3,1,0\). The quotient \(\Delta/\Delta^2\) is two-dimensional, with +basis \(\alpha=[a]+1\) and \(\beta=[b]+1\). For \(a^ib^j\), + +\[ +[a^ib^j]+1\equiv (i\bmod 2)\,\alpha+(j\bmod 2)\,\beta +\pmod{\Delta^2}. +\] + +Every square and the product \(a^2b^2\) therefore lie in \(\Delta^2\). Each +paid deviation has entries in \(\Delta\), and every product of two or more +deviations has entries in \(\Delta^2\). A word is congruent modulo +\(M_3(\Delta^2)\) to + +\[ +I+\alpha\sum_{a\text{-calls}}N_j+\beta\sum_{b\text{-calls}}N_j. +\] + +Each admitted projector has trace 1. A target congruent to \(I\) hence needs +an even number of \(a\)-calls and an even number of \(b\)-calls, so its length +is even. All four central targets in the table are congruent to \(I\). + +## Result + +Lengths 1 through 3 were enumerated in \(M_3(\mathbb F_2[G])\). The exact +frontiers have sizes 56, 2352 and 92374. Meet-in-the-middle on these +frontiers finds the two square triples at length 6 and not earlier, the +mixed triple and the two-role product at no length through 6, and the +three-role product at no length through 6. The coordinate words + +- \((E_{00},a)\) twice, then the same for \(E_{11}\) and \(E_{22}\), +- the same pattern with \(b\), +- \(aa\) on the first role, \(bb\) on the second, and \(aabb\) on the third, +- \(aabb\) on two roles, leaving the third at \(e\), +- \(aabb\) on all three roles, + +realize the five targets at lengths 6, 6, 8, 8 and 12. The symbol lemma +turns the length-6 absence of the two length-8 targets into the exact +minimum 8. The three-role product remains unresolved at the even lengths +8, 10 and 12. + +## Reproduce + +From the repository root, with Python 3.12 and `PYTHONOPTIMIZE` unset: + +```sh +python3 -B research/c4-semidirect-central-floor/verify.py +python3 -B scripts/research_package.py --package research/c4-semidirect-central-floor +``` + +Expected final verifier line: + +```text +PASS_C4_SEMIDIRECT_CENTRAL a2=6 b2=6 mixed=8 two_z=8 z_through6=0 z_upper=12 one_z=4 idempotents=28 calls=56 frontier2=2352 frontier3=92374 center=4 derived=2 involutions=3 aug=15,13,10,6,3,1,0 controls=8 no_new_kappa +``` + +The script uses the standard library only. It checks the presentation, +associativity, the exponent, the order-4 center, the derived subgroup, the +augmentation dimensions and the degree-1 classes, the 28-idempotent scan of +all 512 matrices, call inverses, associativity of the 56 paid calls, the +symbol congruence on every length-2 and length-3 frontier matrix, and the +frontiers. Eight controls are required: the positive lengths listed above; +the symbol congruence on every length-2 and length-3 frontier matrix; +the \(a^2\) witness on the dirty state +\(([ab]+[b],[a^2],[a^2b^2]+[a^3])\); a dropped call failing on that state; +an \(a\)-for-\(b\) substitution failing; the direct-product law +\(ab=ba\) making the \(abab\) witness fail; rejection of the rank-2 matrix +\(E_{00}+E_{11}\); and one explicit five-call word whose matrix is first +reached at length 5. + +Recorded environment: CPython 3.12.3, `g++` 13.3.0 with `/usr/bin/c++` pointing +at `/usr/bin/g++`, `libboost-dev` 1.83.0.1ubuntu2, and +`research/e8-bit-p111-tuned/requirements.txt` already satisfied by +`python3 -m pip install --user` (numpy 2.5.4, sympy 1.14.0, scipy 1.18.1). +`PYTHONOPTIMIZE` was unset. This package imports none of those libraries. +Direct verifier runtime on this machine was about 13 seconds. + +Not run: `make frontier1094-verify`, the three-register C++ replay, a +four-role search, and the length-8, length-10 and length-12 frontiers. +Odd lengths for the central targets are excluded by the symbol argument. +The three-role product was not searched past length 6. + +## Open next questions + +1. Is the minimum of \(\operatorname{diag}([a^2b^2]^3)\) equal to 8, 10, or + 12? A value below 12 would be a real mixing saving in this alphabet. + First step: store one length-4 word for each length-4 matrix and test the + target by inverse meet-in-the-middle, using the symbol lemma to skip odd + lengths. This package builds only the length-2 and length-3 frontiers. + Blocker: the length-4 set was not stored, and the length-6 screen only + splits words as 3+3. +2. Can one restored fourth role realize + \(\operatorname{diag}([a^2b^2],[a^2b^2],[a^2b^2],[e])\) in fewer than 12 + calls? A scratch role outside the three output roles is the usual route + past a sharp three-role bill. First step: shortest-path search on the + four-role alphabet of both generators and every rank-one idempotent in + \(M_4(\mathbb F_2)\). Blocker: that alphabet was not built, and it is + premature until the three-role minimum above is known. + +## Credit + +Predecessor model: the scalar-idempotent calls in +`research/bit-three-register-lower-bound`, from eumemic's pinned PR #288, +section 1.2. That theorem concerns rational rank-one frames, not this +generator alphabet. Order-eight and order-sixteen dihedral certificates, +including the two-register reflection search for \(\operatorname{diag}([r^4],[r^4])\), +use a different relation and do not admit the order-4 element \(b\) of this +presentation. Quaternion, modular, and semidihedral generator alphabets have +elements of order 8; their frontiers and targets are not reused. Apache-2.0. + +Substantial AI assistance was used to build the multiplication table, search +the paid words, and write these notes. + +Discussion writes for this account return FORBIDDEN / Resource not accessible +by integration. No claim was posted, and the write was not retried. + +Base commit `ef5f39403610d231a0c27e8a6b4f08c10209bcd6`. Selector nonce +`b97b573eb6e23057911515d5e72ca8f3`, card `bit-new-register-model`, scope key +`7f5f4426ef796683c485`, priority revision 2. Catalogue SHA-256 +`89b5928bc92c81e2647b6d702a7ad85a6795b8f44e2eb267b2ff3145d8f2b2ce`. +Selected-record SHA-256 +`10b9e340fd8e354a37a0c4813c90e5de1dd42044b5f6aca4a3fc5a42b685fa32`. +The required baseline command returned `PASS_RESEARCH_BASELINE_BINDINGS_ONLY`. diff --git a/research/c4-semidirect-central-floor/SOURCES.json b/research/c4-semidirect-central-floor/SOURCES.json new file mode 100644 index 0000000000..e3b8c42512 --- /dev/null +++ b/research/c4-semidirect-central-floor/SOURCES.json @@ -0,0 +1,18 @@ +{ + "schema_version": 1, + "base_commit": "ef5f39403610d231a0c27e8a6b4f08c10209bcd6", + "files": [ + { + "path": "certificates/selected-result.json", + "usage": "provenance", + "git_blob": "ae6a99f28aa468649eb66a361e764d769363bf11", + "sha256": "10b9e340fd8e354a37a0c4813c90e5de1dd42044b5f6aca4a3fc5a42b685fa32" + }, + { + "path": "docs/research/agent-task-catalog.json", + "usage": "provenance", + "git_blob": "c05fafaa25ba95b2d5af86b1800427198e3d20eb", + "sha256": "89b5928bc92c81e2647b6d702a7ad85a6795b8f44e2eb267b2ff3145d8f2b2ce" + } + ] +} diff --git a/research/c4-semidirect-central-floor/TASK.json b/research/c4-semidirect-central-floor/TASK.json new file mode 100644 index 0000000000..273849dfc8 --- /dev/null +++ b/research/c4-semidirect-central-floor/TASK.json @@ -0,0 +1,66 @@ +{ + "schema_version": 1, + "task_id": "bit-new-register-model", + "priority_revision": 2, + "base_commit": "ef5f39403610d231a0c27e8a6b4f08c10209bcd6", + "original_card": { + "id": "bit-new-register-model", + "question": "Can an explicitly larger or nonabelian register model escape the scoped three-register obstruction?", + "starting_points": [ + "research/bit-three-register-lower-bound/README.md", + "certificates/selected-result.json" + ], + "methods": [ + "Construct the smallest exact identity in a newly specified legal operation alphabet.", + "Derive a target-specific relaxation bound for that same explicit alphabet." + ], + "acceptance_gate": "Name the changed hypothesis, all counted roles and a uniform identity; finite column tests alone do not certify it.", + "first_step": { + "command": [ + "python3", + "-B", + "scripts/research_baseline.py" + ], + "expected": "PASS_RESEARCH_BASELINE_BINDINGS_ONLY", + "inspect": "Read Model section 1 of the three-register bound and identify the exact excluded free operations.", + "scope": "Positive control / source-bound orientation only; this is not a new construction or full cold replay." + }, + "first_experiment": "Write a complete operation alphabet outside one named premise and price a baseline before searching identities.", + "bottleneck_link": "A small-width completed saving changes the bit primitive; a finite-address identity alone is insufficient.", + "allowed_variations": [ + "Choose a fixed larger register count or a specified nonabelian operation family." + ], + "scope_change_triggers": [ + "Remaining inside the already-excluded three-register abelian model." + ], + "overlap_terms": [ + "three-register", + "nonabelian", + "group algebra" + ] + }, + "scope_status": "refined", + "actual_scope": "In F2[C4 rtimes C4] on exactly three roles, with paid labels {a,b} and all 28 binary rank-one idempotents, the coordinate bills of diag([a^2]^3), diag([b^2]^3), diag([a^2],[b^2],[a^2 b^2]) and diag([a^2 b^2],[a^2 b^2],[e]) are sharp at 6, 6, 8 and 8. diag([a^2 b^2]^3) has no word of length at most 6 and a coordinate upper bound of 12.", + "scope_change_reason": "The catalogue card allows one specified nonabelian operation family. This package fixes that family as the unique nonabelian semidirect product C4 rtimes C4, with paid labels exactly {a,b}, three roles, and the central diagonal targets named in actual_scope. It is outside the excluded abelian three-register model.", + "bottleneck_link": "Side result. These finite group-algebra identities do not change either primitive supplier, the selected histogram, or kappa. The complex primitive still binds. Role mixing does not undercut the proved central coordinate bills, so this alphabet does not supply the missing bit-budget factor.", + "budget_minutes": 60, + "stop_rule": "Stop after the exact length-6 screen and the coordinate witnesses. Do not build the length-4 or length-8 frontiers, and do not search a fourth role.", + "outcome": "completed", + "evidence": "PASS_C4_SEMIDIRECT_CENTRAL a2=6 b2=6 mixed=8 two_z=8 z_through6=0 z_upper=12 one_z=4 idempotents=28 calls=56 frontier2=2352 frontier3=92374 center=4 derived=2 involutions=3 aug=15,13,10,6,3,1,0 controls=8 no_new_kappa", + "author": "AI-assisted contributor", + "ai_assistance": "Substantial AI assistance was used to build the semidirect-product multiplication table, search the paid words, and write the package notes.", + "open_questions": [ + { + "question": "Is the minimum of diag([a^2 b^2],[a^2 b^2],[a^2 b^2]) equal to 8, 10, or 12?", + "why_it_matters": "A value below 12 would be a real mixing saving against the coordinate bill in this alphabet.", + "first_step": "Store one length-4 word for each length-4 matrix and test the target by inverse meet-in-the-middle. The symbol lemma already excludes odd lengths.", + "blocked_by": "This package stores only the length-2 and length-3 frontiers. The length-6 screen splits words as 3+3 and does not decide lengths 8, 10, or 12." + }, + { + "question": "Can one restored fourth role realize diag([a^2 b^2]^3, [e]) in fewer than 12 calls?", + "why_it_matters": "A scratch role outside the three output roles is the usual route past a three-role central bill.", + "first_step": "Shortest-path search on the four-role alphabet of both generators and every rank-one idempotent in M_4(F_2).", + "blocked_by": "That alphabet was not built. The search is also premature until the three-role minimum of the same product is known." + } + ] +} diff --git a/research/c4-semidirect-central-floor/VERIFY.json b/research/c4-semidirect-central-floor/VERIFY.json new file mode 100644 index 0000000000..75f156f0df --- /dev/null +++ b/research/c4-semidirect-central-floor/VERIFY.json @@ -0,0 +1,18 @@ +{ + "schema_version": 1, + "status": "ready", + "python": "3.12", + "requirements": [], + "inputs": [], + "commands": [ + { + "argv": [ + "{python}", + "-B", + "{package}/verify.py" + ], + "timeout_seconds": 180, + "checks": "Group law, center, derived subgroup, augmentation classes, 28 idempotents, 56-call inverses and associativity, length-2 and length-3 symbol congruence, exact frontiers 2352 and 92374, sharp central minima 6, 6, 8 and 8, product absence through length 6, coordinate witnesses, and eight failure controls." + } + ] +} diff --git a/research/c4-semidirect-central-floor/verify.py b/research/c4-semidirect-central-floor/verify.py new file mode 100644 index 0000000000..f80c04b765 --- /dev/null +++ b/research/c4-semidirect-central-floor/verify.py @@ -0,0 +1,563 @@ +#!/usr/bin/env python3 +"""Exact three-role central bills in F2[C4 rtimes C4]. + +G = . +Elements are a^i b^j = i + 4j for i, j in {0,1,2,3}. +Paid calls are D_N([g]) = I + ([g] + [e]) N for g in {a, b} and every +rank-one idempotent N in M_3(F_2). Equality is in M_3(F_2[G]). + +Coordinate bills and proved minima: + + diag([a^2]^3) 6 + diag([b^2]^3) 6 + diag([a^2], [b^2], [a^2 b^2]) 8 + diag([a^2 b^2], [a^2 b^2], [e]) 8 + +diag([a^2 b^2]^3) has no word of length at most 6. The coordinate word +has length 12, and the symbol lemma excludes every odd length, so its +minimum lies in {8, 10, 12}. +""" + +from __future__ import annotations + + +def fail(message: str) -> None: + raise SystemExit("FAIL_C4_SEMIDIRECT_CENTRAL " + message) + + +def mul_semidirect(left: int, right: int) -> int: + li, lj = left & 3, left >> 2 + ri, rj = right & 3, right >> 2 + sign = 1 if (lj & 1) == 0 else -1 + ni = (li + sign * ri) & 3 + nj = (lj + rj) & 3 + return ni | (nj << 2) + + +def mul_direct(left: int, right: int) -> int: + """Control law: the same encoding with trivial action, so ab = ba.""" + li, lj = left & 3, left >> 2 + ri, rj = right & 3, right >> 2 + return ((li + ri) & 3) | (((lj + rj) & 3) << 2) + + +def invert_table(multiply) -> dict[int, int]: + table = {} + for element in range(16): + hits = [other for other in range(16) if multiply(element, other) == 0] + if len(hits) != 1: + fail(f"element {element} does not have a unique right inverse") + table[element] = hits[0] + return table + + +def algebra(multiply): + table = [[multiply(left, right) for right in range(16)] for left in range(16)] + + def amul(left: int, right: int) -> int: + result = 0 + remaining = left + while remaining: + i = (remaining & -remaining).bit_length() - 1 + remaining ^= 1 << i + row = table[i] + scan = right + while scan: + j = (scan & -scan).bit_length() - 1 + scan ^= 1 << j + result ^= 1 << row[j] + return result + + return amul + + +def pack(entries: list[int]) -> int: + value = 0 + for index, entry in enumerate(entries): + value |= (entry & 0xFFFF) << (16 * index) + return value + + +def unpack(value: int) -> list[int]: + return [(value >> (16 * index)) & 0xFFFF for index in range(9)] + + +def make_matmul(amul): + def matmul(left: int, right: int) -> int: + left_entries = unpack(left) + right_entries = unpack(right) + out = [0] * 9 + for i in range(3): + row0 = left_entries[3 * i] + row1 = left_entries[3 * i + 1] + row2 = left_entries[3 * i + 2] + for j in range(3): + acc = amul(row0, right_entries[j]) + acc ^= amul(row1, right_entries[3 + j]) + acc ^= amul(row2, right_entries[6 + j]) + out[3 * i + j] = acc + return pack(out) + + return matmul + + +def identity() -> int: + return pack([1 if i == j else 0 for i in range(3) for j in range(3)]) + + +def make_call(label: int, projector: int) -> int: + coeff = (1 << label) ^ 1 + entries = [1 if i == j else 0 for i in range(3) for j in range(3)] + for pos in range(9): + if (projector >> pos) & 1: + entries[pos] ^= coeff + return pack(entries) + + +def f2_mul(left: int, right: int) -> int: + out = 0 + for i in range(3): + for j in range(3): + bit = 0 + for k in range(3): + bit ^= ((left >> (3 * i + k)) & 1) & ((right >> (3 * k + j)) & 1) + out |= bit << (3 * i + j) + return out + + +def f2_rank(matrix: int) -> int: + rows = [(matrix >> (3 * i)) & 7 for i in range(3)] + rank = 0 + for col in range(3): + pivot = next((r for r in range(rank, 3) if (rows[r] >> col) & 1), None) + if pivot is None: + continue + rows[rank], rows[pivot] = rows[pivot], rows[rank] + for r in range(3): + if r != rank and (rows[r] >> col) & 1: + rows[r] ^= rows[rank] + rank += 1 + return rank + + +def f2_trace(matrix: int) -> int: + return ((matrix >> 0) & 1) ^ ((matrix >> 4) & 1) ^ ((matrix >> 8) & 1) + + +def projector_bit(role: int) -> int: + return 1 << (4 * role) + + +def rref(vectors: list[int]) -> list[int]: + rows: list[int] = [] + for vector in vectors: + reduced = vector + for row in rows: + pivot = row.bit_length() - 1 + if (reduced >> pivot) & 1: + reduced ^= row + if reduced: + pivot = reduced.bit_length() - 1 + rows = [row ^ reduced if (row >> pivot) & 1 else row for row in rows] + rows.append(reduced) + return rows + + +def in_space(rows: list[int], vector: int) -> bool: + reduced = vector + for row in rows: + pivot = row.bit_length() - 1 + if (reduced >> pivot) & 1: + reduced ^= row + return reduced == 0 + + +def reduce_mod(rows: list[int], mask: int) -> int: + reduced = mask + for row in rows: + pivot = row.bit_length() - 1 + if (reduced >> pivot) & 1: + reduced ^= row + return reduced + + +def element_order(multiply, element: int) -> int: + seen = 0 + for order in range(1, 17): + seen = multiply(seen, element) + if seen == 0: + return order + fail("an element order did not close") + return 0 + + +def chain(multiply, labels: tuple[int, ...]) -> int: + acc = 0 + for label in labels: + acc = multiply(acc, label) + return acc + + +def target_matrix(diags: tuple[int, int, int]) -> int: + entries = [1 if i == j else 0 for i in range(3) for j in range(3)] + for role, element in enumerate(diags): + entries[3 * role + role] = 1 << element + return pack(entries) + + +def coordinate_word(spec: tuple[tuple[int, tuple[int, ...]], ...]) -> list[tuple[int, int]]: + word = [] + for role, labels in spec: + projector = projector_bit(role) + word.extend((label, projector) for label in labels) + return word + + +def realize(matmul, factors_of, word: list[tuple[int, int]], eye: int) -> int: + state = eye + for label, projector in word: + state = matmul(state, factors_of(label, projector)) + return state + + +def apply_state(amul, matrix: int, vector: tuple[int, int, int]) -> tuple[int, int, int]: + entries = unpack(matrix) + out = [] + for row in range(3): + acc = 0 + for col in range(3): + acc ^= amul(entries[3 * row + col], vector[col]) + out.append(acc) + return tuple(out) + + +def idempotents() -> list[int]: + found = [] + for column in range(1, 8): + for row in range(1, 8): + if bin(column & row).count("1") & 1: + matrix = 0 + for i in range(3): + for j in range(3): + if (column >> i) & 1 and (row >> j) & 1: + matrix |= 1 << (3 * i + j) + found.append(matrix) + found = sorted(set(found)) + if len(found) != 28: + fail(f"expected 28 rank-one idempotents, found {len(found)}") + scanned = [] + for matrix in range(512): + if f2_mul(matrix, matrix) == matrix and f2_rank(matrix) == 1: + scanned.append(matrix) + if scanned != found: + fail("rank-one idempotent scan does not match the xy^T list") + for matrix in found: + if f2_trace(matrix) != 1: + fail("a rank-one idempotent has trace other than 1") + rank_two = projector_bit(0) | projector_bit(1) + if f2_rank(rank_two) != 2 or rank_two in found: + fail("rank-2 matrix E00+E11 was admitted as a rank-one idempotent") + return found + + +def augmentation_powers(amul) -> list[list[int]]: + delta = rref([(1 << element) ^ 1 for element in range(1, 16)]) + if len(delta) != 15: + fail("augmentation ideal is not 15-dimensional") + powers = [delta] + current = delta + for _ in range(8): + products = [amul(left, right) for left in current for right in delta] + current = rref(products) + powers.append(current) + if not current: + break + dims = [len(rows) for rows in powers] + if dims != [15, 13, 10, 6, 3, 1, 0]: + fail(f"unexpected augmentation dimensions {dims}") + return powers + + +def check_group(multiply, inverses: dict[int, int]) -> None: + for left in range(16): + for right in range(16): + for third in range(16): + if multiply(multiply(left, right), third) != multiply(left, multiply(right, third)): + fail("multiplication is not associative") + if multiply(0, left) != left or multiply(left, 0) != left: + fail("identity element is not e") + if multiply(left, inverses[left]) != 0 or multiply(inverses[left], left) != 0: + fail("recorded inverse is not two-sided") + + +def shortest(matmul, inverse_factors, factors, layer2, layer3, target: int, limit: int): + if target in factors: + return 1 + if limit >= 2 and target in layer2: + return 2 + if limit >= 3 and target in layer3: + return 3 + + def hit(words, pool) -> bool: + for word in words: + state = target + for index in word: + state = matmul(inverse_factors[index], state) + if state in pool: + return True + return False + + if limit >= 4 and hit(layer2.values(), layer2): + return 4 + if limit >= 5 and hit(layer2.values(), layer3): + return 5 + if limit >= 6 and hit(layer3.values(), layer3): + return 6 + return None + + +def main() -> None: + A, B = 1, 4 + A2, A3 = 2, 3 + B2, B3 = 8, 12 + Z = 10 # a^2 b^2 + inverses = invert_table(mul_semidirect) + check_group(mul_semidirect, inverses) + if mul_semidirect(mul_semidirect(B, A), inverses[B]) != A3: + fail("b a b^{-1} is not a^{-1}") + if mul_semidirect(A, B) == mul_semidirect(B, A): + fail("the address group collapsed to an abelian group") + if B2 == A2 or mul_semidirect(B, B) != B2: + fail("b^2 collapsed onto a^2") + orders = [element_order(mul_semidirect, element) for element in range(16)] + histogram: dict[int, int] = {} + for order in orders: + histogram[order] = histogram.get(order, 0) + 1 + if histogram != {1: 1, 2: 3, 4: 12}: + fail(f"order histogram is {histogram}, not exponent 4 with three involutions") + if orders[A] != 4 or orders[B] != 4 or orders[A2] != 2 or orders[B2] != 2 or orders[Z] != 2: + fail("generator or central orders do not match C4 rtimes C4") + center = [ + element + for element in range(16) + if all(mul_semidirect(element, other) == mul_semidirect(other, element) for other in range(16)) + ] + if center != [0, A2, B2, Z]: + fail(f"center is {center}, not {{e, a^2, b^2, a^2 b^2}}") + commutators = { + mul_semidirect(mul_semidirect(mul_semidirect(left, right), inverses[left]), inverses[right]) + for left in range(16) + for right in range(16) + } + if commutators != {0, A2}: + fail(f"derived subgroup is {sorted(commutators)}, not {{e, a^2}}") + if B2 in commutators: + fail("b^2 lies in the derived subgroup, so this is not the stated semidirect product") + reachable = {0} + frontier = [0] + while frontier: + current = frontier.pop() + for step in (A, B): + nxt = mul_semidirect(current, step) + if nxt not in reachable: + reachable.add(nxt) + frontier.append(nxt) + if reachable != set(range(16)): + fail("generators a and b do not exhaust the encoded group") + lengths = {0: 0} + queue = [0] + while queue: + current = queue.pop(0) + for step in (A, B): + nxt = mul_semidirect(current, step) + if nxt not in lengths: + lengths[nxt] = lengths[current] + 1 + queue.append(nxt) + if (lengths[A], lengths[B], lengths[A2], lengths[B2], lengths[Z]) != (1, 1, 2, 2, 4): + fail( + "generator lengths are " + f"{lengths[A], lengths[B], lengths[A2], lengths[B2], lengths[Z]}" + ) + if chain(mul_semidirect, (A, A)) != A2 or chain(mul_semidirect, (B, B)) != B2: + fail("aa or bb missed its square") + if chain(mul_semidirect, (A, A, B, B)) != Z: + fail("aabb is not a^2 b^2") + if chain(mul_semidirect, (A, B, A, B)) != B2: + fail("abab is not b^2 in the semidirect product") + if chain(mul_direct, (A, B, A, B)) == B2: + fail("direct-product control still sends abab to b^2") + + amul = algebra(mul_semidirect) + matmul = make_matmul(amul) + powers = augmentation_powers(amul) + delta2 = powers[1] + if len(powers[0]) - len(delta2) != 2: + fail("Delta/Delta^2 is not two-dimensional") + alpha = reduce_mod(delta2, (1 << A) ^ 1) + beta = reduce_mod(delta2, (1 << B) ^ 1) + if alpha == 0 or beta == 0 or alpha == beta: + fail("[a]+1 and [b]+1 are not independent modulo Delta^2") + for element in range(16): + i, j = element & 3, element >> 2 + expected = 0 + if i & 1: + expected ^= alpha + if j & 1: + expected ^= beta + if reduce_mod(delta2, (1 << element) ^ 1) != expected: + fail(f"class of a^{i} b^{j} is not {i % 2} alpha + {j % 2} beta") + + projectors = idempotents() + eye = identity() + call_of = make_call + factors = [] + inverse_factors = [] + for label in (A, B): + for projector in projectors: + factors.append(call_of(label, projector)) + inverse_factors.append(call_of(inverses[label], projector)) + factor_set = set(factors) + if len(factors) != 56 or len(factor_set) != 56: + fail("alphabet does not have 56 distinct calls") + for index, factor in enumerate(factors): + if matmul(factor, inverse_factors[index]) != eye or matmul(inverse_factors[index], factor) != eye: + fail("a paid call is not inverted by the inverse label") + for left in factors: + left_row = [matmul(left, middle) for middle in factors] + for middle_index, left_middle in enumerate(left_row): + for right_index, right in enumerate(factors): + if matmul(left_middle, right) != matmul(left, matmul(factors[middle_index], right)): + fail("paid-call multiplication is not associative") + + layer2: dict[int, tuple[int, int]] = {} + for i, left in enumerate(factors): + deviation_left = left ^ eye + for entry in unpack(deviation_left): + if not in_space(powers[0], entry): + fail("a paid deviation leaves the augmentation ideal") + for j, right in enumerate(factors): + product = matmul(left, right) + residue = product ^ eye ^ deviation_left ^ (right ^ eye) + for entry in unpack(residue): + if not in_space(delta2, entry): + fail("a two-call symbol residue leaves Delta^2") + layer2.setdefault(product, (i, j)) + layer3: dict[int, tuple[int, int, int]] = {} + for prefix, (i, j) in layer2.items(): + for k, factor in enumerate(factors): + layer3.setdefault(matmul(prefix, factor), (i, j, k)) + if (len(layer2), len(layer3)) != (2352, 92374): + fail(f"unexpected frontier sizes {len(layer2)}, {len(layer3)}") + for matrix, (i, j, k) in layer3.items(): + residue = matrix ^ eye ^ (factors[i] ^ eye) ^ (factors[j] ^ eye) ^ (factors[k] ^ eye) + for entry in unpack(residue): + if not in_space(delta2, entry): + fail("a three-call symbol residue leaves Delta^2") + + def length_of(diags: tuple[int, int, int], limit: int = 6): + return shortest( + matmul, + inverse_factors, + factor_set, + layer2, + layer3, + target_matrix(diags), + limit, + ) + + expected = { + "a": ((A, A, A), 3), + "b": ((B, B, B), 3), + "a2": ((A2, A2, A2), 6), + "b2": ((B2, B2, B2), 6), + "one_a2": ((A2, 0, 0), 2), + "one_b2": ((B2, 0, 0), 2), + "one_z": ((Z, 0, 0), 4), + "two_a2": ((A2, A2, 0), 4), + "mixed": ((A2, B2, Z), 8), + "two_z": ((Z, Z, 0), 8), + "z": ((Z, Z, Z), 12), + } + found = {} + for name, (diags, _upper) in expected.items(): + found[name] = length_of(diags) + for name in ("a", "b", "a2", "b2", "one_a2", "one_b2", "one_z", "two_a2"): + if found[name] != expected[name][1]: + fail(f"{name} minimum is {found[name]}, not {expected[name][1]}") + for name in ("mixed", "two_z", "z"): + if found[name] is not None: + fail(f"{name} appeared by length 6 at length {found[name]}") + residue_ok = True + matrix = target_matrix(expected[name][0]) + for entry in unpack(matrix ^ eye): + if not in_space(delta2, entry): + residue_ok = False + if not residue_ok: + fail(f"{name} is not congruent to I modulo Delta^2, so odd lengths remain possible") + + words = { + "a2": coordinate_word(((0, (A, A)), (1, (A, A)), (2, (A, A)))), + "b2": coordinate_word(((0, (B, B)), (1, (B, B)), (2, (B, B)))), + "mixed": coordinate_word(((0, (A, A)), (1, (B, B)), (2, (A, A, B, B)))), + "two_z": coordinate_word(((0, (A, A, B, B)), (1, (A, A, B, B)))), + "z": coordinate_word(((0, (A, A, B, B)), (1, (A, A, B, B)), (2, (A, A, B, B)))), + "abab": coordinate_word(((0, (A, B, A, B)), (1, (A, B, A, B)), (2, (A, B, A, B)))), + } + if len(words["a2"]) != 6 or realize(matmul, call_of, words["a2"], eye) != target_matrix((A2, A2, A2)): + fail("coordinate aa word missed diag([a^2]^3)") + if len(words["b2"]) != 6 or realize(matmul, call_of, words["b2"], eye) != target_matrix((B2, B2, B2)): + fail("coordinate bb word missed diag([b^2]^3)") + if len(words["mixed"]) != 8 or realize(matmul, call_of, words["mixed"], eye) != target_matrix((A2, B2, Z)): + fail("coordinate mixed word missed diag([a^2],[b^2],[a^2 b^2])") + if len(words["two_z"]) != 8 or realize(matmul, call_of, words["two_z"], eye) != target_matrix((Z, Z, 0)): + fail("coordinate two-role word missed diag([a^2 b^2],[a^2 b^2],[e])") + if len(words["z"]) != 12 or realize(matmul, call_of, words["z"], eye) != target_matrix((Z, Z, Z)): + fail("coordinate aabb word missed diag([a^2 b^2]^3)") + if realize(matmul, call_of, words["abab"], eye) != target_matrix((B2, B2, B2)): + fail("semidirect abab word missed diag([b^2]^3)") + + direct_matmul = make_matmul(algebra(mul_direct)) + if realize(direct_matmul, call_of, words["abab"], eye) == target_matrix((B2, B2, B2)): + fail("direct-product multiplication still produced diag([b^2]^3)") + + dirty = ((1 << 5) ^ (1 << B), 1 << A2, (1 << Z) ^ (1 << A3)) + triple = target_matrix((A2, A2, A2)) + witnessed = realize(matmul, call_of, words["a2"], eye) + image = apply_state(amul, triple, dirty) + if apply_state(amul, witnessed, dirty) != image: + fail("a^2 witness disagrees on the dirty state") + expected_coordinates = tuple(amul(1 << A2, coordinate) for coordinate in dirty) + if image != expected_coordinates: + fail("diagonal a^2 action does not match left multiplication") + dropped = realize(matmul, call_of, words["a2"][:-1], eye) + if dropped == triple or apply_state(amul, dropped, dirty) == image: + fail("dropped call still matched diag([a^2]^3)") + swapped = list(words["a2"]) + swapped[0] = (B, swapped[0][1]) + if realize(matmul, call_of, swapped, eye) == triple: + fail("replacing the first a call by b still matched diag([a^2]^3)") + + synthetic = [(B, 3), (A, 24), (B, 65), (A, 146), (B, 260)] + if any(projector not in projectors for _, projector in synthetic): + fail("synthetic word uses a projector outside the alphabet") + synthetic_matrix = realize(matmul, call_of, synthetic, eye) + synthetic_length = shortest( + matmul, inverse_factors, factor_set, layer2, layer3, synthetic_matrix, 6 + ) + if synthetic_length != 5: + fail(f"synthetic five-call matrix was found at length {synthetic_length}") + + controls = 8 + print( + "PASS_C4_SEMIDIRECT_CENTRAL " + "a2=6 b2=6 mixed=8 two_z=8 z_through6=0 z_upper=12 " + "one_z=4 idempotents=28 calls=56 " + f"frontier2={len(layer2)} frontier3={len(layer3)} " + "center=4 derived=2 involutions=3 " + "aug=15,13,10,6,3,1,0 " + f"controls={controls} no_new_kappa" + ) + + +if __name__ == "__main__": + main()