diff --git a/.github/workflows/kappa-ceiling.yml b/.github/workflows/kappa-ceiling.yml new file mode 100644 index 0000000000..fb684679bb --- /dev/null +++ b/.github/workflows/kappa-ceiling.yml @@ -0,0 +1,28 @@ +name: kappa-ceiling + +on: + pull_request: + paths: + - 'research/kappa-ceiling/**' + - 'scripts/paired_cube/**' + - 'scripts/paired_cube_*.py' + - 'certificates/paired-cube-*.json' + - '.github/workflows/kappa-ceiling.yml' + push: + paths: + - 'research/kappa-ceiling/**' + - '.github/workflows/kappa-ceiling.yml' + +jobs: + ceiling: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ['3.11', '3.14'] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Ceilings on this tree, compared with expected.json + run: python3 research/kappa-ceiling/ceiling.py --check diff --git a/research/kappa-ceiling/NOTICE b/research/kappa-ceiling/NOTICE new file mode 100644 index 0000000000..a3c70ce232 --- /dev/null +++ b/research/kappa-ceiling/NOTICE @@ -0,0 +1,9 @@ +A κ ceiling for #168's word +Copyright 2026 DaysSky. Licensed under the Apache License, Version 2.0. + +Prepared by DaysSky with Anthropic Claude assistance. + +baseline-pr168.tar.gz contains 48 unmodified files of PR #168 at commit 4a3c769 (scripts, frozen data and +certificates), under their original licenses and notices; SOURCE.json pins each one. ceiling.py imports and runs, +unchanged, the paired-cube producer, compiler and physical-layer scripts among them (icekylinx, PR #144 and #130; eumemic, PR #155 lineage). The closed form of the assembly ceiling G is +romainhedouin's PR #183. Their notices and licenses are retained. diff --git a/research/kappa-ceiling/PROOF.md b/research/kappa-ceiling/PROOF.md new file mode 100644 index 0000000000..4a116b61a2 --- /dev/null +++ b/research/kappa-ceiling/PROOF.md @@ -0,0 +1,152 @@ +# Ceilings on κ for the paired-cube constructions + +This note proves upper bounds on the κ that a paired-cube construction can certify, and applies them to #168's word. It +makes no κ claim and changes no existing file. `ceiling.py` evaluates every bound on #168's pinned files and checks its +own model against #168's certified ledger. + +## Setting + +A shared-core supplier is described per group vertex by `W` roles, `m = 3h`, and a multiset of children: `n_r` +children of width `r`, with `0 < r < m` (`notes/paired-cube-sharing.tex`). Its rank mass telescopes: + + Σ_r r·n_r = W·m − D, D = 2v − 3ℓ, + +where `v` is the number of ports and `ℓ` the copied-centre loss. Write `f(r) = r·ln(m/r)` and + + C = Σ_r n_r·f(r). + +The repository certifies a saving `a` when an exact rational upper bound on the moment `Σ n_r·(r/(W·m))·(m/r)^a` is +below 1 (`exact_moment` in `scripts/paired_cube_network.py`). The bit side also adds a positive bad-class term +before testing. `exp_upper` and `log_upper` bound `exp` and `ln` from above, so a certified `a` always satisfies + + Σ_r n_r·r·(m/r)^a < W·m. (∗) + +## Fact 1: the moment root + +**Lemma 1.** If (∗) holds then `a < D / C`. + +*Proof.* For `0 < r < m` we have `ln(m/r) > 0`. Since `e^x ≥ 1 + x`, each term satisfies +`(m/r)^a ≥ 1 + a·ln(m/r)`. Summing gives `W·m > Σ n_r·r + a·Σ n_r·r·ln(m/r) = W·m − D + a·C`. ∎ + +The bound is nearly tight. Let `a*` be the root of the moment. Using `e^x ≤ 1 + x + (x²/2)·e^x` for `x ≥ 0`, +`a* ≥ D / (C + (a*/2)·Σ n_r·r·ln²(m/r)·(m/r)^{a*})`. The relative gap is at most about `a*·ln(m)/2`, which is +below 0.2% here. On #168 the certified complex and bit savings lie within 0.09% of `D/C` (`ceiling.py`, fact 1). + +## Fact 2: the assembly + +PR #183 (romainhedouin) gives the assembly's acceptance region in closed form. From a supplier saving `a`, with +`q = a(1 − 2η)`, the assembly accepts κ exactly below + + G(a) = q(1 − η) / (1 + q) (balanced prefix, scripts/paired_cube_assembly.py), + G(a) = q(1 − η) / (1 + q(2 + η)) (original prefix, scripts/structured_bulk_assembly.py). + +Both assemblies pass `a = min(AB, (1 − β)·AC − 10⁻¹⁰)`. Here `AB = (1 − ATOM)·COARSE + ATOM·OLD` is at most +`COARSE` (since `OLD < COARSE`), and `(1 − β)·AC − 10⁻¹⁰ < AC`. `G` is increasing in `a`, and `G(a) < a`. Hence + + κ < G(min(COARSE, AC)) < G(D_c / C_c), + +where `D_c`, `C_c` belong to the complex supplier. The bit supplier gives the same bound with its own `D`, `C`. + +## Fact 3: chains + +`f` is strictly concave on `(0, m)` and tends to 0 at 0, so it is strictly subadditive: `f(x) + f(y) > f(x + y)`. + +**Lemma 3.** A chain of positive steps `r_1, …, r_k` with total `t` costs at least `f(t)`. If `k ≥ 2`, it costs at least +`f(1) + f(t − 1)`. + +*Proof.* Merging steps lowers the cost, so `Σ f(r_i) ≥ f(r_1) + f(t − r_1)`. The function `x ↦ f(x) + f(t − x)` is +concave and symmetric about `t/2`, so its minimum over `[1, t − 1]` is at the endpoints. ∎ + +## Fact 4: the frame floor of a fixed word + +The complex child multiset consists of: + +- three times the steps of every physical slot's frame chain; +- three times the source and target data steps; +- three copy children per copied centre, each of width equal to the centre's frame dimension; +- one child of width `3·dim σ` per gauged first occupant; +- `2v` children of width 2. + +`ceiling.py` rebuilds this multiset from the tree's own word and frames, and checks that it equals the certified +complex ledger child for child. + +Fix the word: its DAG, the operations and their order, the carrier arcs, the gauges, the reuse pairs and the +terminal sinks. The free choice is the frame `F_i` of each operation. A layout is admissible when the conditions +checked by `scripts/paired_cube/verify.py` and `scripts/paired_cube_physical.py` hold: + +- `span(x_i) ⊆ F_i`; +- both registers of operation `i` are at `F_i` while it runs; +- every slot's frames nest along its chain: entrance (0, the source line, or the gauge), its operations' frames, + its root frame, then the full space; +- a recipient is read exactly at its gauge `σ`, after its donor's last operation. + +**Lemma 4a (joint bounds).** Every admissible layout has `L_i ⊆ F_i ⊆ U_i`, where: + +- `L_i = span(x_i) + L_{prev(a)} + L_{prev(b)}`, computed forward in operation order from the registers' entrance + frames; +- `U_i = up(x_i) ∩ U_{next(a)} ∩ U_{next(b)}`, computed backward from root frames, recipients' gauges (for + donors) and the full space; +- `up(x)` is the full backward intersection of `x`, i.e. the intersection of the root frames reachable from `x` + through DAG and carrier edges. + +Registers of terminal sinks are skipped in both passes, since the certified word deletes them. + +*Proof.* By induction along the operation order. Both registers sit at `F_i`, so `F_i` contains everything below +either register's previous frame and lies inside everything above either register's next frame. `F_i ⊆ up(x_i)` +because every root reachable from `x_i` is read from a nested register chain that passes through `F_i`. For a DAG +edge, the register holding the operand, or the copy that carries it, takes part in the consuming operation. For a +carrier arc, the control register continues to the use. A root is read at its root frame. ∎ + +`ceiling.py` checks that the tree's own layout satisfies every `L_i ⊆ F_i ⊆ U_i`. + +**Lemma 4b (slot floor).** For each slot, take the minimum of `Σ f(steps)` over nondecreasing dimension sequences +with `d_i ∈ [dim(cumulative L), dim(cumulative U)]` at each event. Exact events (source line, recipient gauge, root +read) are fixed. The sequence starts at the entrance dimension and ends at `h`. The sum of these minima over the +slots is at most the slots' actual cost in every admissible layout. + +*Proof.* An admissible layout gives each slot such a sequence. The only constraints dropped are that the two +registers of an operation share one dimension, and that the frames are subspaces rather than dimensions. ∎ + +**Lemma 4c (coupling).** For any prices `λ_i`, add `+λ_i·d` to the dest register's cost at operation `i` and +`−λ_i·d` to the control register's cost. The sum of the per-slot minima is still a lower bound. + +*Proof.* In an admissible layout both registers have `d = dim F_i`, so the price terms cancel. Weak duality. ∎ + +`ceiling.py` runs 400 rounds of integer subgradient steps on the prices. It keeps the best round, and asserts that +every round's total lies below the tree layout's own cost. + +The data banks climb a total rank of `h − 1` per bank and stage, so they cost at least `6v·f(h − 1)` (Lemma 3). The +copy, gauge and endpoint children are fixed. Write `C_floor` for the sum of all these floors. Then +`C ≥ C_floor` for every admissible layout of the word, and + + κ < G(D / C_floor). + +For terminal sinks, the certified record removes exactly one child of width `r` and one of width `h − r` per sink and +stage (`research/terminal-sinks`, re-checked by `ceiling.py`). The other slots' chains are unchanged: an operation +that writes a sink keeps its control register at `F_i`. The target registers still climb `h − 1`. + +**Arithmetic.** Every logarithm is replaced by a rational lower bound: partial sums of `2·atanh`, whose terms are all +nonnegative. Every `f(r)` is rounded down to the grid `2^-64`, and all sums and the dynamic programme use exact +integers. So `C_floor` is a true lower bound, and the printed ceiling is rounded up. + +## Fact 5: design ceilings + +These keep the tree's `h`, `v`, `ℓ` and decoder, but allow any word. + +- **No auxiliary registers:** `κ < G(D / (6v·f(h−1) + 2v·f(2) + copies))`. +- **No centre loss either:** take `D = 2v` and drop the copies. +- **One register per source:** in every construction in this repository, each source is injected into its own + register before the first addition (the transparent word `z ← z + Vx`, then `z ← Mz`). Those `v` slots are + simultaneously live, so they are distinct. Each needs at least two steps (its source line, then the full space), + so it costs at least `3·(f(1) + f(h − 1))` (Lemma 3). + +These are far from tight. They show where the room is: either in the numerator `D`, or in the auxiliary registers, +which carry about 85% of the cost in every certified word. + +## Scope + +- **Facts 1–3** are proved here and are elementary. +- **Fact 4** is a statement about one fixed word, varying only its operation frames. It does not bound constructions + that change the circuit, the gauges, the reuse pairs or the sinks. +- **Fact 5** bounds whole designs under the stated hypotheses. +- Nothing here is machine-checked beyond `ceiling.py`'s exact arithmetic and self-tests. diff --git a/research/kappa-ceiling/README.md b/research/kappa-ceiling/README.md new file mode 100644 index 0000000000..0e928335fa --- /dev/null +++ b/research/kappa-ceiling/README.md @@ -0,0 +1,78 @@ +# A κ ceiling for #168's word + +This package proves that no choice of operation frames for #168's complex word (eumemic, `4a3c769`) can certify more +than + + κ < 7010/10^7 = 7.010e-4. + +#168's κ = 6.558894e-4 is 93.6% of that. So every frame refinement still possible on this word is worth at most +**+6.9%** in total. Going further needs a different circuit, different reuse or gauges, or a different design. + +The package makes no κ claim and changes no existing file. #168's word is not on main, so the package carries the 48 +files of #168 it reads as a pinned archive (`baseline-pr168.tar.gz`, 0.6 MB), following the convention of +`research/coordinated-frames-and-entrance-banks`. + +## What is bounded, and what is not + +The ceiling covers one word. Its DAG, operations, carrier arcs, 2,310 gauges and reuse pairs, and 47 terminal sinks +stay fixed, and only the operation frames vary. It does not bound constructions that change any of those, which is +how every large past gain was made. + +## The argument + +For a shared-core supplier with deficit `D = 2v − 3ℓ` and cost `C = Σ n_r·r·ln(m/r)` over its children: + +1. **Every certified saving satisfies `a < D/C`,** from `e^x ≥ 1 + x`. On #168 the certified savings sit within + 0.09% of `D/C`. +2. **κ is below the assembly's ceiling `G`** of each supplier's saving, using PR #183's closed form. +3. **Splitting a frame climb into more steps only costs more,** because `r·ln(m/r)` is subadditive. +4. **No frame layout can push `C` below a floor `C_floor`.** The floor comes from three ingredients: + - frame bounds propagated jointly through both registers of every operation; + - an exact per-register dynamic programme over frame dimensions; + - Lagrangian coupling of the two registers of each operation (400 integer rounds; each round is a valid floor). + + So `κ < G(D/C_floor)`. + +[PROOF.md](PROOF.md) has the statements and proofs. + +| Floor used | Ceiling on κ | +|---|---:| +| two steps per register (Fact 3 alone) | 1.153e-3 | +| joint bounds + per-register programme | 7.158e-4 | +| **+ Lagrangian coupling** | **7.010e-4** | + +Design ceilings for any word with #168's ports and decoder (h = 22, v = 1,320), under the hypotheses of PROOF.md: + +| Hypothesis | Ceiling on κ | +|---|---:| +| one auxiliary register per source | 4.08e-3 | +| no auxiliary registers | 6.24e-3 | +| no auxiliary registers and no centre loss | 1.25e-2 | + +## Run + + python3 research/kappa-ceiling/ceiling.py --check # stdlib only, about 50 seconds; -O is refused + +The script first rebuilds the pinned #168 files in a temporary directory. It checks the archive's sha256 and every +file's sha256 against `SOURCE.json`, whose hashes are those of commit `4a3c769`. Anyone can confirm them with +`git show 4a3c769: | sha256sum`, or rebuild the archive byte for byte with +`git archive --format=tar 4a3c769 | gzip -n -9`. It then regenerates #168's complex word with #168's own +producer, reads the frozen physical layer and the terminal sinks, computes every bound, and compares the results with +`expected.json`. `--tree PATH` runs on a full #168 checkout instead. + +It checks itself: + +- #168's own layout lies inside every derived frame bound; +- its slot chains reproduce #168's certified complex ledger child for child, and the sink record; +- every per-slot floor, and every Lagrangian round's total, lies below #168's actual cost. + +All bounds use exact integers with rounded-down logarithms, so the printed ceilings are upper bounds. + +## Credits + +- eumemic: #168, its physical-layer word and checker, and the terminal-sink gate. +- icekylinx: the paired-cube framework, compiler, gauges, shared cores and assembly (#144, #130). +- romainhedouin: the closed-form assembly ceiling (#183), used for `G`. +- jamesyc: the terminal-sink lemma (#166). + +The bounds, the frame floor and this script were prepared by DaysSky, assisted with Claude. diff --git a/research/kappa-ceiling/SOURCE.json b/research/kappa-ceiling/SOURCE.json new file mode 100644 index 0000000000..e1cdb9a48c --- /dev/null +++ b/research/kappa-ceiling/SOURCE.json @@ -0,0 +1,58 @@ +{ + "archive": "baseline-pr168.tar.gz", + "archive_sha256": "0f1fcdec3b6564db1169396846074666d81a843104492029719528c2b6ff147b", + "commit": "4a3c769e5c5430e7114c4d3e099ff34664677f17", + "files": { + "certificates/paired-cube-bit-physical-input.json": "6735294e8639da09eb3eb2234cbc5814298442a6c946b5534bcf6e48903547f3", + "certificates/paired-cube-complex-input.json": "b1898950f95a7ce618f376d14e628061b345a519e7bcac76b67d277deeec9af5", + "certificates/paired-cube-physical-input.json": "489e7e0d9c373ae592b42931ac3c467f7b95cae01c9f863fd38de617a46dbf5f", + "certificates/paired-cube-sinks-input.json": "1808a409f356fe39b7cd9c8c3cfb88579062c2e32e8eb84792c39d859bb4fbf2", + "references/paired-cube/physical/frames.json": "3681fc6ef151cdfa5453512f56f7165607ece64ccf6b71478af8c327350f5800", + "references/paired-cube/physical/pairs.json": "07c204bd2a69dd8860b7333896b9bf3fe41a326675bbe41b14e6e3795453dc1e", + "references/paired-cube/selected-module/SOURCE.json": "7689968c429109119a232c3f7631cdfa5b7a7895c61af36ee82e3229988e45ab", + "references/paired-cube/selected-module/matching-arcs.json": "46c77eb75ebcb2435595a3b6522a54e1d12d3c61eecb3b51c489edd1f78447e1", + "references/paired-cube/sources/SOURCE.json": "54c6b27f55849b5a74b16387ef361dac6943525a244046795f78605b84f90826", + "references/paired-cube/sources/local_L1.json": "1861724ee3b58ea8acb3334416fbfd9172a9ae02ad11a6f6885a4909f8692f05", + "references/paired-cube/sources/pmod_J0_full_6.0666810e-4.json": "7d1ae23430e110d32aa1cab84415abdbc918dfab6b317cb24d30756fc90c5925", + "references/paired-cube/sources/qmod_climb3u_best.json": "3f85f6b2134af8781f16229186d0d7c1de71ab4d5235244259e1b79f2e3d14ba", + "references/paired-cube/sources/tmod_TE_TD_TB3_1_1_4_full_6.0617964e-4.json": "9dcdb6f4e33410418c01f55f092862edfa7c05001ceada3974f8d1196bcd894b", + "references/three-stage-cover/pr117/LICENSE": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4", + "references/three-stage-cover/pr117/NOTICE": "a0030510cd3b5334fb113eae759aadd2c25951bd353dfb69ad9cb780ae5adf30", + "references/three-stage-cover/pr117/SOURCE.json": "74e9f4b4ae0529d21d19c159b2b50ee0923e93e0d7eb22cf208ad41dd5992275", + "references/three-stage-cover/pr117/dag.json.gz": "3c034d0aae388ef567a454826f4f48b26fd8a94c71e8ffed4835271b349a783b", + "references/three-stage-cover/pr117/replayed.py": "99c1e3694ae06a7ce7a370b04ecb45ea3fcfe69a6e6e7f4c4606e0a6c74c3184", + "research/terminal-sinks/sinks.json": "fb20a17ca5f4ffb7f46d4a53776ef643fb4183c85a0276975aae8add3ab40e27", + "scripts/audit_short_guards.py": "c2c929c956e617bee739963b517b9069e0d067cfebc6820f401a4e90a1769eb1", + "scripts/certify.py": "2348d95008276c5d5764f7e028c3918e484582cd589bd4d9b82919161745e53e", + "scripts/compact_control_layer.py": "e814c9f8d84f2e5d3aa2e06c36697f27f110218addc40ff8df2801fde861ff9f", + "scripts/complex_circuit.py": "d623617f7c5cb3189580883a8ab30c0bd3832688ae3f1f0c1f881bbbbff8e0c1", + "scripts/complex_network.py": "0da395e67ac04fa213494e71012c1b19b544596d34cdce5a3b172b1fb6af5f1b", + "scripts/exclusion_circuit.py": "b253f9d7318becda1c1f3b24e2287ed44a9c27fcea00cf9d531afb950d0457a0", + "scripts/fast_gaussian.py": "11430c2df0783b933dc91080595bb7514bb052b8c7b12107e27a1ef701e31e43", + "scripts/paired_cube/__init__.py": "b5fb17c79fd41a75c0648e7a5f99076f9f42a619857d5e34e71699bf00ef0c91", + "scripts/paired_cube/closure.py": "e4b3086f4b5663297fe06f4646e1349f10d8647997989f17eef6ec4e84aec693", + "scripts/paired_cube/frames.py": "7d897418dbfee47de0723ea4983098a982610ccba45e1bac9000a79626d8421c", + "scripts/paired_cube/gauges.py": "8a501c66e1330f054e0e11a35e9321d2deea7b51cd03ca56b6a41765e43fe061", + "scripts/paired_cube/graph.py": "d10478ba509c7d8abcefcaaf7c5e0b580b9cd9cbe7777b3e49b2fe24620985c8", + "scripts/paired_cube/modules.py": "6c134f3d24c6cd9aca03763050aaf600005c126c09b35ac0ff5f040e0e29db87", + "scripts/paired_cube/verify.py": "b1eb42a91381ae031d97879f7c43e6c00fc4787e132ed60eb3e124994bdb7d35", + "scripts/paired_cube_assembly.py": "c1ebb52163a7219504c728a5c2b098bf1e50bf20148a2879c5a74c3cadc7c03c", + "scripts/paired_cube_bit_physical.py": "2c1f8268f6bfb8026f83995376806e4ad1c2b3bb3173d05d9970e290652c98a2", + "scripts/paired_cube_network.py": "7e9dca7de0e0fa843c642ed50f97c84a2c676a1b9129ea9263e5394b145d44f7", + "scripts/paired_cube_physical.py": "9c9ea1ea29e551ab6116d7c8ca09f4703ff9e2fa102032d5055327fde07ba319", + "scripts/paired_cube_producer.py": "97efa5842c040537f05072e26b7ba1851a1965eca6ec2472e75aa452cf7a7256", + "scripts/paired_exclusion_circuit.py": "8f05f04cf06217befd6d0ee0f2d7d0c5c5eb32cfab8c9a8ca7917fd6d620b7d2", + "scripts/paired_network.py": "991223dc3c1374284c361ed017e42bf732dd971fa0bea2be2282cfb18257b3f7", + "scripts/partial_gauge_bit.py": "7d58e86061da2d73991531e9bc431589019359391adea866c61c7dd1f99ac855", + "scripts/partial_swap_network.py": "acdd81b4dea2815f8838a0c3ea8ac4968f2bc13fd7c99173473d865779096782", + "scripts/prepare_layers.py": "a6e2adc612f264b1c0e51d18ce6315de9ae5e2ae2420aa22e99a22d8bf304947", + "scripts/search_network.py": "3a4faa5037d9b8e7ca5828afac193a96b6966a8b114f14937921795b24ec4f6a", + "scripts/shared_point_circuit.py": "c9bb7a5df92d260a4e3ad532b53e26936730d7c93f7facfb0784957c1e81c8ba", + "scripts/shared_point_network.py": "a85e7f0d8c42d6a27e3846308571ca324a1824ac5c2d59e4daa5c7673c33c697", + "scripts/structured_bulk_assembly.py": "e99467e14211fba699878ed036e1e2362eb8e395dc22f9ecdcecc7fbf2cc736d", + "scripts/three_stage_cover_network.py": "7e0ad6fd1921b6905a53ce1eeaa03d3bb3a8a8bbaa0382516ea98b636f46335e" + }, + "note": "The exact files of #168 that ceiling.py reads (scripts, frozen frames, pairs, sinks, modules and certificates), byte for byte from commit 4a3c769; reproducible with: git archive --format=tar 4a3c769 | gzip -n -9", + "pr": 168, + "repository": "CrocSwap/integer-mult-bounds" +} diff --git a/research/kappa-ceiling/baseline-pr168.tar.gz b/research/kappa-ceiling/baseline-pr168.tar.gz new file mode 100644 index 0000000000..9cf23fe698 Binary files /dev/null and b/research/kappa-ceiling/baseline-pr168.tar.gz differ diff --git a/research/kappa-ceiling/ceiling.py b/research/kappa-ceiling/ceiling.py new file mode 100644 index 0000000000..428b994fa2 --- /dev/null +++ b/research/kappa-ceiling/ceiling.py @@ -0,0 +1,427 @@ +#!/usr/bin/env python3 +"""Rigorous ceilings on kappa for #168's paired-cube complex word (Python stdlib only; no -O). + +PROOF.md states and proves the four facts used here. For a shared-core profile with children n_r (0 < r < m), +W roles per vertex, deficit D = W m - sum r n_r and cost C = sum n_r r ln(m/r): + + 1. every saving a certified by the repository's moment test satisfies a < D / C; + 2. kappa < min(certified bit saving, certified complex saving); + 3. a chain of frame steps of total rank t costs at least f(t) = t ln(m/t), and at least f(1) + f(t-1) if it has + two or more steps; + 4. for a fixed complex word (its DAG, operations, carrier arcs, gauges, reuse pairs and terminal sinks), every + admissible choice of operation frames has C >= C_floor, where C_floor relaxes the coupling between the two + registers of an operation and minimises each register's chain exactly by dynamic programming. + +Hence kappa < D / C_floor for every frame layout of that word. The script + + - checks fact 1 numerically against the tree's certified bit and complex profiles (D/C next to the saving); + - computes C_floor for the tree's complex word with exact integer arithmetic: every logarithm is replaced by a + rational lower bound (partial sums of the atanh series), scaled down to the grid 2^-64, so the printed ceilings + are upper bounds, rounded up; + - prints the weaker design ceilings of PROOF.md (no auxiliary registers; no centre loss; one register per source). + +#168's word is not on main, so by default the script rebuilds the 48 files of #168 it reads (scripts, frozen frames, +pairs, sinks, modules and certificates) in a temporary directory, from baseline-pr168.tar.gz. Before use it checks +the archive's sha256 and every file's sha256 against SOURCE.json, whose hashes are those of commit 4a3c769. Nothing +outside the temporary directory is read from or written to. + +Usage: python3 research/kappa-ceiling/ceiling.py [--check] [--tree PATH] + --check compare the results with expected.json + --tree run on a full checkout instead of the archive (for example one of #168). A tree with + scripts/paired_cube_physical.py is read with its physical layer and, if present, research/terminal-sinks. +""" +import argparse +import hashlib +import importlib +import io +import tarfile +import json +import math +import sys +import tempfile +import time +from collections import defaultdict +from fractions import Fraction as Q +from pathlib import Path + +if sys.flags.optimize: + raise SystemExit('Assertions must remain enabled: run without -O') + +HERE = Path(__file__).resolve().parent +SCALE = 64 # costs are integers in units of 2^-64, rounded down +T0 = time.time() + + +def log(*a): + print('[%4.0fs]' % (time.time() - T0), *a, flush=True) + + +# ---------------------------------------------------------------------------------------------- rigorous logarithms +def log_lower(x): + """A rational lower bound on ln x for rational x >= 1: k ln 2 + ln y with y in [1, 2), each from partial sums of + 2 atanh((y-1)/(y+1)), whose terms are all nonnegative. The truncation error is below 3^-80.""" + assert x >= 1 + def series(y): + z = (y - 1) / (y + 1) + return 2 * sum((z ** (2 * j + 1) / (2 * j + 1) for j in range(40)), Q(0)) + k = 0 + while x >= 2: + x /= 2 + k += 1 + return k * series(Q(2)) + series(x) + + +def assembly_ceiling(a, balanced, eta=Q(1, 10**8)): + """The largest kappa the repository's assembly can accept from a supplier saving a (its minimum margin + epsilon*q), in the closed form of PR #183 (romainhedouin): q = a(1 - 2 eta) and G = q(1 - eta)/(1 + q) for the + balanced prefix (scripts/paired_cube_assembly.py), G = q(1 - eta)/(1 + q(2 + eta)) for the original prefix + (scripts/structured_bulk_assembly.py). G is increasing in a.""" + q = a * (1 - 2 * eta) + return q * (1 - eta) / (1 + q) if balanced else q * (1 - eta) / (1 + q * (2 + eta)) + + +def cost_table(m): + """f(r) = r ln(m/r), as integers rounded down in units of 2^-SCALE, for 0 <= r < m.""" + out = [0] * m + for r in range(1, m): + val = r * log_lower(Q(m, r)) + out[r] = (val.numerator << SCALE) // val.denominator + return out + + +# ---------------------------------------------------------------------------------------------- the tree's word +def load(tree): + """Regenerate the tree's complex word with the tree's own scripts. Returns a dict with the graph, witness, word, + record, reuse pairs, sink roles and the certified complex and bit profiles.""" + sys.path.insert(0, str(tree / 'scripts')) + cert = lambda name: json.loads((tree / 'certificates' / name).read_text()) + pcn = importlib.import_module('paired_cube_network') + physical = (tree / 'scripts/paired_cube_physical.py').exists() + if physical: + PCP = importlib.import_module('paired_cube_physical') + g, witness, word, record = PCP.regenerated_word() + pairs = [(int(a), int(b)) for a, b, _ in json.loads((PCP.REF / 'pairs.json').read_text())['pairs']] + moved = {int(i): tuple(F) for i, F in json.loads((PCP.REF / 'frames.json').read_text())['frames']} + base_profile = cert('paired-cube-physical-input.json') + sink_file = tree / 'research/terminal-sinks/sinks.json' + sinks = [int(s) for s, _ in json.loads(sink_file.read_text())['sinks']] if sink_file.exists() else [] + sink_root = {} + for r, s in zip(g['roots'], word['rootroles']): + if s in sinks: + from paired_cube.frames import basis, perp + sink_root[s] = perp(basis(g['inputs'][t] for t in r['targets']), g['h']) + complex_profile = cert('paired-cube-sinks-input.json' if sinks else 'paired-cube-physical-input.json') + bit_name = 'paired-cube-bit-physical-input.json' + else: + producer = importlib.import_module('paired_cube_producer') + with tempfile.TemporaryDirectory(prefix='kappa-ceiling-') as d: + producer.regenerate(cert('paired-cube-complex-input.json'), Path(d)) + g, witness, word = (json.loads((Path(d) / n).read_text()) for n in ('graph.json', 'frames.json', 'selection.json')) + record = cert('paired-cube-complex-input.json') + pairs, sinks, complex_profile, moved, base_profile, sink_root = [], [], record, {}, record, {} + bit_name = 'paired-cube-bit-input.json' + bit_profile = cert(bit_name) if (tree / 'certificates' / bit_name).exists() else None + return dict(pcn=pcn, g=g, witness=witness, word=word, record=record, pairs=pairs, sinks=sinks, moved=moved, sink_root=sink_root, + complex_profile=complex_profile, base_profile=base_profile, bit_profile=bit_profile, physical=physical) + + +def slot_chains(T): + """The physical slots of the complex word and, for each, its events in time order: (start subspace, events), + each event (lo, hi) bounding the frame the slot must occupy at that moment. Mirrors the role chains of + scripts/paired_cube/verify.py and scripts/paired_cube_physical.py: + start = 0, the source line for an injected source, or the gauge sigma of a gauged first occupant; + op of node x: span(x) <= frame <= the full backward intersection of x (any admissible frame lies there); + side or centre root read, recipient's gauge read: exactly that frame; + the end of the chain is the full space. + A recipient continues its donor's slot (the hand-off is the step into its gauge).""" + from paired_cube.frames import basis, perp + g, witness, word, record = T['g'], T['witness'], T['word'], T['record'] + h, R = g['h'], record['R'] + inputs = g['inputs'] + args = [None] + [None if a is None else (a[0] + 1, a[1] + 1) for a in g['args']] + roots = [dict(r, node=r['node'] + 1) for r in g['roots']] + ann = witness['annihilators'] + ops = [tuple(o) for o in word['ops']] + span = [()] * len(args) + for x in range(1, len(args)): + span[x] = (inputs[x - 1],) if args[x] is None else basis(span[args[x][0]] + span[args[x][1]]) + upper = {x: perp(tuple(ann[x]), h) for _, _, x in ops} + sources = {int(x): s for x, s in word['sources'].items()} + source_line = {s: (inputs[x - 1],) for x, s in sources.items()} + gauge = {z['role']: perp(tuple(z['annihilator']), h) for z in word['selected']} + rootframe = {} + for r, s in zip(roots, word['rootroles']): + rootframe[s] = span[r['node']] if r['kind'] == 'center' else perp(basis(inputs[t] for t in r['targets']), h) + role_ops = defaultdict(list) + for i, (a, b, _) in enumerate(ops): + role_ops[a].append(i) + role_ops[b].append(i) + donor_of = {b: a for a, b in T['pairs']} + recipient_of = {a: b for a, b in T['pairs']} + sinks = set(T['sinks']) + assert not sinks & (set(donor_of) | set(recipient_of) | set(gauge) | set(sources.values())) + # joint bounds: an operation's frame is shared by both of its registers, so it contains everything either + # register has accumulated (forward pass) and lies in everything either will need later (backward pass) + join = lambda A, B: basis(A + B) + meet = lambda A, B: perp(basis(perp(A, h) + perp(B, h)), h) + full = perp((), h) + entry = {} + for s in range(R): + entry[s] = source_line.get(s, gauge.get(s, ())) + # a terminal sink is deleted in the certified word: its writes go to a target register instead, so the + # propagation does not pass through sink registers (dropping constraints only weakens the bounds) + live = lambda r: r not in sinks + lo, cur = [None] * len(ops), dict(entry) + for i, (a, b, x) in enumerate(ops): + F = span[x] + for r in (a, b): + if live(r): F = join(F, cur[r]) + lo[i] = F + for r in (a, b): + if live(r): cur[r] = F + leave = {} + for s in range(R): + leave[s] = rootframe.get(s, gauge[recipient_of[s]] if s in recipient_of else full) + hi, cur = [None] * len(ops), dict(leave) + for i in reversed(range(len(ops))): + a, b, x = ops[i] + U = upper[x] + for r in (a, b): + if live(r): U = meet(U, cur[r]) + hi[i] = U + for r in (a, b): + if live(r): cur[r] = U + for i in range(len(ops)): + assert len(join(lo[i], hi[i])) == len(hi[i]), 'joint bounds infeasible' + A = T['moved'].get(i, upper[ops[i][2]]) # self-test: the tree's own layout respects every bound + assert len(join(lo[i], A)) == len(A) and len(join(A, hi[i])) == len(hi[i]), 'tree layout outside the bounds at op %d' % i + slots, gauged_starts = [], [] + for s in range(R): + if s in donor_of: + continue + start, events = (), [] + lives = [s] + ([recipient_of[s]] if s in recipient_of else []) + for k, u in enumerate(lives): + if k == 0 and u in source_line: + events.append((source_line[u], source_line[u], source_line[u], None)) + elif k == 0 and u in gauge: + start = gauge[u] + gauged_starts.append(len(gauge[u])) + elif k > 0: + events.append((gauge[u], gauge[u], gauge[u], None)) + for i in role_ops[u]: + a, b, x = ops[i] + tag = (i, 1 if a == u else -1) if live(a) and live(b) else None + events.append((lo[i], hi[i], T['moved'].get(i, upper[x]), tag)) + if u in rootframe: + events.append((rootframe[u], rootframe[u], rootframe[u], None)) + slots.append((s, start, events, s in sinks)) + centres = [len(rootframe[s]) for r, s in zip(roots, word['rootroles']) if r['kind'] == 'center'] + return slots, gauged_starts, centres + + +def slot_bounds(start, events, h, basis, perp): + """Dimension bounds of a slot at each event: the frame contains the join of the lower bounds of events 0..i + (frames nest upward) and lies in the intersection of the upper bounds of events i..end.""" + n = len(events) + lo, acc = [0] * n, start + for i, ev in enumerate(events): + acc = basis(acc + ev[0]) + lo[i] = len(acc) + hi, acc = [0] * n, perp((), h) + for i in reversed(range(n)): + acc = perp(basis(perp(acc, h) + perp(events[i][1], h)), h) + hi[i] = len(acc) + assert all(l <= u for l, u in zip(lo, hi)), 'infeasible slot' + return lo, hi + + +def slot_dp(d_start, lo, hi, tags, h, F, price): + """min over nondecreasing dimensions d_i in [lo_i, hi_i] of sum f(d_i - d_(i-1)) + f(h - d_last) + + sum sign * price[op] * d_i (the Lagrangian term of coupled events). Exact integers. Returns (cost, dims).""" + best, back = {d_start: 0}, [] + for i in range(len(lo)): + w = 0 + if tags[i] is not None: + w = tags[i][1] * price.get(tags[i][0], 0) + nxt, arg = {}, {} + for d in range(lo[i], hi[i] + 1): + c, p = None, None + for d0, c0 in best.items(): + if d0 <= d and (c is None or c0 + F[d - d0] < c): + c, p = c0 + F[d - d0], d0 + if c is not None: + nxt[d], arg[d] = c + w * d, p + best = nxt + back.append(arg) + d = min(best, key=lambda e: best[e] + F[h - e]) + cost = best[d] + F[h - d] + dims = [0] * len(lo) + for i in reversed(range(len(lo))): + dims[i] = d + d = back[i][d] + return cost, dims + + +def rebuild(dest): + """Write the pinned #168 files into dest after checking the archive and every file against SOURCE.json.""" + src = json.loads((HERE / 'SOURCE.json').read_text()) + data = (HERE / src['archive']).read_bytes() + if hashlib.sha256(data).hexdigest() != src['archive_sha256']: + raise SystemExit('baseline archive differs from SOURCE.json') + with tarfile.open(fileobj=io.BytesIO(data), mode='r:gz') as archive: + members = [x for x in archive.getmembers() if x.isfile()] + if sorted(x.name for x in members) != sorted(src['files']): + raise SystemExit('baseline archive lists other files than SOURCE.json') + for x in members: + part = Path(x.name) + if part.is_absolute() or '..' in part.parts: + raise SystemExit('unsafe path in baseline archive: ' + x.name) + body = archive.extractfile(x).read() + if hashlib.sha256(body).hexdigest() != src['files'][x.name]: + raise SystemExit('baseline file differs from SOURCE.json: ' + x.name) + (dest / part).parent.mkdir(parents=True, exist_ok=True) + (dest / part).write_bytes(body) + return src['commit'] + + +# ---------------------------------------------------------------------------------------------- main +def main(): + ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument('--tree', type=Path, default=None) + ap.add_argument('--check', action='store_true') + ap.add_argument('--rounds', type=int, default=400, help='Lagrangian rounds (default 400)') + a = ap.parse_args() + if a.tree is not None: + return run(a, a.tree.resolve()) + with tempfile.TemporaryDirectory(prefix='kappa-ceiling-pr168-') as d: + commit = rebuild(Path(d)) + log('rebuilt the pinned files of #168 (commit %s) from baseline-pr168.tar.gz; hashes match SOURCE.json' % commit[:7]) + return run(a, Path(d).resolve()) + + +def run(a, tree): + T = load(tree) + pcn, rec = T['pcn'], T['record'] + from paired_cube.frames import basis, perp + h, v, ell = rec['h'], rec['v'], rec['loss'] + m, D = 3 * h, 2 * v - 3 * rec['loss'] + F = cost_table(m) + unit = Q(1, 1 << SCALE) + balanced = (tree / 'scripts/paired_cube_assembly.py').exists() + G = lambda a: assembly_ceiling(a, balanced) + out = dict(tree=str(tree), mode='physical layer' if T['physical'] else 'gauged word', h=h, v=v, loss=ell, D=D, + assembly='balanced prefix' if balanced else 'original prefix') + log('tree %s (%s): h %d, v %d, centre loss %d, deficit D = 2v - 3 loss = %d' % (tree, out['mode'], h, v, ell, D)) + + # fact 1, numerically: D/C of each certified profile against its certified saving + f = lambda r: r * math.log(m / r) + def ratio(p): + H = {int(r): n for r, n in p['child_histogram'].items()} + mm = p['m'] + return p['deficit_per_vertex'] / math.fsum(n * r * math.log(mm / r) for r, n in H.items() if r) + rc = ratio(T['complex_profile']) + log('fact 1, complex: certified saving %.7e < D/C = %.7e (gap %.3f%%)' % (float(pcn.AC), rc, 100 * (1 - float(pcn.AC) / rc))) + assert pcn.AC < rc + if T['bit_profile'] is not None and 'child_histogram' in T['bit_profile']: + rb = ratio(T['bit_profile']) + log('fact 1, bit: certified coarse saving %.7e < D/C = %.7e (gap %.3f%%)' % ( + float(pcn.COARSE), rb, 100 * (1 - float(pcn.COARSE) / rb))) + assert pcn.COARSE < rb + assert pcn.KAPPA < G(min(pcn.AC, pcn.COARSE)) < min(pcn.AC, pcn.COARSE) + log('fact 2 (%s): kappa %s < G(min(complex %s, bit coarse %s)) = %.7e' % ( + out['assembly'], pcn.KAPPA, pcn.AC, pcn.COARSE, float(G(min(pcn.AC, pcn.COARSE))))) + + # fact 4: the frame-layout floor of this word + slots, gauged_starts, centres = slot_chains(T) + from collections import Counter + # self-test 1: with the tree's own frames, the slot chains (sinks included) reproduce the certified ledger of the + # word before sinks exactly, and no slot's floor exceeds its actual chain + local, sink_steps, prepared, actual_total = Counter(), Counter(), [], 0 + for s, start, ev, is_sink in slots: + dims = [len(start)] + [len(e[2]) for e in ev] + [h] + steps = [b - c for c, b in zip(dims, dims[1:]) if b > c] + local.update(steps) + lo, hi = slot_bounds(start, ev, h, basis, perp) + tags = [e[3] for e in ev] + own, _ = slot_dp(len(start), lo, hi, tags, h, F, {}) + assert own <= sum(F[r] for r in steps), 'floor above the actual chain of slot %d' % s + if is_sink: + sink_steps.update((len(T['sink_root'][s]), h - len(T['sink_root'][s]))) + else: + prepared.append((len(start), lo, hi, tags)) + actual_total += sum(F[r] for r in steps) + base = T['base_profile'] + children = Counter() + for r, n in local.items(): children[r] += 3 * n + for name in ('source_data_histogram', 'target_data_histogram'): + for r, n in base[name].items(): + if int(r): children[int(r)] += 3 * n + for r in centres: children[r] += 3 + for d in gauged_starts: children[3 * d] += 1 + children[2] += 2 * v + assert dict(children) == {int(r): n for r, n in base['child_histogram'].items() if n}, 'slot model differs from the certified ledger' + if T['sinks']: # the certified sink record removes exactly [r] and [h - r] per sink and stage (research/terminal-sinks) + less = Counter({int(r): n for r, n in base['child_histogram'].items()}) + for r, n in sink_steps.items(): less[r] -= 3 * n + assert +less == Counter({int(r): n for r, n in T['complex_profile']['child_histogram'].items()}), 'sink record' + slots = [x for x in slots if not x[3]] + log('self-test: the tree layout lies inside every derived frame bound; its slot chains reproduce the certified ' + 'complex ledger exactly; every slot floor is below its actual chain') + # Lagrangian coupling: each round's total is a valid floor (the price terms cancel on any common-frame layout); + # prices move along the disagreement between the two registers of each operation (integer subgradient steps) + price, best, step, history = {}, None, (6 << SCALE) // 10, [] + for k in range(a.rounds + 1): + total, grad = 0, defaultdict(int) + for d0, lo, hi, tags in prepared: + c, dims = slot_dp(d0, lo, hi, tags, h, F, price) + total += c + for tg, d in zip(tags, dims): + if tg is not None: + grad[tg[0]] += tg[1] * d + assert total <= actual_total, 'Lagrangian floor above the tree layout' + history.append(total) + if best is None or total > best: + best = total + if k == 0: + independent = total + for i, g in grad.items(): + if g: + price[i] = price.get(i, 0) + step * g + step = step * 995 // 1000 + two_step = len(slots) * (F[1] + F[h - 1]) + fixed = 3 * 2 * v * F[h - 1] + 2 * v * F[2] + 3 * sum(F[r] for r in centres) + sum(F[3 * d] for d in gauged_starts) + C_dp, C_ind, C_two = (3 * best + fixed) * unit, (3 * independent + fixed) * unit, (3 * two_step + fixed) * unit + C_actual = Q(math.fsum(n * f(int(r)) for r, n in T['complex_profile']['child_histogram'].items() if int(r))) + ceil7 = lambda x: -((-x.numerator * 10**7) // x.denominator) # round up on the 10^-7 grid + out.update(physical_slots=len(slots), sinks=len(T['sinks']), reuse_pairs=len(T['pairs']), + C_certified=float(C_actual), C_floor=float(C_dp), + ceiling_frames='%d/10^7' % ceil7(G(Q(D) / C_dp)), ceiling_two_step='%d/10^7' % ceil7(G(Q(D) / C_two)), + ceiling_uncoupled='%d/10^7' % ceil7(G(Q(D) / C_ind)), rounds=a.rounds, + kappa=str(pcn.KAPPA)) + log('fact 4: %d physical slots (%d reuse pairs, %d sinks); certified C = %.0f; floor C >= %.0f' % ( + len(slots), len(T['pairs']), len(T['sinks']), C_actual, C_dp)) + log('CEILING (any frames for this word): kappa < %s = %.7e [kappa %.7e is %.1f%% of it]' % ( + out['ceiling_frames'], ceil7(G(Q(D) / C_dp)) / 1e7, float(pcn.KAPPA), 100 * float(pcn.KAPPA) / (ceil7(G(Q(D) / C_dp)) / 1e7))) + log('without coupling the two registers of an operation: kappa < %s' % out['ceiling_uncoupled']) + log('with only two steps per slot: kappa < %s' % out['ceiling_two_step']) + + # design ceilings (PROOF.md, section 5): the same h, v and decoder, any word + data_end_centres = (3 * 2 * v * F[h - 1] + 2 * v * F[2] + 3 * sum(F[r] for r in centres)) * unit + one_per_source = (3 * v * (F[1] + F[h - 1])) * unit + design = dict(one_register_per_source='%d/10^7' % ceil7(G(Q(D) / (data_end_centres + one_per_source))), + no_auxiliary_registers='%d/10^7' % ceil7(G(Q(D) / data_end_centres)), + no_auxiliary_registers_and_no_centre_loss='%d/10^7' % ceil7(G(Q(2 * v) / ((3 * 2 * v * F[h - 1] + 2 * v * F[2]) * unit)))) + out['design'] = design + log('design ceilings: one register per source %s, no auxiliary registers %s, no centre loss either %s' % ( + design['one_register_per_source'], design['no_auxiliary_registers'], design['no_auxiliary_registers_and_no_centre_loss'])) + if a.check: + expected = json.loads((HERE / 'expected.json').read_text()) + keys = ('h', 'v', 'loss', 'D', 'physical_slots', 'ceiling_frames', 'ceiling_uncoupled', 'ceiling_two_step', 'design', 'kappa', 'rounds') + assert {k: out[k] for k in keys} == {k: expected[k] for k in keys}, 'results differ from expected.json' + log('PASS results equal expected.json') + print(json.dumps(out, indent=1, sort_keys=True)) + + +if __name__ == '__main__': + main() diff --git a/research/kappa-ceiling/expected.json b/research/kappa-ceiling/expected.json new file mode 100644 index 0000000000..446062c5b7 --- /dev/null +++ b/research/kappa-ceiling/expected.json @@ -0,0 +1,17 @@ +{ + "D": 1320, + "ceiling_frames": "7010/10^7", + "ceiling_two_step": "11530/10^7", + "ceiling_uncoupled": "7158/10^7", + "design": { + "no_auxiliary_registers": "62319/10^7", + "no_auxiliary_registers_and_no_centre_loss": "124788/10^7", + "one_register_per_source": "40787/10^7" + }, + "h": 22, + "kappa": "3279447/5000000000", + "loss": 440, + "physical_slots": 11015, + "rounds": 400, + "v": 1320 +}