From 39112c2577452fa278489fb9fd77a9aecb2cdc24 Mon Sep 17 00:00:00 2001 From: DreamingOfClouds Date: Mon, 12 Oct 2026 09:30:18 -0500 Subject: [PATCH] Format limits (no kappa claim): certified ceiling, spec census, structural lemmas, helper bounds research/format-limits records results about the limits of the current format, each with its scope and with a machine check where one is possible (verify.sh: 298 claims). - Format ceiling, rev. 2: under the accounting axioms (pressure identity, deficit, window, B_K schedule, batching, hosting) every supplier has a <= X(s, h/k, x); on the punctured units (bit 111/63, complex 112/64) kappa <= 5.1595e-3, complex binding. Axioms checked on #555/#568, gap decomposition, scope multipliers; the x = 3/5 scope of rev. 1 is superseded. - Spec versus convention census: 25 rules with file:line anchors in the upstream paper and the hub's checkers (checked), and the corrections found. - Structural lemmas with exact toys: frame sandwich, port gauge (Lemma N), complement sinks pinned (Lemma R), quotient domination (Lemma Q), lambda = +-i (Lemma lambda), pairing uniqueness at the E8 pattern, per-call descent pricing. - Design-free helper floors (bit 212/138, complex 107/0), coupling adds 0, carriers register-neutral; complex helper prices by class and width on #569 and the moment-cost census on #555/#569. Co-Authored-By: Claude Opus 5.5 --- research/format-limits/DEPENDS.json | 48 ++ research/format-limits/LICENSE | 201 +++++ research/format-limits/MANIFEST.sha256 | 50 ++ research/format-limits/NOTICE.md | 81 ++ research/format-limits/README.md | 805 ++++++++++++++++++ research/format-limits/SOURCES.json | 77 ++ .../data/ceiling/gap568-inputs.json | 10 + .../format-limits/data/census/anchors.json | 440 ++++++++++ .../data/prices/helper-price-table.json | 94 ++ .../format-limits/data/prices/k1-specs.json | 12 + .../data/prices/pr569-provider-calls.json | 91 ++ .../data/toys/sol_p3_cone_R16.json | 1 + .../data/vendor/pr543/EXPECTED.json | 239 ++++++ .../data/vendor/pr545/EXPECTED.json | 127 +++ .../data/vendor/pr555/EXPECTED.json | 221 +++++ .../data/vendor/pr555/bitE8mut-p111.json.gz | Bin 0 -> 42345 bytes .../data/vendor/pr568/PLAN.json.gz | Bin 0 -> 7679 bytes .../pr569/fourcold-eight-FULL-WORD.json.gz | Bin 0 -> 36577 bytes .../pr569/width7-eight-FULL-WORD.json.gz | Bin 0 -> 36690 bytes .../combined-eight-local-FULL-WORD.json.gz | Bin 0 -> 36781 bytes research/format-limits/requirements.txt | 7 + .../format-limits/scripts/a1_ceiling_roots.py | 81 ++ .../format-limits/scripts/a2_ceiling_sup.py | 43 + research/format-limits/scripts/a3_axioms.py | 86 ++ research/format-limits/scripts/a4_gap.py | 175 ++++ .../scripts/a5_scope_multipliers.py | 53 ++ .../format-limits/scripts/b_census_anchors.py | 57 ++ .../format-limits/scripts/c1_sandwich_toy.py | 237 ++++++ .../format-limits/scripts/c2_port_gauge.py | 227 +++++ .../scripts/c3_complement_gate0.py | 319 +++++++ .../scripts/c3_complement_taint.py | 337 ++++++++ research/format-limits/scripts/c4_quotient.py | 507 +++++++++++ research/format-limits/scripts/c5_lambda.py | 291 +++++++ research/format-limits/scripts/c6_asym.py | 311 +++++++ .../format-limits/scripts/c7_descent_price.py | 28 + .../format-limits/scripts/c7_descent_toy.py | 429 ++++++++++ research/format-limits/scripts/cert.py | 133 +++ .../format-limits/scripts/d1_cover_numbers.py | 371 ++++++++ .../format-limits/scripts/d2_coupled_count.py | 521 ++++++++++++ .../scripts/d3_carrier_ledger.py | 297 +++++++ research/format-limits/scripts/d_geometry.py | 452 ++++++++++ .../format-limits/scripts/e1_run_census.py | 234 +++++ .../format-limits/scripts/e2_helper_prices.py | 362 ++++++++ research/format-limits/scripts/e8units.py | 275 ++++++ research/format-limits/scripts/e_hosting.py | 176 ++++ research/format-limits/scripts/minrank_sat.py | 133 +++ research/format-limits/scripts/provenance.py | 62 ++ research/format-limits/scripts/report.py | 60 ++ research/format-limits/scripts/run_all.py | 78 ++ research/format-limits/scripts/skel.py | 85 ++ research/format-limits/verify.sh | 28 + 51 files changed, 8952 insertions(+) create mode 100644 research/format-limits/DEPENDS.json create mode 100644 research/format-limits/LICENSE create mode 100644 research/format-limits/MANIFEST.sha256 create mode 100644 research/format-limits/NOTICE.md create mode 100644 research/format-limits/README.md create mode 100644 research/format-limits/SOURCES.json create mode 100644 research/format-limits/data/ceiling/gap568-inputs.json create mode 100644 research/format-limits/data/census/anchors.json create mode 100644 research/format-limits/data/prices/helper-price-table.json create mode 100644 research/format-limits/data/prices/k1-specs.json create mode 100644 research/format-limits/data/prices/pr569-provider-calls.json create mode 100644 research/format-limits/data/toys/sol_p3_cone_R16.json create mode 100644 research/format-limits/data/vendor/pr543/EXPECTED.json create mode 100644 research/format-limits/data/vendor/pr545/EXPECTED.json create mode 100644 research/format-limits/data/vendor/pr555/EXPECTED.json create mode 100644 research/format-limits/data/vendor/pr555/bitE8mut-p111.json.gz create mode 100644 research/format-limits/data/vendor/pr568/PLAN.json.gz create mode 100644 research/format-limits/data/vendor/pr569/fourcold-eight-FULL-WORD.json.gz create mode 100644 research/format-limits/data/vendor/pr569/width7-eight-FULL-WORD.json.gz create mode 100644 research/format-limits/data/vendor/pr571/combined-eight-local-FULL-WORD.json.gz create mode 100644 research/format-limits/requirements.txt create mode 100644 research/format-limits/scripts/a1_ceiling_roots.py create mode 100644 research/format-limits/scripts/a2_ceiling_sup.py create mode 100644 research/format-limits/scripts/a3_axioms.py create mode 100644 research/format-limits/scripts/a4_gap.py create mode 100644 research/format-limits/scripts/a5_scope_multipliers.py create mode 100644 research/format-limits/scripts/b_census_anchors.py create mode 100644 research/format-limits/scripts/c1_sandwich_toy.py create mode 100644 research/format-limits/scripts/c2_port_gauge.py create mode 100644 research/format-limits/scripts/c3_complement_gate0.py create mode 100644 research/format-limits/scripts/c3_complement_taint.py create mode 100644 research/format-limits/scripts/c4_quotient.py create mode 100644 research/format-limits/scripts/c5_lambda.py create mode 100644 research/format-limits/scripts/c6_asym.py create mode 100644 research/format-limits/scripts/c7_descent_price.py create mode 100644 research/format-limits/scripts/c7_descent_toy.py create mode 100644 research/format-limits/scripts/cert.py create mode 100644 research/format-limits/scripts/d1_cover_numbers.py create mode 100644 research/format-limits/scripts/d2_coupled_count.py create mode 100644 research/format-limits/scripts/d3_carrier_ledger.py create mode 100644 research/format-limits/scripts/d_geometry.py create mode 100644 research/format-limits/scripts/e1_run_census.py create mode 100644 research/format-limits/scripts/e2_helper_prices.py create mode 100644 research/format-limits/scripts/e8units.py create mode 100644 research/format-limits/scripts/e_hosting.py create mode 100644 research/format-limits/scripts/minrank_sat.py create mode 100644 research/format-limits/scripts/provenance.py create mode 100644 research/format-limits/scripts/report.py create mode 100644 research/format-limits/scripts/run_all.py create mode 100644 research/format-limits/scripts/skel.py create mode 100755 research/format-limits/verify.sh diff --git a/research/format-limits/DEPENDS.json b/research/format-limits/DEPENDS.json new file mode 100644 index 0000000000..276212957a --- /dev/null +++ b/research/format-limits/DEPENDS.json @@ -0,0 +1,48 @@ +{ + "description": "Files of the main branch read by the checks (read-only), pinned by sha256; checked by scripts/provenance.py. The census anchors (data/census/anchors.json) are checked by content instead.", + "base_commit": "7dfa22b", + "files": [ + { + "path": "research/direct-delivery504-global/vendor/clifford/clifford_exact.py", + "sha256": "2982ead07721e83358dc67c6f49c2b4c16a236a6bebdfd72f58221be1f5d0962", + "from_pr": "#531 (icekylinx)", + "used_for": "exact Gaussian-dyadic Clifford compiler (toys c1, c2, c3_gate0, c7)" + }, + { + "path": "research/direct-delivery504-global/vendor/gk/gk_word.py", + "sha256": "f027a9273bcf4e0971258c160b7d3b3c196366b32a529cff6e6a0e666b03826b", + "from_pr": "#531 (icekylinx)", + "used_for": "complex word helpers (a4, e1, e2)" + }, + { + "path": "research/direct-delivery504-global/vendor/gk/gk_common.py", + "sha256": "e68d190fd436b2f3955decb590adb53344d5d4ea0a19d62d86df1e4b55ecd198", + "from_pr": "#531 (icekylinx)", + "used_for": "complex word helpers (a4, e1, e2)" + }, + { + "path": "research/direct-delivery504-global/evidence/local/FULL-WORD.json.gz", + "sha256": "bd6b004c4948d5ce80d4f81477338fe30434c2a94586463fdcc531df0dbc9509", + "from_pr": "#531 (icekylinx)", + "used_for": "the #531 complex word (c3_taint, d1)" + }, + { + "path": "research/direct-delivery504-global/inputs/hamming/PROTOTYPE.json", + "sha256": "327d2a868043e846381aa58a33053c24425e8cad522ff401b10ec8af1d649a3f", + "from_pr": "#531 (icekylinx)", + "used_for": "P112 port geometry (d1, d3)" + }, + { + "path": "research/punctured112-global/inputs/ASYNC112-PLAN.json", + "sha256": "a26acfea7ba8179130633b51a4c559e4dab179ddbae0578ff0809397efa09b55", + "from_pr": "#523 (icekylinx)", + "used_for": "idle hosting windows of the 112-port unit (e2)" + }, + { + "path": "research/e8-r480-nine-stage/EXPECTED.json", + "sha256": "883f7a1a84f31d9e4430e348bc8d9c1ec10210b206ff602dbe126349056af13d", + "from_pr": "#510 (DreamingOfClouds)", + "used_for": "R480 nine-stage profile and bit c (a3)" + } + ] +} \ No newline at end of file diff --git a/research/format-limits/LICENSE b/research/format-limits/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/research/format-limits/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/research/format-limits/MANIFEST.sha256 b/research/format-limits/MANIFEST.sha256 new file mode 100644 index 0000000000..847f929c23 --- /dev/null +++ b/research/format-limits/MANIFEST.sha256 @@ -0,0 +1,50 @@ +31f6e8be64f321cc58730c4fbbdba0d2f7f8b704fb3d5da23303e219d754c47d DEPENDS.json +c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4 LICENSE +d16991367957b1de0cb3c9d4b61667482c3d4985903f15846051c65812c81611 NOTICE.md +0fa0d6b2df96ddc06b876c41c5fa8d2335b97f36bc8f17d1371296d23199c263 README.md +ecdc9cb66e0631cd311ffae9c742dd1173efb99803a537778c76cdd4ea0bda1e SOURCES.json +810e9c5c3222811ee5d4b52b3429f7700ab578836f76418bada521bea98b1001 data/ceiling/gap568-inputs.json +5445f842587ab968c5001491e6479e7cc691b5114ab234652ce25b9272e2092f data/census/anchors.json +e92a4a7a23455c1aa8ba0aa93518627235d872f4fff11505f276d2789bd924d3 data/prices/helper-price-table.json +90d2e216d5c0807c22c4c8b772d0b2c4bde08c8810a2688136fd4385b4a9c470 data/prices/k1-specs.json +5a2a06f515a60cdda2b9743dfc6b9322ada30a743dc99385cc55fe938f2f132f data/prices/pr569-provider-calls.json +fe3ad604eec43845747603414222fc6a82ed16168ef64909440c0a72eba97dc9 data/toys/sol_p3_cone_R16.json +06eccee9f6b77e140833df83407e7996dc948c17d49b0779ab171fbe463fd28d data/vendor/pr543/EXPECTED.json +dd078d075b153ea2ed02a4bbd1fe467315930f4d7dc90f2c34cd2c33e06a5029 data/vendor/pr545/EXPECTED.json +7c2675fd49afd7e328606399a4421fd49c43fe8a6c5b57154cc908c00588abe2 data/vendor/pr555/EXPECTED.json +1bc3c53d4baf4927c6a00c339ea3975516471a48074d4d97c6addaded2a4d858 data/vendor/pr555/bitE8mut-p111.json.gz +cde64e210720e0cafbea5a242ee3747d3f34447bfdbc53c62114d6c34c71feeb data/vendor/pr568/PLAN.json.gz +4bb27af5d2142114ac59d24c8fc9bb638b932cf1cdffaf17733b5372ee101f3b data/vendor/pr569/fourcold-eight-FULL-WORD.json.gz +6f99cffd73076a768d39c21bf97ced42b6a5d3228b1dcef79d6f072dd35c0ee5 data/vendor/pr569/width7-eight-FULL-WORD.json.gz +ee0fecf86bddcc5f4e675c60fbd5c572bd48269368a37a38c070346eefa3dd4a data/vendor/pr571/combined-eight-local-FULL-WORD.json.gz +6a080563ee39cc2c1c527bb6fb9c822253a7ee7202b7c5e53030e5526f973817 requirements.txt +d763514fc92933875b8cef8535b881cae4536937918f2a3a5d8dc87dda207dff scripts/a1_ceiling_roots.py +1628325b8c15519a6fb5850461344b90a88d950948d8c47d8d19c00a95c17a1d scripts/a2_ceiling_sup.py +1dc82914bf5a6ee5d1f27ea5cfbe404b83279c327cac2f56142a47a443a87159 scripts/a3_axioms.py +b2e90cbb329bfebd954166637ed04488479dd7acca32e0c6102d4a3631c347f4 scripts/a4_gap.py +6479c189696f53cc505febb2c81c4b69a5bf54f211ee4ae64ff841041244b060 scripts/a5_scope_multipliers.py +49983e533cec16f72009bc5f0c57f1fe0811c96cdf0391ac8b4e44ba3e42eb91 scripts/b_census_anchors.py +23a57ea454773b09a407c383581efc32868d885a574ed59cde444ad804d10b6d scripts/c1_sandwich_toy.py +588709cb843477d435f00a3242c6eb6700f01bc88d060cbaf290b7fa27fc0d2f scripts/c2_port_gauge.py +da9f655c9292d0be25db6559f13069513c29a181e2664dcb3fdae1a072976d4b scripts/c3_complement_gate0.py +7aac51bc3819b89661d5f78f21a81a68b573073b4b1f837364a17d3b8ec2bbb2 scripts/c3_complement_taint.py +f3ece572a2c96b03139bff751ad2ab343be9edf4e0f84b2351772779e7bd8f31 scripts/c4_quotient.py +1ebc26e942785da50a354d34a55f487a00fc5a191570623fb8613da3a6a85aba scripts/c5_lambda.py +a9e17c1cf50a8403734a0e35863776a5ec7e60b44e9556f7b0206163c7f18cd0 scripts/c6_asym.py +9853b2f5a7cc9e8fe9db337ada7b669c3fb5404ce44ff23f48734454269270bb scripts/c7_descent_price.py +b59d52a954590aa35ddf1a495e61fa0b7c3edc4a91cbe4c232620d27079f8ce6 scripts/c7_descent_toy.py +cab1c055cf4dbad6a89b4527e2ef3d138131b6742eaf21bd8b4338a9ee2febf4 scripts/cert.py +67c40caeac7baf3156b2b863d185dedc2fc7a96ef6422b3571909099bc47eb75 scripts/d1_cover_numbers.py +85e05bfb9c380ecd6cd479a4e615735e7b377011e4aa8fb379367c224fd217cf scripts/d2_coupled_count.py +72bf9fb0479c2910413e34fe8e1ac050013df30b7751aaabea88241e086c7f55 scripts/d3_carrier_ledger.py +8699bcd1e00d08903b6dade00251947344e2ca53ece36a62aa98aa821d598241 scripts/d_geometry.py +17b521fd60adb6237114dcc632823e48771f3dca67f61d6766c74c9b1a7daddd scripts/e1_run_census.py +743ea47f90c189931bbc2add8c7c477be2db40d823e612f1c3112390cd477f5c scripts/e2_helper_prices.py +3134443d8327d0bf2a8a66c26327cf5268cec6b7ffd85dde750b7e197ce63ef1 scripts/e8units.py +de0c4091e98a724eb20f50abb546edfe94206d66d63dc56718f858c4c774b33f scripts/e_hosting.py +bf79e88c99f5632a01957008d52a00233b4135b211c9e6a26a9180a6bbb58423 scripts/minrank_sat.py +55d8bc41b48025dfa5a7c07a1b59338ab53733ee261bf75f1cc6da2f3f0a862e scripts/provenance.py +96fbb236eb58bd471be33ccafbd128cd0590e5dc6f160ad9b9e85bcc2680b437 scripts/report.py +616cd927168b9bea5675ce6241d9574a222761ef267e8b688c8d7a1ef9e1e887 scripts/run_all.py +9cfbc1316389a46922765cdae85fdde2042f7681986d1c97a19174c4922ea29c scripts/skel.py +ac74b228875bec9c4212e12e2a773e8cc0746d2f85fac743b10dbc4004b5bb24 verify.sh diff --git a/research/format-limits/NOTICE.md b/research/format-limits/NOTICE.md new file mode 100644 index 0000000000..1c45fbefdb --- /dev/null +++ b/research/format-limits/NOTICE.md @@ -0,0 +1,81 @@ +# Notice + +Prepared by DreamingOfClouds with Claude Opus 5.5 (Anthropic), via Claude Code; Apache-2.0 (`LICENSE`, copied from +the repository root). + +## New in this package + +Everything under `scripts/` and `data/` except `data/vendor/`, plus `README.md`, `verify.sh`, `SOURCES.json`, +`DEPENDS.json`, `requirements.txt` and `MANIFEST.sha256`. + +## Vendored, unchanged (git blob ids in `SOURCES.json`, checked by `scripts/provenance.py`) + +- **#555 (eumemic), commit 65a6bc1**: the p111 bit word `bitE8mut-p111.json.gz` and its `EXPECTED.json`. +- **#569 (DominikScholz), commit 1424da8**: the width-7 and four-cold provider words (`FULL-WORD.json.gz`). +- **#571 (DominikScholz), commit b7bdc5a**: the combined-eight-local provider word. +- **#568 (jeremykothe2), commit 71c99ac**: `research/complex-multicold/data/PLAN.json`, stored gzip-compressed (the + blob id is of the decompressed file). +- **#543 (eumemic), commit 2065fe4**: `research/e8-bit-r420/EXPECTED.json`. +- **#545 (DreamingOfClouds), commit 376426f**: `research/e8-punctured111-star/EXPECTED.json`. + +## Other inputs + +- `data/toys/sol_p3_cone_R16.json`: the p = 3 paired-cube program (h = 6, v = 8, R = 16) in the gcert/1 format of Jacob + Sussman's checker, the same toy instance as #446's. +- `data/prices/pr569-provider-calls.json`: the 72 (time, provider) pairs of #569's `research/larger-gain/global/width7/ + PLAN.json` (blob id recorded in the file). + +## Used from main, unchanged (pinned by sha256 in `DEPENDS.json`) + +- #531 (icekylinx): + - the exact Clifford compiler `research/direct-delivery504-global/vendor/clifford/clifford_exact.py`; + - the word helpers `vendor/gk/gk_word.py` and `gk_common.py`; + - the word `evidence/local/FULL-WORD.json.gz`; + - the geometry `inputs/hamming/PROTOTYPE.json`. +- #523 (icekylinx): the hosting windows `research/punctured112-global/inputs/ASYNC112-PLAN.json`. +- #510 (DreamingOfClouds): `research/e8-r480-nine-stage/EXPECTED.json`. +- The census anchors (`data/census/anchors.json`) cite, read-only: + - the upstream paper (`upstream/build/sections/*.tex`); + - #493's checkers (eumemic and contributors); + - #509 / #516's Lean files (DreamingOfClouds); + - #442's hosting proof (DaysSky); + - #130's general frame lemma note (icekylinx); + - the five-stage bank files (#276 lineage); + - the maintainer's review documents. + +## Results and constructions this package builds on (credit) + +**Interfaces, lemmas and checkers:** + +- the upstream paper (`upstream/build/sections`); +- **#130** (icekylinx): the general Clifford frame lemma for arbitrary binary subspaces; +- **#370** (utcorvusvolat-dotcom): the first E8 bit word and its checker; +- **#386** (jacklightChen): compressed windows; +- **#425** (jacklightChen): the mutable-basis interface and scalar-interface lemma; +- **#433** (icekylinx): split centres; +- **#442** (DaysSky): idle-axis whole-residual hosting and its stream model; +- **#446** (DreamingOfClouds, open): Theorems A–C, used as premises; +- **#458** (chafreaky): the one-helper-per-family rule; +- **#493** (eumemic): the bit carrier skeleton, checkers and outer assembly; +- **#509 / #516** (DreamingOfClouds): Lemmas G and A in Lean; +- **#514** (Dugongue): the repeated-axis identities C_v² = X_v. + +**Words and pipelines read by the checks:** + +- **#510** and **#545** (DreamingOfClouds); +- **#543** and **#555** (eumemic); +- **#523** and **#531** (icekylinx): the punctured 112-port complex unit and direct delivery; +- **#568** (jeremykothe2); +- **#569** and **#571** (DominikScholz). + +**Lineage the measured words build on (credited, not read by any script):** + +- **#535** (icekylinx): post-centre kernels; +- **#552** (Dugongue): the static44 harness lineage of the complex words; +- **#558** and **#564** (eumemic): rank-5 pivots and cold kernels; +- **#588** (jeremykothe2): frame coalescences on the #569 regime (cited in C.8, not reproduced). + +**Split-residual hosting:** + +- **#448** (higherbeing): the cross-copy split-residual construction; +- **#451** (chafreaky): its trace obstruction. diff --git a/research/format-limits/README.md b/research/format-limits/README.md new file mode 100644 index 0000000000..2f7aaf5b82 --- /dev/null +++ b/research/format-limits/README.md @@ -0,0 +1,805 @@ +# Format limits (no κ claim): a certified ceiling, a spec census, structural lemmas and helper bounds + +This package states results about the limits of the current construction format (B_K compressed windows, star +copies, whole-residual hosting) and checks the ones that can be checked. **It makes no κ claim and ships no word.** +Each result comes with its scope and assumptions. Each is marked: + +- **checked**: `verify.sh` recomputes it from the files listed below (exact or certified arithmetic unless stated); +- **argued**: a proof or proof sketch in this README; +- **recorded**: an input taken from a run or a PR that `verify.sh` does not repeat (the file and its source are named). + +```sh +python3 -m pip install -r research/format-limits/requirements.txt +bash research/format-limits/verify.sh /fresh/output/dir # from a checkout or `git archive` of the whole repository +``` + +Python ≥ 3.11 with numpy, scipy, sympy, python-sat and ortools (`requirements.txt` pins a tested set). Step 0 +checks the package against `MANIFEST.sha256`, the vendored inputs against the git blob ids of their PR commits +(`SOURCES.json`) and the files of main it reads (`DEPENDS.json`). Steps A–E print one `PASS`/`FAIL` line per claim. +About 2 minutes on 12 cores (six checks at a time). + +**Tested environments** (fresh `git archive`, every claim PASS): + +| Python | numpy | scipy | sympy | python-sat | ortools | how installed | +|---|---|---|---|---|---|---| +| 3.11.17 | 2.4.6 | 1.17.1 | 1.14.0 | 1.9.dev15 | 9.15.6755 | fresh venv from `requirements.txt` | +| 3.14.7 | 2.4.6 | 1.17.1 | 1.14.0 | 1.9.dev15 | 9.15.6755 | fresh venv from `requirements.txt` | +| 3.14.7 | 2.5.4 | 1.18.1 | 1.14.0 | 1.9.dev15 | 9.15.6755 | fresh venv, latest releases | +| 3.14.7 | 2.3.5 | 1.17.0 | 1.14.0 | 1.9.dev15 | 9.15.6755 | development venv | + +Values that come from floating-point solvers (the hosting LP, the coupled-cover MILP) are compared with explicit +tolerances; the certified roots are rational intervals. + +## Results at a glance + +| id | result | status | +|---|---|---| +| A | **Format ceiling, rev. 2.** Under the accounting axioms of §A.1, every supplier in the format has saving a ≤ X(s, h/k, x) (root of the slot-domination bound). On the realised units, X* = 6.0005·10⁻³ (bit, 111 lines, decoder constant 63) and 5.1863·10⁻³ (complex, 112 ports, copy cost 64), both at s = 5, so **κ ≤ 5.1595·10⁻³** (complex binds). | theorem argued; every number checked (`a1`–`a5`) | +| A.4 | The axioms hold on the frontier words #555 (bit) and #568 (complex): published values reproduced from their profiles, deficit identity with equality, B_K idle windows literal, slot domination at nine values of q. | checked (`a3`); (Bat)/(Hst) for #568 argued from its README | +| A.5 | At s = 9 the frontier sits 2.66× (bit) / 2.50× (complex) under the ceiling; 87% / 93% of the log-gap is helper cost, 19% / 25% data-run fragmentation, and hosting recovers 7% / 18%. | checked (`a4`); #568 uses a recorded idle remainder | +| A.6 | The rev. 1 scope x = 3/5 (κ ≤ 4.5151·10⁻³) is superseded: the punctured units have x = 63/111 and 64/112. | checked (`a1`) | +| B | 25 format rules classified as spec / proved / convention, with file:line evidence in the upstream paper and the hub's checkers, and the corrections the census found. | anchors checked (`b`); classification argued | +| C.1 | Frame sandwich: a register's frame lies between the span of what it carries and the orthogonal of what it will serve; the three complex frame walls are its faces; leaving it is exact and costs d(f, g). | argued; toy checked (`c1`) | +| C.2 | Lemma N: the per-target rank-1 port factor is a gauge of the endpoint frames, always paid; any detour off the geodesic interval costs exactly 2. | proof argued; toys checked (`c2`) | +| C.3 | Lemma R: complement sinks w_t^⊥ (w·u = 1) are legal at zero rank and port cost, and forced to w_t = u_t on every target of the four complex words checked (#531; #569 width-7 and four-cold; #571 combined). | checked (`c3_gate0`, `c3_taint`) | +| C.4 | Lemma Q: a degenerate-label unit is dominated by its quotient (copy cost and window), so degenerate spans give no new (w, x) points. | proof argued; toys and families checked (`c4`) | +| C.5 | Lemma λ: a phase-frame butterfly needs λ = ±i (and 2 invertible) in its payload ring; b < c is the price of √−1, and the outer already uses shift twiddles. | argued; instances checked (`c5`), anchors (`b`) | +| C.6 | At the E8 decoder pattern with window ≤ 9 the label pairing B = W·Uᵀ is unique (= B0); every W ≠ U is a gauge. Window 8 is impossible. | checked (`c6`, SAT without proof logs) | +| C.7 | A rank-r descent costs 2r data mass in every call (−5.63% c on #555, −5.65% b on #568 per rank-1 descent); within that price class a schedule with a paid descent is dominated. | toy and prices checked (`c7`); dominance argued | +| C.8 | Splitting one helper residual across two hosting streams meets a trace obstruction under every invertible storage change; uncharged, it is worth at most +0.67% b on the current complex frontier. | argued | +| D | Design-free helper floors in the drop-free monotone-chain model: bit ≥ 212 (sink-only targets) / 138 (strict) vs 439 shipped; complex ≥ 107 / 0 vs 496. Coupling the levels adds 0. The "224 carriers" complex floor is a design choice, and carriers are register-neutral in the calibrated ledger. | checked (`d1`–`d3`), given the argued crossing lemma | +| E | Complex helper price by class and width on #569 (0.039–0.140% b per helper at k = 1; the hosting offset depends on width only), and the moment-cost census by class on #555 and #569. | k = 1 prices, control and census checked (`e1`, `e2`); k > 1 rows recorded | +| F | Cost structure: a change paid in every local word must buy about 2.8% of the supplier value per rank unit; open problems: per-word enablers, a non-star decoder, the schedule/ρ freedom, new skeletons. | conclusion from checked prices | + +Earlier statements this package relies on: Theorems A–C of #446 (DreamingOfClouds, open; content-frame invariant, +stage-window additivity, pairing-rank copy floor). They are cited as premises and are not re-proved here. #446 states +Theorem B for rank-1 labels (every number in this package is at k = 1), and its Theorem C value 72 for the bit E8 +unit is computed there and not replayed here. + +## A. Format ceiling, rev. 2 + +### A.1 Scope S2 (accounting axioms) + +A word is a B_K compressed-window block for one supplier (bit or complex), replicated T times, with s = 2K + 1 +stages, v ports of label rank k and stage window h, copy rank cst per stage, and any helper network (any R, widths, +local programs), any banking or hosting rule and any halving depth. Writing z_r for the number of paid children of rank +r and g_q(r) = r((m/r)^q − 1): + +- **(P) pressure identity.** The supplier saving a is the root q of Σ_r z_r g_q(r) = D. (The hub pricers add a + nonnegative fallback term, which only lowers their root.) +- **(Dfc) deficit.** D = T[(s − 1)vk − s·cst], with cst ≥ cst_min(U), the star floor of the unit U (the pairing-rank + floor of #446 Theorem C holds for any copy tree; the star form β_j ≥ k·w_in is the scope assumption). +- **(Win) window.** Every data register climbs a ≥ h − k per stage and m = k + s·a (#446 Theorem B gives + m ≥ k + s(h − k); #386's compressed windows attain it). X is non-increasing in a, so a = h − k is the extreme case. +- **(Sch) B_K schedule.** Stage j activates one bank pair (2 data registers per port per replica); pair 0 is active at + stages 0, 1, 2 and pair p ≥ 1 at 2p + 1, 2p + 2. Idle frame changes per copy (2v copies): pair 0 has [(2K − 2)a]; + pair p ∈ 1..K − 1 has [2pa, a, (2K − 2p − 2)a] (a bridge read splits the pre-stretch). +- **(Bat) batching.** A paid child executes part of one frame change Δ and has rank ≤ rank Δ (Δ is a register's + frame change, or, as in #448, one idle change across the copies of one idle child). Local children have rank ≤ a + (data registers) and ≤ h (helpers, copies). +- **(Hst) hosting is accounting.** A hosted helper child is a local child (rank ≤ h) that executes coordinates of an + idle frame change; the rest of that change is paid in children of rank ≤ its rank; an unhosted helper lives on a bank + wire. + +### A.2 Theorem and proof sketch + +Per port (v and T cancel), the **forced slots** are: the active climbs (mass 2sa, cap a), the copy drops (mass s·x, +x = cst/v, cap h) and each idle frame change of rank r of the schedule (mass 2r, cap max(r, h)). Let +F_s(q) = Σ_slots mass · ((m/cap)^q − 1) and D_s = (s − 1)k − s·x. + +**Theorem.** For every word in S2 on a unit U, a ≤ X(s, h/k, x_U) := the root q of F_s(q) = D_s, with +x_U = cst_min(U)/v(U). Since e^y − 1 ≥ y, also X ≤ D_s/Cost_s with Cost_s = Σ_slots mass · ln(m/cap). + +*Proof sketch.* (1) Monotonicity: each g_q(r) increases in q for r < m, so a(z, D) ≤ a(z', D') whenever f_z ≥ f_z' +pointwise and D ≤ D'. (2) Slot domination: by (P) the paid mass executes every coordinate of every forced slot; by +(Win), (Sch), (Bat) and (Hst) every child touching a slot has rank ≤ its cap; φ_q(r) = (m/r)^q − 1 decreases in r, so +splitting each child's mass over the slots it touches gives f_N(q) ≥ F_s(q; cst_N); children touching no slot +contribute ≥ 0. (3) Helpers never shift D or m (call this Lemma H): Theorem B bounds m with target frames only; a bank wire is restored +scratch, so its saving is ≤ 0; a data register saves ≤ k whoever executes its coordinates; copies cost ≥ cst_min for +any tree. X is non-increasing in a (d/da of each slot term is ≥ 0 by Bernoulli's inequality). The cap max(r, h) on +idle slots absorbs the one place where hosting lowers paid cost (a width-h helper child on a single-stage idle slot); +it costs 1% against the literal R = 0 profile. (4) F_s increases and D_s decreases in cst, so cst_N may be replaced by +cst_min. Apply (1). + +**General k (argued; no number here uses it).** Scaling every rank by k (a = k(w − 1), h = kw, m = k(1 + s(w − 1)), +every slot mass and D multiplied by k at fixed x/k) leaves each ratio m/cap unchanged, so X depends only on +(s, w = h/k, x/k). Every number in this package is at k = 1. + +**κ.** The outer assembly seeds s0 = min(c, (1 − β)b) − 10⁻²⁴ and bootstraps x ↦ (1 − s0)s0 + s0·x from below; the map +is increasing with fixed point s0, so κ ≤ X/(1 + X) with X = min(X_bit*, X_cx*), each side at its own best s. + +### A.3 Numbers (certified; `a1_ceiling_roots.py`, `a2_ceiling_sup.py`) + +| unit | x | s = 3 | **s = 5** | s = 7 | s = 9 | s = 11 | s = 13 | +|---|---|---|---|---|---|---|---| +| bit, 111 live lines, decoder constant 63 (#545, #555), h = 8 | 0.5676 | 5.9474e-3 | **6.0005e-3** | 5.2095e-3 | 4.6072e-3 | 4.1490e-3 | 3.7879e-3 | +| complex, 112 ports, copy cost 64 (#523 → #569), h = 9 | 0.5714 | 5.0474e-3 | **5.1863e-3** | 4.5130e-3 | 3.9946e-3 | 3.5989e-3 | 3.2865e-3 | + +Roots are rational-interval brackets of width < 10⁻¹⁵; the upper end is the certified value. + +- **κ ≤ 5.1594938·10⁻³ < 5.1595·10⁻³**; the complex side binds. The bit-only corollary is c/(1 + c) ≤ 5.9647·10⁻³. +- **The supremum over s is at s = 5** on both units. For odd s = 7..401, the certified D_s/Cost_s stays below X(5); + the largest values are 5.2311·10⁻³ (bit) and 4.5292·10⁻³ (complex), both at s = 7. For s ≥ 403 a tail bound applies: + Cost_s/v ≥ 2Σ_{p 2Ka), and + Σ_{p=1}^{K−1} p ln(K/p) ≥ K²/4 − K/e − K/2. Then X ≤ D_s/Cost_s < (1 − x)s/(4a(K²/4 − K/e − K/2)), which is + decreasing in K and equals 6.27·10⁻⁴ (bit) / 5.44·10⁻⁴ (complex) at s = 403. + - *Proof of the inequality (erratum to rev. 1, which invoked "trapezoid error for a concave function", the wrong + direction).* f(p) = p ln(K/p) is concave, so by Hermite–Hadamard f(p) ≥ ∫_{p−1/2}^{p+1/2} f. Summing gives + Σ_{p=1}^{K−1} f(p) ≥ ∫_{1/2}^{K−1/2} f = K²/4 − ∫_0^{1/2} f − ∫_{K−1/2}^K f. Both removed pieces are small: + the first is (1/8)(ln K + ln 2 + 1/2), and the second is ≤ (K/2)·ln(K/(K − 1/2)) ≤ K/(4K − 2) ≤ 1/3. Their sum is + ≤ K/e + K/2 for every K ≥ 2. The script also checks the inequality numerically for K = 2..5000. +- **Frontier against the ceiling at s = 9.** X(9)/c = 2.658 for #555 (c = 1.733053·10⁻³), and X(9)/b = 2.499 for + #568 (b = 1.598685·10⁻³). +- **Copy charge.** One copy-rank unit per stage (bit cst 63 → 62) raises X by +3.90% (s = 5), +2.82% (s = 9) and + +2.65% (s = 11). + +### A.4 The axioms on the frontier words (`a3_axioms.py`, checked) + +| check | words | result | +|---|---|---| +| (P): certified root of the published global profile at the word's (m, D) vs its published value | #510 R480 s9, #543 R420 s9/s11, #545 p111 s11, #555 p111 s9/s11, #568 | root ≥ published; relative difference 1.2–1.7·10⁻⁹ on the bit pipelines (consistent with their nonnegative 10⁻¹⁶ fallback term), 5·10⁻¹⁸ on #568 | +| (Dfc): m·W − rank mass = T[(s − 1)v − s·cst] with integral W | same seven profiles | equality (e.g. #555: 64·28416 − 1,808,352 = 10,272; #568: 73·7168 − 520,704 = 2,560) | +| (Win): m = k + s(h − k); largest child = literal idle remainder (2K − 2)a | same | m = 64/78/73; largest child 42/56/48 on #543, #545, #555, #568 (#510's largest child is 8) | +| (Sch): active + copies + idle slot mass = rank mass | #555, #568 | 447,552 + 18,144 + 1,342,656 = 1,808,352; 129,024 + 4,608 + 387,072 = 520,704 | +| (Sch): idle windows per replica = idle_slots(K = 4, a = 8) on both banks | #568 PLAN, 8 replicas | {8: 6, 16: 4, 32: 4, 48: 4} on every replica | +| (Hst): helpers hosted whole, no banks | #555 | bank assignments 0; hosted residuals 126,432 = 439·288 | +| Lemma 2 on a grid: min of f_N(q)/F_s(q; cst_N) over nine values of q in [10⁻⁵, 1] | all seven | 2.29–2.66 (≥ 1) | + +**(Bat) at child level.** #555's helper runs have rank ≤ 5 ≤ h (checked by the replay in `a4_gap.py`). #568's helper +children have rank ≤ 6 ≤ h (its implied helper histogram in `a4_gap.py`). #568's README (commit 71c99ac, +research/complex-multicold/README.md:22) reports one whole owner for all 36,288 helpers, no auxiliary banks and 27,679 +cross-replica roles; a cross-replica role hosts helper children of one idle change at rank ≤ h, so it falls inside +(Bat)/(Hst) (argued). No structural axiom is violated by #555 or #568. + +### A.5 Gap decomposition at s = 9 (`a4_gap.py`, checked) + +| step | #555 bit | #568 complex | +|---|---|---| +| A: theorem skeleton (R-free; idle slots at cap max(r, h)) | 4.6072e-3 | 3.9946e-3 | +| B: literal R = 0 (each idle slot one child of its own rank) | 4.5661e-3 | 3.9631e-3 | +| C: B with the real data-register runs and copy children | 3.7868e-3 | 3.1544e-3 | +| D: C plus every helper run paid unhosted (the only step with more rank mass) | 1.6183e-3 | 1.3517e-3 | +| E: the real word (helpers hosted whole, idle remainders paid) | 1.7331e-3 | 1.5987e-3 | + +Shares of ln(A/E) (0.978 / 0.916): + +| component | #555 | #568 | +|---|---|---| +| helpers | 87% | 93% | +| data fragmentation | 19% | 25% | +| literal vs cap | 1% | 1% | +| hosting | −7% | −18% | + +Steps A, B, C and E have the same rank mass, m·W − D (1,808,352 and 520,704). + +Inputs: + +- **#555.** Data, helper and copy runs are replayed from the published word. The replay matches #555's local + histogram, and the helper mass is 8R. The idle remainder is the published profile minus 288 × the local profile; + 75.3% of the skeleton idle mass is hosted. +- **#568.** The data-register runs per call come from #569's four-cold provider word (the same x/y ladders). The paid + idle remainder {1: 93, 2: 93, 3: 2118, 7: 1, 48: 1713} is **recorded** from our replay of #568's hosting + (`data/ceiling/gap568-inputs.json`). Its 4,018 children match the "4,018 positive idle remainders" of #568's README + (checked), and the implied helper histogram is nonnegative (checked). + +The headroom under S2 is therefore almost entirely helper cost. Even with zero helper cost and real data +fragmentation, the words would sit 2.19× (bit) / 1.97× (complex) above today. + +### A.6 The superseded numeric scope of (Dfc) + +Rev. 1 of this ceiling fixed cst_min = 72 and x = 3/5 (the full 120-line E8 unit) on both sides. That gave X_bit* = +5.1598·10⁻³, X_cx* = 4.5356·10⁻³ and κ ≤ 4.5151·10⁻³ (checked, `A1.rev1-superseded`). + +The punctured units that the frontier now uses have smaller x: + +- bit: #545/#555 keep 111 live lines with decoder constant 63; +- complex: the #523 lineage has 112 ports with copy cost 64. + +So x = 63/111 = 0.5676 and 64/112 = 0.5714. With x_U taken from the realised units the ceiling is §A.3's +κ ≤ 5.1595·10⁻³. The structural axioms are unchanged. + +Status of cst_min per unit: + +| unit | cst_min | status | +|---|---|---| +| bit 120/72 | 72 | proved given #446 Theorem C: the F2 decoder of the E8 pattern needs 9 totals (its F2 minrank is 9; c = 8 is UNSAT, checked in `c6`), and each star total costs β = 8, the Gram rank of an 8-line in-cap clique (premise, not replayed) | +| bit 111/63 | 63 | the star floor among punctures of p ≤ 25 lines: Δcst ≤ min(p, 9), from an exact matroid-greedy search over the 9-dimensional code that this package does not reproduce (**argued**, not checked here). For 26 ≤ p ≤ 52 only the total D gain s·Δcst − (s − 1)p ≤ 9 is bounded, which does not pin x. | +| complex 112/64 | 64 | 8 × 8, the star floor at w_in = 8, attained (**argued**) | + +A unit with smaller x would move X by about +2.8% per copy-rank unit at s = 9 (§A.3). For scale, two units with +smaller x that no word realises are checked as well (`A1.outside-scope`): bit x = 54/102 gives X* = 8.2457·10⁻³ and +complex x = 56/104 gives X* = 6.8000·10⁻³, both at s = 3. They lie outside the realised-unit scope of the theorem. + +### A.7 What each scope rule is worth (`a5_scope_multipliers.py`, checked) + +Multiplier of the ceiling if one rule is dropped. Each cell is best-s multiplier / s = 9 multiplier. + +| rule dropped | bit 111/63 | complex 112/64 | +|---|---|---| +| star scope (copy charge x → 0) | ×6.77 / ×2.79 | ×6.91 / ×2.83 | +| B_K idle frame changes free | ×1.67 / ×2.14 | ×1.68 / ×2.15 | +| all idle changes of a copy in one child | ×1.19 / ×1.55 | ×1.20 / ×1.55 | +| bridges off the idle stretches (no split) | ×1.15 / ×1.18 | ×1.15 / ×1.18 | +| data local climbs batched to h (cross-register) | ×1.12 / ×1.03 | ×1.08 / ×1.02 | +| idle free and local batching together | ×1.79 / ×2.28 | ×1.79 / ×2.27 | +| family window one smaller at the same x | ×1.16 / ×1.16 | ×1.14 / ×1.14 | + +Hosting cannot raise the ceiling (step (3) of the proof in §A.2). Full relaxation of (Bat), with arbitrary batching up to rank m, +would give no bound, because rank-m children cost 0. + +**Not covered:** + +- non-B_K schedules (a different number of active pairs per stage changes (Sch) and (Dfc)); +- non-star decoders (totals with β_j < k·w_in), and units with smaller x than the realised ones (see A.6); +- labels outside the cross-field unit; +- any change to (P), such as children of rank larger than their frame change, or a new outer assembly. + +## B. Spec versus convention census + +`b_census_anchors.py` checks every file:line anchor of the rule table (B.2), 73 in all, and the four corrections +marked "checked" in B.3. Line numbers are as of the base commit 7dfa22b. Each anchor's quoted text must lie within two +lines of the cited line, except in docs/research/ (the hub's ledgers and reviews, which main rewrites often), where it +is matched by text anywhere in the file. The line ranges cited in the prose of B.1 and B.3 were read by hand at +7dfa22b and are not machine-checked. Paths are relative to the repository root, and `U/` is +`upstream/build/sections/`. The classification is argued: + +- **S**: mandated by the upstream paper or interfaces; +- **P**: proved necessary given the cited S/C rules; +- **C**: a convention of the paper's instance or of the hub's checkers and templates, changeable if the replacement is + proved. + +### B.1 What upstream imposes + +- **Bit contract** (the only bit-side interface), U/04-swap.tex:105-123. A finite circuit of W wires whose pointwise + bit gates induce a *permutation* ρ. There is one rational m×m matrix per terminal and per gate, common to every wire + at that gate. The cost is s = Σ rank_Q(M_head − M_tail), with M_out(ρ(w)) − M_in(w) = I_m on every role and s < Wm. + The recurrence is F_k ≤ (s/W)F_{k−1} + O(1) (:197-211). No subspaces, nesting, banks, pairs or nondegeneracy appear. +- **Complex interface**: + - frames C_U = H^m diag(i^{q_U}) H^m, with q_U(x) = wt(P_U x) mod 4 for nondegenerate binary U (U/03-motifs.tex:528-561); + - an edge is a product of one kernel (aI + bX_v)^{±1} per vector of an orthonormal basis of the residual of + comparable labels (:568-583); + - endpoint C^{⊗m} on every role after free per-role corrections (:579-582, :641-668; U/05-layers.tex:127-154); + - the implementation P_M conjugation needs only invertible M (U/05-layers.tex:86-103). +- **Common-frame identity** for any invertible D_v: U/03-motifs.tex:154-186. +- **The paper's instance**: + - nondegenerate nested labels (U/03-motifs.tex:199-208, 351-364); + - terminal labels X_a: U_a → F, Y_a: 0 → U_a^⊥ (:235-243); + - the signed exchange of X_a and Y_a (:89-100); + - arbitrary, restored scratch (:34-46). +- **Outer.** Two suppliers are composed through 08-assembly.tex (:16-48, κ at :849-866, :995-1000). The transforms + are Nussbaumer synthetic transforms whose roots are signed shifts (U/00-introduction.tex:44-56, U/06-transforms.tex). +- **The maintainer's interface list**, docs/research/community-pr531-review.md:116-125: + - all-size compilation; + - common-label and mutable-basis realization; + - normalized residual wrappers as uniformly priced operations; + - whole-call correlated-helper hosting; + - compressed windows and restored rows; + - grids, tape, precision; + - the analytic transfer. + + The same review of #531's integration says the fixed-word ceiling and family-optimality discussions it carries + were "not independently certified" (:130-134). + +### B.2 Rule table + +Multipliers are from §A.7 (best s / s = 9, bit unit unless stated). + +| # | rule | class | anchors (file:line) | evidence and history | ceiling multiplier if relaxed | +|---|---|---|---|---|---| +| 1 | Common frame at every gate | S | U/03-motifs.tex:157; U/04-swap.tex:111; legality_h.cpp:11; audit_local.py:64; LemmaG/Stream.lean:106 | never changed; an ADD across frames needs an adapter edge priced d(f, g) | 1.00 | +| 2 | Frames are nondegenerate subspaces | spec: any invertible D_v (S); nondegenerate subspaces: the paper's instance (C) | U/03-motifs.tex:154, :203; U/04-swap.tex:110; gram_frames.py:46; notes/general-clifford-frames.tex:21 | #130's general frame lemma covers arbitrary (also degenerate) binary subspaces, d = dim U + dim V − 2 dim(U ∩ V); main has its TeX note and Python checks, its Lean proof is in an external repository (a conditional interface); degenerate labels are dominated by their quotient (C.4) | none by itself (only through x) | +| 3 | Nesting: climbs along a register's life; drops only as copies or centre steps | C (priced, not forbidden) | U/03-motifs.tex:352 (the paper has decreasing edges); U/05-layers.tex:83; legality_h.cpp:11; audit_local.py:45, :128; LemmaG/Stream.lean:111 | #425's full → 0 centre copies; a non-nested step is exact and costs d(f, g) (C.1, C.7) | 1.00 (two nested edges) | +| 4 | Exact restoration of helpers and dirty registers | S (same endpoint operator on every role; scratch arbitrary); "ρ fixes scratch" is C | U/03-motifs.tex:46, :98, :581; U/05-layers.tex:150; columns_k.cpp:57; audit_local.py:101 | no PR permutes scratch | 1.00 | +| 5 | ρ = signed exchange of X_a and Y_a, pairwise | C (any permutation ρ is legal) | U/04-swap.tex:107; U/03-motifs.tex:99, :628; bit_global.py:55; columns_k.cpp:56 | never varied | not quantified | +| 6 | Terminal labels X_a: U_a → F, Y_a: 0 → U_a^⊥; targets inside u_t^⊥ | C (the paper's choice; the ⊥ relations then follow from #446 Thm A) | U/03-motifs.tex:235, :240; U/05-layers.tex:128, :146 | complement sinks are legal at zero cost but forced on every current decoder (C.3) | 1.00 on current decoders | +| 7 | Rank-1 labels (k = 1) | C | U/03-motifs.tex:233 | X depends only on (s, h/k, x/k) (§A.2) | through (w, x) | +| 8 | Star copies (cst = Σβ_j, β_j ≥ k·w_in) | scope assumption of the ceiling; the pairing-rank floor itself is proved for any copy tree (#446 Thm C) | legality_h.cpp:1; columns_k.cpp:39 | every PR's totals are star | ×6.77 / ×2.79 (complex ×6.91 / ×2.83); +2.8% per copy-rank unit at s = 9 | +| 9 | E8 cross-field unit (h = 8 bit, 9 complex) | C (family) | U/03-motifs.tex:221, :225 ("the scalar domain need not equal the label field") | — | window one smaller: ×1.16 (complex ×1.14) | +| 10 | Window m = k + s(h − k) | P (#446 Thm B) | — | attained by #386 | 1.00 in model | +| 11 | Deficit D = T[(s − 1)vk − s·cst] | P given rows 8 and 12; spec form D = mW − s | U/04-swap.tex:122; U/03-motifs.tex:360 | equality on seven profiles (§A.4) | through rows 8, 12 | +| 12 | B_K schedule (one dependency chain, alternating locals, bridges, idle children) | P given row 13 | bit_global.py:49; columns_k.cpp:46; Work__Bridge__Net.lean:56 | no other schedule on record | idle free ×1.67 / ×2.14; one idle child ×1.19 / ×1.55; no split ×1.15 / ×1.18 | +| 13 | One (X, Y) bank pair per invocation (LOCAL = 2V + R) | C | bit_global.py:12; columns_k.cpp:31; Work__Bridge__Net.lean:55 (one I : Inv for all stages) | no local on two pairs on record | feeds row 12 | +| 14 | (Bat): a paid child executes one frame change | axiom of the ceiling: one register across a local/bridge is the equal-frame rule; cross-register batching is open | docs/research/pr-ledger.md:403, :405 (#448 and #451 unreviewed) | #451's trace obstruction to #448's split residuals (C.8) | local batching ×1.12 / ×1.03; with idle free ×1.79 / ×2.28 | +| 15 | Whole-residual hosting | accepted conditional interface | community-pr531-review.md:122; idle-axis-hosting/PROOF.md:43 | #442 accepted; split residuals disputed (C.8) | 1.00 (Lemma H, §A.2) | +| 16 | One owner per stream (#458) | P inside #442's stream model | LemmaA/Realize.lean:95 (`one_owner_needed`) | relaxed to disjoint footprints | 1.00 | +| 17 | Source registers never destinations | C (#370's checker assertion) | e8-bit-carrier-skeleton/README.md:67-68 | sufficient, not necessary (#425); relaxed by #433, #493 | already taken | +| 18 | Payload bounds (2^104 bit, 2^192 complex prefix, 2^80 prime witnesses) | C, never binding | columns_h.cpp:25; columns_k.cpp:34; finite_check.py:98; audit_local.py:103; gram_frames.py:17 | — | 1.00 | +| 19 | F2 payload coefficients | C | columns_h.cpp:27 | — | 1.00 | +| 20 | Replication T | C | bit_global.py:11; finite_check.py:86 | #505 asynchronous replicas | 1.00 (T cancels) | +| 21 | 47-inequality outer assembly | structure S, constants C | outer.py:27; U/08-assembly.tex:37; assembly-domain-audit.md:21 | the 1/32 domain cap is conservative and not binding | 1.00 | +| 22 | κ is the composition of the two suppliers | S | U/08-assembly.tex:37; outer.py:29, :30 | — | — | +| 23 | Two suppliers: complex butterfly layer, bit address shear | S | U/00-introduction.tex:48, :56, :118; U/06-transforms.tex:12 | shift twiddles are already in the outer (C.5) | — | +| 24 | Pressure identity (a rank-r child priced as a fraction of a call) | inherited conditional ("normalized residual wrappers as uniformly priced operations") on the paper's rank-1 recurrence | U/04-swap.tex:197; community-pr531-review.md:122 | — | defines the ceiling | +| 25 | Bridges X → X and Y → Y only | P (X wires contain q, Y wires do not) | columns_k.cpp:43 | — | — | + +Abbreviations: + +- `legality_h.cpp`, `columns_h.cpp`, `columns_k.cpp` and `gram_frames.py` are in + research/e8-bit-carrier-skeleton/code/bit/checkers/; +- `audit_local.py` is research/direct-delivery504-global/code/audit_local.py; +- `bit_global.py` is research/e8-early-reset-kernel/bit_global.py; +- `Work__Bridge__Net.lean` is in research/five-stage-gen4-banks/inputs/complex/sources/; +- `finite_check.py` is research/five-stage-gen4-banks/finite_check.py; +- `outer.py` is research/e8-bit-carrier-skeleton/code/pricing/outer.py; +- `LemmaG/`, `LemmaA/` are under research/e8-lemma-g-lean/ and research/e8-lemma-a-lean/; +- `community-pr531-review.md` and `assembly-domain-audit.md` are under docs/research/. + +### B.3 Corrections, as the census found them + +1. **The bit interface is the shear contract alone** (U/04-swap.tex:105-123). Subspace labels, nesting, Gram + orthogonality, bank pairs and the pair exchange are construction-layer choices of the paper's instance + (:199-243, 351-364), not interface requirements. +2. **Nesting is priced, not forbidden.** + - The paper itself has decreasing edges (U/03-motifs.tex:352-353). + - The paper counts kernels on decreasing edges (U/05-layers.tex:83). + - #130's frame lemma prices non-comparable labels by d(f, g). + - The checkers enforce nesting as a pricing rule. +3. **The bit legality checker** keeps its MOVE (nested and exactly priced), ADD (common frame) and COPY-lifecycle rules + on legality_h.cpp:11; line 10 holds the containment helper and the owner/helper selection (checked). +4. **The invariant `Inv`/`GInv`** used by the complex five-stage bridge is imported (`import Work.Bridge.Inv`) and + defined nowhere in main (checked). "One X bank and one Y bank per invariant" therefore rests on its use in + `Bridge2` and in bit_global.py / columns_k.cpp, not on a definition in the repository. +5. **#370's assertion "x registers are never destinations"** is a sufficient, not a necessary, condition (#425's + scalar-interface lemma; #493's README) (checked). +6. **The outer already uses shift twiddles** (Nussbaumer synthetic transforms; U/00-introduction.tex:44-50); the + complex supplier is the H_0 butterfly layer (:114-121). A ring change does not remove it (C.5). +7. **Complement sinks** (sink = any complement of the source label, w·u = 1) are not covered by the earlier phase-frame + null result: the sink stays a plain label with a new projection. They are legal (C.3, gate 0), and Lemma R closes + them on every current decoder. +8. **The only untested spec freedom with a ceiling term ≥ ×1.15** is the schedule/ρ family (rows 5, 12, 13). The + large multipliers (×6.8 copy charge, ×1.16 window) are scope and family terms, not rules. + +Caveats of the census itself: + +- "Sink = any complement of the source label" (row 6) was the census's own reading of U/05-layers.tex:145-148; it is now + settled by C.3. +- "A plain swap of two scratch roles cannot gain" (row 4: shared gate frames give ≥ 2m on the swapped pair) is the + census's own argument and is not re-derived here. +- #448 and #451 are unreviewed in the hub's ledger, so the "conceded" status of split-residual hosting rests on the + contributors' own record. + +## C. Structural lemmas + +### C.1 Frame sandwich and the complex frame walls (argued; toy `c1_sandwich_toy.py` checked) + +**Lemma (sandwich).** Let R be a register at time t, and consider paths that carry no priced drop. Then + + span{frame(r, t′) : r's content reached R drop-free by t} ⊆ frame(R, t) ⊆ (span{L_q : target q that R's content reaches drop-free after t})^⊥. + +*Proof sketch.* The lower side is #446 Theorem A, applied with any register as origin. For the upper side, a target q +ends at L_q^⊥ because its terminal label absorbs the port gate (T_{u^⊥} = F·C_u^{−1} up to monomials). Chains are +monotone, and a copy taken at t sits at its parent's frame (common frame at the copy gate), so it inherits the +parent's future. Exactness uses neither side: both are accounting. + +**Consequences** on the complex unit (labels in F_2^9, 112 ports): + +- **(W1)** The common station of a term whose readers span u_a^⊥ is the point ⟨u_a⟩. A cut shared by all readers of a + term is therefore one point per term. +- **(W2)** A copy serving a face of four targets of rank-3 span has frame dimension ≤ 6. A group of eight copies whose + faces span rank 4 has a common frame of dimension ≤ 5. +- **(W3)** Run backwards on the cleanup, the lower side gives end(X) ≥ the join of the frames of every register that + X's restoration reads. + +**Leaving the sandwich is legal and priced.** The toy (n = 4) puts three readers whose labels span u_a^⊥ at a station +⟨w⟩ outside every u_i^⊥ and then takes a non-nested step to u_i^⊥: + +- both networks are exact on 64/64 basis columns; +- every child's Clifford rank equals d(A, B) = dim A + dim B − 2 dim(A ∩ B), including the non-nested rank-4 steps; +- the charge is 18 = 13 + 3·2 − 1 against the standard network's 13. + +Relaxing "targets avoid u_t" costs +2 data mass per relaxed target per call (C.7 prices this). + +### C.2 Lemma N: the port factor is a gauge, always paid (`c2_port_gauge.py`) + +**Setting.** C_v = ((1 + i)I + (1 − i)X_v)/2, so C_v² = X_v. The full transform is F = Π_{w ∈ orthonormal basis} C_w, +and T_{q^⊥} = F·C_q^{−1} (#514's repeated-axis identities). In the hub's controller a source family runs +⟨q⟩ → … → F and a target family runs 0 → … → q^⊥. Each data family saves exactly one unit (m − 1 instead of m), which +is the C_q gauge: (s − 1)vkT = 8·112·8 = 7168 on the complex side, of which copies take 4608 and D = 2560 remains. + +**Lemma N.** For a parent child Z = C^{⊗A}, + +- rank(C_q^{±1}Z) = rank Z − 1 if q ∈ span(A) with odd support; +- rank(C_q^{±1}Z) = rank Z + 1 if q ∉ span(A) and q ⊥ A. + +**Theorem N (proof).** With d(f, g) = dim f + dim g − 2 dim(f ∩ g), any path f0 → G → f1 costs at least d(f0, f1), +with excess + +- **target** (0 → q^⊥): d(0, G) + d(G, q^⊥) − d(0, q^⊥) = 2(dim G − dim(G ∩ q^⊥)) ∈ {0, 2}, which is 0 iff G ⊆ q^⊥; +- **source** (⟨q⟩ → L): 2(1 − dim(⟨q⟩ ∩ G)) ∈ {0, 2}, which is 0 iff q ∈ G. + +(dim G − dim(G ∩ q^⊥) ≤ 1 because q^⊥ is a hyperplane.) + +The route T_{fY}·T_{fX}^{−1} equals F modulo monomials *only if* dim fX + dim fY = n and fX ∩ fY = 0. The source's +saving and the target's saving are therefore tied: un-labelling one side alone leaves a rank-1 mismatch on the pair. + +**Consequence.** "y avoids u_t" is the geodesic condition of the endpoint frames, not an artifact of where the +factor is paid. Moving the rank-1 factor to the parent costs at least d(0, L) − d(0, q^⊥) = 1 per family. Leaving +the interval costs exactly 2. + +**Checks (exact Clifford).** + +- The F2 rank of B = Σqqᵀ equals the tableau child rank on 240 random C-products (n = 4..7). +- Detour excess is 0 on the geodesic set and exactly 2 otherwise: + + | n | geodesic | off-geodesic | + |---|---|---| + | 3 | 20 | 44 | + | 4 | 128 | 408 | + | 5 | 1072 | 4912 | + | 6 | 3740 | 24510 | + + The source and target families give the same counts. +- The rigid pairs at n = 3, 4 are a subset of the complementary pairs. The converse fails: complementary pairs need + not be rigid. +- The gauge descent rank − 1 holds in 9/9 controls. + +**Not reproduced here.** A run on the 112 retained ports of the #523 controller found rank(C_q Z) = rank Z + 1 on +2016/2016 idle children and 2016/2016 stage climbs (no parent object absorbs the gauge). It needs the controller's +generated data-phase files, which this package does not ship, so it is reported, not checked. + +### C.3 Lemma R: complement sinks are legal but pinned + +**Legal (gate 0, `c3_complement_gate0.py`, checked).** For an odd w with w·u = 1, the route T_{w^⊥}·T_{⟨u⟩}^{−1} has +Clifford rank n: + +- for every complement w at n = 3..6, and on 40 seeded samples at n = 9; +- it equals C^{⊗n} after monomial wrappers of the same class as the standard sink, on the tableau and on every basis + column (n ≤ 6); +- non-complements (w·u = 0) give rank n − 2. + +The port price is unchanged: Y: 0 → w^⊥ and X: ⟨u⟩ → F both cost n − 1. The geodesic interval is G ⊆ w^⊥, with +excess exactly 2 outside it. A corrector that the standard sink forbids is exact and correctly priced. So the sink +stays a plain label with a new projection, and the port gate stays a gauge (C_w instead of C_u). + +**Lemma R.** If the sources whose content reaches target t drop-free span u_t^⊥, the only admissible complement sink +of t is w_t = u_t. + +*Proof.* By #446 Theorem A the sink V_t = w_t^⊥ contains u_t^⊥, and V_t is a hyperplane. + +**Pinned (`c3_complement_taint.py`, checked).** The checks run on four complex words: main's #531 word, the width-7 +and four-cold provider words of #569, and the combined-eight-local word of #571. The closure starts every helper at frame 0, the most permissive choice, +so the result holds for every frame assignment of the same scalar word; the words' own entrance frames are not +needed (on the three vendored words the closure with them gives the same result). + +- The most permissive lower closure (helpers enter at 0; every centre may drop to 0 at the cut) has dimension 8 on + 112/112 targets, so w_t = u_t is forced on 112/112. +- The pure drop-free source taint of every target is exactly its Gram-orthogonal neighbourhood: 52 sources, span + u_t^⊥. Phase A contributes nothing to any target. +- Any w ≠ u_t breaks at least 20 of t's 52 lossless corrections (112/112 targets). + +A positive control removes the 4 correction gates of one target. The closure then reports dimension 7 and 2 +admissible sinks, so the instrument sees freedom when it exists. + +On these four words the obstruction is the decoder's lossless pattern, not the endpoint rule. A decoder whose pairing is not symmetric +would be needed, and C.6 shows the E8 pattern admits none at window ≤ 9. + +### C.4 Lemma Q: a degenerate unit is dominated by its quotient (`c4_quotient.py`) + +**Setting.** The ports span W (dim d), with radical rad = W ∩ W^⊥ (dim ρ) and Gram rank r = d − ρ. Write +π: W → W/rad. + +**Lemma Q.** + +- A copy of total j read at a common cut g (g lies in every target sink, so WLOG g ⊆ rad) costs + d(f_j, g) ≥ dim π(f_j). +- Summed over j, this is the quotient unit's reads-at-0 cost, which is ≥ Σ_j rank G[S_j, R_j] (#446 Theorem C at + Gram level). +- Every stage window contains W and is nondegenerate, so h ≥ r + 2ρ. + +Since X is non-increasing in a and in cst (§A.2), X_deg(s) ≤ X(s, r/k, x_quot) ≤ X(s, r/k, x_ThmC) for every s. +Star scope is Gram-level (an in-cap clique inside S_j has a nonsingular diagonal Gram block in every realisation), so +degenerate spans are not outside the ceiling's scope and add no (w, x) points. + +**Checks (exact ranks).** + +- **The paired-cube p = 3 toy:** d 4, ρ 2; copy cost 18 (reads at 0), 16 (optimum over all flags of rad), 12 + (quotient = Theorem C bound); window 6 vs quotient 2. +- **Paired cubes p = 3..12:** only the complex p = 3 cube is degenerate. +- **E8 + an extra vector (bit):** 81 → 72 = quotient 72, window 10 vs 8. +- **E8 + weight-2 isotropic decorations (complex), ρ = 1, 2, 3:** 80 / 88 / 96 → 72 = quotient, windows 11 / 13 / 15 + vs 9. +- **Random radicals:** 0 violations of "cut cost ≥ quotient cost" on 737 seeded instances. +- **Certified X:** each realisation sits below its quotient. + + | unit | quotient | realisation(s) | + |---|---|---| + | bit | 5.1598e-3 | 4.0462e-3 | + | complex (ρ = 1 / 2 / 3) | 4.5356e-3 | 3.6522e-3 / 3.0570e-3 / 2.6286e-3 | + +**Out of scope.** Repeated-label (vector-linear) decoders are a Gram-level class that Lemma Q does not close. + +### C.5 Lemma λ: a phase-frame butterfly needs √−1 (argued; instances checked by `c5_lambda.py`) + +**Lemma λ.** Let K be a directional kernel defined by its direction alone. If P_N(K ⊗ I)P_N^{−1} = K ⊗ I for the +e₁-fixing N, then K = aI + bX. If moreover H_0 = [[1, 1], [1, −1]] satisfies u·H_0 = D₁(aI + bX)D₂ with D₁, D₂ +invertible diagonal and u a unit, then (b/a)² = −1, i.e. the Walsh eigenvalue ratio λ = (a − b)/(a + b) = ±i. + +*Proof.* The first part is a 2 × 2 symbolic computation (T5). For the second, write D₁ = diag(d, d′) and +D₂ = diag(e, e′). Entrywise, d·a·e = u, d·b·e′ = u, d′·b·e = u and d′·a·e′ = −u. Multiplying the first and last +equations gives dd′ee′·a² = −u², and multiplying the middle two gives dd′ee′·b² = u². Hence a² = −b², so a and b are +units and (b/a)² = −1. With b = ±i·a, λ = (1 ∓ i)/(1 ± i) = ∓i. Writing λ needs a + b = a(1 ± i) invertible, and +(1 + i)(1 − i) = 2, so the ring must also make 2 invertible (H_0 itself has determinant −2). ∎ + +**Consequences.** + +- A payload ring in which −1 is not a square (Z[1/2]; Z/M with a prime factor ≡ 3 mod 4) or in which 2 is not + invertible (F2; even M) carries no phase-frame butterfly supplier. +- In a ring with √−1, such as the Fermat ring Z/(2^N + 1) where i = 2^{N/2} is a shift, the ring hosts the identical + C-network with the same paid Fourier-rank histogram and the same b. +- The odd-weight residuals that the phase calculus needs (U/03-motifs.tex:600) force an odd-form unit. For E8 this + means F2 rank 9 and h = 9, which is the structural part of b < c. +- Bit networks have signed-permutation endpoints per role for any payload alphabet. Each two-point butterfly level at + most doubles row support, so a length-8 transform needs ≥ 3 butterfly levels, with the same Fourier-rank mass as the + complex DFT₈. +- The outer already uses shift twiddles (B.3, item 6). + +**Checks (exact, sympy / modular).** + +- **T1–T3:** the product of three H_0 factors and three monomial twiddles equals the bit-reversed DFT₈. Over Z/17 it is + the length-8 NTT with root 2. Row support 8 needs 3 levels. +- **T4:** brute force over Z/M for the 16 odd moduli M ∈ {3, 5, 7, 9, 11, 13, 15, 17, 21, 25, 29, 33, 37, 41, 45, 65}: + solutions exist iff −1 is a square mod M, and λ² = −1 in every solution. +- **T5:** the commutation forces K = aI + bX. +- **T6:** C² = X and H_0 = b·S·C·S in Z/17 with i = 4. +- **T7:** there is no real solution. +- **T8:** weight-2-mod-4 residuals act as translations. + +### C.6 Asymmetric pairing uniqueness at the E8 pattern (`c6_asym.py`) + +**Setting.** A unit is (N over Q, B = W·Uᵀ over F2): + +- window = rank B (the pairing version of Lemma Q); +- a correction N_TS is lossless iff B_TS = 0 (#446 Theorem A, directed); +- β_j = rank B[R_j, A_j] (Theorem C); +- exactness is N∘B = diag. + +The gauge (U, W) ↦ (UP, WP^{−T}) fixes B, so "W ≠ U" means nothing by itself. A symmetric unit-diagonal F2 matrix of +rank r is congruent to I_r. + +**Result (SAT).** At the E8-linear decoder pattern (B_TS = 0 where G_TS ≠ 0, B_TT = 1), gauge-fixed on 8 +pairwise non-orthogonal lines (where the pattern forces B[K, K] = I, so U_K = W_K = [I_8 | 0]): + +- P112 at c = 9: "B ≠ Bᵀ" and "B ≠ B0 = (J + G) mod 2" are UNSAT with U and W both free; with U fixed to the E8 + labels and W free, "B ≠ Bᵀ" is UNSAT too (the "B ≠ B0" question was not run with U fixed); +- P120 at c = 9: the same two questions are UNSAT; +- c = 8: UNSAT, symmetric and non-symmetric (P120), and non-symmetric on P112; +- calibration: c = 9 without the extra constraint is SAT, with B = B0. + +So at window ≤ 9 the pairing is unique and every W ≠ U is a gauge. The UNSAT answers come from CaDiCaL (through +python-sat) without proof logs; the SAT calibration runs show that each encoding is satisfiable where it should be. + +**Off the E8 pattern.** An exact asymmetric P112 unit exists at copy cost 65 (+1) and window 9: N′∘B = diag on all +112 × 112 entries. Two of its targets take sinks w ≠ u and need 28 corrections instead of 52. In this verified unit one extra copy-rank +unit (65 instead of 64) frees exactly two sinks; no lower bound on the price of a freed sink is claimed. A directed +3-cycle toy (B non-symmetric, rank 2, +exact on 343 inputs) shows that the asymmetric class is larger in general. + +### C.7 Per-call descent pricing (`c7_descent_toy.py`, `c7_descent_price.py`) + +**Statement.** A descent of rank r followed by its re-climb costs exactly 2r rank units in the local word. It +therefore costs 2r units of data mass in every call, which is 2r copy-rank units by the deficit identity. There is no +fixed cost to share between uses. + +**Checks.** + +- **Exact toy (n = 5).** One reader reads three cheap terms at point stations ⟨u_a⟩, ⟨u_b⟩, ⟨u_c⟩, and each term's + readers span u_j^⊥. + - Four networks (standard, clones, direct descents, descents through 0) are exact on 224/224 basis columns. + - Every child, including the rank-2 non-comparable ones, has Clifford rank d(A, B). + - Each rank-1 descent costs +2, whether direct or through 0, and a reader at k point stations pays 2(k − 1). +- **Prices on the published profiles** (certified root, hosting held fixed): one rank-1 descent per local word costs + **−5.630% c on #555** (288 stage-replicas) and **−5.646% b on #568** (72 calls); two cost −11.26% / −11.29%. + +**Dominance within the price class (argued).** In the known designs (the complex factory, face and M-copy ledger and +the bit pair-term and star units; this list is a census of those designs, not a theorem), each use of a descent buys at +most one of the following: + +- one target removed from one last-read antichain (≤ |T_t| ≤ 7 private copies); +- two monotone arms for a copy register; +- two fixed pair-split conflicts; +- one supplied retired row. + +That is at most about 7 helpers' work. One rank-1 descent costs −5.65% b on #568, about 40 width-9 complex helpers +at 0.14% b each (§E.1). So within the class where every non-comparable step is one child of rank d(f, g) +paid in every call, a schedule with ≥ 1 paid descent is dominated by the best schedule without one. + +This is price-class dominance, not an impossibility. It does not cover a descent paid once per word. + +### C.8 Split residuals across hosting streams (argued) + +Split a helper residual of width w = a + b over two streams. #451 (chafreaky) shows that the identity +trace(D_{a+b} + I) − trace(D_a + D_b) = q^{2m−a−b}(q^a − 1)(q^b − 1) ≠ 0 is invariant under every invertible linear +change of storage; there is a complex analogue. So no relabelling makes an uncharged split exact. An exact split +needs one of: + +- a cross-term repair (≥ min(a, b) doubly paid coordinates); +- one helper per piece; +- a new hub primitive that prices split routing. + +The "one further route factor per helper selector" of #448 (higherbeing) is the last kind. + +Even uncharged, the gain is small on the current complex frontier. #569 hosts all residual supply and pays the idle +remainder {3: 1200, 7: 48, 48: 1808} (90,720 coordinates; checked in `e2`). #588 uses the same hosting regime, but +its remainder differs and is not reproduced here. φ(r) = r((m/r)^q − 1) is concave, so with free +splitting the best case puts the whole remainder in rank-48 children: 1890·φ(48) = 60.98 against 78.06 today, a +saving of 17.08 D-units. That is **at most +0.67% b**, and it ignores windows and timing. + +Status: argued (concavity bound on #569's reproduced hosting profile; the profile itself is checked in `e2`). + +## D. Design-free helper bounds (`d1_cover_numbers.py`, `d2_coupled_count.py`, `d3_carrier_ledger.py`) + +**Model.** Registers are monotone frame chains: + +- helpers 0 → F; +- sources ⟨u_s⟩ → F; +- targets 0 → u_t^⊥. + +Gates act at a common frame. The only drops are the decoder copies: 9 totals at p^⊥ (bit), 8 rank-8 centres +(complex). The demand is the drop-free source → terminal map. + +**Crossing lemma (argued; #446 Thm A plus the sandwich).** Fix a level d. Every drop-free path s → q crosses level d +exactly once, inside one register's climb a → b, so N = Σ_e α_e β_eᵀ over the level-d crossing events, with +supp β_e ⊆ S(a_e) and supp α_e ⊆ Q(b_e^⊥). Each register crosses each level at most once. Only the extreme levels +bind: at the middle levels the rectangle areas are below the free data crossings. + +**Cover numbers (exact).** + +| side | per-source cover | per-terminal cover | evidence | +|---|---|---|---| +| complex P112 (F2^9) | 2 | 2 | two proper hyperplane sections cover every source's 52 readers (112/112; max section 32; 26 with port-spanned frames only, still 2 sections) | +| bit #555 (Q^8, E7 = the 63 lines ⊥ a source) | 3 | 3 | exhaustive census of the 8821 hyperplane flats of E7: two cover at most 52 of 63 (≥ 11 left), three D6 flats (30 lines each) cover all; on the terminal side at least 6 live sources stay uncovered by any two flats (all 120 terminals) | + +**Floors.** + +| side | sink-only targets | strict (y → y mixing allowed) | shipped | +|---|---|---|---| +| bit | 2·111 − ⌊111/11⌋ = **212** | max(3·111 − 222, 3·120 − 222) = **138** | 439 | +| complex | 112 − ⌊112/20⌋ = **107** (108 with port-spanned frames) | 2·112 − 224 = **0** | 496 (+ 8 centres) | + +**Coupling adds 0 (checked).** The coupled cover model (hop areas through all levels, exact unit rectangle caps +32/32/32/36/32/32/32 for complex d = 2..8, exhaustive for d = 2..5 and by the symmetry d ↔ 10 − d above; end +patterns; bit flat sizes 1, 3, 6, 12, 20, 36, 63 per rank, enumerated in E7, with maximality in E8 from the +root-subsystem chain A1 ⊂ A2 ⊂ A3 ⊂ D4 ⊂ D5 ⊂ E6 ⊂ E7) has integer count optima 212 / 138 / 107 / 0 (HiGHS, zero gap), +identical to the single-cut bounds. The shipped words are feasible in it. With the shipped data ladders held fixed the +floors rise to 222 (bit) and 146 / 123 (complex). + +**Corrections.** + +- **The census of E7's hyperplane flats.** An earlier sampled census found 2258 flats; the exhaustive census has 8821 + (5040 / 2016 / 1008 / 666 / 63 / 28 of sizes 10 / 13 / 16 / 21 / 30 / 36). The sampled set is reproduced (seed 1) and + undercounts the small types; the cover number 3 is unchanged. +- **The "224 carriers = 2 per source" complex floor is a design choice.** #531 gives every source exactly 3 level-2 + crossings (X_s + 2 helpers) into port-spanned 2-dim frames whose section is 20 (3 × 20 = 60 ≥ 52), while two + sections already cover every source. The 224 is a property of the section type, not of the format. +- **Carriers are register-neutral in the calibrated ledger (checked).** The register ledger rebuilt from #531's real + tile objects (168 block-local planes, 168 keys, mate matching 2) reproduces 224 scratch + 168 face copies + 112 M + copies = 504, with B = N2 + K + R − 112 + waste. The carrier stage and the face stage together cost 2·N2 − 112 + + waste for any edge cover. The cover axis therefore only enters through wasted single-variant butterflies: + - rebuilding the same planes from cross-tetrad edges costs +168 (12 per block, exact); + - tetrad edges give waste 0 (CP-SAT over all three splits). + +**What the floors certify.** At most 48% (bit, sink-only, 212/439) / 22% (complex, 107/496 helpers without the 8 +centres) of the shipped helper count is design-free necessary. No single-cut or coupled cover argument closes the helper gap that §A.5 identifies as the whole headroom. + +## E. Complex helper prices and the moment-cost census (`e1_run_census.py`, `e2_helper_prices.py`) + +### E.1 Price per helper by class and width on #569 + +The b gain per deleted helper is measured as follows: + +- the helper is removed in all 72 calls; +- its width returns to the idle remainders; +- hosting is re-solved (the LP relaxation of the whole-owner model; integer CP-SAT spot checks at k = 1, not + reproduced here, agreed with it to < 0.002% b). + +The control reproduces #569's hosting (remainder {3: 1200, 7: 48, 48: 1808}, b = 1.602631811·10⁻³). + +| cell (count) | width | % b per helper, k = 1 (checked) | k = 8 / 14 / whole cell (recorded) | +|---|---|---|---| +| factory scratch (216) | 9 | 0.140 | 0.142 / 0.143 / 0.179 | +| face copy (64) | 9 | 0.134 | 0.140 / 0.141 / 0.151 | +| face-copy entrant (72) | 8 | 0.105 | 0.102 / 0.101 / 0.081 | +| face-copy provider (32) | 7 | 0.075 | 0.069 / 0.062 / 0.050 | +| M-copy entrant (56) | 8 | 0.111 | 0.111 / 0.109 / 0.098 | +| rank-4 pivot (48) | 5 | 0.039 | 0.039 / 0.036 / 0.032 | +| rank-5 pivot (8) | 4 | 0.042 | 0.041 / – / – | + +**Width law.** Price = local fee saved − hosting offset, where the offset is the increase of the hosting LP objective. +The offset depends on width only: cells of equal width have identical LP objective increases (checked). Its share of +the local fee at k = 1 (checked) is: + +| width | 9 | 8 | 7 | 5 | 4 | +|---|---|---|---|---|---| +| offset share, k = 1 | 6.3–6.6% | 17.0–17.9% | 31.5% | 57.4% | 45.4% | + +The share is not monotone in width (w4 sits between w7 and w5). At larger k the w7 share rises to 55% and the w5 share +to 65% (recorded in `data/prices/helper-price-table.json`). One conversion constant links all cells: fee saved minus offset is 25.64–25.66 moment units per 1% b. + +A count-based "0.11% per helper" is therefore about right for the width-8 entrants (0.105–0.111), overprices the +width-7 providers by about 1.5× (0.075) and the rank-4/5 pivots by 2.6–2.8× (0.039–0.042), and underprices the +width-9 scratch and face copies by about 25% (0.134–0.140). Summed k = 1 marginals give: + +| class | share of the marginal helper cost | % b | +|---|---|---| +| factory scratch | 53% | 30.2 | +| face copies | 32% | 18.5 | +| M copies | 15% | 8.4 | + +Full table, including the mixed-class runs: `data/prices/helper-price-table.json` (k > 1 rows recorded). + +### E.2 Moment-cost census by class (checked) + +The F-share of a class is its share of Σφ = D at the word's own root. The all-single-run ceiling is the supplier value +if each register of the class climbed in one child, with hosting proportional. + +**#555** (R = 439; 288 stage-replicas): + +| class | n | runs | mean run | stops/register | F-share | all-single-run ceiling | +|---|---|---|---|---|---|---| +| line-born carriers (0,1,2,…) | 205 | 1120 | 1.46 | 4.46 | 29.5% | +14.75% c | +| cell-born (0,2,…) | 153 | 686 | 1.78 | 3.48 | 21.2% | +9.60% | +| line → quad/D6 keepers and totals | 80 | 349 | 1.83 | 3.36 | 10.7% | +4.35% | +| quad-born | 1 | 4 | 2.00 | 3.00 | 0.1% | +0.05% | +| data x / data y | 111 / 111 | 539 / 213 | 1.44 / 3.65 | – | 14.1% / 10.4% | – | + +Copies take 0.7% and the idle remainder 13.4%. + +**#569** (R = 504; width-7 provider word for all 72 calls): + +| class | n | runs | mean run | stops/register | F-share | all-single-run ceiling | +|---|---|---|---|---|---|---| +| factory scratch | 216 | 1304 | 1.49 | 5.04 | 32.5% | +16.4% b | +| face copies | 168 | 696 | 1.98 | 3.14 | 21.9% | +9.1% | +| M copies | 112 | 504 | 1.43 | 3.50 | 12.4% | +4.9% | + +Data take 28.3%, centres 1.8% and the idle remainder 3.0%. The published global profile is reproduced exactly from +63 width-7 calls, 9 four-cold calls and the remainder. + +## F. What the results imply + +**Cost structure (result).** Under S2 the ceiling is set by the copy charge and the B_K idle structure (A.7); on the +real words the headroom is helper cost, 87–93% of the log-gap (A.5). A change inside the local word is paid in every +call: the local word runs 288 times (stage-replicas) in #555's bit pipeline and 72 times in #568's complex pipeline, +and by the deficit identity each rank unit added per local word lowers D by one per call. On the published profiles one +rank unit per local word costs 2.82% of c (#555) and 2.82% of b (#568): half the checked price of a rank-1 descent +(C.7), and the same size as one copy-rank unit per stage (+2.8% of X at s = 9, A.3). What such a change unlocks (a +shared station, a freed sink, a removed copy, a supplied row) is a gain fixed per word. + +**Break-even.** A change that costs Δ rank units in every local word pays only if its per-word gain exceeds about +2.8·Δ % of the supplier value. On the complex side that is about 20·Δ width-9 helpers (0.14% b each, E.1). The +per-call prices of C.1, C.2, C.6 and C.7 are all of this form: +2 per relaxed target per call, at least 1 per +un-gauged family per call, one copy-rank unit for the two sinks freed in the verified asymmetric unit, and 2r per +rank-r descent per call. + +**Open problems.** + +1. **Per-word enablers.** A mechanism charged once per word or per outer level instead of per call, such as a descent, + gauge change or station hoisted out of the local word. At 1/72 of its per-call price a rank-1 descent would cost + about 0.08% b, roughly half a width-9 helper. The current interface has no such primitive (B.2 rows 1, 3, 24). +2. **A non-star decoder.** The copy charge is the largest scope term (×6.8 at the best s, ×2.8 at s = 9; A.7). On the + E8 pattern the pairing is unique at window ≤ 9 (C.6), and on the four complex words checked the lossless pattern + pins every sink (C.3). Lowering x therefore needs a different unit or decoder; for scale, x = 54/102 (bit) and + 56/104 (complex) would give X* = 8.25·10⁻³ and 6.80·10⁻³ (A.6). +3. **The schedule/ρ freedom.** Any permutation ρ is spec-legal, and one bank pair per invocation is a convention (B.2 + rows 5, 12, 13). The B_K idle terms are worth ×1.15 to ×2.15 (A.7). +4. **New skeletons.** The design-free floors certify at most 48% (bit, 212/439) and 22% (complex, 107/496) of today's + helper counts (D). The other 52% and 78% of the helpers are not shown necessary by any argument here. + +## Files + +- `scripts/`: one check per claim group, plus shared code. + - Shared: `report.py` (PASS/FAIL, paths), `cert.py` (rational-interval ln/expm1, certified roots), `skel.py` + (skeleton slots, variants), `e8units.py` and `minrank_sat.py` (E8 geometry, SAT encodings), `d_geometry.py`, + `e_hosting.py` (the hosting LP and the moment root). + - Driver and step 0: `run_all.py`, `provenance.py`. +- `data/vendor/`: inputs copied from open PRs (`SOURCES.json` gives PR, author, commit, path, git blob id). +- `data/ceiling/`, `data/census/`, `data/toys/`, `data/prices/`: small inputs (#568's recorded idle remainder, the + census anchors, the p = 3 paired-cube instance, #569's call dispatch with its source blob id, the deleted-helper + specs and the recorded price table). +- `DEPENDS.json`: files of main read by the checks, by sha256. +- `MANIFEST.sha256`: every file of the package. + +## Credits + +Prepared by DreamingOfClouds with Claude Opus 5.5 (Anthropic), via Claude Code; Apache-2.0. See `NOTICE.md` for the +PRs whose words, pipelines and results are used. diff --git a/research/format-limits/SOURCES.json b/research/format-limits/SOURCES.json new file mode 100644 index 0000000000..140316e5c4 --- /dev/null +++ b/research/format-limits/SOURCES.json @@ -0,0 +1,77 @@ +{ + "description": "Files copied unchanged (or gzip-compressed) from open PRs of CrocSwap/integer-mult-bounds. blob = git blob id of the file at the cited commit (for a .gz copy of a non-.gz source: of the decompressed content); scripts/provenance.py recomputes it.", + "files": [ + { + "file": "data/vendor/pr543/EXPECTED.json", + "pr": 543, + "author": "eumemic", + "commit": "2065fe4f72a914d907f1046603f5a36f17018535", + "source_path": "research/e8-bit-r420/EXPECTED.json", + "blob": "b61d859826246e0c3dabbe096e57ad7015eadfd5", + "used_for": "profiles and bit c of the R420 word (nine and eleven stages): axiom (P), Lemma 2" + }, + { + "file": "data/vendor/pr545/EXPECTED.json", + "pr": 545, + "author": "DreamingOfClouds", + "commit": "376426f3b2b26ab430d10baf16bbd9ff42990a7c", + "source_path": "research/e8-punctured111-star/EXPECTED.json", + "blob": "ac7f102363a64ae19ba4a3edda9176f0b17ae9dc", + "used_for": "profile and bit c of the punctured R489 word (eleven stages): axiom (P), Lemma 2" + }, + { + "file": "data/vendor/pr555/EXPECTED.json", + "pr": 555, + "author": "eumemic", + "commit": "65a6bc19f820c8b1873f65a517bcf70cd6887223", + "source_path": "research/e8-bit-p111/EXPECTED.json", + "blob": "c8ad4439e83f07bcda437598d6a4c31f4e65f2a9", + "used_for": "profiles, bit c, rank mass, hosting counts and local histogram of the p111 word" + }, + { + "file": "data/vendor/pr555/bitE8mut-p111.json.gz", + "pr": 555, + "author": "eumemic", + "commit": "65a6bc19f820c8b1873f65a517bcf70cd6887223", + "source_path": "research/e8-bit-p111/data/bitE8mut-p111.json.gz", + "blob": "bdbb95aa970647bd52895c120b494f0f591f3f87", + "used_for": "the bit word (v = 111, R = 439): run replay, cover numbers, run census" + }, + { + "file": "data/vendor/pr568/PLAN.json.gz", + "pr": 568, + "author": "jeremykothe2", + "commit": "71c99ac935a33bb7b59a1b4a7af8725a939b0b90", + "source_path": "research/complex-multicold/data/PLAN.json", + "blob": "c1b711faccd5ea2f635b31e0e06bfe3a5ccc1948", + "used_for": "global profile and idle windows of the complex word (gzip of the original; blob id of the decompressed file)" + }, + { + "file": "data/vendor/pr569/width7-eight-FULL-WORD.json.gz", + "pr": 569, + "author": "DominikScholz", + "commit": "1424da81263a5305b35e4cf74ef686c275f03f3f", + "source_path": "research/larger-gain/providers/width7/eight/FULL-WORD.json.gz", + "blob": "d4f2dae82814ae4c7553c08e0dc39a1c383e3e3d", + "used_for": "width-7 provider word (63 of 72 calls): taint rigidity, run census, helper prices" + }, + { + "file": "data/vendor/pr569/fourcold-eight-FULL-WORD.json.gz", + "pr": 569, + "author": "DominikScholz", + "commit": "1424da81263a5305b35e4cf74ef686c275f03f3f", + "source_path": "research/larger-gain/providers/fourcold-eight/word/FULL-WORD.json.gz", + "blob": "cdf6203dc4f542bf1bfcdc802646c1da418fea4b", + "used_for": "four-cold provider word (9 of 72 calls): taint rigidity, data runs, helper prices" + }, + { + "file": "data/vendor/pr571/combined-eight-local-FULL-WORD.json.gz", + "pr": 571, + "author": "DominikScholz", + "commit": "b7bdc5a8de18fcf418cfb9c7d83a2b1097a5e4a3", + "source_path": "research/structural-gain/frontier-composition/combined-eight-local/FULL-WORD.json.gz", + "blob": "f12928699315f163629b08b67c4be2678e9cf3d0", + "used_for": "combined provider word: taint rigidity" + } + ] +} \ No newline at end of file diff --git a/research/format-limits/data/ceiling/gap568-inputs.json b/research/format-limits/data/ceiling/gap568-inputs.json new file mode 100644 index 0000000000..1406ab33ca --- /dev/null +++ b/research/format-limits/data/ceiling/gap568-inputs.json @@ -0,0 +1,10 @@ +{ + "description": "Paid idle remainder of #568's whole-residual hosting (global, all 72 calls): rank -> number of children, recorded from our replay of #568's PLAN (jeremykothe2, commit 71c99ac, research/complex-multicold/data/PLAN.json). Its 4,018 children match the '4,018 positive idle remainders' of #568's README (research/complex-multicold/README.md:22); 88,864 of the 387,072 skeleton idle mass stay paid, the rest is hosted by helper runs. Used only for steps D/E of the #568 gap decomposition (scripts/a4_gap.py).", + "idle_remainder": { + "1": 93, + "2": 93, + "3": 2118, + "7": 1, + "48": 1713 + } +} \ No newline at end of file diff --git a/research/format-limits/data/census/anchors.json b/research/format-limits/data/census/anchors.json new file mode 100644 index 0000000000..941d1ef8a7 --- /dev/null +++ b/research/format-limits/data/census/anchors.json @@ -0,0 +1,440 @@ +[ + { + "row": 0, + "path": "docs/research/community-pr531-review.md", + "line": 120, + "needle": "the following remain explicit interface assumptions" + }, + { + "row": 0, + "path": "docs/research/community-pr531-review.md", + "line": 134, + "needle": "independently certified** in this integration" + }, + { + "row": 1, + "path": "research/direct-delivery504-global/code/audit_local.py", + "line": 64, + "needle": "('common frame',r,cur[r[1]],f)" + }, + { + "row": 1, + "path": "research/e8-bit-carrier-skeleton/code/bit/checkers/legality_h.cpp", + "line": 11, + "needle": "ADD not in common frame" + }, + { + "row": 1, + "path": "research/e8-lemma-g-lean/LemmaG/Stream.lean", + "line": 106, + "needle": "WellLabelled" + }, + { + "row": 1, + "path": "upstream/build/sections/03-motifs.tex", + "line": 157, + "needle": "All incoming and outgoing" + }, + { + "row": 1, + "path": "upstream/build/sections/04-swap.tex", + "line": 111, + "needle": "assignment being common to every wire at that gate" + }, + { + "row": 2, + "path": "notes/general-clifford-frames.tex", + "line": 21, + "needle": "degenerate" + }, + { + "row": 2, + "path": "research/e8-bit-carrier-skeleton/code/bit/checkers/gram_frames.py", + "line": 46, + "needle": "'degenerate frame'" + }, + { + "row": 2, + "path": "upstream/build/sections/03-motifs.tex", + "line": 154, + "needle": "assign an invertible" + }, + { + "row": 2, + "path": "upstream/build/sections/03-motifs.tex", + "line": 203, + "needle": "A label is a nondegenerate subspace of a fixed bilinear space" + }, + { + "row": 2, + "path": "upstream/build/sections/04-swap.tex", + "line": 110, + "needle": "rational $m\\times m$ matrix to each terminal" + }, + { + "row": 3, + "path": "research/direct-delivery504-global/code/audit_local.py", + "line": 45, + "needle": "contains(frames[a],frames[b]) and rank==len(frames[b])-len(frames[a])>0" + }, + { + "row": 3, + "path": "research/direct-delivery504-global/code/audit_local.py", + "line": 128, + "needle": "centre_step_not_nested" + }, + { + "row": 3, + "path": "research/e8-bit-carrier-skeleton/code/bit/checkers/legality_h.cpp", + "line": 11, + "needle": "illegal or mispriced MOVE" + }, + { + "row": 3, + "path": "research/e8-lemma-g-lean/LemmaG/Stream.lean", + "line": 111, + "needle": "RecipesExact" + }, + { + "row": 3, + "path": "upstream/build/sections/03-motifs.tex", + "line": 352, + "needle": "The only decreasing" + }, + { + "row": 3, + "path": "upstream/build/sections/05-layers.tex", + "line": 83, + "needle": "including on decreasing edges" + }, + { + "row": 4, + "path": "research/direct-delivery504-global/code/audit_local.py", + "line": 101, + "needle": "assert rows['L',r]==expected" + }, + { + "row": 4, + "path": "research/e8-bit-carrier-skeleton/code/bit/checkers/columns_k.cpp", + "line": 57, + "needle": "need(row(i)[w]==want" + }, + { + "row": 4, + "path": "upstream/build/sections/03-motifs.tex", + "line": 46, + "needle": "their initial values may be arbitrary" + }, + { + "row": 4, + "path": "upstream/build/sections/03-motifs.tex", + "line": 98, + "needle": "and restores every auxiliary value" + }, + { + "row": 4, + "path": "upstream/build/sections/03-motifs.tex", + "line": 581, + "needle": "on every role, including auxiliary roles" + }, + { + "row": 4, + "path": "upstream/build/sections/05-layers.tex", + "line": 150, + "needle": "including roles whose initial scratch" + }, + { + "row": 5, + "path": "research/e8-bit-carrier-skeleton/code/bit/checkers/columns_k.cpp", + "line": 56, + "needle": "std::swap(row(bank*v+i)[w],row((bank+1)*v+i)[w])" + }, + { + "row": 5, + "path": "research/e8-early-reset-kernel/bit_global.py", + "line": 55, + "needle": "out.append(dict(kind='exchange'))" + }, + { + "row": 5, + "path": "upstream/build/sections/03-motifs.tex", + "line": 99, + "needle": "permutation exchanging $X_a$ and $Y_a$" + }, + { + "row": 5, + "path": "upstream/build/sections/03-motifs.tex", + "line": 628, + "needle": "exchanges $X_a,Y_a$ and fixes each scratch role" + }, + { + "row": 5, + "path": "upstream/build/sections/04-swap.tex", + "line": 107, + "needle": "induce a permutation $\\rho$" + }, + { + "row": 6, + "path": "upstream/build/sections/03-motifs.tex", + "line": 235, + "needle": "The prescribed terminal labels are" + }, + { + "row": 6, + "path": "upstream/build/sections/03-motifs.tex", + "line": 240, + "needle": "Y_a&0&U_a^\\perp" + }, + { + "row": 6, + "path": "upstream/build/sections/05-layers.tex", + "line": 128, + "needle": "so they add no recursive calls" + }, + { + "row": 6, + "path": "upstream/build/sections/05-layers.tex", + "line": 146, + "needle": "turn every endpoint kernel into a" + }, + { + "row": 7, + "path": "upstream/build/sections/03-motifs.tex", + "line": 233, + "needle": "U_a=\\langle u_a\\rangle" + }, + { + "row": 8, + "path": "research/e8-bit-carrier-skeleton/code/bit/checkers/columns_k.cpp", + "line": 39, + "needle": "copies==NCOPY" + }, + { + "row": 8, + "path": "research/e8-bit-carrier-skeleton/code/bit/checkers/legality_h.cpp", + "line": 1, + "needle": "the copied-centre count is the macro NCOPY" + }, + { + "row": 9, + "path": "upstream/build/sections/03-motifs.tex", + "line": 221, + "needle": "For the complex network, take instead" + }, + { + "row": 9, + "path": "upstream/build/sections/03-motifs.tex", + "line": 225, + "needle": "The scalar domain need not equal the label field" + }, + { + "row": 11, + "path": "upstream/build/sections/03-motifs.tex", + "line": 360, + "needle": "=Wm-2N+2L" + }, + { + "row": 11, + "path": "upstream/build/sections/04-swap.tex", + "line": 122, + "needle": "s_k; suffix _q = re-solved at the deleted profile's own b", + "deleted_helpers": "helper indices h (register 224 + h) deleted in every call", + "lp_objective": "optimal value of the hosting LP (sum of idle-remainder fees)", + "idle_remainder": "idle remainder ranks of the LP solution: rank -> (fractional) count", + "b": "moment root b_float of local profiles plus idle remainders, W = 7168", + "pct_b_total": "100 (b / b_control - 1)", + "pct_b_per_helper": "pct_b_total / k", + "local_fee_saved": "sum over the 72 calls of r ((73 / r)^q - 1) over the deleted helpers' paid climbs, q = b_control", + "hosting_offset": "lp_objective - control lp_objective (cost of returning the deleted widths to the idle remainders)", + "offset_share_pct": "100 hosting_offset / local_fee_saved" + }, + "q_published": 0.00160263181098207, + "status_recomputed": "recomputed by verify.sh (scripts/e2_helper_prices.py)", + "status_recorded": "recorded (not re-run by verify.sh)", + "control": { + "name": "ctl", + "k": 0, + "lp_objective": 78.0585701066651, + "idle_remainder": { + "3": 1200.0, + "7": 47.99999999999999, + "48": 1808.0000000000002 + }, + "b": 0.0016026318109820283, + "mass": 520704.0, + "status": "recomputed by verify.sh (scripts/e2_helper_prices.py)" + }, + "rows": [ + {"name": "F9_k1", "cell": "F9", "class_description": "factory scratch register, width 9", "class_size": 216, "k": 1, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [16], "lp_objective": 78.30160559083036, "idle_remainder": {"3": 1185.6, "7": 48.0, "48": 1822.4}, "b": 0.0016048723869705752, "mass": 520704.0, "pct_b_total": 0.13980603487298016, "pct_b_per_helper": 0.13980603487298016, "local_fee_saved": 3.8274643062908424, "hosting_offset": 0.24303548416526155, "offset_share_pct": 6.349777939556668, "status": "recomputed by verify.sh (scripts/e2_helper_prices.py)"}, + {"name": "F9_k8", "cell": "F9", "class_description": "factory scratch register, width 9", "class_size": 216, "k": 8, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [16, 47, 74, 101, 128, 155, 182, 209], "lp_objective": 80.00285397998694, "idle_remainder": {"3": 1084.8, "7": 48.0, "48": 1923.2000000000003}, "b": 0.0016208426535987194, "mass": 520704.0, "pct_b_total": 1.1363085701844522, "pct_b_per_helper": 0.14203857127305652, "local_fee_saved": 30.79043849690584, "hosting_offset": 1.9442838733218366, "offset_share_pct": 6.3145702634836445, "status": "recorded (not re-run by verify.sh)"}, + {"name": "F9_k14", "cell": "F9", "class_description": "factory scratch register, width 9", "class_size": 216, "k": 14, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [12, 29, 47, 62, 78, 93, 108, 124, 139, 155, 170, 186, 201, 216], "lp_objective": 81.46106688497837, "idle_remainder": {"3": 998.3999999999999, "7": 48.0, "48": 2009.6}, "b": 0.0016347175166030905, "mass": 520704.0, "pct_b_total": 2.002063443468116, "pct_b_per_helper": 0.143004531676294, "local_fee_saved": 53.79497323902656, "hosting_offset": 3.402496778313264, "offset_share_pct": 6.324934419420548, "status": "recorded (not re-run by verify.sh)"}, + {"name": "F9_k28", "cell": "F9", "class_description": "factory scratch register, width 9", "class_size": 216, "k": 28, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [11, 20, 28, 38, 45, 53, 61, 69, 76, 84, 92, 99, 107, 115, 123, 130, 138, 146, 153, 161, 169, 177, 184, 192, 200, 207, 215, 223], "lp_objective": 84.86356366329149, "idle_remainder": {"3": 796.8000000000001, "7": 48.0, "48": 2211.2}, "b": 0.0016681143627928093, "mass": 520704.0, "pct_b_total": 4.08593860187112, "pct_b_per_helper": 0.14592637863825428, "local_fee_saved": 107.58918598929226, "hosting_offset": 6.8049935566263855, "offset_share_pct": 6.324979126901887, "status": "recorded (not re-run by verify.sh)"}, + {"name": "F9_k216", "cell": "F9", "class_description": "factory scratch register, width 9", "class_size": 216, "k": 216, "whole_class": true, "q": 0.00160263181098207, "deleted_helpers": [2, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 34, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223], "lp_objective": 193.3759093686693, "idle_remainder": {"1": 30.0, "32": 1832.0625000000002, "48": 3584.0}, "b": 0.002220939773836586, "mass": 520704.0, "pct_b_total": 38.5807868418438, "pct_b_per_helper": 0.178614753897425, "local_fee_saved": 829.9218475782271, "hosting_offset": 115.31733926200421, "offset_share_pct": 13.894963676219474, "status": "recorded (not re-run by verify.sh)"}, + {"name": "F9_k216_q", "cell": "F9", "class_description": "factory scratch register, width 9", "class_size": 216, "k": 216, "whole_class": true, "q": 0.002220939773836586, "deleted_helpers": [2, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 34, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223], "lp_objective": 268.0304197146809, "idle_remainder": {"1": 30.0, "32": 1832.0625, "48": 3584.0}, "b": 0.002220939773836586, "mass": 520704.0, "pct_b_total": 38.5807868418438, "pct_b_per_helper": 0.178614753897425, "local_fee_saved": 829.9218475782271, "hosting_offset": null, "offset_share_pct": null, "status": "recorded (not re-run by verify.sh)", "note": "re-solved at q = this row's own b (the hosting LP at the deleted profile's own pressure root); the objective is not comparable with the fixed-q rows, b is the same as in the fixed-q row"}, + {"name": "P9_k1", "cell": "P9", "class_description": "face copy, width 9", "class_size": 64, "k": 1, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [318], "lp_objective": 78.30160559083036, "idle_remainder": {"3": 1185.6, "7": 48.0, "48": 1822.4}, "b": 0.0016047716270276408, "mass": 520704.0, "pct_b_total": 0.1335188800664966, "pct_b_per_helper": 0.1335188800664966, "local_fee_saved": 3.666486229436238, "hosting_offset": 0.24303548416526155, "offset_share_pct": 6.628566670019402, "status": "recomputed by verify.sh (scripts/e2_helper_prices.py)"}, + {"name": "P9_k8", "cell": "P9", "class_description": "face copy, width 9", "class_size": 64, "k": 8, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [240, 252, 268, 286, 295, 318, 342, 362], "lp_objective": 80.00285397998694, "idle_remainder": {"3": 1084.8, "7": 48.0, "48": 1923.2000000000003}, "b": 0.0016205242294818414, "mass": 520704.0, "pct_b_total": 1.1164397447501972, "pct_b_per_helper": 0.13955496809377466, "local_fee_saved": 30.291651938768798, "hosting_offset": 1.9442838733218366, "offset_share_pct": 6.418546856579463, "status": "recorded (not re-run by verify.sh)"}, + {"name": "P9_k14", "cell": "P9", "class_description": "face copy, width 9", "class_size": 64, "k": 14, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [239, 243, 252, 256, 269, 280, 288, 293, 302, 318, 334, 343, 355, 364], "lp_objective": 81.46106688497837, "idle_remainder": {"3": 998.3999999999999, "7": 48.0, "48": 2009.6}, "b": 0.0016342239152122373, "mass": 520704.0, "pct_b_total": 1.9712640179562424, "pct_b_per_helper": 0.14080457271116018, "local_fee_saved": 53.03484549124345, "hosting_offset": 3.402496778313264, "offset_share_pct": 6.415587236650001, "status": "recorded (not re-run by verify.sh)"}, + {"name": "P9_k64", "cell": "P9", "class_description": "face copy, width 9", "class_size": 64, "k": 64, "whole_class": true, "q": 0.00160263181098207, "deleted_helpers": [238, 239, 240, 241, 242, 243, 244, 245, 250, 251, 252, 253, 254, 255, 256, 257, 266, 267, 268, 269, 271, 278, 279, 280, 281, 283, 286, 287, 288, 289, 290, 291, 292, 293, 295, 298, 300, 302, 303, 304, 305, 307, 318, 319, 330, 331, 334, 335, 336, 337, 342, 343, 346, 348, 354, 355, 358, 360, 362, 363, 364, 365, 374, 376], "lp_objective": 93.61284109323972, "idle_remainder": {"3": 278.40000000000003, "7": 48.0, "48": 2729.6}, "b": 0.0017579830181498803, "mass": 520704.0, "pct_b_total": 9.693505776143251, "pct_b_per_helper": 0.1514610277522383, "local_fee_saved": 242.42854536937412, "hosting_offset": 15.554270986574622, "offset_share_pct": 6.416022899809713, "status": "recorded (not re-run by verify.sh)"}, + {"name": "P8_k1", "cell": "P8", "class_description": "face copy of an entrant, width 8", "class_size": 72, "k": 1, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [224], "lp_objective": 78.64325928997846, "idle_remainder": {"1": 12.600000000000128, "3": 1201.62, "7": 52.5, "48": 1818.98}, "b": 0.0016043095787383996, "mass": 520704.0, "pct_b_total": 0.10468828491201343, "pct_b_per_helper": 0.10468828491201343, "local_fee_saved": 3.2696910771915357, "hosting_offset": 0.5846891833133583, "offset_share_pct": 17.882092513020236, "status": "recomputed by verify.sh (scripts/e2_helper_prices.py)"}, + {"name": "P8_k8", "cell": "P8", "class_description": "face copy of an entrant, width 8", "class_size": 72, "k": 8, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [224, 233, 258, 285, 320, 357, 372, 383], "lp_objective": 82.73608357317183, "idle_remainder": {"1": 100.79999999999998, "3": 1212.96, "7": 84.0, "48": 1895.8399999999995}, "b": 0.0016156812055760665, "mass": 520704.0, "pct_b_total": 0.8142478206545789, "pct_b_per_helper": 0.10178097758182236, "local_fee_saved": 25.413989004777555, "hosting_offset": 4.677513466506724, "offset_share_pct": 18.405270678394494, "status": "recorded (not re-run by verify.sh)"}, + {"name": "P8_k14", "cell": "P8", "class_description": "face copy of an entrant, width 8", "class_size": 72, "k": 14, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [225, 230, 235, 249, 270, 285, 301, 321, 347, 361, 371, 378, 383, 388], "lp_objective": 87.15428796054395, "idle_remainder": {"1": 173.0, "2": 45.0, "3": 1437.6666666666667, "48": 1962.6666666666667}, "b": 0.0016252010857745894, "mass": 520704.0, "pct_b_total": 1.4082632478592538, "pct_b_per_helper": 0.1005902319899467, "local_fee_saved": 44.74988564622837, "hosting_offset": 9.095717853878853, "offset_share_pct": 20.325678429181554, "status": "recorded (not re-run by verify.sh)"}, + {"name": "P8_k72", "cell": "P8", "class_description": "face copy of an entrant, width 8", "class_size": 72, "k": 72, "whole_class": true, "q": 0.00160263181098207, "deleted_helpers": [224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 246, 247, 248, 249, 258, 259, 260, 261, 270, 272, 273, 282, 284, 285, 294, 296, 297, 299, 301, 306, 308, 309, 320, 321, 332, 333, 344, 345, 347, 349, 356, 357, 359, 361, 366, 367, 368, 369, 370, 371, 372, 373, 375, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391], "lp_objective": 166.35289339023453, "idle_remainder": {"1": 128.0, "3": 1328.0, "7": 1328.0, "8": 1312.0, "48": 2256.0}, "b": 0.001696336551844271, "mass": 520704.0, "pct_b_total": 5.8469287967536365, "pct_b_per_helper": 0.08120734439935606, "local_fee_saved": 230.11335296316986, "hosting_offset": 88.29432328356943, "offset_share_pct": 38.36992601541951, "status": "recorded (not re-run by verify.sh)"}, + {"name": "P7_k1", "cell": "P7", "class_description": "face copy of a width-7 provider", "class_size": 32, "k": 1, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [262], "lp_objective": 78.9431695444484, "idle_remainder": {"1": 23.599999999999998, "3": 1215.62, "7": 56.5, "48": 1815.9800000000002}, "b": 0.001603832889308516, "mass": 520704.0, "pct_b_total": 0.07494412117976523, "pct_b_per_helper": 0.07494412117976523, "local_fee_saved": 2.807306914794056, "hosting_offset": 0.8845994377832938, "offset_share_pct": 31.510606593158624, "status": "recomputed by verify.sh (scripts/e2_helper_prices.py)"}, + {"name": "P7_k8", "cell": "P7", "class_description": "face copy of a width-7 provider", "class_size": 32, "k": 8, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [262, 274, 310, 314, 322, 326, 338, 350], "lp_objective": 86.36895154859006, "idle_remainder": {"1": 178.0, "2": 50.0, "3": 1571.8666666666666, "48": 1871.4666666666667}, "b": 0.0016115121614485874, "mass": 520704.0, "pct_b_total": 0.5541104579172007, "pct_b_per_helper": 0.06926380723965009, "local_fee_saved": 22.458455318352513, "hosting_offset": 8.31038144192496, "offset_share_pct": 37.00335274231401, "status": "recorded (not re-run by verify.sh)"}, + {"name": "P7_k14", "cell": "P7", "class_description": "face copy of a width-7 provider", "class_size": 32, "k": 14, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [262, 264, 274, 276, 311, 313, 315, 322, 324, 326, 328, 339, 341, 351], "lp_objective": 95.38865623219716, "idle_remainder": {"1": 230.0, "2": 102.0, "3": 2026.2, "7": 1.0, "48": 1903.8}, "b": 0.0016164655564406575, "mass": 520704.0, "pct_b_total": 0.8631892468272229, "pct_b_per_helper": 0.06165637477337306, "local_fee_saved": 39.30229680711651, "hosting_offset": 17.330086125532063, "offset_share_pct": 44.0943342588418, "status": "recorded (not re-run by verify.sh)"}, + {"name": "P7_k32", "cell": "P7", "class_description": "face copy of a width-7 provider", "class_size": 32, "k": 32, "whole_class": true, "q": 0.00160263181098207, "deleted_helpers": [262, 263, 264, 265, 274, 275, 276, 277, 310, 311, 312, 313, 314, 315, 316, 317, 322, 323, 324, 325, 326, 327, 328, 329, 338, 339, 340, 341, 350, 351, 352, 353], "lp_objective": 127.36533803647909, "idle_remainder": {"1": 128.0, "3": 2112.0, "7": 1216.0, "48": 1920.0}, "b": 0.0016283353569115045, "mass": 520704.0, "pct_b_total": 1.6038335039491258, "pct_b_per_helper": 0.05011979699841018, "local_fee_saved": 89.833821273407, "hosting_offset": 49.30676792981399, "offset_share_pct": 54.88664205850719, "status": "recorded (not re-run by verify.sh)"}, + {"name": "M8_k1", "cell": "M8", "class_description": "M copy of an entrant, width 8", "class_size": 56, "k": 1, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [392], "lp_objective": 78.64325928997846, "idle_remainder": {"1": 12.600000000000128, "3": 1201.62, "7": 52.5, "48": 1818.98}, "b": 0.0016044102805557185, "mass": 520704.0, "pct_b_total": 0.11097181283332702, "pct_b_per_helper": 0.11097181283332702, "local_fee_saved": 3.4306691540461403, "hosting_offset": 0.5846891833133583, "offset_share_pct": 17.04300697791783, "status": "recomputed by verify.sh (scripts/e2_helper_prices.py)"}, + {"name": "M8_k8", "cell": "M8", "class_description": "M copy of an entrant, width 8", "class_size": 56, "k": 8, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [394, 409, 424, 436, 450, 465, 480, 492], "lp_objective": 82.73608357317183, "idle_remainder": {"1": 100.79999999999998, "3": 1212.96, "7": 84.0, "48": 1895.8399999999995}, "b": 0.001616907477959993, "mass": 520704.0, "pct_b_total": 0.8907639845996407, "pct_b_per_helper": 0.11134549807495508, "local_fee_saved": 27.3453684774846, "hosting_offset": 4.677513466506724, "offset_share_pct": 17.105322498609063, "status": "recorded (not re-run by verify.sh)"}, + {"name": "M8_k14", "cell": "M8", "class_description": "M copy of an entrant, width 8", "class_size": 56, "k": 14, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [392, 400, 408, 416, 424, 432, 440, 448, 456, 464, 472, 480, 488, 496], "lp_objective": 87.15428796054395, "idle_remainder": {"1": 173.0, "2": 45.0, "3": 1437.6666666666667, "48": 1962.6666666666667}, "b": 0.0016271803930528418, "mass": 520704.0, "pct_b_total": 1.5317668039904309, "pct_b_per_helper": 0.10941191457074506, "local_fee_saved": 47.829398646876925, "hosting_offset": 9.095717853878853, "offset_share_pct": 19.017002327443578, "status": "recorded (not re-run by verify.sh)"}, + {"name": "M8_k56", "cell": "M8", "class_description": "M copy of an entrant, width 8", "class_size": 56, "k": 56, "whole_class": true, "q": 0.00160263181098207, "deleted_helpers": [392, 393, 394, 396, 400, 401, 402, 404, 408, 409, 410, 412, 416, 417, 418, 420, 424, 425, 426, 428, 432, 433, 434, 436, 440, 441, 442, 444, 448, 449, 450, 452, 456, 457, 458, 460, 464, 465, 466, 468, 472, 473, 474, 476, 480, 481, 482, 484, 488, 489, 490, 492, 496, 497, 498, 500], "lp_objective": 136.12886612209252, "idle_remainder": {"1": 128.0, "3": 1346.4, "7": 1236.0, "8": 344.0, "48": 2237.6}, "b": 0.001690363267552532, "mass": 520704.0, "pct_b_total": 5.474211604270196, "pct_b_per_helper": 0.09775377864768207, "local_fee_saved": 191.31759458752092, "hosting_offset": 58.07029601542742, "offset_share_pct": 30.35282569835068, "status": "recorded (not re-run by verify.sh)"}, + {"name": "M5_k1", "cell": "M5", "class_description": "M copy of a rank-4 pivot, width 5", "class_size": 48, "k": 1, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [395], "lp_objective": 79.39079928540599, "idle_remainder": {"1": 57.599999999999966, "3": 1254.72, "7": 48.0, "48": 1810.8799999999999}, "b": 0.0016032504527875107, "mass": 520704.0, "pct_b_total": 0.03860161774171811, "pct_b_per_helper": 0.03860161774171811, "local_fee_saved": 2.3229214587904465, "hosting_offset": 1.3322291787408886, "offset_share_pct": 57.35145171178474, "status": "recomputed by verify.sh (scripts/e2_helper_prices.py)"}, + {"name": "M5_k8", "cell": "M5", "class_description": "M copy of a rank-4 pivot, width 5", "class_size": 48, "k": 8, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [395, 406, 427, 438, 459, 470, 483, 494], "lp_objective": 88.71640353659193, "idle_remainder": {"1": 460.8, "3": 1637.7600000000002, "7": 48.0, "48": 1831.0399999999997}, "b": 0.0016075943697525037, "mass": 520704.0, "pct_b_total": 0.30965058452412375, "pct_b_per_helper": 0.03870632306551547, "local_fee_saved": 18.583371670323714, "hosting_offset": 10.657833429926825, "offset_share_pct": 57.35145171178277, "status": "recorded (not re-run by verify.sh)"}, + {"name": "M5_k14", "cell": "M5", "class_description": "M copy of a rank-4 pivot, width 5", "class_size": 48, "k": 14, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [395, 399, 406, 414, 429, 437, 443, 459, 463, 470, 478, 485, 493, 499], "lp_objective": 97.58140742546013, "idle_remainder": {"1": 576.0, "3": 1747.2, "5": 288.0, "7": 48.0, "48": 1836.8}, "b": 0.0016107867141125247, "mass": 520704.0, "pct_b_total": 0.5088444566378136, "pct_b_per_helper": 0.03634603261698669, "local_fee_saved": 32.52090042306716, "hosting_offset": 19.522837318795027, "offset_share_pct": 60.03166291468187, "status": "recorded (not re-run by verify.sh)"}, + {"name": "M5_k48", "cell": "M5", "class_description": "M copy of a rank-4 pivot, width 5", "class_size": 48, "k": 48, "whole_class": true, "q": 0.00160263181098207, "deleted_helpers": [395, 397, 398, 399, 403, 405, 406, 407, 411, 413, 414, 415, 427, 429, 430, 431, 435, 437, 438, 439, 443, 445, 446, 447, 459, 461, 462, 463, 467, 469, 470, 471, 475, 477, 478, 479, 483, 485, 486, 487, 491, 493, 494, 495, 499, 501, 502, 503], "lp_objective": 150.28604444224518, "idle_remainder": {"1": 576.0, "3": 1747.1999999999998, "5": 2736.0, "7": 48.0, "48": 1836.8}, "b": 0.0016275280317628567, "mass": 520704.0, "pct_b_total": 1.553458543018249, "pct_b_per_helper": 0.03236371964621352, "local_fee_saved": 111.50023002194669, "hosting_offset": 72.22747433558008, "offset_share_pct": 64.77787025314969, "status": "recorded (not re-run by verify.sh)"}, + {"name": "M4_k1", "cell": "M4", "class_description": "M copy of a rank-5 pivot, width 4", "class_size": 8, "k": 1, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [419], "lp_objective": 78.96089292243292, "idle_remainder": {"2": 17.999999999999986, "3": 1268.3999999999999, "7": 30.000000000000014, "48": 1811.6}, "b": 0.0016033092416224345, "mass": 520704.0, "pct_b_total": 0.042269886056445216, "pct_b_per_helper": 0.042269886056445216, "local_fee_saved": 1.9871196285160408, "hosting_offset": 0.9023228157678176, "offset_share_pct": 45.40858048096794, "status": "recomputed by verify.sh (scripts/e2_helper_prices.py)"}, + {"name": "M4_k8", "cell": "M4", "class_description": "M copy of a rank-5 pivot, width 4", "class_size": 8, "k": 8, "whole_class": true, "q": 0.00160263181098207, "deleted_helpers": [419, 421, 422, 423, 451, 453, 454, 455], "lp_objective": 85.60411891355172, "idle_remainder": {"2": 48.0, "3": 1689.5999999999995, "48": 1830.4}, "b": 0.0016078619035174678, "mass": 520704.0, "pct_b_total": 0.326343986160782, "pct_b_per_helper": 0.04079299827009775, "local_fee_saved": 15.896957028128327, "hosting_offset": 7.545548806886615, "offset_share_pct": 47.465365815202254, "status": "recorded (not re-run by verify.sh)"}, + {"name": "XEnt_k8", "cell": "XEnt", "class_description": "all entrants (width < 9: P8, P7, M8, M5, M4), stratified selection", "class_size": 216, "k": 8, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [258, 262, 347, 386, 395, 400, 456, 459], "lp_objective": 84.53107381849674, "idle_remainder": {"1": 201.8, "3": 1333.1599999999999, "7": 79.0, "48": 1876.64}, "b": 0.0016135858878998195, "mass": 520704.0, "pct_b_total": 0.6835055215258068, "pct_b_per_helper": 0.08543819019072585, "local_fee_saved": 23.90194742276502, "hosting_offset": 6.472503711831635, "offset_share_pct": 27.079399001886365, "status": "recorded (not re-run by verify.sh)"}, + {"name": "XEnt_k14", "cell": "XEnt", "class_description": "all entrants (width < 9: P8, P7, M8, M5, M4), stratified selection", "class_size": 216, "k": 14, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [227, 249, 262, 299, 322, 359, 380, 394, 395, 424, 435, 450, 475, 480], "lp_objective": 89.21766357755996, "idle_remainder": {"1": 330.0, "3": 1407.6, "7": 114.0, "48": 1930.3999999999999}, "b": 0.0016221884395397094, "mass": 520704.0, "pct_b_total": 1.2202820650176527, "pct_b_per_helper": 0.08716300464411805, "local_fee_saved": 42.111297554663956, "hosting_offset": 11.15909347089486, "offset_share_pct": 26.499049231169927, "status": "recorded (not re-run by verify.sh)"}, + {"name": "XEnt_k216", "cell": "XEnt", "class_description": "all entrants (width < 9: P8, P7, M8, M5, M4), stratified selection", "class_size": 216, "k": 216, "whole_class": true, "q": 0.00160263181098207, "deleted_helpers": [224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 246, 247, 248, 249, 258, 259, 260, 261, 262, 263, 264, 265, 270, 272, 273, 274, 275, 276, 277, 282, 284, 285, 294, 296, 297, 299, 301, 306, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 332, 333, 338, 339, 340, 341, 344, 345, 347, 349, 350, 351, 352, 353, 356, 357, 359, 361, 366, 367, 368, 369, 370, 371, 372, 373, 375, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503], "lp_objective": 418.30037062967733, "idle_remainder": {"3": 1280.0000000000002, "5": 3584.0, "7": 3584.0, "8": 5376.0, "48": 2303.9999999999995}, "b": 0.0018134503204100582, "mass": 520704.0, "pct_b_total": 13.154519209178117, "pct_b_per_helper": 0.06090055189434313, "local_fee_saved": 638.6619558741733, "hosting_offset": 340.2418005230122, "offset_share_pct": 53.27416129825735, "status": "recorded (not re-run by verify.sh)"}, + {"name": "XFc_k8", "cell": "XFc", "class_description": "all face copies (P9, P8, P7), stratified selection", "class_size": 168, "k": 8, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [255, 258, 262, 295, 322, 347, 355, 386], "lp_objective": 82.31094298466734, "idle_remainder": {"1": 84.99999999999999, "3": 1192.9, "7": 78.5, "48": 1900.1}, "b": 0.001616675363954683, "mass": 520704.0, "pct_b_total": 0.8762806825885594, "pct_b_per_helper": 0.10953508532356993, "local_fee_saved": 26.554899902764998, "hosting_offset": 4.252372878002234, "offset_share_pct": 16.013514995624067, "status": "recorded (not re-run by verify.sh)"}, + {"name": "XFc_k14", "cell": "XFc", "class_description": "all face copies (P9, P8, P7), stratified selection", "class_size": 168, "k": 14, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [224, 236, 243, 262, 267, 273, 290, 312, 319, 320, 327, 358, 366, 380], "lp_objective": 85.605008468824, "idle_remainder": {"1": 141.0, "2": 13.0, "3": 1314.5, "7": 23.5, "48": 1972.4999999999995}, "b": 0.0016272892527645906, "mass": 520704.0, "pct_b_total": 1.5385593630175887, "pct_b_per_helper": 0.10989709735839918, "local_fee_saved": 46.449387207890624, "hosting_offset": 7.546438362158895, "offset_share_pct": 16.246583250676206, "status": "recorded (not re-run by verify.sh)"}, + {"name": "XFc_k28", "cell": "XFc", "class_description": "all face copies (P9, P8, P7), stratified selection", "class_size": 168, "k": 28, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [227, 233, 241, 247, 251, 257, 261, 262, 271, 276, 285, 286, 292, 303, 306, 314, 325, 330, 333, 339, 342, 355, 357, 365, 369, 377, 383, 389], "lp_objective": 95.5088607211357, "idle_remainder": {"1": 206.5, "2": 78.5, "3": 1591.9666666666667, "48": 2128.866666666667}, "b": 0.0016521151918888989, "mass": 520704.0, "pct_b_total": 3.0876325159519435, "pct_b_per_helper": 0.11027258985542655, "local_fee_saved": 94.34845219992408, "hosting_offset": 17.450290614470603, "offset_share_pct": 18.495576988898016, "status": "recorded (not re-run by verify.sh)"}, + {"name": "XFc_k168", "cell": "XFc", "class_description": "all face copies (P9, P8, P7), stratified selection", "class_size": 168, "k": 168, "whole_class": true, "q": 0.00160263181098207, "deleted_helpers": [224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391], "lp_objective": 242.77752020278996, "idle_remainder": {"1": 128.0, "3": 403.2, "7": 3360.0, "8": 1568.0, "48": 3180.7999999999997}, "b": 0.001896381783299722, "mass": 520704.0, "pct_b_total": 18.329223861948396, "pct_b_per_helper": 0.10910252298778807, "local_fee_saved": 562.3757196059283, "hosting_offset": 164.71895009612484, "offset_share_pct": 29.28984028889935, "status": "recorded (not re-run by verify.sh)"}, + {"name": "XFc_k168_q", "cell": "XFc", "class_description": "all face copies (P9, P8, P7), stratified selection", "class_size": 168, "k": 168, "whole_class": true, "q": 0.001896381783299722, "deleted_helpers": [224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391], "lp_objective": 287.34157240603844, "idle_remainder": {"1": 128.0, "3": 403.19999999999993, "7": 3360.0, "8": 1568.0, "48": 3180.7999999999997}, "b": 0.001896381783299722, "mass": 520704.0, "pct_b_total": 18.329223861948396, "pct_b_per_helper": 0.10910252298778807, "local_fee_saved": 562.3757196059283, "hosting_offset": null, "offset_share_pct": null, "status": "recorded (not re-run by verify.sh)", "note": "re-solved at q = this row's own b (the hosting LP at the deleted profile's own pressure root); the objective is not comparable with the fixed-q rows, b is the same as in the fixed-q row"}, + {"name": "XMc_k8", "cell": "XMc", "class_description": "all M copies (M8, M5, M4), stratified selection", "class_size": 112, "k": 8, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [394, 395, 419, 424, 435, 450, 475, 480], "lp_objective": 85.22946364775281, "idle_remainder": {"1": 208.8, "3": 1425.3600000000001, "7": 66.0, "48": 1863.44}, "b": 0.0016123075894029924, "mass": 520704.0, "pct_b_total": 0.6037430652917841, "pct_b_per_helper": 0.07546788316147302, "local_fee_saved": 22.57857586618764, "hosting_offset": 7.1708935410877075, "offset_share_pct": 31.7597247212851, "status": "recorded (not re-run by verify.sh)"}, + {"name": "XMc_k14", "cell": "XMc", "class_description": "all M copies (M8, M5, M4), stratified selection", "class_size": 112, "k": 14, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [392, 395, 408, 411, 419, 424, 435, 440, 456, 459, 472, 475, 488, 491], "lp_objective": 90.98021873391541, "idle_remainder": {"1": 419.4, "3": 1594.38, "7": 79.5, "48": 1905.02}, "b": 0.0016196760763867186, "mass": 520704.0, "pct_b_total": 1.06351722759368, "pct_b_per_helper": 0.07596551625669143, "local_fee_saved": 39.93933245958186, "hosting_offset": 12.921648627250306, "offset_share_pct": 32.3531912816191, "status": "recorded (not re-run by verify.sh)"}, + {"name": "XMc_k28", "cell": "XMc", "class_description": "all M copies (M8, M5, M4), stratified selection", "class_size": 112, "k": 28, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [392, 395, 400, 403, 408, 411, 416, 419, 424, 427, 432, 435, 440, 443, 448, 451, 456, 459, 464, 467, 472, 475, 480, 483, 488, 491, 496, 499], "lp_objective": 105.64932257474904, "idle_remainder": {"1": 432.0, "3": 1603.8, "5": 501.0, "7": 126.0, "48": 1980.2}, "b": 0.001635819636789334, "mass": 520704.0, "pct_b_total": 2.070832837579184, "pct_b_per_helper": 0.073958315627828, "local_fee_saved": 79.67869540939571, "hosting_offset": 27.590752468083934, "offset_share_pct": 34.62751533056656, "status": "recorded (not re-run by verify.sh)"}, + {"name": "XMc_k56", "cell": "XMc", "class_description": "all M copies (M8, M5, M4), stratified selection", "class_size": 112, "k": 56, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [392, 394, 395, 398, 400, 402, 403, 406, 408, 410, 411, 414, 416, 418, 419, 422, 424, 426, 427, 430, 432, 434, 435, 438, 440, 442, 443, 446, 448, 450, 451, 454, 456, 458, 459, 462, 464, 466, 467, 470, 472, 474, 475, 478, 480, 482, 483, 486, 488, 490, 491, 494, 496, 498, 499, 502], "lp_objective": 139.70005108739647, "idle_remainder": {"1": 288.0, "3": 1491.2, "5": 1568.0, "7": 512.0, "48": 2092.8}, "b": 0.0016660432112652946, "mass": 520704.0, "pct_b_total": 3.9567042067142344, "pct_b_per_helper": 0.07065543226275418, "local_fee_saved": 159.3573908187893, "hosting_offset": 61.64148098073137, "offset_share_pct": 38.68128152953131, "status": "recorded (not re-run by verify.sh)"}, + {"name": "XMc_k112", "cell": "XMc", "class_description": "all M copies (M8, M5, M4), stratified selection", "class_size": 112, "k": 112, "whole_class": true, "q": 0.00160263181098207, "deleted_helpers": [392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503], "lp_objective": 218.76916786377555, "idle_remainder": {"3": 1346.4, "5": 3584.0, "7": 1236.0, "8": 568.0, "48": 2237.6000000000004}, "b": 0.0017220331830326008, "mass": 520704.0, "pct_b_total": 7.450330839084507, "pct_b_per_helper": 0.06652081106325453, "local_fee_saved": 318.71478163755944, "hosting_offset": 140.71059775711046, "offset_share_pct": 44.14937927702573, "status": "recorded (not re-run by verify.sh)"}, + {"name": "XMc_k112_q", "cell": "XMc", "class_description": "all M copies (M8, M5, M4), stratified selection", "class_size": 112, "k": 112, "whole_class": true, "q": 0.0017220331830326008, "deleted_helpers": [392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503], "lp_objective": 235.0948550924295, "idle_remainder": {"3": 1346.3999999999999, "5": 3584.0, "7": 1236.0, "8": 568.0, "48": 2237.6000000000004}, "b": 0.0017220331830326008, "mass": 520704.0, "pct_b_total": 7.450330839084507, "pct_b_per_helper": 0.06652081106325453, "local_fee_saved": 318.71478163755944, "hosting_offset": null, "offset_share_pct": null, "status": "recorded (not re-run by verify.sh)", "note": "re-solved at q = this row's own b (the hosting LP at the deleted profile's own pressure root); the objective is not comparable with the fixed-q rows, b is the same as in the fixed-q row"}, + {"name": "SFcar_k1", "cell": "SFcar", "class_description": "factory scratch: packet carriers", "class_size": 98, "k": 1, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [2], "lp_objective": 78.30160559083036, "idle_remainder": {"3": 1185.6, "7": 48.0, "48": 1822.4}, "b": 0.0016049349759488852, "mass": 520704.0, "pct_b_total": 0.14371142211671017, "pct_b_per_helper": 0.14371142211671017, "local_fee_saved": 3.9274490611751522, "hosting_offset": 0.24303548416526155, "offset_share_pct": 6.188125686155727, "status": "recorded (not re-run by verify.sh)"}, + {"name": "SFcar_k8", "cell": "SFcar", "class_description": "factory scratch: packet carriers", "class_size": 98, "k": 8, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [14, 40, 70, 98, 126, 152, 182, 210], "lp_objective": 80.00285397998694, "idle_remainder": {"3": 1084.8, "7": 48.0, "48": 1923.2000000000003}, "b": 0.0016218684075593715, "mass": 520704.0, "pct_b_total": 1.2003129131422874, "pct_b_per_helper": 0.15003911414278592, "local_fee_saved": 32.39592572073481, "hosting_offset": 1.9442838733218366, "offset_share_pct": 6.001630853466891, "status": "recorded (not re-run by verify.sh)"}, + {"name": "SFcar_k14", "cell": "SFcar", "class_description": "factory scratch: packet carriers", "class_size": 98, "k": 14, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [2, 18, 34, 50, 66, 82, 98, 114, 130, 146, 162, 178, 194, 210], "lp_objective": 81.46106688497837, "idle_remainder": {"3": 998.3999999999999, "7": 48.0, "48": 2009.6}, "b": 0.0016360103461670666, "mass": 520704.0, "pct_b_total": 2.082732599984105, "pct_b_per_helper": 0.14876661428457894, "local_fee_saved": 55.78394485562201, "hosting_offset": 3.402496778313264, "offset_share_pct": 6.0994194424927874, "status": "recorded (not re-run by verify.sh)"}, + {"name": "SFcar_k98", "cell": "SFcar", "class_description": "factory scratch: packet carriers", "class_size": 98, "k": 98, "whole_class": true, "q": 0.00160263181098207, "deleted_helpers": [2, 4, 6, 7, 8, 12, 14, 18, 20, 22, 23, 24, 28, 30, 34, 36, 38, 39, 40, 44, 46, 50, 52, 54, 55, 56, 60, 62, 66, 68, 70, 71, 72, 76, 78, 82, 84, 86, 87, 88, 92, 94, 98, 100, 102, 103, 104, 108, 110, 114, 116, 118, 119, 120, 124, 126, 130, 132, 134, 135, 136, 140, 142, 146, 148, 150, 151, 152, 156, 158, 162, 164, 166, 167, 168, 172, 174, 178, 180, 182, 183, 184, 188, 190, 194, 196, 198, 199, 200, 204, 206, 210, 212, 214, 215, 216, 220, 222], "lp_objective": 103.89216241884438, "idle_remainder": {"1": 18.776470588235227, "2": 11.22352941176477, "48": 3212.1411764705886}, "b": 0.0018738503231193427, "mass": 520704.0, "pct_b_total": 16.92332014619893, "pct_b_per_helper": 0.172686940267336, "local_fee_saved": 397.4056647703472, "hosting_offset": 25.833592312179277, "offset_share_pct": 6.500559655360724, "status": "recorded (not re-run by verify.sh)"}, + {"name": "SFtot_k1", "cell": "SFtot", "class_description": "factory scratch: block totals (retired donors)", "class_size": 48, "k": 1, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [16], "lp_objective": 78.30160559083036, "idle_remainder": {"3": 1185.6, "7": 48.0, "48": 1822.4}, "b": 0.0016048723869705752, "mass": 520704.0, "pct_b_total": 0.13980603487298016, "pct_b_per_helper": 0.13980603487298016, "local_fee_saved": 3.8274643062908424, "hosting_offset": 0.24303548416526155, "offset_share_pct": 6.349777939556668, "status": "recorded (not re-run by verify.sh)"}, + {"name": "SFtot_k8", "cell": "SFtot", "class_description": "factory scratch: block totals (retired donors)", "class_size": 48, "k": 8, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [16, 51, 80, 99, 128, 147, 176, 195], "lp_objective": 80.00285397998694, "idle_remainder": {"3": 1084.8, "7": 48.0, "48": 1923.2000000000003}, "b": 0.001620733655024353, "mass": 520704.0, "pct_b_total": 1.1295073464960614, "pct_b_per_helper": 0.14118841831200768, "local_fee_saved": 30.619714450326363, "hosting_offset": 1.9442838733218366, "offset_share_pct": 6.34977793955591, "status": "recorded (not re-run by verify.sh)"}, + {"name": "SFtot_k14", "cell": "SFtot", "class_description": "factory scratch: block totals (retired donors)", "class_size": 48, "k": 14, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [16, 37, 51, 67, 81, 97, 112, 128, 133, 147, 163, 177, 193, 208], "lp_objective": 81.46106688497837, "idle_remainder": {"3": 998.3999999999999, "7": 48.0, "48": 2009.6}, "b": 0.0016345808183062089, "mass": 520704.0, "pct_b_total": 1.9935338301193095, "pct_b_per_helper": 0.1423952735799507, "local_fee_saved": 53.584500288071034, "hosting_offset": 3.402496778313264, "offset_share_pct": 6.349777939556015, "status": "recorded (not re-run by verify.sh)"}, + {"name": "SFtot_k48", "cell": "SFtot", "class_description": "factory scratch: block totals (retired donors)", "class_size": 48, "k": 48, "whole_class": true, "q": 0.00160263181098207, "deleted_helpers": [16, 21, 32, 37, 48, 49, 51, 53, 64, 65, 67, 69, 80, 81, 83, 85, 96, 97, 99, 101, 112, 113, 115, 117, 128, 129, 131, 133, 144, 145, 147, 149, 160, 161, 163, 165, 176, 177, 179, 181, 192, 193, 195, 197, 208, 209, 211, 213], "lp_objective": 89.72427334659609, "idle_remainder": {"3": 508.80000000000007, "7": 47.99999999999997, "48": 2499.1999999999994}, "b": 0.0017177473613902688, "mass": 520704.0, "pct_b_total": 7.182906867279915, "pct_b_per_helper": 0.14964389306833156, "local_fee_saved": 183.71828670195862, "hosting_offset": 11.665703239930991, "offset_share_pct": 6.349777939555879, "status": "recorded (not re-run by verify.sh)"}, + {"name": "SFopc_k1", "cell": "SFopc", "class_description": "factory scratch: own partial plus later packet carrier", "class_size": 28, "k": 1, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [11], "lp_objective": 78.30160559083036, "idle_remainder": {"3": 1185.6, "7": 48.0, "48": 1822.4}, "b": 0.0016049349759488852, "mass": 520704.0, "pct_b_total": 0.14371142211671017, "pct_b_per_helper": 0.14371142211671017, "local_fee_saved": 3.9274490611751522, "hosting_offset": 0.24303548416526155, "offset_share_pct": 6.188125686155727, "status": "recorded (not re-run by verify.sh)"}, + {"name": "SFopc_k8", "cell": "SFopc", "class_description": "factory scratch: own partial plus later packet carrier", "class_size": 28, "k": 8, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [15, 43, 75, 95, 127, 155, 187, 207], "lp_objective": 80.00285397998694, "idle_remainder": {"3": 1084.8, "7": 48.0, "48": 1923.2000000000003}, "b": 0.001621091598212793, "mass": 520704.0, "pct_b_total": 1.1518420578119803, "pct_b_per_helper": 0.14398025722649754, "local_fee_saved": 31.180274097170454, "hosting_offset": 1.9442838733218366, "offset_share_pct": 6.235621493456584, "status": "recorded (not re-run by verify.sh)"}, + {"name": "SFopc_k14", "cell": "SFopc", "class_description": "factory scratch: own partial plus later packet carrier", "class_size": 28, "k": 14, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [11, 27, 43, 59, 75, 91, 107, 123, 139, 155, 171, 187, 203, 219], "lp_objective": 81.46106688497837, "idle_remainder": {"3": 998.3999999999999, "7": 48.0, "48": 2009.6}, "b": 0.0016362255585855726, "mass": 520704.0, "pct_b_total": 2.0961612875361135, "pct_b_per_helper": 0.14972580625257953, "local_fee_saved": 56.11471594144006, "hosting_offset": 3.402496778313264, "offset_share_pct": 6.063466100165286, "status": "recorded (not re-run by verify.sh)"}, + {"name": "SFopc_k28", "cell": "SFopc", "class_description": "factory scratch: own partial plus later packet carrier", "class_size": 28, "k": 28, "whole_class": true, "q": 0.00160263181098207, "deleted_helpers": [11, 15, 27, 31, 43, 47, 59, 63, 75, 79, 91, 95, 107, 111, 123, 127, 139, 143, 155, 159, 171, 175, 187, 191, 203, 207, 219, 223], "lp_objective": 84.86356366329149, "idle_remainder": {"3": 796.8000000000001, "7": 48.0, "48": 2211.2}, "b": 0.0016690766942306164, "mass": 520704.0, "pct_b_total": 4.145985546603703, "pct_b_per_helper": 0.14807091237870368, "local_fee_saved": 109.01130014398161, "hosting_offset": 6.8049935566263855, "offset_share_pct": 6.242466191705247, "status": "recorded (not re-run by verify.sh)"}, + {"name": "SFops_k1", "cell": "SFops", "class_description": "factory scratch: own partials stranded at a late read", "class_size": 28, "k": 1, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [9], "lp_objective": 78.30160559083036, "idle_remainder": {"3": 1185.6, "7": 48.0, "48": 1822.4}, "b": 0.0016044945743324893, "mass": 520704.0, "pct_b_total": 0.11623152228081857, "pct_b_per_helper": 0.11623152228081857, "local_fee_saved": 3.223750421530255, "hosting_offset": 0.24303548416526155, "offset_share_pct": 7.538905075967298, "status": "recorded (not re-run by verify.sh)"}, + {"name": "SFops_k8", "cell": "SFops", "class_description": "factory scratch: own partials stranded at a late read", "class_size": 28, "k": 8, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [9, 29, 61, 89, 121, 141, 173, 201], "lp_objective": 80.00285397998694, "idle_remainder": {"3": 1084.8, "7": 48.0, "48": 1923.2000000000003}, "b": 0.0016176561737476758, "mass": 520704.0, "pct_b_total": 0.9374806279703884, "pct_b_per_helper": 0.11718507849629856, "local_fee_saved": 25.790003372242424, "hosting_offset": 1.9442838733218366, "offset_share_pct": 7.538905075966194, "status": "recorded (not re-run by verify.sh)"}, + {"name": "SFops_k14", "cell": "SFops", "class_description": "factory scratch: own partials stranded at a late read", "class_size": 28, "k": 14, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [9, 25, 41, 57, 73, 89, 105, 121, 137, 153, 169, 185, 201, 217], "lp_objective": 81.46106688497837, "idle_remainder": {"3": 998.3999999999999, "7": 48.0, "48": 2009.6}, "b": 0.0016291106439120322, "mass": 520704.0, "pct_b_total": 1.6522093688992046, "pct_b_per_helper": 0.11801495492137175, "local_fee_saved": 45.13250590142472, "hosting_offset": 3.402496778313264, "offset_share_pct": 7.538905075966223, "status": "recorded (not re-run by verify.sh)"}, + {"name": "SFops_k28", "cell": "SFops", "class_description": "factory scratch: own partials stranded at a late read", "class_size": 28, "k": 28, "whole_class": true, "q": 0.00160263181098207, "deleted_helpers": [9, 13, 25, 29, 41, 45, 57, 61, 73, 77, 89, 93, 105, 109, 121, 125, 137, 141, 153, 157, 169, 173, 185, 189, 201, 205, 217, 221], "lp_objective": 84.86356366329149, "idle_remainder": {"3": 796.8000000000001, "7": 48.0, "48": 2211.2}, "b": 0.0016564792602351479, "mass": 520704.0, "pct_b_total": 3.359938875799795, "pct_b_per_helper": 0.11999781699284982, "local_fee_saved": 90.26501180284592, "hosting_offset": 6.8049935566263855, "offset_share_pct": 7.538905075966361, "status": "recorded (not re-run by verify.sh)"}, + {"name": "SFwst_k1", "cell": "SFwst", "class_description": "factory scratch: retired extra rows (exact source copies, waste)", "class_size": 14, "k": 1, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [10], "lp_objective": 78.30160559083036, "idle_remainder": {"3": 1185.6, "7": 48.0, "48": 1822.4}, "b": 0.001604690748262451, "mass": 520704.0, "pct_b_total": 0.12847225833867437, "pct_b_per_helper": 0.12847225833867437, "local_fee_saved": 3.5372560113625795, "hosting_offset": 0.24303548416526155, "offset_share_pct": 6.870734925167103, "status": "recorded (not re-run by verify.sh)"}, + {"name": "SFwst_k8", "cell": "SFwst", "class_description": "factory scratch: retired extra rows (exact source copies, waste)", "class_size": 14, "k": 8, "whole_class": false, "q": 0.00160263181098207, "deleted_helpers": [10, 26, 58, 90, 122, 138, 170, 202], "lp_objective": 80.00285397998694, "idle_remainder": {"3": 1084.8, "7": 48.0, "48": 1923.2000000000003}, "b": 0.001619252829461201, "mass": 520704.0, "pct_b_total": 1.037107735243814, "pct_b_per_helper": 0.12963846690547676, "local_fee_saved": 28.298048090901002, "hosting_offset": 1.9442838733218366, "offset_share_pct": 6.87073492516611, "status": "recorded (not re-run by verify.sh)"}, + {"name": "SFwst_k14", "cell": "SFwst", "class_description": "factory scratch: retired extra rows (exact source copies, waste)", "class_size": 14, "k": 14, "whole_class": true, "q": 0.00160263181098207, "deleted_helpers": [10, 26, 42, 58, 74, 90, 106, 122, 138, 154, 170, 186, 202, 218], "lp_objective": 81.46106688497837, "idle_remainder": {"3": 998.3999999999999, "7": 48.0, "48": 2009.6}, "b": 0.0016319466855075886, "mass": 520704.0, "pct_b_total": 1.829170887828413, "pct_b_per_helper": 0.13065506341631522, "local_fee_saved": 49.521584159075594, "hosting_offset": 3.402496778313264, "offset_share_pct": 6.870734925166371, "status": "recorded (not re-run by verify.sh)"} + ] +} diff --git a/research/format-limits/data/prices/k1-specs.json b/research/format-limits/data/prices/k1-specs.json new file mode 100644 index 0000000000..29ff8e86ae --- /dev/null +++ b/research/format-limits/data/prices/k1-specs.json @@ -0,0 +1,12 @@ +{ + "what": "The helper deleted in each k = 1 price run of e2_helper_prices.py: for every cell (role, width in the width7_eight word) the single helper whose local fee at q = 1.6026e-3 is closest to the cell mean (ties: lowest index). Helper index h is register 224 + h of the unit (0..223 factory scratch, 224..391 face copies, 392..503 M copies; the eight centre accumulators 0,1,3,5,17,19,33,35 are not priced).", + "cells": { + "F9": {"description": "factory scratch register", "role": "F", "width": 9, "deleted_helpers": [16]}, + "P9": {"description": "face copy of width 9", "role": "P", "width": 9, "deleted_helpers": [318]}, + "P8": {"description": "face copy of an entrant, width 8", "role": "P", "width": 8, "deleted_helpers": [224]}, + "P7": {"description": "face copy of a width-7 provider", "role": "P", "width": 7, "deleted_helpers": [262]}, + "M8": {"description": "M copy of an entrant, width 8", "role": "M", "width": 8, "deleted_helpers": [392]}, + "M5": {"description": "M copy of a rank-4 pivot, width 5", "role": "M", "width": 5, "deleted_helpers": [395]}, + "M4": {"description": "M copy of a rank-5 pivot, width 4", "role": "M", "width": 4, "deleted_helpers": [419]} + } +} diff --git a/research/format-limits/data/prices/pr569-provider-calls.json b/research/format-limits/data/prices/pr569-provider-calls.json new file mode 100644 index 0000000000..0fe303801b --- /dev/null +++ b/research/format-limits/data/prices/pr569-provider-calls.json @@ -0,0 +1,91 @@ +{ + "what": "Per-call provider dispatch of PR #569: which of the 72 calls of the global word use the width7_eight local word and which the fourcold_eight local word. Only the time and provider of each call are kept; the rest of the plan is not needed here.", + "source": { + "pull_request": 569, + "author": "DominikScholz", + "repository": "https://github.com/CrocSwap/integer-mult-bounds (head of pull request 569)", + "commit": "1424da81263a5305b35e4cf74ef686c275f03f3f", + "path": "research/larger-gain/global/width7/PLAN.json", + "key": "provider_calls", + "git_blob_id": "54c7ae22839dcb87f37194b2354ba9268edc48d7" + }, + "provider_words": { + "width7_eight": "data/vendor/pr569/width7-eight-FULL-WORD.json.gz", + "fourcold_eight": "data/vendor/pr569/fourcold-eight-FULL-WORD.json.gz" + }, + "calls_format": "[time, provider] for time 0..71", + "calls": [ + [0, "width7_eight"], + [1, "width7_eight"], + [2, "width7_eight"], + [3, "width7_eight"], + [4, "width7_eight"], + [5, "width7_eight"], + [6, "width7_eight"], + [7, "width7_eight"], + [8, "fourcold_eight"], + [9, "width7_eight"], + [10, "width7_eight"], + [11, "width7_eight"], + [12, "width7_eight"], + [13, "width7_eight"], + [14, "width7_eight"], + [15, "width7_eight"], + [16, "width7_eight"], + [17, "width7_eight"], + [18, "width7_eight"], + [19, "width7_eight"], + [20, "width7_eight"], + [21, "fourcold_eight"], + [22, "width7_eight"], + [23, "width7_eight"], + [24, "width7_eight"], + [25, "width7_eight"], + [26, "width7_eight"], + [27, "width7_eight"], + [28, "width7_eight"], + [29, "width7_eight"], + [30, "width7_eight"], + [31, "width7_eight"], + [32, "fourcold_eight"], + [33, "fourcold_eight"], + [34, "width7_eight"], + [35, "width7_eight"], + [36, "fourcold_eight"], + [37, "width7_eight"], + [38, "width7_eight"], + [39, "fourcold_eight"], + [40, "width7_eight"], + [41, "width7_eight"], + [42, "width7_eight"], + [43, "width7_eight"], + [44, "width7_eight"], + [45, "width7_eight"], + [46, "width7_eight"], + [47, "width7_eight"], + [48, "width7_eight"], + [49, "width7_eight"], + [50, "fourcold_eight"], + [51, "width7_eight"], + [52, "width7_eight"], + [53, "width7_eight"], + [54, "width7_eight"], + [55, "width7_eight"], + [56, "fourcold_eight"], + [57, "width7_eight"], + [58, "width7_eight"], + [59, "width7_eight"], + [60, "width7_eight"], + [61, "width7_eight"], + [62, "width7_eight"], + [63, "width7_eight"], + [64, "width7_eight"], + [65, "width7_eight"], + [66, "width7_eight"], + [67, "width7_eight"], + [68, "width7_eight"], + [69, "width7_eight"], + [70, "fourcold_eight"], + [71, "width7_eight"] + ] +} diff --git a/research/format-limits/data/toys/sol_p3_cone_R16.json b/research/format-limits/data/toys/sol_p3_cone_R16.json new file mode 100644 index 0000000000..8908f77525 --- /dev/null +++ b/research/format-limits/data/toys/sol_p3_cone_R16.json @@ -0,0 +1 @@ +{"format": "gcert/1", "derived_from": "p3 cone-A + B0", "p": 0, "h": 6, "v": 8, "R": 16, "ports": [21, 37, 25, 41, 22, 38, 26, 42], "frames": [[], [32, 16, 8, 4, 2, 1], [21], [37], [25], [41], [22], [38], [26], [42], [37, 21], [25, 3], [41, 3], [38, 12], [21, 12], [37, 12], [22, 12], [21, 12, 3], [37, 21, 12], [38, 22], [37, 12, 3], [38, 22, 12], [37, 21, 12, 3], [41, 25, 3], [37, 21, 3], [21, 15], [42, 25], [37, 22], [51, 15], [38, 15], [38, 21, 15], [37, 22, 15], [35, 16, 9, 6], [33, 18, 11, 4], [33, 18, 8, 7], [34, 17, 11, 4], [33, 17, 8, 4, 3], [33, 16, 8, 5, 2], [34, 16, 10, 4, 1], [34, 16, 8, 6, 1], [32, 17, 8, 5, 2], [32, 17, 9, 4, 2], [33, 16, 9, 4, 2], [32, 18, 8, 6, 1], [32, 18, 10, 4, 1]], "start": [2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "final": [1, 1, 1, 1, 1, 1, 1, 1, 40, 37, 41, 42, 43, 39, 44, 38, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], "ext": [], "ret": [[0, 16, 18], [1, 17, 21], [2, 18, 24], [3, 19, 23], [4, 20, 17], [5, 21, 20]], "scat": {"inside": [1, 3], "outside": [-1, 6]}, "A": [["out", 2, 0, [[26, -1, 1]], []], ["out", 2, 0, [[27, 1, 1]], []], ["out", 3, 1, [[16, 1, 1]], []], ["out", 3, 1, [[29, 1, 1]], []], ["out", 4, 2, [[20, 1, 1]], []], ["out", 4, 2, [[24, 1, 1]], []], ["out", 5, 3, [[19, 1, 1]], []], ["out", 5, 3, [[30, -1, 1]], []], ["out", 6, 4, [[22, 1, 1]], []], ["out", 7, 5, [[18, 1, 1]], []], ["out", 7, 5, [[25, -1, 1]], []], ["out", 8, 6, [[23, -1, 1]], []], ["out", 8, 6, [[31, 1, 1]], []], ["out", 9, 7, [[17, 1, 1]], []], ["out", 9, 7, [[21, 1, 1]], []], ["out", 10, 16, [[26, -1, 1]], []], ["out", 11, 20, [[23, -1, 1]], []], ["out", 12, 21, [[19, 1, 1]], []], ["out", 13, 25, [[17, -1, 1]], []], ["out", 14, 27, [[24, 1, 1]], []], ["out", 15, 30, [[29, -1, 1]], []], ["out", 16, 31, [[22, 1, 1]], []], ["out", 6, 4, [[28, 1, 1]], []], ["out", 17, 22, [[20, 1, 1]], []], ["out", 18, 24, [[30, -1, 1]], []], ["out", 19, 28, [[18, 1, 1]], []], ["out", 20, 29, [[25, -1, 1]], []], ["out", 21, 31, [[17, 1, 1]], []], ["out", 22, 22, [[29, -1, 1]], []], ["out", 23, 23, [[19, -1, 1]], []], ["out", 20, 25, [[21, -1, 1]], []], ["out", 24, 26, [[18, -1, 1]], []], ["out", 17, 27, [[20, 1, 1]], []], ["out", 21, 28, [[17, 1, 1]], []], ["out", 18, 30, [[16, -1, 1]], []]], "B": [["out", 8, 6, [[10, -1, 2]], []], ["out", 8, 6, [[9, 1, 2]], []], ["out", 8, 6, [[12, -1, 2]], []], ["out", 8, 6, [[15, 1, 2]], []], ["out", 25, 0, [[9, 1, 2]], []], ["out", 26, 2, [[7, 1, 1]], []], ["out", 27, 4, [[1, -1, 1]], []], ["out", 27, 4, [[11, 1, 2]], []], ["out", 25, 6, [[15, -1, 2]], []], ["out", 28, 13, [[14, -1, 1]], []], ["out", 25, 0, [[6, -1, 1]], []], ["out", 29, 5, [[3, -1, 1]], []], ["out", 26, 7, [[2, -1, 1]], []], ["out", 28, 8, [[14, 1, 1]], []], ["out", 25, 15, [[10, 1, 1]], []], ["out", 30, 0, [[5, 1, 1]], []], ["out", 30, 0, [[10, 1, 2]], []], ["out", 30, 0, [[12, -1, 2]], []], ["out", 30, 0, [[15, 1, 1]], []], ["out", 31, 1, [[8, -1, 2]], []], ["out", 31, 2, [[8, -1, 1]], []], ["out", 31, 2, [[14, -1, 1]], []], ["out", 30, 3, [[10, -1, 1]], []], ["out", 30, 3, [[15, -1, 2]], []], ["out", 31, 4, [[8, -1, 1]], []], ["out", 31, 4, [[14, -1, 1]], []], ["out", 31, 7, [[8, -1, 2]], []], ["out", 31, 7, [[14, -1, 1]], []], ["out", 31, 1, [[4, 1, 1]], []], ["out", 31, 2, [[4, -1, 1]], []], ["out", 31, 2, [[11, -1, 2]], []], ["out", 31, 2, [[14, 1, 2]], []], ["out", 30, 3, [[0, -1, 1]], []], ["out", 30, 3, [[9, -1, 1]], []], ["out", 30, 5, [[0, -1, 1]], []], ["out", 30, 5, [[9, -1, 1]], []], ["out", 31, 7, [[4, -1, 1]], []], ["out", 31, 7, [[11, -1, 1]], []], ["out", 31, 8, [[14, -1, 1]], []], ["out", 30, 12, [[9, 1, 1]], []], ["out", 31, 13, [[14, 1, 1]], []], ["out", 30, 15, [[10, -1, 1]], []], ["out", 32, 1, [[13, 1, 2]], []], ["out", 33, 2, [[14, 1, 2]], []], ["out", 34, 3, [[12, 1, 2]], []], ["out", 32, 4, [[13, -1, 2]], []], ["out", 35, 5, [[6, 1, 1]], []], ["out", 35, 5, [[15, -1, 2]], []], ["out", 33, 2, [[14, -1, 1]], []], ["out", 34, 3, [[0, 1, 1]], []], ["out", 32, 4, [[1, -1, 1]], []], ["out", 32, 4, [[7, 1, 1]], []], ["out", 32, 4, [[11, -1, 2]], []], ["out", 35, 5, [[15, 1, 1]], []], ["out", 35, 6, [[15, -1, 2]], []], ["out", 34, 12, [[9, -1, 1]], []], ["out", 36, 0, [[6, 1, 1]], []], ["out", 37, 3, [[9, 1, 1]], []], ["out", 38, 5, [[15, -1, 1]], []], ["out", 39, 7, [[13, 1, 2]], []], ["out", 36, 0, [[2, 1, 1]], []], ["out", 36, 0, [[6, -1, 1]], []], ["out", 39, 7, [[13, -1, 1]], []], ["out", 1, 3, [[1, -1, 1]], []], ["out", 1, 3, [[2, -1, 1]], []], ["out", 1, 4, [[1, 1, 1]], []], ["out", 1, 4, [[2, -1, 1]], []], ["out", 1, 5, [[0, 1, 1]], []], ["out", 1, 5, [[1, -1, 1]], []], ["out", 1, 7, [[2, 1, 1]], []], ["out", 1, 0, [[3, -1, 1]], []], ["out", 1, 0, [[4, -1, 1]], []], ["out", 1, 0, [[6, 1, 1]], []], ["out", 1, 0, [[7, -1, 1]], []], ["out", 1, 1, [[4, -1, 1]], []], ["out", 1, 1, [[7, -1, 1]], []], ["out", 1, 2, [[4, 1, 1]], []], ["out", 1, 5, [[3, 1, 1]], []], ["out", 1, 5, [[6, -1, 1]], []], ["out", 1, 0, [[1, 1, 1]], []], ["out", 1, 0, [[5, -1, 1]], []], ["out", 1, 3, [[1, 1, 1]], []], ["out", 1, 3, [[2, 1, 1]], []], ["out", 1, 3, [[7, -1, 1]], []], ["out", 1, 4, [[1, 1, 1]], []], ["out", 1, 4, [[7, -1, 1]], []]], "blocks": {"x": {"1": 34, "2": 3}, "y": {"1": 26, "2": 7}, "s": {"1": 50, "2": 2, "3": 14}, "c": {"3": 6}}, "N": 194, "cst": 18} \ No newline at end of file diff --git a/research/format-limits/data/vendor/pr543/EXPECTED.json b/research/format-limits/data/vendor/pr543/EXPECTED.json new file mode 100644 index 0000000000..b61d859826 --- /dev/null +++ b/research/format-limits/data/vendor/pr543/EXPECTED.json @@ -0,0 +1,239 @@ +{ + "word": { + "file": "data/bitE8mut-r420.json.gz", + "file_sha256": "9721a3819efd68307f66f00d84a952eb28eb7c3ad2ce79653ce295c98a764784", + "content_sha256": "e597e3885c0a8daa262afa5985ee2930443329126aece76d33159d81882bdcc2", + "plan_sha256": "d5c2494b4baae801143049e172db303b3c8e25a002e9d9715a1d79dcb2d74c58", + "R": 420, + "retained_totals": 9, + "gates": { + "D": 120, + "A": 2064, + "B": 837, + "C": 2661 + }, + "local_histogram": { + "1": 1320, + "2": 1440, + "4": 60, + "5": 120, + "8": 9 + }, + "legality": { + "records": 29874, + "common_frame_ADDs": 26916, + "rank_mass": 5112 + }, + "five_stage_columns": 900, + "five_stage_payload_bits": 100, + "nine_stage_columns": 1380, + "nine_stage_payload_bits": 100, + "eleven_stage_columns": 1620, + "eleven_stage_payload_bits": 100 + }, + "interface": { + "hypotheses": { + "H1_no_target_feeds_z": true, + "H2_D_frame0_targets_from_helpers": true, + "H3_A_z_only_scatter_targets_B_split": true, + "H4_C_literal_reverse_at_full": true, + "H5_V_identity": true + }, + "conclusion": { + "U_block_form": true, + "J_identity": true, + "K_equals_D": true, + "C_is_U_inverse": true + }, + "interleaving": { + "z_gates_A": 2064, + "source_writes_A": 0, + "z_gates_B": 597, + "source_writes_B": 597, + "helper_writes_B": 0, + "sources_rewritten_B": 120, + "B_z_write_frame_dims": { + "4": 477, + "6": 120 + }, + "B_z_operand_kinds": { + "source<-helper": 120, + "source<-source": 477 + }, + "B_z_operand_kinds_by_dim": { + "dim4:source<-source": 477, + "dim6:source<-helper": 120 + }, + "B_z_writes_after_a_target_read_of_the_same_register": 597, + "B_target_reads_of_registers_rewritten_earlier_in_B": 120, + "target_reads_of_targets": 0, + "cleanup_gates": 2661 + } + }, + "nine": { + "pipeline": "PR #510 (PR #463 nine-stage template, 32 replicas, PR #442 hosting, PR #493 outer assembly)", + "bit_lower": "413456747036699736067/250000000000000000000000", + "bit_lower_decimal": "0.001653826988146798944268", + "profile": { + "1": 380160, + "2": 426624, + "4": 32640, + "5": 34560, + "6": 15360, + "7": 23040, + "8": 2592, + "42": 3456 + }, + "bit": { + "live": 30720, + "physical": 30729, + "maxchild": 42, + "rank_mass": 1956096, + "hosted_residuals": 120960, + "bank_assignments": 0, + "prefix_bits": 189, + "charged_prefix_bits": 192, + "coefficient_bits": 189, + "two_level_coefficient_bits": 416, + "charged_two_level_coefficient_bits": 512, + "row_coefficient": 18355, + "halving_depth": 2 + }, + "kappa": { + "pr535": { + "kappa": "793733736881443/500000000000000000", + "kappa_decimal": "0.001587467473762886", + "complex_lower": "1589991533606044174031/1000000000000000000000000", + "binding_component": "complex" + }, + "pr531": { + "kappa": "1517760977820331/1000000000000000000", + "kappa_decimal": "0.001517760977820331", + "complex_lower": "304013615566499291631/200000000000000000000000", + "binding_component": "complex" + } + } + }, + "eleven": { + "pipeline": "PR #493 (PR #445 geometry, PR #472 emitter, audit and invoice, PR #442 hosting)", + "bit_lower": "82015816228313440961/50000000000000000000000", + "bit_lower_decimal": "0.00164031632456626881922", + "profile": { + "1": 116160, + "2": 130560, + "4": 9120, + "5": 10560, + "6": 3839, + "7": 7680, + "8": 792, + "14": 1, + "56": 3497 + }, + "bit": { + "live": 9600, + "physical": 9609, + "maxchild": 56, + "rank_mass": 745536, + "hosted_residuals": 36960, + "prefix_bits": 4710, + "charged_prefix_bits": 4736, + "coefficient_bits": 4710, + "three_level_coefficient_bits": 14185, + "charged_three_level_coefficient_bits": 14208, + "row_coefficient": 19431, + "halving_depth": 3 + }, + "emitted_program_sha256": "3de4325e0df33ca85116d05cbcc6810f025d6083e5134d128236769c97699f51", + "kappa": { + "pr535": { + "kappa": "793733736881443/500000000000000000", + "kappa_decimal": "0.001587467473762886", + "complex_lower": "1589991533606044174031/1000000000000000000000000", + "binding_component": "complex" + }, + "pr531": { + "kappa": "1517760977820331/1000000000000000000", + "kappa_decimal": "0.001517760977820331", + "complex_lower": "304013615566499291631/200000000000000000000000", + "binding_component": "complex" + } + } + }, + "controls": { + "pr510_r480_nine": { + "word": "vendor/pr510/e8-r480-dead-totals/data/bitE8mut-r480.json.gz", + "bit_lower": "396861776523137034277/250000000000000000000000", + "bit_lower_decimal": "0.001587447106092548137108", + "kappa": { + "pr535": { + "kappa": "1584931111785787/1000000000000000000", + "kappa_decimal": "0.001584931111785787", + "complex_lower": "1589991533606044174031/1000000000000000000000000", + "binding_component": "bit" + }, + "pr531": { + "kappa": "1517760977820331/1000000000000000000", + "kappa_decimal": "0.001517760977820331", + "complex_lower": "304013615566499291631/200000000000000000000000", + "binding_component": "complex" + }, + "pr484": { + "kappa": "662515748707749/500000000000000000", + "kappa_decimal": "0.001325031497415498", + "complex_lower": "663394767670133/500000000000000000", + "binding_component": "complex" + }, + "pr471": { + "kappa": "260706019542937/200000000000000000", + "kappa_decimal": "0.001303530097714685", + "complex_lower": "1305231506267591/1000000000000000000", + "binding_component": "complex" + }, + "pr458": { + "kappa": "323880830669511/250000000000000000", + "kappa_decimal": "0.001295523322678044", + "complex_lower": "1297203880559577927527/1000000000000000000000000", + "binding_component": "complex" + } + } + }, + "pr493_r489_eleven": { + "word": "../e8-bit-carrier-skeleton/data/bit/bitE8mut-r489.json.gz", + "bit_lower": "391360506745414227149/250000000000000000000000", + "bit_lower_decimal": "0.001565442026981656908596", + "emitted_program_sha256": "e37b52e4861c39154b5891460fb45df8651c92cd68961078f8f06596f057c822", + "kappa": { + "pr535": { + "kappa": "97687203033227/62500000000000000", + "kappa_decimal": "0.001562995248531632", + "complex_lower": "1589991533606044174031/1000000000000000000000000", + "binding_component": "bit" + }, + "pr531": { + "kappa": "1517760977820331/1000000000000000000", + "kappa_decimal": "0.001517760977820331", + "complex_lower": "304013615566499291631/200000000000000000000000", + "binding_component": "complex" + }, + "pr484": { + "kappa": "662515748707749/500000000000000000", + "kappa_decimal": "0.001325031497415498", + "complex_lower": "663394767670133/500000000000000000", + "binding_component": "complex" + }, + "pr471": { + "kappa": "260706019542937/200000000000000000", + "kappa_decimal": "0.001303530097714685", + "complex_lower": "1305231506267591/1000000000000000000", + "binding_component": "complex" + }, + "pr458": { + "kappa": "323880830669511/250000000000000000", + "kappa_decimal": "0.001295523322678044", + "complex_lower": "1297203880559577927527/1000000000000000000000000", + "binding_component": "complex" + } + } + } + } +} diff --git a/research/format-limits/data/vendor/pr545/EXPECTED.json b/research/format-limits/data/vendor/pr545/EXPECTED.json new file mode 100644 index 0000000000..ac7f102363 --- /dev/null +++ b/research/format-limits/data/vendor/pr545/EXPECTED.json @@ -0,0 +1,127 @@ +{ + "word": { + "v": 111, + "R": 489, + "cst": 63, + "copy_ranks": [ + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7 + ], + "punctured": [ + 39, + 52, + 57, + 71, + 76, + 78, + 103, + 109, + 114 + ], + "content_sha256": "599dbe2184a0b99f01345543a6523e41df7b164513fc25dc7b5dadab548205d3", + "local_histogram": { + "1": 1182, + "2": 1332, + "3": 63, + "4": 153, + "5": 9, + "6": 129, + "7": 9 + }, + "five_stage_payload_bits": 91, + "eleven_stage_payload_bits": 91, + "source_destination_gates_A": 501 + }, + "eleven": { + "bit_lower": "165947204563779652541/100000000000000000000000", + "bit_lower_decimal": "0.00165947204563779652541", + "profile": { + "1": 104016, + "2": 120768, + "3": 5544, + "4": 17015, + "5": 792, + "6": 14904, + "7": 7896, + "28": 1, + "56": 1971 + }, + "emitted_program_sha256": "80c4e0f4d635a0c09a3e1a0ef81cc7e0590233410840d5b26d1149daa049718b", + "bit": { + "live": 8880, + "physical": 8889, + "maxchild": 56, + "rank_mass": 689304, + "hosted_residuals": 43032, + "prefix_bits": 1843, + "charged_prefix_bits": 1856, + "coefficient_bits": 1843, + "three_level_coefficient_bits": 5583, + "charged_three_level_coefficient_bits": 5632, + "row_coefficient": 19431, + "halving_depth": 3 + } + }, + "kappa": { + "pr535": { + "kappa": "793733736881443/500000000000000000", + "kappa_decimal": "0.001587467473762886", + "binding_component": "complex", + "complex_lower": "1589991533606044174031/1000000000000000000000000" + } + }, + "control": { + "word_content_sha256": "8639e22008c24a5690dd250eba702fe5fca85ddd036dcbec20baba77b7e213ba", + "five_stage_payload_bits": 102, + "bit_lower": "393688338238452162729/250000000000000000000000", + "bit_lower_decimal": "0.001574753352953808650916", + "profile": { + "1": 105600, + "2": 130560, + "4": 19680, + "6": 14400, + "7": 7680, + "8": 792, + "48": 1, + "56": 2742 + }, + "emitted_program_sha256": "7da5e3cb8daa231104f4e55f16c560fa56a59f10b8b2789e173923d9d2ca46fc", + "kappa": { + "pr484": "662515748707749/500000000000000000", + "pr471": "260706019542937/200000000000000000", + "pr458": "323880830669511/250000000000000000" + }, + "stacking": { + "calls": 88, + "calls_with_stacked_helpers": 80, + "max_helpers_on_one_family": 7 + } + }, + "live_bit": { + "source": "PR #510 (R480, nine stages), the bit c of PR #535", + "bit_lower": "396861776523137034277/250000000000000000000000", + "bit_lower_decimal": "0.001587447106092548137108" + }, + "pr535": { + "kappa": "1584931111785787/1000000000000000000", + "kappa_decimal": "0.001584931111785787", + "complex_lower": "1589991533606044174031/1000000000000000000000000" + }, + "hosting": { + "rule": "PR #442's pooled whole-residual integer programme (hosting_core.solve), eight replicas pooled, as in #472 / #493 / #503 / #510", + "stacking": { + "calls": 88, + "calls_with_stacked_helpers": 82, + "max_helpers_on_one_family": 7 + }, + "c_strict_per_stage": "0.001651282591289915996071", + "note": "strict = one helper per family copy per STAGE (stricter than #458 per invocation): a lower bound on the c of any #458-compliant hosting; kappa unchanged (complex-bound)" + } +} diff --git a/research/format-limits/data/vendor/pr555/EXPECTED.json b/research/format-limits/data/vendor/pr555/EXPECTED.json new file mode 100644 index 0000000000..c8ad4439e8 --- /dev/null +++ b/research/format-limits/data/vendor/pr555/EXPECTED.json @@ -0,0 +1,221 @@ +{ + "word": { + "file": "data/bitE8mut-p111.json.gz", + "file_sha256": "1bc3c53d4baf4927c6a00c339ea3975516471a48074d4d97c6addaded2a4d858", + "content_sha256": "d0df3c89ae84989f4045ba1681a5e2e9fcdfa38ed0301c8abf5185537155cbe3", + "plan_sha256": "d5c2494b4baae801143049e172db303b3c8e25a002e9d9715a1d79dcb2d74c58", + "params_sha256": "dd371e1d6fe57602f242920b582ec4c133efc8ede4a849fd701ac90b7f6e1853", + "v": 111, + "R": 439, + "punctured": [ + 10, + 15, + 51, + 53, + 56, + 66, + 72, + 89, + 111 + ], + "retained_totals": 9, + "copy_ranks": [ + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7 + ], + "decoder_constant": 63, + "gates": { + "D": 111, + "A": 2073, + "B": 712, + "C": 2572 + }, + "stats": { + "extra_carriers": 10, + "junk_wires": 0, + "rcv_flips": 10 + }, + "local_histogram": { + "1": 1321, + "2": 1342, + "3": 80, + "4": 47, + "5": 102, + "6": 10, + "7": 18 + }, + "legality": { + "records": 27234, + "common_frame_ADDs": 24305, + "rank_mass": 5129 + }, + "five_stage_columns": 883, + "five_stage_payload_bits": 93, + "nine_stage_columns": 1327, + "nine_stage_payload_bits": 93, + "eleven_stage_columns": 1549, + "eleven_stage_payload_bits": 93 + }, + "interface": { + "hypotheses": { + "H1_no_target_feeds_z": true, + "H2_D_frame0_targets_from_helpers": true, + "H3_A_z_only_scatter_targets_B_split": true, + "H4_C_literal_reverse_at_full": true, + "H5_V_identity": true + }, + "conclusion": { + "U_block_form": true, + "J_identity": true, + "K_equals_D": true, + "C_is_U_inverse": true + }, + "interleaving": { + "z_gates_A": 2073, + "source_writes_A": 0, + "z_gates_B": 499, + "source_writes_B": 483, + "helper_writes_B": 16, + "sources_rewritten_B": 108, + "B_z_write_frame_dims": { + "4": 388, + "6": 111 + }, + "B_z_operand_kinds": { + "helper<-helper": 3, + "helper<-source": 13, + "source<-helper": 108, + "source<-source": 375 + }, + "B_z_operand_kinds_by_dim": { + "dim4:helper<-source": 13, + "dim4:source<-source": 375, + "dim6:helper<-helper": 3, + "dim6:source<-helper": 108 + }, + "B_z_writes_after_a_target_read_of_the_same_register": 454, + "B_target_reads_of_registers_rewritten_earlier_in_B": 111, + "target_reads_of_targets": 0, + "cleanup_gates": 2572 + } + }, + "nine": { + "pipeline": "PR #510 (PR #463 nine-stage template, 32 replicas, PR #442 hosting, PR #493 outer assembly), parameterised for v <= 120 by code/adapt9p.py", + "bit_lower": "1733052823330162099157/1000000000000000000000000", + "bit_lower_decimal": "0.001733052823330162099157", + "profile": { + "1": 380448, + "2": 400416, + "3": 23040, + "4": 27744, + "5": 29376, + "6": 17088, + "7": 26496, + "42": 288 + }, + "bit": { + "live": 28416, + "physical": 28425, + "maxchild": 42, + "rank_mass": 1808352, + "hosted_residuals": 126432, + "bank_assignments": 0, + "prefix_bits": 177, + "charged_prefix_bits": 192, + "coefficient_bits": 177, + "two_level_coefficient_bits": 393, + "charged_two_level_coefficient_bits": 512, + "row_coefficient": 18355, + "halving_depth": 2 + }, + "kappa": { + "pr548": { + "kappa": "49718254847979/31250000000000000", + "kappa_decimal": "0.001590984155135328", + "complex_lower": "796759709641153381751/500000000000000000000000", + "binding_component": "complex" + }, + "pr544": { + "kappa": "397677800014309/250000000000000000", + "kappa_decimal": "0.001590711200057236", + "complex_lower": "31864911873350496687/20000000000000000000000", + "binding_component": "complex" + }, + "pr535": { + "kappa": "793733736881443/500000000000000000", + "kappa_decimal": "0.001587467473762886", + "complex_lower": "1589991533606044174031/1000000000000000000000000", + "binding_component": "complex" + }, + "pr531": { + "kappa": "1517760977820331/1000000000000000000", + "kappa_decimal": "0.001517760977820331", + "complex_lower": "304013615566499291631/200000000000000000000000", + "binding_component": "complex" + } + } + }, + "eleven": { + "pipeline": "PR #493 (PR #445 geometry, PR #472 emitter, audit and invoice, PR #442 hosting), parameterised by PR #545's code/adapt.py", + "bit_lower": "427652201524503168379/250000000000000000000000", + "bit_lower_decimal": "0.001710608806098012673516", + "profile": { + "1": 116248, + "2": 121648, + "3": 7040, + "4": 7688, + "5": 8976, + "6": 4432, + "7": 8688, + "56": 2600 + }, + "bit": { + "live": 8880, + "physical": 8889, + "maxchild": 56, + "rank_mass": 689304, + "hosted_residuals": 38632, + "prefix_bits": 4304, + "charged_prefix_bits": 4352, + "coefficient_bits": 4304, + "three_level_coefficient_bits": 12966, + "charged_three_level_coefficient_bits": 12992, + "row_coefficient": 19431, + "halving_depth": 3 + }, + "emitted_program_sha256": "790bbd2d8ef4e1169fca344c76b1965ee2fe78e34e74d16d458e4849cc2ba3b6", + "kappa": { + "pr548": { + "kappa": "49718254847979/31250000000000000", + "kappa_decimal": "0.001590984155135328", + "complex_lower": "796759709641153381751/500000000000000000000000", + "binding_component": "complex" + }, + "pr544": { + "kappa": "397677800014309/250000000000000000", + "kappa_decimal": "0.001590711200057236", + "complex_lower": "31864911873350496687/20000000000000000000000", + "binding_component": "complex" + }, + "pr535": { + "kappa": "793733736881443/500000000000000000", + "kappa_decimal": "0.001587467473762886", + "complex_lower": "1589991533606044174031/1000000000000000000000000", + "binding_component": "complex" + }, + "pr531": { + "kappa": "1517760977820331/1000000000000000000", + "kappa_decimal": "0.001517760977820331", + "complex_lower": "304013615566499291631/200000000000000000000000", + "binding_component": "complex" + } + } + } +} diff --git a/research/format-limits/data/vendor/pr555/bitE8mut-p111.json.gz b/research/format-limits/data/vendor/pr555/bitE8mut-p111.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..bdbb95aa970647bd52895c120b494f0f591f3f87 GIT binary patch literal 42345 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�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 S9}6I{AnklFlkEa|j{FCPvmfIC literal 0 HcmV?d00001 diff --git a/research/format-limits/data/vendor/pr569/width7-eight-FULL-WORD.json.gz b/research/format-limits/data/vendor/pr569/width7-eight-FULL-WORD.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..d4f2dae82814ae4c7553c08e0dc39a1c383e3e3d GIT binary patch literal 36690 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 QYdeCneO^97hO+z|)I7=$nNUfX^O!vlgcN(C-H? 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

GcX}3fJma`+#Fi@PQ!__GPZp-+& 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

Pl3$^ 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 z zQP*dMYpLp4Y=Ia*X48Ze&aQ2hfa=u+OubJzN%u5PXYPaHm7yj!uOza8%p1em<8YQ1JE=p1OfF}MO+fS1&`jI~_ z4<%D)-(#u4IY{&6a{)GbHX{H4@93b&&>d+Xa?=t>+^ObICUNOA@@;;{q}xd_Ef$k= zkqmM1rsuhXA+&95CkW;Y^uV@ab4p7CQ{m^Y=PH2smk$9QtpnyGEM>Fao|O-nMSXq_ z|8(Tx75bVt^n6XXkg^AwZGVdyboD%$QeP#BIczkBqGJm~N~GQr1=}9Z-nnzk22!ev z56CM;SGqkk#ss{*DD`dKL0I?u^&~Jf)3CIYY7Az0pSI|qdpgEOEOrw%Apb$^*-3sK znmq12!VB1mdhig$v=-wu*rZ;(&?B|A7?MoNumbq^_JOHZOytoJ2~O|MfYX=AWOex7 zQ3m*I<&;c{q&Z@rVH@0QBa!_Kjoh$9S!I&ic@+nrL6JTUg{ zEjsr)je*a%N75Egw--yLraSl6`UuWrw&rw=P4nRN$v>dt72$OlSIt{&uOStoY+wA) z;N+ZaH%bQm)A=Bmpo!+^9u5!B&mtnc<2rG<%B4!jIkY^@E-oQ&sel9#Z|BdN#=u@g z_I$4=&$j}JH_BHLyoOT$;yuSK@o!6iozJb=tu8GQ<^? zvCpL@_BiD2+!&oiVTpg8=i-SvRZRc*mj8EK)(_72B=G3u#$AAi|K$iUNDizf=B%0j z))v*Csxo4RsUM)b9VKrHJeu6VNTUjfY(;s_iPEvytdr!g4O*n*)Q6QcS()XR>NDb2 zMF&(BPP)@%xcY0SJ`*wm5W)RIHR~5vwi?QlrO<=dQ^mGa4nAV=$@n^ya3g}d3d);N zJp}Ha5q8mV(IO>7lR}!gcPnhE)ruV~WNVc~u$%GH=Sqj~pV~dc_GLu*KpZQXxWiuodz^^WIS!Ft+1|$CKrj5N5 zl+wIDX}DDS^OAI^Q;?qhm}5*}s);YoK(eY1_n@IJNxEArXgkYfJNL_;*i12adwkWm zaMjgDc3H|m6d#DRDzhuJLuGik3n2)~j;%*Cl?3;0#qf3@xmdc$R9h_6H)u^yGf`>r z&^@ITx+0S|KW)b0-tm-3e4LRRLSA_o`l^LBr*;3CQ;WW+l5aZY$qaI<3Sd^MTHE~0 z!*#-8?MUa_N3R2Sn2#P4K94xto%X~|=AAnzh{I%6f?($8LRwG^jpCRJ=2L78XyCm$ z5g4MSlJBW$=Z}xgISh15mxSTg>M-if*F~q0_7-5-iM}HvqG7H5^h&?9^}CTpY20(!PLVo)97%noIBE6ZMKckC710_ z#+cQ^7xiu;`jvH5Y*dbHl<`PkX{*TV7OVt0HutQnwY%n0_IMw`$?LXc-G%Q~U-!#Z z8lsWE*BM&wSyg3_Bc|oRbwhogn~!1J%(EylRNRv4?r01)?T2$}R3fb7ggLc_uw?3Z zP2yYH)P08hMk3c}eRPx=VacUr;OU5A^mtu&(tM|Yy2ZDs2?~b{A9o9@;)`mBjpn1k zmEqS27zf};>RZ~#(%3N3@C+4Qe%XF0iOs#;zL{w`{`G!2=-S=xc4~DEx7Jl*=mL^8 zxdO7h30mrR%3aGHP7X@FwA}9#Tm>aDByjmp^fhg|+J(p^%A?~2JJ zBE2nZt{{1V)UJZT!1~=nU4d77LrT)6kC;Ki(DALW*NmmtIV@wX1}47aGqKaaQN(lT u=2w3ucc@2I$M4J@)!+mNnTuc3)xo44_zVgEs2&_efdiGf=h=fV{r2C6D^!aB literal 0 HcmV?d00001 diff --git a/research/format-limits/requirements.txt b/research/format-limits/requirements.txt new file mode 100644 index 0000000000..b010097e9d --- /dev/null +++ b/research/format-limits/requirements.txt @@ -0,0 +1,7 @@ +# Tested set (a fresh venv from this file passes verify.sh on Python 3.11.17 and 3.14.7). +# Also tested and passing: numpy 2.3.5 / scipy 1.17.0 and numpy 2.5.4 / scipy 1.18.1 (see README, "Tested environments"). +numpy==2.4.6 +scipy==1.17.1 +sympy==1.14.0 +python-sat==1.9.dev15 +ortools==9.15.6755 diff --git a/research/format-limits/scripts/a1_ceiling_roots.py b/research/format-limits/scripts/a1_ceiling_roots.py new file mode 100644 index 0000000000..99d470c031 --- /dev/null +++ b/research/format-limits/scripts/a1_ceiling_roots.py @@ -0,0 +1,81 @@ +"""A. Format ceiling, rev. 2: certified skeleton roots X(s, w, x) and the kappa ceiling. + +X(s, w, x) is the root q of F_s(q) = D_s for the R-free B_K skeleton (skel.profile, theorem variant: active climbs at +cap a = h - k, copies at cap h, idle slots of rank r at cap max(r, h)); D_s/v = (s-1)k - s x, m = k + s(h-k). +Roots are bracketed with rational-interval arithmetic (cert.root_bracket); the upper end 'hi' is the certified value. + +Units (k = 1): + bit, punctured star unit of #545/#555: v = 111 live lines, decoder constant 63 (x = 63/111), window h = 8; + complex, punctured unit of #523 and its descendants (#531 ... #569): v = 112, copy cost 64 (x = 64/112), h = 9; + superseded rev. 1 scope: the full E8 unit v = 120, copy cost 72 (x = 3/5) on both sides. +kappa <= X/(1+X) with X = min(X_bit*, X_cx*) (README section A.4 for the outer form). +""" +from fractions import Fraction as Fr + +from report import check, finish, near +from skel import X + +S_LIST = (3, 5, 7, 9, 11, 13) +UNITS = { # name: (h, cst, v) + 'bit-111/63': (8, 63, 111), + 'cx-112/64': (9, 64, 112), + 'bit-120/72': (8, 72, 120), + 'cx-120/72': (9, 72, 120), +} +# certified upper ends (hi) recorded by the original runs; compared to relative 1e-12 +EXPECTED = { + 'bit-111/63': {3: 5.947449820544458e-3, 5: 6.00049473312529e-3, 7: 5.2094781808670505e-3, + 9: 4.607152286783883e-3, 11: 4.1489567374021555e-3, 13: 3.7878851034153518e-3}, + 'cx-112/64': {3: 5.047389571662491e-3, 5: 5.186252252886545e-3, 7: 4.512993851037537e-3, + 9: 3.994628507503969e-3, 11: 3.5988803552243387e-3, 13: 3.2864689075796437e-3}, + 'bit-120/72': {3: 3.997571235241277e-3, 5: 5.159798631791548e-3, 7: 4.623205298039335e-3, + 9: 4.139748562309081e-3, 11: 3.752885369025204e-3, 13: 3.440493799826091e-3}, + 'cx-120/72': {3: 3.5307429201300307e-3, 5: 4.535607180902823e-3, 7: 4.059761375276634e-3, + 9: 3.6334780884322328e-3, 11: 3.2929429181725836e-3, 13: 3.0181922014469375e-3}, +} +KAPPA_CEILING = 5.1595e-3 # rev. 2, complex binds +KAPPA_BIT_ONLY = 5.9647e-3 # bit-only corollary c/(1+c) +KAPPA_REV1 = 4.5151e-3 # superseded rev. 1 value (x = 3/5) +FRONTIER = {'bit': 1.733052823330162e-3, 'cx': 0.00159868506668458107128} # #555 c (s = 9), #568 b (s = 9) +HEADROOM_S9 = {'bit': 2.658, 'cx': 2.499} +COPY_UNIT = {5: 3.9, 9: 2.8, 11: 2.65} # % gain of bit X per copy-rank unit per stage (63 -> 62) + +tab = {} +for name, (h, cst, v) in UNITS.items(): + row = {} + for s in S_LIST: + r = X(s, h, 1, Fr(cst, v)) + row[s] = r + tab[name] = row + ok = all(abs(float(row[s]['hi']) / EXPECTED[name][s] - 1) < 1e-12 and row[s]['hi'] - row[s]['lo'] < Fr(1, 10**15) + for s in S_LIST) + check('A1.roots.' + name, ok, ' '.join('s%d:%.5e' % (s, float(row[s]['hi'])) for s in S_LIST)) + best = max(S_LIST, key=lambda s: row[s]['hi']) + check('A1.best-s.' + name, best == 5, 'max over s in %s at s = %d' % (list(S_LIST), best)) + +xb, xc = tab['bit-111/63'][5]['hi'], tab['cx-112/64'][5]['hi'] +Xmin = min(xb, xc) +kap = Xmin / (1 + Xmin) +check('A1.kappa-ceiling', xc < xb and near(kap, KAPPA_CEILING, 7), + 'X_bit* %.5e, X_cx* %.5e (complex binds); kappa <= X/(1+X) = %.7e' % (xb, xc, float(kap))) +check('A1.kappa-bit-only', near(xb / (1 + xb), KAPPA_BIT_ONLY, 7), 'c/(1+c) <= %.7e' % float(xb / (1 + xb))) +x1 = min(tab['bit-120/72'][5]['hi'], tab['cx-120/72'][5]['hi']) +check('A1.rev1-superseded', near(x1 / (1 + x1), KAPPA_REV1, 7), + 'the x = 3/5 scope gives %.7e; the punctured units have x = 63/111, 64/112 < 3/5' % float(x1 / (1 + x1))) +for side, name in (('bit', 'bit-111/63'), ('cx', 'cx-112/64')): + ratio = float(tab[name][9]['hi']) / FRONTIER[side] + check('A1.headroom-s9.' + side, near(ratio, HEADROOM_S9[side], 3), + 'X(s=9) / frontier value = %.3f' % ratio) +for s, pct in COPY_UNIT.items(): + up = X(s, 8, 1, Fr(62, 111))['hi'] + g = 100 * float(up / tab['bit-111/63'][s]['hi'] - 1) + check('A1.copy-unit.s%d' % s, abs(g - pct) < 0.05, 'bit X gain for cst 63 -> 62 at s = %d: +%.2f%%' % (s, g)) +# Units with smaller copy ratio x are outside the realised-unit scope; their ceilings show what a cheaper decoder would +# move (no word exists on either unit). Best s = 3 on both. +HYPO = {'bit-102/54': (8, 54, 102, 8.245729422632946e-3), 'cx-104/56': (9, 56, 104, 6.799964322294041e-3)} +for name, (h, cst, v, exp) in HYPO.items(): + rr = {s: X(s, h, 1, Fr(cst, v))['hi'] for s in S_LIST} + sb = max(rr, key=rr.get) + check('A1.outside-scope.' + name, sb == 3 and abs(float(rr[3]) / exp - 1) < 1e-12, + 'x = %d/%d: X* = %.5e at s = %d (not a realised unit)' % (cst, v, rr[sb], sb)) +finish() diff --git a/research/format-limits/scripts/a2_ceiling_sup.py b/research/format-limits/scripts/a2_ceiling_sup.py new file mode 100644 index 0000000000..dc6a7d0050 --- /dev/null +++ b/research/format-limits/scripts/a2_ceiling_sup.py @@ -0,0 +1,43 @@ +"""A. Format ceiling, rev. 2: the supremum over the stage count s is attained at s = 5 on both realised units. + +For every odd s the root obeys X(s) <= D_s / Cost_s, Cost_s = sum over the skeleton slots of mass * ln(m / cap) +(because e^y - 1 >= y). This script certifies D_s/Cost_s < X(5) for every odd s = 7 .. 401 with rational-interval +logarithms (cost_iv's lower end, so D/Cost is an upper bound), on the bit unit 111/63 and the complex unit 112/64. +For s >= 403 the README (section A.3) gives the analytic tail Cost_s/v >= 4a(K^2/4 - K/e - K/2), K = (s-1)/2; the +value of that bound at s = 403 is printed, and the elementary inequality sum_{p= K^2/4 - K/e - K/2 +that it uses is checked numerically for K = 2 .. 5000 (its proof is in the README). +""" +import math +from fractions import Fraction as Fr + +from cert import cost_iv +from report import check, finish, near +from skel import X, profile + +UNITS = {'bit-111/63': (8, 63, 111), 'cx-112/64': (9, 64, 112)} +EXPECTED_WORST = {'bit-111/63': (5.231066e-3, 7), 'cx-112/64': (4.529190e-3, 7)} # max D/Cost over s = 7..401 +EXPECTED_TAIL = {'bit-111/63': 6.270e-4, 'cx-112/64': 5.438e-4} # analytic tail value at s = 403 + +for name, (h, cst, v) in UNITS.items(): + x = Fr(cst, v) + x5 = X(5, h, 1, x)['hi'] + worst, sworst, allbelow = Fr(0), None, True + for s in range(7, 402, 2): + prof, m, D = profile(s, h, 1, x) + clo, _ = cost_iv(prof, m) + ub = D / clo + allbelow &= ub < x5 + if ub > worst: + worst, sworst = ub, s + ew, es = EXPECTED_WORST[name] + check('A2.sup.' + name, allbelow and sworst == es and abs(float(worst) / ew - 1) < 1e-6, + 'D/Cost < X(5) = %.6e for all odd s in 7..401; largest %.6e at s = %d' % (float(x5), float(worst), sworst)) + a = h - 1 + K = 201 + s = 2 * K + 1 + tail = float(1 - x) * s / (4 * a * (K * K / 4 - K / math.e - K / 2)) + check('A2.tail.' + name, tail < float(x5) and near(tail, EXPECTED_TAIL[name], 7), + 'analytic tail bound at s = 403: %.3e (decreasing in s)' % tail) +bad = [K for K in range(2, 5001) if sum(p * math.log(K / p) for p in range(1, K)) < K * K / 4 - K / math.e - K / 2] +check('A2.tail-inequality', not bad, 'sum_{p= K^2/4 - K/e - K/2 for K = 2..5000') +finish() diff --git a/research/format-limits/scripts/a3_axioms.py b/research/format-limits/scripts/a3_axioms.py new file mode 100644 index 0000000000..5076d8e613 --- /dev/null +++ b/research/format-limits/scripts/a3_axioms.py @@ -0,0 +1,86 @@ +"""A. Format ceiling, rev. 2: the accounting axioms checked on the published profiles of the frontier words. + +(P) pressure identity: the certified root of sum_r z_r r((m/r)^q - 1) = D on a word's published global profile + reproduces the word's published supplier value (bit c or complex b); +(Dfc) deficit identity: m * live - rank mass = D = T[(s-1) v k - s cst] (equality, not just <=); +(Win) m = k + s (h - k) (for #543/#545/#555/#568 the largest child is the literal idle remainder (2K - 2) a); +(Sch) the B_K idle schedule: #568's per-replica idle windows are exactly skel.idle_slots(K, a) on both banks, and the + skeleton mass (active climbs + copies + idle slots) equals each word's real rank mass; +(Hst) #555 hosts every helper residual whole (no bank assignments); +Lemma 2 (slot domination) pointwise: f_N(q) >= F_s(q; cst_N) for q in [1e-5, 1] on every profile (min ratio printed). +Profiles are read from the published files: #510 (main), #543, #545, #555 (EXPECTED.json), #568 (PLAN.json). +""" +from collections import Counter +from fractions import Fraction as Fr + +from cert import root_bracket +from report import VENDOR, check, finish, load_json, main_file, near +from skel import X, idle_slots, profile + +e510 = load_json(main_file('research/e8-r480-nine-stage/EXPECTED.json')) +e543 = load_json(VENDOR / 'pr543' / 'EXPECTED.json') +e545 = load_json(VENDOR / 'pr545' / 'EXPECTED.json') +e555 = load_json(VENDOR / 'pr555' / 'EXPECTED.json') +p568 = load_json(VENDOR / 'pr568' / 'PLAN.json.gz') + + +def prof(d): + return {int(r): z for r, z in d.items()} + + +# name: (profile, h, v, cst, s, T, published value) +WORDS = { + '#510-R480-s9': (prof(e510['nine']['profile']), 8, 120, 72, 9, 32, Fr(e510['nine']['bit_lower'])), + '#543-R420-s9': (prof(e543['nine']['profile']), 8, 120, 72, 9, 32, Fr(e543['nine']['bit_lower'])), + '#543-R420-s11': (prof(e543['eleven']['profile']), 8, 120, 72, 11, 8, Fr(e543['eleven']['bit_lower'])), + '#545-p111-s11': (prof(e545['eleven']['profile']), 8, 111, 63, 11, 8, Fr(e545['eleven']['bit_lower'])), + '#555-p111-s9': (prof(e555['nine']['profile']), 8, 111, 63, 9, 32, Fr(e555['nine']['bit_lower'])), + '#555-p111-s11': (prof(e555['eleven']['profile']), 8, 111, 63, 11, 8, Fr(e555['eleven']['bit_lower'])), + '#568-cx-s9': (prof(p568['profile']), 9, 112, 64, 9, 8, Fr('0.00159868506668458107128')), # #568 README +} +# (min over q of f_N/F_s, X(s, x)/root): recorded values, compared to 3 decimals +EXPECTED_RATIOS = {'#555-p111-s9': (2.66, 2.658), '#568-cx-s9': (2.50, 2.499)} + +for name, (P, h, v, cst, s, T, pub) in WORDS.items(): + k, a, K = 1, h - 1, (s - 1) // 2 + m = k + s * a + D = T * ((s - 1) * v * k - s * cst) + mass = sum(r * z for r, z in P.items()) + W = Fr(mass + D, m) + lo, hi = root_bracket(P, m, D, hi=Fr(1, 100)) + rel = float(hi / pub - 1) + check('A3.P.' + name, hi >= pub and rel < 2e-9, + 'certified root in [%.12e, %.12e]; published %.12e (root - published = %.1e relative)' % (lo, hi, pub, rel)) + check('A3.Dfc.' + name, W.denominator == 1 and m * W - mass == D, + 'm = %d, W = %s, rank mass %d, m W - mass = %d = T[(s-1)v - s cst]' % (m, W, mass, D)) + check('A3.Win.' + name, m == k + s * a, 'm = k + s(h-k) = %d; largest child %d%s' % ( + m, max(P), ' = (2K-2)a: the B_K idle remainders are literal' if max(P) == (2 * K - 2) * a else '')) + pp, mm, Ds = profile(s, h, k, Fr(cst, v)) + ratios = [] + for qq in (1e-5, 1e-4, 1e-3, 3e-3, 1e-2, 0.05, 0.2, 0.5, 1.0): + fN = sum(z * r * ((m / r) ** qq - 1) for r, z in P.items()) + Fs = v * T * sum(float(z) * float(r) * ((m / float(r)) ** qq - 1) for r, z in pp.items()) + ratios.append(fN / Fs) + head = float(X(s, h, k, Fr(cst, v))['hi'] / hi) + ok = min(ratios) >= 1 + if name in EXPECTED_RATIOS: + er, eh = EXPECTED_RATIOS[name] + ok = ok and near(min(ratios), er, 2) and near(head, eh, 3) + check('A3.lemma2.' + name, ok, 'min_q f_N(q)/F_s(q) = %.3f; X(s,x)/root = %.3f' % (min(ratios), head)) + +# (Sch): skeleton mass = real rank mass, and #568's windows are the B_K idle slots +for name, h, v, cst, T, real in (('#555-p111-s9', 8, 111, 63, 32, e555['nine']['bit']['rank_mass']), + ('#568-cx-s9', 9, 112, 64, 8, sum(r * z for r, z in prof(p568['profile']).items()))): + s, a, K = 9, h - 1, 4 + data, copies, idle = 2 * v * s * a * T, cst * s * T, 2 * v * T * sum(idle_slots(K, a)) + check('A3.Sch-mass.' + name, data + copies + idle == real, + 'active %d + copies %d + idle %d = %d = rank mass' % (data, copies, idle, real)) +reps = sorted({int(k.split(',')[0]) for k in p568['windows']}) +want = Counter(idle_slots(4, 8)) + Counter(idle_slots(4, 8)) +okw = all(Counter(r for kk, (r, _) in p568['windows'].items() if int(kk.split(',')[0]) == rep) == want for rep in reps) +check('A3.Sch-windows.#568', okw and len(reps) == p568['T'], + '%d replicas, each with idle windows %s = idle_slots(K=4, a=8) on both banks' % (len(reps), dict(sorted(want.items())))) +b = e555['nine']['bit'] +check('A3.Hst.#555', b['bank_assignments'] == 0 and b['hosted_residuals'] == 439 * 9 * 32, + 'bank assignments 0; hosted residuals %d = R * s * T = 439 * 288' % b['hosted_residuals']) +finish() diff --git a/research/format-limits/scripts/a4_gap.py b/research/format-limits/scripts/a4_gap.py new file mode 100644 index 0000000000..05ffc10d76 --- /dev/null +++ b/research/format-limits/scripts/a4_gap.py @@ -0,0 +1,175 @@ +"""A. Format ceiling, rev. 2: where the frontier words lose against the s = 9 skeleton (gap decomposition). + +All roots are certified upper ends of the pressure root at the word's own (m, D); every step keeps the rank mass +m W - D fixed. Steps: + A theorem skeleton (R-free, idle slots charged at cap max(r, h)); + B literal R = 0 profile (each idle slot one child of its own rank); + C B with the real data-register runs and the real copy children; + D C plus every helper run paid as its own child, unhosted (the idle slots stay paid in full; this is the only step + whose rank mass differs: it adds the helper mass, as if helpers lived on their own bank wires at the same D); + E the real word (helpers hosted whole into idle frame changes; the idle remainders paid). +#555 (bit): data, helper and copy runs are replayed from the published word (bitE8mut-p111.json.gz) and the idle +remainder is the published global profile minus s T times the per-stage local profile. +#568 (complex): the data-register runs per call are measured on the four-cold provider word of #569 (the same x/y +ladders as #568's providers); the paid idle remainder is an input recorded from #568's hosting (data/ceiling/ +gap568-inputs.json); the helper runs are the published global profile minus data, centres and idle remainder. +""" +from collections import Counter, defaultdict +from fractions import Fraction as Fr +import sys + +import numpy as np + +from cert import root_bracket +from report import DATA, REPO, VENDOR, check, finish, load_json, main_file +from skel import idle_slots + +EXPECTED = { + '#555': {'A': 4.6072e-3, 'B': 4.5661e-3, 'C': 3.7868e-3, 'D': 1.6183e-3, 'E': 1.7331e-3}, + '#568': {'A': 3.9946e-3, 'B': 3.9631e-3, 'C': 3.1544e-3, 'D': 1.3517e-3, 'E': 1.5987e-3}, +} +EXPECTED_555_RUNS = {'x': {1: 317, 2: 209, 3: 10, 4: 3}, 'y': {1: 10, 2: 92, 5: 92, 6: 10, 7: 9}, + 'h': {1: 994, 2: 1041, 3: 70, 4: 44, 5: 10}} +EXPECTED_568_DATA = {1: 718, 2: 74, 3: 60, 4: 92, 5: 42, 6: 28} + + +def add(*ds): + c = Counter() + for d in ds: + for r, z in d.items(): + c[r] += z + return c + + +def mul(d, n): + return {r: z * n for r, z in d.items()} + + +def skeleton(s, h, v, T, cst): + a, K = h - 1, (s - 1) // 2 + idle_r, idle_cap = Counter(), Counter() + for r in idle_slots(K, a): + idle_r[r] += 2 * v * T + cap = max(r, h) + idle_cap[Fr(cap)] += Fr(2 * r * v * T, cap) + return {a: 2 * v * s * T}, {h: Fr(s * T * cst, h)}, idle_r, idle_cap + + +def roots(name, cases, m, D): + out = {} + for key, p in cases.items(): + lo, hi = root_bracket(dict(p), m, D, hi=Fr(1, 50)) + out[key] = float(hi) + masses = {key: sum(r * z for r, z in p.items()) for key, p in cases.items()} + hosted = sum(r * z for r, z in cases['D'].items()) - sum(r * z for r, z in cases['C'].items()) + ok = (all(abs(out[k] / EXPECTED[name][k] - 1) < 1.2e-4 for k in out) # 5 significant digits + and masses['A'] == masses['B'] == masses['C'] == masses['E'] + and masses['D'] == masses['E'] + hosted) + check('A4.gap.' + name, ok, ' '.join('%s %.4e' % (k, out[k]) for k in out) + ' (rank mass %s)' % {k: int(v) for k, v in masses.items()}) + lnAE = np.log(out['A'] / out['E']) + shares = {'literal': np.log(out['A'] / out['B']), 'fragmentation': np.log(out['B'] / out['C']), + 'helpers': np.log(out['C'] / out['D']), 'hosting': np.log(out['D'] / out['E'])} + print(' log shares of ln(A/E) = %.3f: ' % lnAE + ', '.join('%s %.0f%%' % (k, 100 * v / lnAE) for k, v in shares.items())) + return out + + +# ---- #555 (bit, s = 9, v = 111, cst = 63, T = 32, h = 8, m = 64) +s, h, v, T, m = 9, 8, 111, 32, 64 +D = T * ((s - 1) * v - s * 63) +e555 = load_json(VENDOR / 'pr555' / 'EXPECTED.json') +w = load_json(VENDOR / 'pr555' / 'bitE8mut-p111.json.gz') +lines = np.array(w['lines'], float) +dims = [int(np.linalg.matrix_rank(lines[F])) if F else 0 for F in w['frames']] +cur = list(w['start']) +runs = defaultdict(list) +for ph in 'DABC': + for f, dst, srcs in w[ph]: + for r in [dst] + list(srcs): + if cur[r] != f: + runs[r].append(abs(dims[f] - dims[cur[r]])) + cur[r] = f +for r in range(len(cur)): + if cur[r] != w['final'][r]: + runs[r].append(abs(dims[w['final'][r]] - dims[cur[r]])) +cls = lambda r: 'x' if r < v else ('y' if r < 2 * v else 'h') +H = {c: Counter() for c in 'xyh'} +for r, L in runs.items(): + for d in L: + H[cls(r)][d] += 1 +check('A4.runs.#555', all(dict(H[c]) == EXPECTED_555_RUNS[c] for c in 'xyh') + and sum(r * z for r, z in H['h'].items()) == 8 * w['R'], + 'per-stage runs x %s, y %s, helpers %s (helper mass 8R = %d)' % (dict(sorted(H['x'].items())), + dict(sorted(H['y'].items())), + dict(sorted(H['h'].items())), 8 * w['R'])) +S = s * T +local = add(H['x'], H['y'], H['h'], {7: 9}) +check('A4.local.#555', dict(local) == {int(k): z for k, z in e555['word']['local_histogram'].items()}, + 'replayed runs + 9 copy children of rank 7 = the published per-stage local histogram') +real = {int(r): z for r, z in e555['nine']['profile'].items()} +rem = Counter(real) +for r, z in mul(local, S).items(): + rem[r] -= z +rem = {r: z for r, z in rem.items() if z} +data1, cop, idle_r, idle_cap = skeleton(s, h, v, T, 63) +datareal = mul(add(H['x'], H['y']), S) +helpers = mul(H['h'], S) +cases = {'A': add(data1, cop, idle_cap), 'B': add(data1, cop, idle_r), 'C': add(datareal, {7: 9 * S}, idle_r), + 'D': add(datareal, {7: 9 * S}, idle_r, helpers), 'E': add(datareal, {7: 9 * S}, helpers, rem)} +hosted = sum(r * z for r, z in helpers.items()) +idlemass = sum(r * z for r, z in idle_r.items()) +check('A4.hosted.#555', hosted == 288 * 3512 and idlemass == 1342656, + 'hosted helper mass %d = 75.3%% of the skeleton idle mass %d; paid idle remainder %s' % (hosted, idlemass, rem)) +roots('#555', cases, m, D) + +# ---- #568 (complex, s = 9, v = 112, cst = 64, T = 8, h = 9, m = 73) +s, h, v, T, m = 9, 9, 112, 8, 73 +D = T * ((s - 1) * v - s * 64) +S = s * T +sys.path.insert(0, str(main_file('research/direct-delivery504-global/vendor/gk/gk_word.py').parent)) +import gk_common as co # noqa: E402 (#531's word helpers on main) +import gk_word as g # noqa: E402 +g.V = co.V = 112 +co.DATA = g.DATA = 7168 +word = load_json(VENDOR / 'pr569' / 'fourcold-eight-FULL-WORD.json.gz') +V = 112 +cur = {r: ((word['ports'][r],) if r < V else ()) for r in range(2 * V)} +paid = Counter() + + +def climb(r, F): + if r >= 2 * V: + return + F = g.basis(F) + o = cur[r] + assert g.basis(list(o) + list(F)) == F, 'frame violation' + if o != F: + paid[len(F) - len(o)] += 1 + cur[r] = F + + +for seq in (word['A'], word['B']): + for e in seq: + for r in [e[1]] + ([e[2]] if e[0] == 'add' else []): + climb(r, e[-1]) +for r in range(2 * V): + climb(r, g.FULL if r < V else g.perp(word['ports'][r - V])) +check('A4.data.#568', dict(paid) == EXPECTED_568_DATA and sum(r * z for r, z in paid.items()) == 2 * 112 * 8, + 'data-register runs per call %s (mass 2 v a = 1792)' % dict(sorted(paid.items()))) +p568 = load_json(VENDOR / 'pr568' / 'PLAN.json.gz') +real = {int(r): z for r, z in p568['profile'].items()} +inp = load_json(DATA / 'ceiling' / 'gap568-inputs.json') +idle_rem = {int(r): z for r, z in inp['idle_remainder'].items()} +datareal = mul(dict(paid), S) +cent = {8: 8 * S} +hel = Counter(real) +for r, z in add(datareal, cent, idle_rem).items(): + hel[r] -= z +check('A4.inputs.#568', sum(idle_rem.values()) == 4018 and all(z >= 0 for z in hel.values()) and max(r for r, z in hel.items() if z) <= 6, + 'recorded idle remainder has 4018 children (= the "4,018 positive idle remainders" of #568\'s README); implied helper ' + 'histogram %s is nonnegative with ranks <= 6 <= h' % dict(sorted((r, z) for r, z in hel.items() if z))) +hel = {r: z for r, z in hel.items() if z} +data1, cop, idle_r, idle_cap = skeleton(s, h, v, T, 64) +cases = {'A': add(data1, cop, idle_cap), 'B': add(data1, cop, idle_r), 'C': add(datareal, cent, idle_r), + 'D': add(datareal, cent, idle_r, hel), 'E': add(datareal, cent, hel, idle_rem)} +roots('#568', cases, m, D) +finish() diff --git a/research/format-limits/scripts/a5_scope_multipliers.py b/research/format-limits/scripts/a5_scope_multipliers.py new file mode 100644 index 0000000000..e45f8882ec --- /dev/null +++ b/research/format-limits/scripts/a5_scope_multipliers.py @@ -0,0 +1,53 @@ +"""A. Format ceiling, rev. 2: how much each scope assumption is worth (ceiling multipliers if it is relaxed). + +Each variant of skel.profile drops one rule of the scope and recomputes the certified root at s = 3, 5, ..., 13: + copyfree copy charge x -> 0 (non-star totals with no pairing-rank floor; star scope broken); + idlefree the B_K idle frame changes cost nothing; + idle_onechild all idle frame changes of one copy batched into one child (cross-stretch batching); + nosplit bridges moved off the idle stretches (no split pre-stretch); + loc_h data local climbs batched up to the window h (cross-register batching inside a stage); + idlefree_loch idle free and local batching to h at once; + window w-1 a family whose stage window is one smaller at the same copy ratio x. +Multiplier = (best-s root of the variant) / (best-s root of the theorem), and the same at s = 9. +These are the multipliers quoted in the rule table of README section B. +""" +from fractions import Fraction as Fr + +from report import check, finish, near +from skel import X + +S_LIST = (3, 5, 7, 9, 11, 13) +UNITS = {'bit-111/63': (8, 63, 111), 'cx-112/64': (9, 64, 112)} +VARIANTS = ('copyfree', 'idlefree', 'idle_onechild', 'nosplit', 'loc_h', 'idlefree_loch', 'window-1') +# (best-s multiplier, s = 9 multiplier), two decimals +EXPECTED = { + 'bit-111/63': {'copyfree': (6.77, 2.79), 'idlefree': (1.67, 2.14), 'idle_onechild': (1.19, 1.55), + 'nosplit': (1.15, 1.18), 'loc_h': (1.12, 1.03), 'idlefree_loch': (1.79, 2.28), + 'window-1': (1.16, 1.16)}, + 'cx-112/64': {'copyfree': (6.91, 2.83), 'idlefree': (1.68, 2.15), 'idle_onechild': (1.20, 1.55), + 'nosplit': (1.15, 1.18), 'loc_h': (1.08, 1.02), 'idlefree_loch': (1.79, 2.27), + 'window-1': (1.14, 1.14)}, +} + + +def roots(h, x, variant): + out = {} + for s in S_LIST: + r = X(s, h, 1, x, variant) + out[s] = r['hi'] if r else Fr(0) + return out + + +for name, (h, cst, v) in UNITS.items(): + x = Fr(cst, v) + base = roots(h, x, 'thm') + b_best = max(base.values()) + for var in VARIANTS: + rr = roots(h - 1, x, 'thm') if var == 'window-1' else roots(h, x, var) + best = max(rr.values()) + sb = max(rr, key=rr.get) + mult = (float(best / b_best), float(rr[9] / base[9])) + exp = EXPECTED[name][var] + check('A5.%s.%s' % (name, var), near(mult[0], exp[0], 2) and near(mult[1], exp[1], 2), + 'best %.4e (s = %d): x%.2f; s = 9: x%.2f' % (best, sb, mult[0], mult[1])) +finish() diff --git a/research/format-limits/scripts/b_census_anchors.py b/research/format-limits/scripts/b_census_anchors.py new file mode 100644 index 0000000000..2d63106ee5 --- /dev/null +++ b/research/format-limits/scripts/b_census_anchors.py @@ -0,0 +1,57 @@ +"""B. Spec versus convention census: every file:line anchor of the rule table (README section B.2) is checked here. + +data/census/anchors.json lists, per table row, (path, line, needle), with line numbers as of the base commit 7dfa22b. +The needle must occur within two lines of the cited line (paths relative to the repository root; the upstream paper is +upstream/build/sections). Files under docs/research/ (the hub's ledgers and reviews, which main rewrites often) are +matched by their text anywhere in the file, and the line where it now sits is printed. The line ranges cited in the +prose of README sections B.1 and B.3 are not in this list. +The classification of each rule (spec / proved / convention) is argued in the README; this script only checks that +the cited text says what the table quotes, plus four corrections that are statements about the repository itself. +""" +import json +import re + +from report import DATA, REPO, check, finish + +WINDOW = 2 +anchors = json.loads((DATA / 'census' / 'anchors.json').read_text()) +by_row = {} +for a in anchors: + lines = (REPO / a['path']).read_text(encoding='utf-8').split('\n') + if a['path'].startswith('docs/research/'): + hits = [i + 1 for i, ln in enumerate(lines) if a['needle'] in ln] + ok = bool(hits) + where = '%s:%d (7dfa22b), text now at %s' % (a['path'], a['line'], ','.join(map(str, hits)) or '-') + else: + lo, hi = max(0, a['line'] - 1 - WINDOW), min(len(lines), a['line'] + WINDOW) + ok = any(a['needle'] in lines[i] for i in range(lo, hi)) + where = '%s:%d' % (a['path'], a['line']) + by_row.setdefault(a['row'], []).append((ok, where)) +for row in sorted(by_row): + res = by_row[row] + check('B.row%02d' % row, all(ok for ok, _ in res), + '%d anchors: %s' % (len(res), ', '.join(loc + ('' if ok else ' MISSING') for ok, loc in res))) + +# Correction 1: in the bit legality checker, the nested-and-priced MOVE rule, the common-frame ADD rule and the COPY +# lifecycle are all on line 11; line 10 holds the containment helper and the owner/helper selection. +L = (REPO / 'research/e8-bit-carrier-skeleton/code/bit/checkers/legality_h.cpp').read_text().split('\n') +check('B.corr.legality-line11', + all(t in L[10] for t in ('illegal or mispriced MOVE', 'ADD not in common frame', 'invalid COPY lifecycle')) + and 'owners' in L[9] and 'illegal or mispriced MOVE' not in L[9], + 'MOVE / ADD / COPY rules on legality_h.cpp:11; owner selection on :10') +# Correction 2: the invocation invariant Inv used by the complex five-stage bridge is imported, not defined, in main. +lean = [p for p in REPO.rglob('*.lean') if '.git' not in p.parts] +defs = [p for p in lean if re.search(r'^\s*(structure|def|class|abbrev|inductive)\s+G?Inv\b', p.read_text(errors='ignore'), re.M)] +imps = [p for p in lean if 'import Work.Bridge.Inv' in p.read_text(errors='ignore')] +check('B.corr.inv-not-defined', not defs and len(imps) >= 1, + '%d Lean files import Work.Bridge.Inv; no Lean file of main defines Inv or GInv' % len(imps)) +# Correction 3: PR #370's assertion 'x registers are never destinations' is sufficient, not necessary (#425, #493). +R = (REPO / 'research/e8-bit-carrier-skeleton/README.md').read_text() +check('B.corr.x-destinations', '"x registers are never destinations"' in R and 'sufficient condition, not a necessary one' in R, + "#493's README records the #370 assertion as sufficient, not necessary (#425's scalar-interface lemma)") +# Correction 4: the maintainer's review lists whole-call hosting and normalized wrappers as interface assumptions and +# says the fixed-word ceiling discussions were not independently certified. +D = (REPO / 'docs/research/community-pr531-review.md').read_text() +check('B.corr.scope-note', 'whole-call correlated-helper hosting' in D and 'independently certified** in this integration' in D, + 'docs/research/community-pr531-review.md, "Scope and research notes"') +finish() diff --git a/research/format-limits/scripts/c1_sandwich_toy.py b/research/format-limits/scripts/c1_sandwich_toy.py new file mode 100644 index 0000000000..97e8a67a23 --- /dev/null +++ b/research/format-limits/scripts/c1_sandwich_toy.py @@ -0,0 +1,237 @@ +"""Exact toy: a source read at its own frame, sandwiched between two non-nested frame steps (n = 4 labels over F2). + +Setting. Labels live in F2^4 with the standard dot product. A port anchor u_a = 0111 is odd (u_a.u_a = 1). Three +reader labels u1 = 1000, u2 = 1011, u3 = 1101 are odd and orthogonal to u_a; they span u_a^perp, so the only subspace +orthogonal to all three readers is : the only frame that is common to the readers' natural read frames is the +point station (asserted below). One source x has label with w not orthogonal to any reader (w.u_i = 1), +so every reader must see x across a drop. + +Two networks compute the SAME linear operator on the registers (x, y1, y2, y3): + x -> T_FULL l_x , y_i -> T_{u_i^perp} ( l_{y_i} + q_i l_x ) , q = (1, -1, 1/2), +where l is the logical content, T_F is the canonical Clifford frame operator of a subspace F, and the physical +registers hold x = T_ l_x and y_i = l_{y_i}. + + standard : one shared copy of x is dropped -> 0 (the shared station), each reader reads it at frame 0 and then + climbs 0 -> u_i^perp (nested). Charged steps: copy -> 0 ; y_i : 0 -> u_i^perp ; x : -> FULL. + relaxed : no copy. Each reader climbs 0 -> (a frame that contains a vector w outside u_i^perp), reads x at x's + own frame , then takes a NON-nested step -> u_i^perp. Charged steps: y_i : 0 -> and + -> u_i^perp ; x : -> FULL. + +Checks. + * The operator is exact on all 4 * 16 = 64 basis columns of the 4 registers (exact Gaussian-dyadic arithmetic). + * For every transition A -> B the Clifford child rank (clifford_exact.lower_child of T_A^-1 then T_B) equals the + frame distance d(A, B) = dim A + dim B - 2 dim (A cap B), in both networks, including the non-nested steps. + * Charged rank total: standard 13 (of which 1 is the copy), relaxed 18 = 13 + 3*2 - 1. +""" +import functools +import sys +from fractions import Fraction as Fr + +from report import check, finish, main_file + +sys.path.insert(0, str(main_file('research/direct-delivery504-global/vendor/clifford/clifford_exact.py').parent)) +import clifford_exact as ce # noqa: E402 (the repository's exact Clifford compiler, unmodified) + +# ---------------------------------------------------------------------------------------------------------------- +# Expected values (from the exact run of the toy; every one is re-derived below and compared). +EXPECTED = { + 'w_candidates': [0b1000], # odd w with w.u_i = 1 for all three readers: unique + 'common_reader_frame': [0, 0b0111], # {z : z.u_i = 0 for all i} = {0, u_a}: the point station + 'columns': 64, # 4 registers x 16 basis vectors, per network + 'std_total': 13, 'std_copy': 1, # standard network: charged rank total, copy part + 'relaxed_total': 18, 'relaxed_copy': 0, # relaxed network: 13 + 3*2 - 1 = 18 + # transitions (register, dim A, dim B, child rank, distance d) + 'std_transitions': [('copy', 1, 0, 1, 1), ('y0', 0, 3, 3, 3), ('y1', 0, 3, 3, 3), ('y2', 0, 3, 3, 3), + ('x', 1, 4, 3, 3)], + 'relaxed_transitions': [('y0', 0, 1, 1, 1), ('y0', 1, 3, 4, 4), ('y1', 0, 1, 1, 1), ('y1', 1, 3, 4, 4), + ('y2', 0, 1, 1, 1), ('y2', 1, 3, 4, 4), ('x', 1, 4, 3, 3)], +} + +n = 4 +N = 1 << n + + +def par(x): + return bin(x).count('1') & 1 + + +def span(vs): + out = {0} + for v in vs: + out |= {o ^ v for o in out} + return out + + +def perp(vs): + return [z for z in range(N) if all(par(z & v) == 0 for v in vs)] + + +def basis(vs): + b = [] + for v in vs: + if ce.rank(b + [v]) > len(b): + b.append(v) + return b + + +def dist(A, B): + """frame distance d(A,B) = dim A + dim B - 2 dim(A cap B).""" + a, b = basis(A), basis(B) + return len(a) + len(b) - 2 * (len(a) + len(b) - ce.rank(a + b)) + + +def T(F): + return ce.canonical_frame(n, tuple(basis(F))) + + +# exact complex dyadic linear algebra: a number is a pair (re, im) of Fractions +def mat(op): + cols = [] + for bidx in range(N): + vec, den = op.zero_state(bidx) + cols.append([(Fr(a, 1 << den), Fr(c, 1 << den)) for a, c in vec]) + return [[cols[j][i] for j in range(N)] for i in range(N)] + + +def cm(a, b): + return (a[0] * b[0] - a[1] * b[1], a[0] * b[1] + a[1] * b[0]) + + +def ca(a, b): + return (a[0] + b[0], a[1] + b[1]) + + +Z = (Fr(0), Fr(0)) + + +def mv(M, v): + return [functools.reduce(ca, [cm(M[i][j], v[j]) for j in range(N)], Z) for i in range(N)] + + +def sc(q, v): + return [(q * a, q * b) for a, b in v] + + +def add(u, v): + return [ca(a, b) for a, b in zip(u, v)] + + +cache = {} + + +def Tm(F): + k = tuple(sorted(span(F))) + if k not in cache: + cache[k] = (mat(T(F)), mat(T(F).adjoint())) + return cache[k] + + +def step(h, A, B, log, who): + """apply T_B T_A^-1 to the physical vector h and record (register, dim A, dim B, child rank, distance).""" + op = T(A).adjoint().then(T(B)) if basis(A) else T(B) + r = ce.lower_child(op)[0]['rank'] + dd = dist(A, B) + log.append((who, len(basis(A)), len(basis(B)), r, dd)) + return mv(Tm(B)[0], mv(Tm(A)[1], h)) + + +ua = 0b0111 +U = [0b1000, 0b1011, 0b1101] +q = [Fr(1), Fr(-1), Fr(1, 2)] + +# ---- C1.c0-station: the readers' labels span u_a^perp, hence the only common reader frame is +labels_ok = par(ua & ua) == 1 and all(par(u & u) == 1 and par(u & ua) == 0 for u in U) +spans_perp = span(U) == set(perp([ua])) and ce.rank(U) == n - 1 +common = sorted(perp(U)) +cands = [w for w in range(1, N) if par(w & w) == 1 and all(par(w & u) == 1 for u in U)] +check('C1.c0-station', + labels_ok and spans_perp and common == EXPECTED['common_reader_frame'] and cands == EXPECTED['w_candidates'], + 'readers odd and orthogonal to u_a; span(u1,u2,u3) = u_a^perp (dim %d); frames orthogonal to all readers = %s; ' + 'source label w = %s (unique odd w with w.u_i = 1 for all i)' + % (ce.rank(U), [format(z, '04b') for z in common], [format(c, '04b') for c in cands])) +w = EXPECTED['w_candidates'][0] +FULL = list(range(1, N)) +W = [w] + + +def run(mode, hx, hy): + """hx: physical source (frame ); hy: physical targets (frame 0). Returns final x, final ys, transition log.""" + log = [] + ys = list(hy) + if mode == 'standard': + C = list(hx) # copy of x taken at its own frame + C = step(C, W, [], log, 'copy') # shared station: -> 0 + for i in range(3): + ys[i] = add(ys[i], sc(q[i], C)) # read at frame 0 + ys[i] = step(ys[i], [], perp([U[i]]), log, 'y%d' % i) # nested climb 0 -> u_i^perp + x = step(hx, W, FULL, log, 'x') + else: + for i in range(3): + ys[i] = step(ys[i], [], W, log, 'y%d' % i) # 0 -> : frame containing a vector outside u_i^perp + ys[i] = add(ys[i], sc(q[i], hx)) # read x at x's own frame, no copy + ys[i] = step(ys[i], W, perp([U[i]]), log, 'y%d' % i) # non-nested step -> u_i^perp + x = step(hx, W, FULL, log, 'x') + return x, ys, log + + +def expect(lx, ly): + ex = mv(Tm(FULL)[0], lx) + ey = [mv(Tm(perp([U[i]]))[0], add(ly[i], sc(q[i], lx))) for i in range(3)] + return ex, ey + + +zero = [Z] * N + + +def e(b): + v = [Z] * N + v[b] = (Fr(1), Fr(0)) + return v + + +results = {} +for mode in ('standard', 'relaxed'): + good = 0 + logs = [] + for reg in range(4): + for b in range(N): + l = [zero] * 4 + l[reg] = e(b) # logical input: one basis vector of one register + hx = mv(Tm(W)[0], l[0]) # physical source = T_ l_x + hy = [l[1], l[2], l[3]] # physical targets start at frame 0 + x, ys, log = run(mode, hx, hy) + ex, ey = expect(l[0], l[1:]) + good += (x == ex and ys == ey) + logs.append(log) + assert all(lg == logs[0] for lg in logs) # the transition costs do not depend on the input column + log = logs[0] + total = sum(r for *_, r, _ in log) + copy = sum(r for who, *_, r, _ in log if who == 'copy') + results[mode] = dict(cols=good, log=log, total=total, copy=copy) + +std, rel = results['standard'], results['relaxed'] + +check('C1.std-exact', + std['cols'] == EXPECTED['columns'] and std['total'] == EXPECTED['std_total'] + and std['copy'] == EXPECTED['std_copy'] and std['log'] == EXPECTED['std_transitions'], + 'standard network exact on %d/%d columns; charged rank %d (copy part %d)' + % (std['cols'], EXPECTED['columns'], std['total'], std['copy'])) + +check('C1.relaxed-exact', + rel['cols'] == EXPECTED['columns'] and rel['total'] == EXPECTED['relaxed_total'] + and rel['copy'] == EXPECTED['relaxed_copy'] and rel['log'] == EXPECTED['relaxed_transitions'] + and rel['total'] == std['total'] + 3 * 2 - 1, + 'relaxed network exact on %d/%d columns; charged rank %d = %d + 3*2 - 1 (copy part %d)' + % (rel['cols'], EXPECTED['columns'], rel['total'], std['total'], rel['copy'])) + + +# the relaxed network's final reader steps -> u_i^perp (dim 1 -> 3) are not nested: w is not orthogonal to u_i +nonnested_ok = all(w not in perp([u]) for u in U) +n_nonnested = sum(1 for who, a, b, r, dd in rel['log'] if who != 'x' and (a, b) == (1, 3)) +all_eq = all(r == dd for m in results.values() for _, _, _, r, dd in m['log']) +n_steps = sum(len(m['log']) for m in results.values()) +check('C1.child-eq-d', all_eq and nonnested_ok and n_nonnested == 3, + 'lower_child rank == d(A,B) on all %d transitions of both networks (including %d non-nested steps -> u_i^perp, ' + 'rank 4 = d)' % (n_steps, n_nonnested)) + +finish() diff --git a/research/format-limits/scripts/c2_port_gauge.py b/research/format-limits/scripts/c2_port_gauge.py new file mode 100644 index 0000000000..31c27d32cc --- /dev/null +++ b/research/format-limits/scripts/c2_port_gauge.py @@ -0,0 +1,227 @@ +"""Exact checks of the port-gauge rank calculus: C_q gates, frame detours and route rigidity (over F2^n). + +Notation. Labels are vectors of F2^n with the standard dot product; q is an ODD vector (q.q = 1). C_a denotes the +single-axis Clifford gate cgate(n, a) and Z a product of such gates. T_F is the canonical Clifford frame operator of +a subspace F, with T_0 = identity and T_FULL = the Fourier transform F on all n axes. The frame distance is +d(F, G) = dim F + dim G - 2 dim(F cap G). The Clifford rank of an operator is the rank reported by +clifford_exact.lower_child (the rank of the Fourier part of its normal form). + +A. Cost instrument. For a product Z = C_{q_1}^{s_1} ... C_{q_k}^{s_k} (s_i = +-1) let B = sum_i q_i q_i^T over F2. + Claim: rank_{F2}(B) equals the exact Clifford rank of Z (240 random products, n = 4..7, seeded). +B. Detour theorem, exhaustive over all subspaces G of F2^n (n = 3..6), with q odd, L = FULL, q^perp the hyperplane: + Y family 0 -> G -> q^perp : extra cost d(0,G) + d(G,q^perp) - d(0,q^perp) is 0 iff G <= q^perp, else exactly 2; + X family -> G -> L : extra cost d(,G) + d(G,L) - d(,L) is 0 iff q in G, else exactly 2. +C. Route rigidity (exact, n = 3, 4, exhaustive over all pairs of subspaces). For frames f_X, f_Y the route + R = T_{f_Y} T_{f_X}^{-1} is compared with F (matrix products, R acting after T_{f_X}^{-1}): the right mismatch rank is + rank(F^{-1} R) (zero iff R = F M with M monomial) and the left mismatch rank is rank(R F^{-1}) (zero iff R = M F), + M a monomial operator (permutation x diagonal x translation). Claim: R equals F modulo a monomial on at least one + side only for pairs with dim f_X + dim f_Y = n and f_X cap f_Y = 0 (a necessary condition: the pairs where it + holds are a proper subset of these complementary pairs), and the number of such pairs by (dim f_X, dim f_Y) is + as logged below. Mismatch ranks are also given for the boundary pairs of a port with label q. +D. Gauge descent. If q lies in span(axes of Z) with odd support (q = sum of an odd subset of the axes of + Z = C^{(x)k}) then multiplying by C_q lowers the rank by exactly one, T_E C_q^{-1} = T_{E cap q^perp}; + if q is outside span(axes) and orthogonal to it the rank rises by exactly one. +""" +import collections +import random +import sys + +from report import check, finish, main_file + +sys.path.insert(0, str(main_file('research/direct-delivery504-global/vendor/clifford/clifford_exact.py').parent)) +import clifford_exact as ce # noqa: E402 (the repository's exact Clifford compiler, unmodified) +from clifford_exact import rank, parity, cgate, ident, canonical_frame, lower_child, fourier # noqa: E402 + +# ---------------------------------------------------------------------------------------------------------------- +# Expected values (from the exact run; each is re-derived below and compared). +EXPECTED = { + # A: number of random products tested and number of mismatches between rank(B) and the exact Clifford rank + 'instrument': dict(products=240, mismatches=0), + # B: per n: number of subspaces of F2^n, number of odd q tested (the first ones in increasing order; all odd q + # for n <= 5, the first 10 for n = 6), and the histogram {(family, G on the geodesic?, extra cost): count} + 'detour': { + 3: dict(subspaces=16, qs=4, geo=20, off=44), + 4: dict(subspaces=67, qs=8, geo=128, off=408), + 5: dict(subspaces=374, qs=16, geo=1072, off=4912), + 6: dict(subspaces=2825, qs=10, geo=3740, off=24510), + }, + # C: mismatch ranks (right, left) for q = 1 and the number of pairs with route == F on a side, by + # (dim f_X, dim f_Y, dim f_X cap f_Y); 'sides' = how many of those pairs are monomial-equal to F on both sides / + # only on the left (R = M F) / only on the right (R = F M); 'complementary' = number of all pairs with + # dim f_X + dim f_Y = n and f_X cap f_Y = 0 (a superset of the rigid pairs); both are extra details of the run + 'route': { + 3: dict(sides=dict(both=8, left=8, right=8), complementary=58, + mism={',q^perp': (0, 0), ',L': (1, 1), '0,L': (0, 0), '0,q^perp': (1, 1), 'L,q^perp': (2, 2)}, + rigid={(0, 3, 0): 1, (1, 2, 0): 11, (2, 1, 0): 11, (3, 0, 0): 1}), + 4: dict(sides=dict(both=16, left=51, right=51), complementary=802, + mism={',q^perp': (0, 0), ',L': (1, 1), '0,L': (0, 0), '0,q^perp': (1, 1), 'L,q^perp': (3, 3)}, + rigid={(0, 4, 0): 1, (1, 3, 0): 26, (2, 2, 0): 64, (3, 1, 0): 26, (4, 0, 0): 1}), + }, + # D: n = 7, Z = C^{(x)k}, q = sum of the first sz axes (sz odd, sz <= k): (k, sz) -> (rank Z, rank C_q Z) + 'descent': {(3, 1): (3, 2), (3, 3): (3, 2), (5, 1): (5, 4), (5, 3): (5, 4), (5, 5): (5, 4), + (7, 1): (7, 6), (7, 3): (7, 6), (7, 5): (7, 6), (7, 7): (7, 6)}, + # D: Z = C^{(x)3} on axes e0,e1,e2 and q = e5 (outside and orthogonal): rank 3 -> 4 + 'outside': (3, 4), +} + +# ---- A: the F2 cost instrument --------------------------------------------------------------------------------- + + +def brank(terms, n): + """rank over F2 of B = sum_i q_i q_i^T (row j of B is the xor of the q_i whose bit j is set).""" + B = [0] * n + for q, _s in terms: + for j in range(n): + if q >> j & 1: + B[j] ^= q + return rank(B) + + +rng = random.Random(14) +bad = 0 +tot = 0 +for n in (4, 5, 6, 7): + for _ in range(60): + k = rng.randrange(1, 8) + terms = [(rng.randrange(1, 1 << n), rng.choice((-1, 1))) for _ in range(k)] + op = ident(n) + for q, s in terms: + op = op.then(cgate(n, q, s)) + r_ex = lower_child(op)[0]['rank'] + r_b = brank(terms, n) + tot += 1 + bad += (r_ex != r_b) +check('C2.instrument', (tot, bad) == (EXPECTED['instrument']['products'], EXPECTED['instrument']['mismatches']), + 'rank_F2(sum q q^T) vs exact Clifford child rank: %d random C-products, n=4..7, mismatches = %d' % (tot, bad)) + + +# ---- B: detour theorem ----------------------------------------------------------------------------------------- +def subspaces(n): + """all subspaces of F2^n, as frozensets of vectors (breadth-first closure under adding one vector).""" + vecs = range(1, 1 << n) + layer = {frozenset({0})} + seen = {frozenset({0})} + while layer: + nxt = set() + for S in layer: + for v in vecs: + if v not in S: + T = frozenset(S | {x ^ v for x in S}) + if T not in seen: + seen.add(T) + nxt.add(T) + layer = nxt + return list(seen) + + +def dim(S): + return len(S).bit_length() - 1 + + +def dd(f, g): + return dim(f) + dim(g) - 2 * dim(f & g) + + +for n in (3, 4, 5, 6): + subs = subspaces(n) + full = frozenset(range(1 << n)) + qs = [q for q in range(1, 1 << n) if parity(q)] + qsel = qs if n <= 5 else qs[:10] + counts = collections.Counter() + theorem = True + for q in qsel: + perp = frozenset(x for x in range(1 << n) if parity(x & q) == 0) + zero = frozenset({0}) + line = frozenset({0, q}) + for G in subs: + ex_y = dd(zero, G) + dd(G, perp) - dd(zero, perp) + ex_x = dd(line, G) + dd(G, full) - dd(line, full) + geo_y = G <= perp + geo_x = q in G + counts[('Y', geo_y, ex_y)] += 1 + counts[('X', geo_x, ex_x)] += 1 + theorem &= (ex_y == (0 if geo_y else 2)) and (ex_x == (0 if geo_x else 2)) + e = EXPECTED['detour'][n] + want = {('Y', True, 0): e['geo'], ('Y', False, 2): e['off'], ('X', True, 0): e['geo'], ('X', False, 2): e['off']} + ok = theorem and dict(counts) == want and len(subs) == e['subspaces'] and len(qsel) == e['qs'] + check('C2.detour-n%d' % n, ok, + '%d subspaces x %d odd q: extra cost of a detour via G is 0 on the geodesic set (%d per family) and exactly 2 ' + 'off it (%d per family) for Y: 0->G->q^perp (G <= q^perp) and X: ->G->L (q in G)' + % (len(subs), len(qsel), counts[('Y', True, 0)], counts[('Y', False, 2)])) + + +# ---- C: route rigidity ----------------------------------------------------------------------------------------- +def frame_op(n, S): + b = [] + for v in sorted(S): + if v and rank(b + [v]) > len(b): + b.append(v) + return canonical_frame(n, tuple(b)) + + +for n in (3, 4): + subs = subspaces(n) + full = frozenset(range(1 << n)) + zero = frozenset({0}) + F = fourier(n) + Fi = F.adjoint() + ops = {S: frame_op(n, S) for S in subs} + q = next(x for x in range(1, 1 << n) if parity(x)) + perp = frozenset(x for x in range(1 << n) if parity(x & q) == 0) + line = frozenset({0, q}) + + def mism(fx, fy): + R = ops[fx].adjoint().then(ops[fy]) + return lower_child(R.then(Fi))[0]['rank'], lower_child(Fi.then(R))[0]['rank'] + + got_mism = {',q^perp': mism(line, perp), ',L': mism(line, full), '0,L': mism(zero, full), + '0,q^perp': mism(zero, perp), 'L,q^perp': mism(full, perp)} + rigid = collections.Counter() + rigid_pairs = set() + sides = collections.Counter() + for fx in subs: + for fy in subs: + a, b = mism(fx, fy) # (right, left) mismatch ranks + if a == 0 or b == 0: + rigid[(dim(fx), dim(fy), dim(fx & fy))] += 1 + rigid_pairs.add((fx, fy)) + sides['both' if a == 0 and b == 0 else 'right' if a == 0 else 'left'] += 1 + # necessary condition: dims sum to n and the frames meet only in 0 + complementary = {(fx, fy) for fx in subs for fy in subs if dim(fx) + dim(fy) == n and dim(fx & fy) == 0} + e = EXPECTED['route'][n] + ok = (got_mism == e['mism'] and dict(rigid) == e['rigid'] and rigid_pairs <= complementary + and len(complementary) == e['complementary'] and dict(sides) == e['sides']) + check('C2.route-n%d' % n, ok, + 'q=%d: route == F modulo monomials on a side for %d pairs, all with dim fX + dim fY = %d and fX cap fY = 0 ' + '(of %d such pairs; by (dim fX, dim fY): %s; monomial-equal on both sides / only left / only right: %d/%d/%d); ' + 'mismatch ranks (right,left): (,q^perp)=%s (,L)=%s (0,L)=%s (0,q^perp)=%s (L,q^perp)=%s' + % (q, len(rigid_pairs), n, len(complementary), dict(sorted((k[:2], v) for k, v in rigid.items())), + sides['both'], sides['left'], sides['right'], got_mism[',q^perp'], + got_mism[',L'], got_mism['0,L'], got_mism['0,q^perp'], got_mism['L,q^perp'])) + +# ---- D: gauge descent ------------------------------------------------------------------------------------------ +n = 7 +descent = {} +for k in (3, 5, 7): + for sz in (1, 3, 5, 7): + if sz > k: + continue + q = sum(1 << i for i in range(sz)) # odd support: q is a sum of an odd number of the axes of Z + op = ident(n) + for a in [1 << i for i in range(k)]: + op = op.then(cgate(n, a)) + r0 = lower_child(op)[0]['rank'] + r1 = lower_child(op.then(cgate(n, q)))[0]['rank'] + descent[(k, sz)] = (r0, r1) +op = ident(n) +for a in (1, 2, 4): + op = op.then(cgate(n, a)) +outside = (lower_child(op)[0]['rank'], lower_child(op.then(cgate(n, 1 << 5)))[0]['rank']) +n_down = sum(1 for r0, r1 in descent.values() if r1 == r0 - 1) +check('C2.gauge-descent', + descent == EXPECTED['descent'] and outside == EXPECTED['outside'] and n_down == len(descent) == 9, + 'n=7, Z = C^(x)k (k=3,5,7), q = sum of an odd number of the axes: rank(C_q Z) = rank(Z) - 1 in %d/%d cases; ' + 'q = e5 outside and orthogonal to the axes e0,e1,e2: rank %d -> %d (+1)' + % (n_down, len(descent), outside[0], outside[1])) + +finish() diff --git a/research/format-limits/scripts/c3_complement_gate0.py b/research/format-limits/scripts/c3_complement_gate0.py new file mode 100644 index 0000000000..2f462d3e8e --- /dev/null +++ b/research/format-limits/scripts/c3_complement_gate0.py @@ -0,0 +1,319 @@ +"""Exact checks for complement (oblique) sinks: the sink hyperplane w^perp with w odd and w.u = 1 for a rank-1 source . + +Notation. Labels are vectors of F2^n with the standard dot product; u is odd (u.u = 1). For a subspace F, T_F is the +canonical Clifford frame operator of F (T_0 = identity, T_FULL = F = the Fourier transform on all n axes, "C^(x)n"), +and d(A, B) = dim A + dim B - 2 dim(A cap B) is the frame distance. The Clifford rank of an operator is the rank +reported by clifford_exact.lower_child (rank of the Fourier part of its normal form). A monomial operator is an +address-linear permutation times a Z4 diagonal phase times a translation times a global phase. A weight-odd vector w +is a COMPLEMENT of u when w.u = 1, i.e. u is not in w^perp, so that F2^n = (+) w^perp. + +(E) Endpoint. For every odd w with w.u = 1 the route R = T_{w^perp} T_^{-1} has Clifford rank n, and + R = Lmon . F . Rmon with Lmon, Rmon monomial (Rmon acts first): the explicit check is + Lmon^{-1} R Rmon^{-1} == F on the Clifford tableau and on every basis column (n <= 6; 6 sampled columns at n = 9). + Controls: w = u (the format's own sink u^perp) has the same property; a non-complement w (w.u = 0, so u is in + w^perp) gives rank n - 2. The wrapper class of each monomial is reported as 'id' or 'perm' (trivial or nontrivial + address map) plus '+quad' if its phase has an off-diagonal (quadratic) part: Lmon is always 'perm+quad', Rmon has + trivial address map ('id', or 'id+quad'). +(P) Port price. A target (Y) climbing 0 -> w^perp costs n - 1 = d(0, w^perp), the same as 0 -> u^perp; a source (X) + climbing -> FULL costs n - 1. A detour station G (Y: 0 -> G -> w^perp) costs +0 exactly when G <= w^perp and + exactly +2 otherwise (exhaustive over all subspaces G, n = 3, 4, 5). +(N) Newly lossless corrector. A source with label s, s.u = 1 and s.w = 0 cannot be read drop-free by the sink u^perp + (s is not in u^perp) but lies on the geodesic of the sink w^perp. Network on two registers: the target y climbs + 0 -> (read c * x there, c = 1/2) -> w^perp ; the source x climbs -> FULL. The operator is + x -> T_FULL l_x , y -> T_{w^perp}(l_y + c l_x) (exact on every basis column of x and sampled columns of y, with + exact Gaussian-dyadic arithmetic), and every step's child rank equals d(A, B) (n = 3, 4, 5). +""" +import random +import sys +from fractions import Fraction as Fr + +from report import check, finish, main_file + +sys.path.insert(0, str(main_file('research/direct-delivery504-global/vendor/clifford/clifford_exact.py').parent)) +import clifford_exact as ce # noqa: E402 (the repository's exact Clifford compiler, unmodified) + +# ---------------------------------------------------------------------------------------------------------------- +# Expected values (from the exact run; every one is re-derived below and compared). +# ENDPOINT[n]: histogram of (kind, Clifford rank, tableau equal to F, columns equal to F, Lmon class, Rmon class) over +# the odd w tested (kind 'compl': w.u = 1, 'noncompl': w.u = 0, 'u': w = u). For n <= 6 every odd w is tested; +# for n = 9 a sample of 160 vectors w (random.Random(1), same draw sequence as the run below) plus w = u is tested, so +# the counts at n = 9 (41 complements, 50 non-complements, w = u drawn twice) are properties of that seeded sample. + +def _hist(n, n_id, n_idq, n_non, n_u): + h = {('compl', n, True, True, 'perm+quad', 'id'): n_id, ('compl', n, True, True, 'perm+quad', 'id+quad'): n_idq, + ('u', n, True, True, 'perm+quad', 'id+quad'): n_u} + if n_non: + h[('noncompl', n - 2, None, None, None, None)] = n_non # control w.u = 0: rank n - 2 + return h + + +ENDPOINT = {3: _hist(3, 1, 2, 0, 1), 4: _hist(4, 1, 2, 4, 1), 5: _hist(5, 1, 6, 8, 1), 6: _hist(6, 1, 14, 16, 1), + 9: _hist(9, 1, 40, 50, 2)} +PORT_PRICE = {n: n - 1 for n in (3, 4, 5, 6, 9)} # Y 0->w^perp, Y 0->u^perp, X ->FULL all cost n - 1 +INTERVAL_SUBSPACES = {3: 16, 4: 67, 5: 374} # number of subspaces of F2^n, all with zero violations +CORRECTOR = {3: dict(s=0b010, charged=4), 4: dict(s=0b0010, charged=6), 5: dict(s=0b00010, charged=8)} # charged = 2(n-1) + + +def par(x): + return bin(x).count('1') & 1 + + +def basis(vs, n): + b = [] + for v in vs: + if ce.rank(b + [v]) > len(b): + b.append(v) + return b + + +def perp(vs, n): + return [z for z in range(1, 1 << n) if all(par(z & v) == 0 for v in vs)] + + +def hyper(w, n): + """a basis of w^perp.""" + return basis(perp([w], n), n) + + +def T(n, U): + return ce.canonical_frame(n, tuple(U)) + + +def d(A, B): + a, b = len(A), len(B) + return a + b - 2 * (a + b - ce.rank(A + B)) + + +def opstep(n, A, B): + """the operator T_B T_A^{-1} (T_A^{-1} is applied first).""" + return (T(n, A).adjoint().then(T(n, B))) if A else T(n, B) + + +def rk(op): + return ce.lower_child(op)[0]['rank'] + + +def lower_ops(op): + """clifford_exact.lower_child, but also returning the monomial operators: + op == rmon.then(fourier(n, range(r))).then(lmon) (rmon acts first); returns (r, lmon, rmon, lrec, rrec).""" + n = op.n + Bcols = [x for x, z, p in op.tab[n:]] + r = ce.rank(Bcols) + G = ce.transpose(ce.basis_extend(Bcols, n), n) + A = ce.inv(G, n) + PA = ce.perm(n, A) + part = op.then(PA) + Bcols = [x for x, z, p in part.tab[n:]] + Dcols = [z for x, z, p in part.tab[n:]] + picks = [] + sel = [] + for j, x in enumerate(Bcols): + if ce.rank(sel + [x]) > len(sel): + picks.append(j) + sel.append(x) + Br = ce.transpose(sel, r) + Bi = ce.inv(Br, r) + Dr = ce.transpose([Dcols[j] & ((1 << r) - 1) for j in picks], r) + Qs = tuple(sum(ce.parity(row & col) << j for j, col in enumerate(ce.transpose(Bi, r))) for row in Dr) + DQ = ce.diag(n, Qs + (0,) * (n - r)) + V = part.then(DQ).then(ce.hadamard(n, range(r))) + Vmon, _ = ce.monomial(V) + S = ce.diag(n, tuple(1 << i if i < r else 0 for i in range(n))) + left = S.adjoint().then(DQ.adjoint()).then(PA.adjoint()) + right = Vmon.then(S.adjoint()).then(ce.xgate(n, (1 << r) - 1)) + lmon, lrec = ce.monomial(left) + rmon, rrec = ce.monomial(right) + comp = rmon.then(ce.fourier(n, range(r))).then(lmon) + assert comp.tab == op.tab and comp.zero_state() == op.zero_state() + return r, lmon, rmon, lrec, rrec + + +def cols_equal(A, B, n, cols): + return all(A.zero_state(c) == B.zero_state(c) for c in cols) + + +def endpoint(n, u, w, ncol, rng): + U = [u] + V = hyper(w, n) + R = opstep(n, U, V) + r, lmon, rmon, lrec, rrec = lower_ops(R) + assert r == rk(R) # agrees with lower_child + out = dict(rank=r) + if r == n: + fixed = rmon.adjoint().then(R).then(lmon.adjoint()) # Lmon^{-1} R Rmon^{-1} + F = ce.fourier(n) + cols = list(range(1 << n)) if ncol is None else rng.sample(range(1 << n), ncol) + out['tab_eq'] = fixed.tab == F.tab + out['cols_eq'] = cols_equal(fixed, F, n, cols) + + def cls(rec): # is the address map trivial? is there a quadratic (off-diagonal) phase part? + Gid = tuple(rec['matrix_rows']) == tuple(1 << i for i in range(n)) + Qoff = any((rec['quadratic_rows'][i] & ~(1 << i)) for i in range(n)) + return ('perm' if not Gid else 'id') + ('+quad' if Qoff else '') + out['Lclass'] = cls(lrec) + out['Rclass'] = cls(rrec) + return out + + +def all_subspace_bases(n): + """bases of all subspaces of F2^n (closure enumeration).""" + layer = {frozenset({0})} + seen = set(layer) + while layer: + nxt = set() + for S in layer: + for v in range(1, 1 << n): + if v not in S: + t = frozenset(S | {x ^ v for x in S}) + if t not in seen: + seen.add(t) + nxt.add(t) + layer = nxt + return [basis(sorted(S), n) for S in seen] + + +def interval(n, w, Gs): + """number of violations of: Y family 0 -> G -> w^perp has excess 0 iff G <= w^perp, else exactly 2.""" + V = hyper(w, n) + bad = 0 + n_inside = 0 + for G in Gs: + ex = d([], G) + d(G, V) - d([], V) + inside = all(par(g & w) == 0 for g in G) + n_inside += inside + if ex != (0 if inside else 2): + bad += 1 + return bad, n_inside + + +def network(n, u, w, s, rng): + """Roles x (source, label , ends FULL) and y (target, 0 -> -> w^perp); gate y += c x at frame . + Expected operator: x -> T_FULL l_x ; y -> T_V (l_y + c l_x) with l = T_start^{-1} h. Exact on every basis column + of x (and 3 sampled columns of y per column of x).""" + V = hyper(w, n) + N = 1 << n + c = Fr(1, 2) + + def mat(op): # exact matrix from the columns op.zero_state(b) + cols = [] + for bi in range(N): + vec, den = op.zero_state(bi) + cols.append([(Fr(a, 1 << den), Fr(b, 1 << den)) for a, b in vec]) + return [[cols[j][i] for j in range(N)] for i in range(N)] + + def cm(a, b): + return (a[0] * b[0] - a[1] * b[1], a[0] * b[1] + a[1] * b[0]) + + def mv(M, v): + out = [] + for i in range(N): + re = im = Fr(0) + for j in range(N): + pr = cm(M[i][j], v[j]) + re += pr[0] + im += pr[1] + out.append((re, im)) + return out + + def add(a, b, k=Fr(1)): + return [(x[0] + k * y[0], x[1] + k * y[1]) for x, y in zip(a, b)] + + S = [s] + FULL = [1 << i for i in range(n)] + steps = [('y', [], S), ('x', S, FULL), ('y', S, V)] + charged = 0 + ok_rank = True + for who, A, B in steps: + r = rk(opstep(n, A, B)) + dd = d(A, B) + charged += r + ok_rank &= (r == dd) + Ty_s = mat(T(n, S)) + Ty_V_from_S = mat(opstep(n, S, V)) + Tx_full_from_S = mat(opstep(n, S, FULL)) + TSinv = mat(T(n, S).adjoint()) + TV = mat(T(n, V)) + TF = mat(T(n, FULL)) + good = True + for bx in range(N): + for by in rng.sample(range(N), 3): + hx = [(Fr(int(i == bx)), Fr(0)) for i in range(N)] # physical x stored in frame + hy = [(Fr(int(i == by)), Fr(0)) for i in range(N)] # physical y at frame 0 + y1 = mv(Ty_s, hy) # y climbs 0 -> + y1 = add(y1, hx, c) # gate at the common frame + x1 = mv(Tx_full_from_S, hx) # x climbs -> FULL + y2 = mv(Ty_V_from_S, y1) # y climbs -> w^perp + lx = mv(TSinv, hx) # expected: logical content l_x = T_^{-1} h_x + ex_x = mv(TF, lx) + ex_y = mv(TV, add(hy, lx, c)) + good &= (x1 == ex_x) and (y2 == ex_y) + return charged, ok_rank, good + + +rng = random.Random(1) +for n in (3, 4, 5, 6, 9): + u = 0b111 if n < 9 else 0b000011111 # odd weight (3 / 5) + stats = {} + ws = list(range(1, 1 << n)) + if n == 9: + ws = rng.sample(ws, 160) + [u] + for w in ws: + if not par(w): + continue # even w: w^perp contains w, frame labels would be degenerate + kind = 'u' if w == u else ('compl' if par(w & u) else 'noncompl') + e = endpoint(n, u, w, None if n <= 6 else 6, rng) + key = (kind, e['rank'], e.get('tab_eq'), e.get('cols_eq'), e.get('Lclass'), e.get('Rclass')) + stats[key] = stats.get(key, 0) + 1 + + # --- endpoint claims + compl = {k: v for k, v in stats.items() if k[0] == 'compl'} + non = {k: v for k, v in stats.items() if k[0] == 'noncompl'} + ctl_u = {k: v for k, v in stats.items() if k[0] == 'u'} + n_compl = sum(compl.values()) + exp = ENDPOINT[n] + ok_c = (all(k[1] == n and k[2] is True and k[3] is True and k[4] == 'perm+quad' and k[5] in ('id', 'id+quad') + for k in compl) and n_compl > 0 + and {k: v for k, v in exp.items() if k[0] == 'compl'} == compl) + cols_txt = 'all %d basis columns' % (1 << n) if n <= 6 else '6 sampled columns each' + check('C3g.complement-n%d' % n, ok_c, + '%d complement w (odd, w.u=1)%s: route T_{w^perp} T_^-1 has Clifford rank %d; Lmon^-1 R Rmon^-1 == F on the ' + 'tableau and on %s; wrapper classes (Lmon perm+quad; Rmon id: %d, id+quad: %d)' + % (n_compl, '' if n <= 6 else ' (seeded sample)', n, cols_txt, + compl.get(('compl', n, True, True, 'perm+quad', 'id'), 0), + compl.get(('compl', n, True, True, 'perm+quad', 'id+quad'), 0))) + ok_u = (all(k[1] == n and k[2] is True and k[3] is True for k in ctl_u) + and {k: v for k, v in exp.items() if k[0] == 'u'} == ctl_u) + check('C3g.format-sink-n%d' % n, ok_u, + 'control w = u (sink u^perp, %d draw%s): rank %d, exact (tableau and columns)' + % (sum(ctl_u.values()), '' if sum(ctl_u.values()) == 1 else 's', n)) + if n >= 4: + ok_n = (non and all(k[1] == n - 2 for k in non) and {k: v for k, v in exp.items() if k[0] == 'noncompl'} == non) + check('C3g.noncomplement-n%d' % n, bool(ok_n), + 'control w.u=0 (%d odd w): Clifford rank n-2 = %d' % (sum(non.values()), n - 2)) + else: + check('C3g.noncomplement-n%d' % n, not non, 'no odd w with w.u=0 exists at n=3 (control vacuous)') + + # --- port price: first odd complement w != u + w = next(w for w in range(1, 1 << n) if par(w) and par(w & u) and w != u) + V = hyper(w, n) + Uperp = hyper(u, n) + FULL = [1 << i for i in range(n)] + rY, dY, rYu, rX = rk(T(n, V)), d([], V), rk(T(n, Uperp)), rk(opstep(n, [u], FULL)) + check('C3g.port-price-n%d' % n, (rY, dY, rYu, rX) == (PORT_PRICE[n],) * 4, + 'Y: 0 -> w^perp rank %d (d %d), 0 -> u^perp rank %d; X: -> FULL rank %d; all n-1 = %d' + % (rY, dY, rYu, rX, n - 1)) + + if n <= 5: + Gs = all_subspace_bases(n) + bad, n_inside = interval(n, w, Gs) + check('C3g.interval-n%d' % n, bad == 0 and len(Gs) == INTERVAL_SUBSPACES[n] and 0 < n_inside < len(Gs), + 'Y: 0 -> G -> w^perp excess 0 iff G <= w^perp (%d of %d subspaces) else exactly 2: violations %d' + % (n_inside, len(Gs), bad)) + s = next((s for s in range(1, 1 << n) if par(s) and par(s & w) == 0 and par(s & u) == 1), None) + ch, okr, good = network(n, u, w, s, rng) + exp_c = CORRECTOR[n] + check('C3g.corrector-n%d' % n, s == exp_c['s'] and ch == exp_c['charged'] == 2 * (n - 1) and okr and good, + 's=%s (s.u=1, s.w=0): charged %d = d-sum 2(n-1), child rank == d at every step %s, operator exact on ' + 'columns %s' % (format(s, '0%db' % n), ch, okr, good)) + +finish() diff --git a/research/format-limits/scripts/c3_complement_taint.py b/research/format-limits/scripts/c3_complement_taint.py new file mode 100644 index 0000000000..0547b0dbfc --- /dev/null +++ b/research/format-limits/scripts/c3_complement_taint.py @@ -0,0 +1,337 @@ +"""Rigidity of the complement sink (Lemma R): on the 112-target, 9-bit-port decoder every target has exactly one +admissible sink, the standard one. + +Setting (frames and the common-frame identity: upstream/build/sections/03-motifs.tex:154-186; the route operator +C_sink C_source^-1 of a complex interface: 03-motifs.tex:568-640; the recursive contract: 05-layers.tex:141-154). +A word is a scalar program on registers 0..n-1, n = 2V+R, V = 112 ports. Registers 0..V-1 are the sources s +(port vector u_s in F_2^9), V..2V-1 the targets T_t, 2V.. the helper and centre registers; its events are +['add', t, s, ...] (register t receives register s) in three phases A, cut, B. A target T_t must end at a sink +V_t = w_t^perp with w_t odd-weight and w_t . u_t = 1 (the standard sink is w_t = u_t, V_t = u_t^perp; every other +choice is a 'complement' sink). + +Two quantities are computed, both over GF(2) with 9-bit integers (bit 0 = least significant): + +* lower closure lo(r): the smallest frame register r can stand at in ANY frame assignment of the same scalar word + whose only drops are the centre drops at the cut. Sources start at ; targets, helpers and centres start at 0 + (an entrance is a choice, 0 is the most permissive); every 'add' puts both registers at one common frame, so both + get lo(t)+lo(s) (monotone from then on); at the cut every centre drops to 0 (the most permissive drop). The final + frame of T_t is at least lo(T_t), so any sink must satisfy lo(T_t) <= w_t^perp, i.e. w_t is orthogonal to lo(T_t); + u_t in lo(T_t) would make the word illegal. +* drop-free source taint of T_t: the set of sources whose content reaches T_t along 'add' events (centres reset at + the cut). A source s whose content reaches T_t drop-free must be lossless, i.e. w_t . u_s = 0. + +Lemma R: if the taint of T_t spans u_t^perp then w_t = u_t is the only complement sink of t. +Measured here: the taint of T_t is exactly the Gram-orthogonal neighbourhood {s != t : u_s . u_t = 0} (52 sources, +span of dimension 8 = u_t^perp), the most permissive closure has dimension 8 on all 112 targets, and w_t = u_t is the +only admissible sink on all 112; nothing reaches a target in phase A, no target carries its own source. + +Further checks on one word (#569 width7/eight): +* kmin: for a complement sink w != u_t (w odd, w . u_t = 1) the violated correctors are the taint sources s with + w . u_s = 1; 'newly allowed' are sources s with u_s . u_t = 1 (illegal under u_t^perp) and w . u_s = 0. + The minimum over w of the violated count is 20 on every target. +* positive control: the instrument does see freedom when it exists. For the target T_0 and the complement sink + w_0 (the first minimiser of the violated count) delete (i) the 3 events that write taint carrying a violated source + into T_0 -- this does NOT free T_0, because the gate closure is stickier than the content taint -- and + (ii) the 4 gates touching T_0 whose partner frame (at that point of the closure) is not inside w_0^perp -- now T_0 + has dim 7 and the two admissible sinks {u_0, w_0}, while the other 111 targets stay forced. + +Words: #569 width7-eight and fourcold-eight, #571 combined-eight-local (DominikScholz), #531 direct-delivery504 +(icekylinx, file of main). Run: python3 c3_complement_taint.py +""" +import gzip +import json +import time +from collections import Counter + +from report import check, finish, DATA, VENDOR, main_file + +V = 112 # ports (sources) and targets +NB = 9 # port bits + +# --------------------------------------------------------------------------------------------------------------- +# expected values (from the recorded measurements) +EXPECTED = { + 'dim': 8, # dim lo(T_t) = dim u_t^perp + 'n_taint': 52, # |{s != t : u_s . u_t = 0}| + 'kmin': 20, # min over complement sinks w of the number of violated correctors + # (violated, newly allowed) over all (t, w), w odd, w.u_t = 1, w != u_t: 127 sinks per target + 'kdist': {(20, 32): 336, (22, 30): 5376, (24, 28): 896, (24, 32): 448, (28, 28): 896, (28, 32): 448, + (30, 30): 5376, (32, 32): 336, (52, 0): 112}, + 'w0': 0b110000001, # first minimiser for target 0, printed as 110000001 + 'w0_violated': 20, + 'taint_filter_removed': 3, # events writing violated taint into T_0 + 'closure_filter_removed': 4, # gates touching T_0 whose partner frame is not inside w0^perp + 'closure_dims': {7: 1, 8: 111}, + 'closure_admissible': {1: 111, 2: 1}, +} + +WORDS = [ # (key, label, path, has recorded entrance-frame variant) + ('width7_eight', '#569 width7/eight', VENDOR / 'pr569' / 'width7-eight-FULL-WORD.json.gz', True), + ('fourcold_eight', '#569 fourcold/eight', VENDOR / 'pr569' / 'fourcold-eight-FULL-WORD.json.gz', True), + ('combined_eight_local', '#571 combined-eight-local', VENDOR / 'pr571' / 'combined-eight-local-FULL-WORD.json.gz', True), + ('direct504_local', '#531 direct-delivery504 local', + 'research/direct-delivery504-global/evidence/local/FULL-WORD.json.gz', False), +] + + +# --------------------------------------------------------------------------------------------------------------- +# GF(2) helpers on 9-bit integers +def par(x): + return bin(x).count('1') & 1 + + +def basis(vs): + """reduced row echelon basis (tuple, canonical order) of the span of the integers vs.""" + b = {} + for x in vs: + for p in sorted(b, reverse=True): + if x >> p & 1: + x ^= b[p] + if x: + p = x.bit_length() - 1 + for q in list(b): + if b[q] >> p & 1: + b[q] ^= x + b[p] = x + return tuple(b[p] for p in sorted(b, reverse=True)) + + +def in_span(x, B): + return len(basis(list(B) + [x])) == len(B) + + +def perp_vectors(B): + return [z for z in range(1, 1 << NB) if all(par(z & b) == 0 for b in B)] + + +def admissible_sinks(D, u): + """odd-weight w with w.u = 1 and w orthogonal to the frame D.""" + return [z for z in perp_vectors(D) if par(z & u) and par(z)] + + +# --------------------------------------------------------------------------------------------------------------- +def lower_closure(word, skip=None, entrances=False): + """lo(r) for every register. skip(phase, i, t, s, lo) -> True deletes the i-th event of the phase (an 'add' gate + of registers t, s); used by the positive control.""" + R = word['report']['R'] + n = 2 * V + R + P = word['ports'] + lo = {} + for r in range(n): + if r < V: + lo[r] = (P[r],) + elif entrances and r >= 2 * V and str(r) in word.get('start_extra', {}): + lo[r] = basis(word['start_extra'][str(r)]) + else: + lo[r] = () + removed = [] + + def run(phase, seq): + for i, e in enumerate(seq): + if e[0] != 'add': + continue + t, s = e[1], e[2] + if skip is not None and skip(phase, i, t, s, lo): + removed.append((phase, i, t, s)) + continue + F = basis(list(lo[t]) + list(lo[s])) + lo[t] = F + lo[s] = F + + run('A', word['A']) + for c in word['centers']: + lo[c] = () + run('cut', word['cut']) + run('B', word['B']) + return lo, removed + + +def closure_report(word, lo): + """per-target (dim lo, #admissible sinks, w==u forced) plus counters.""" + P = word['ports'] + dims, free = Counter(), Counter() + illegal = forced = 0 + sinks = {} + for t in range(V): + D, u = lo[V + t], P[t] + if in_span(u, D): + illegal += 1 + sinks[t] = (len(D), None) + continue + ws = admissible_sinks(D, u) + dims[len(D)] += 1 + free[len(ws)] += 1 + forced += (ws == [u]) + sinks[t] = (len(D), ws) + return dict(dims=dict(sorted(dims.items())), free=dict(sorted(free.items())), illegal=illegal, forced=forced, + sinks=sinks) + + +def source_taint(word, skip=None): + """taint[r] = set of sources whose content reaches register r drop-free (centres reset at the cut). + Returns (taint after A, final taint, removed events); skip(phase, i, t, s, taint) deletes an event.""" + n = 2 * V + word['report']['R'] + taint = {r: ({r} if r < V else set()) for r in range(n)} + removed = [] + + def run(phase, seq): + for i, e in enumerate(seq): + if e[0] == 'add': + if skip is not None and skip(phase, i, e[1], e[2], taint): + removed.append((phase, i, e[1], e[2])) + continue + taint[e[1]] |= taint[e[2]] + run('A', word['A']) + after_A = {t: set(taint[V + t]) for t in range(V)} + for c in word['centers']: + taint[c] = set() + run('cut', word['cut']) + run('B', word['B']) + return after_A, taint, removed + + +def kmin_stats(word, taint): + """(min violated per target, Counter over all (t, w != u_t) of (violated, newly allowed)).""" + P = word['ports'] + dist = Counter() + mins = {} + argmin0 = None + for t in range(V): + u, S = P[t], taint[V + t] + nonorth = [s for s in range(V) if s != t and par(P[s] & u)] + best = None + for z in range(1, 1 << NB): + if par(z) and par(z & u) and z != u: + viol = sum(1 for s in S if par(z & P[s])) + new = sum(1 for s in nonorth if not par(z & P[s])) + dist[(viol, new)] += 1 + if best is None or viol < best: + best = viol + if t == 0: + argmin0 = z # first z (increasing) attaining the minimum + mins[t] = best + return mins, dist, argmin0 + + +# --------------------------------------------------------------------------------------------------------------- +def load_word(path): + p = main_file(path) if isinstance(path, str) else path + return json.loads(gzip.decompress(p.read_bytes())) + + +def run_word(key, label, path, has_entrance_variant, words_out): + word = load_word(path) + words_out[key] = word + P = word['ports'] + assert len(P) == V and word['report']['R'] == 504 + tag = 'C3t.%s.' % key + t0 = time.time() + + lo, _ = lower_closure(word) + rep = closure_report(word, lo) + is_perp = all(all(par(b & P[t]) == 0 for b in lo[V + t]) for t in range(V)) # lo(T_t) lies in u_t^perp + check(tag + 'closure_dim8', rep['dims'] == {EXPECTED['dim']: V} and rep['illegal'] == 0 and is_perp, + '%s: dim lo(T_t) %s on %d targets (= u_t^perp: %s), u_t in lo(T_t): %d' % (label, rep['dims'], V, is_perp, rep['illegal'])) + only_u = all(s[1] == [P[t]] for t, s in rep['sinks'].items()) + check(tag + 'sink_forced', rep['free'] == {1: V} and rep['forced'] == V and only_u, + '#admissible sinks %s, w_t = u_t forced on %d/%d' % (rep['free'], rep['forced'], V)) + + after_A, taint, _ = source_taint(word) + eq = sum(1 for t in range(V) + if taint[V + t] == {s for s in range(V) if s != t and not par(P[s] & P[t])}) + nsrc = Counter(len(taint[V + t]) for t in range(V)) + span = Counter(len(basis([P[s] for s in taint[V + t]])) for t in range(V)) + check(tag + 'taint_is_orth_neighbourhood', + eq == V and dict(nsrc) == {EXPECTED['n_taint']: V} and dict(span) == {EXPECTED['dim']: V}, + 'taint == {s != t : u_s.u_t = 0} on %d/%d, #sources %s, span dim %s' % (eq, V, dict(nsrc), dict(span))) + spanA = Counter(len(basis([P[s] for s in after_A[t]])) for t in range(V)) + check(tag + 'nothing_reaches_targets_in_A', dict(spanA) == {0: V}, + 'span dim of the taint after phase A only: %s' % dict(spanA)) + own = sum(1 for t in range(V) if t in taint[V + t]) + check(tag + 'no_own_source', own == 0, 'targets carrying their own source drop-free: %d' % own) + + mins, dist, _ = kmin_stats(word, taint) + check(tag + 'kmin20', Counter(mins.values()) == Counter({EXPECTED['kmin']: V}), + 'min over complement sinks w != u_t of #violated correctors: %s' % dict(Counter(mins.values()))) + if key == 'width7_eight': + check(tag + 'kmin_distribution', dict(dist) == EXPECTED['kdist'], + '(violated, newly allowed) over %d (t, w) pairs: %s' % (sum(dist.values()), sorted(dist.items()))) + + if has_entrance_variant: + lo2, _ = lower_closure(word, entrances=True) + rep2 = closure_report(word, lo2) + check(tag + 'closure_with_word_entrances', rep2['dims'] == {EXPECTED['dim']: V} and rep2['free'] == {1: V} + and rep2['illegal'] == 0, + 'closure with the word\'s own entrance frames (start_extra): dims %s, sinks %s' % (rep2['dims'], rep2['free'])) + else: + print('NOTE %s: the word stores its helper entrance frames in a convention this closure does not model (frames ' + 'taken from the start would put u_t in the closure); the claims above use the most permissive ' + 'entrances, every helper at 0, which bound every frame assignment' % label, flush=True) + return time.time() - t0 + + +def positive_control(key, word, label): + """delete the gates/events that carry the lossless corrections of T_0 and watch the instrument see freedom.""" + P = word['ports'] + tag = 'C3t.posctl.%s.' % key + T0 = V + _, taint, _ = source_taint(word) + _, _, w0 = kmin_stats(word, taint) + u0 = P[0] + viol = {s for s in taint[T0] if par(w0 & P[s])} + check(tag + 'w0', w0 == EXPECTED['w0'] and len(viol) == EXPECTED['w0_violated'], + '%s: target 0, u_0 = %s, w_0 = %s, violated correctors %d' % ( + label, format(u0, '09b'), format(w0, '09b'), len(viol))) + + # (i) taint-filtered: delete the events writing violated taint into T_0 (from the content taint), then recompute + # the gate closure without those events + def skip_taint(phase, i, t, s, taint_now): + return t == T0 and bool(taint_now[s] & viol) + _, _, removed_t = source_taint(word, skip=skip_taint) + gone = {(ph, i) for ph, i, _, _ in removed_t} + lo_t, _ = lower_closure(word, skip=lambda ph, i, t, s, lo: (ph, i) in gone) + rep_t = closure_report(word, lo_t) + d0, ws0 = rep_t['sinks'][0] + check(tag + 'taint_filter_does_not_free', + len(removed_t) == EXPECTED['taint_filter_removed'] and rep_t['dims'] == {EXPECTED['dim']: V} + and rep_t['free'] == {1: V} and (d0, ws0) == (EXPECTED['dim'], [u0]) and w0 not in ws0, + 'removed %d events (partners %s); dims %s, #admissible %s, T_0 (dim, sinks) = (%d, %s), w_0 admissible: %s' % ( + len(removed_t), [s for _, _, _, s in removed_t], rep_t['dims'], rep_t['free'], d0, ws0, w0 in ws0)) + + # (ii) closure-filtered: delete the gates touching T_0 whose partner frame is not inside w0^perp + def skip_closure(phase, i, t, s, lo_now): + if T0 not in (t, s): + return False + partner = s if t == T0 else t + return any(par(w0 & b) for b in lo_now[partner]) + lo_c, rm_c = lower_closure(word, skip=skip_closure) + rep_c = closure_report(word, lo_c) + d0, ws0 = rep_c['sinks'][0] + check(tag + 'closure_filter_frees_T0', + len(rm_c) == EXPECTED['closure_filter_removed'] and rep_c['dims'] == EXPECTED['closure_dims'] + and rep_c['free'] == EXPECTED['closure_admissible'] and rep_c['illegal'] == 0 + and sorted(ws0) == sorted([u0, w0]) and rep_c['forced'] == V - 1, + 'removed gates %s (phase, event, t, s); dims %s, #admissible %s, T_0 (dim, sinks) = (%d, %s), others forced: %d/%d' % ( + rm_c, rep_c['dims'], rep_c['free'], d0, ws0, rep_c['forced'], V - 1)) + # (the gates removed are the correction gates read at T_0: each partner holds a violated source) + reads_target = sum(1 for e in word['B'] if e[0] == 'add' and V <= e[2] < 2 * V) + check(tag + 'every_target_read_once', reads_target == V, + 'B events whose source register is a target: %d' % reads_target) + + +def main(): + t_start = time.time() + words = {} + for key, label, path, flag in WORDS: + dt = run_word(key, label, path, flag, words) + print(' (%s: %.1f s)' % (key, dt), flush=True) + t0 = time.time() + for key in ('width7_eight', 'fourcold_eight', 'combined_eight_local'): + positive_control(key, words[key], dict((k, l) for k, l, _, _ in WORDS)[key]) + print(' (positive control: %.1f s)' % (time.time() - t0), flush=True) + print('total %.1f s' % (time.time() - t_start)) + finish() + + +if __name__ == '__main__': + main() diff --git a/research/format-limits/scripts/c4_quotient.py b/research/format-limits/scripts/c4_quotient.py new file mode 100644 index 0000000000..dec39d9b01 --- /dev/null +++ b/research/format-limits/scripts/c4_quotient.py @@ -0,0 +1,507 @@ +"""Lemma Q: a unit with degenerate labels is dominated by its quotient. + +Setting. A unit has ports with F_2 (complex) or Q (bit) labels spanning a space W of dimension d. For the complex +side the form is the standard dot product on F_2^n, rad = W cap W^perp has dimension rho, the Gram rank is +r = d - rho, and pi : W -> W/rad is the quotient map. A 'total' j is read by the targets from a frame f_j, the span +of the labels of its sources. Reading total j at a cut frame g costs the frame-lemma price + d(f_j, g) = dim f_j + dim g - 2 dim(f_j cap g) (upstream/build/sections/03-motifs.tex:154-186), +where all targets stand at one common frame g that lies in every target's sink; because f_j lies in W, f_j cap g lies +in rad, so only cuts g inside rad matter. Per total, for g inside rad, + d(f_j, g) >= dim f_j - dim(f_j cap rad) = dim pi(f_j). +Hence for every schedule of cuts (a chain 0 = g_0 < g_1 < ... of subspaces of rad, each total read at one of them) + cst_E2' >= cst_quotient = sum_j dim pi(f_j) >= cst_pair = sum_j rank pairing(sources_j, readers_j), +the last being the pairing-rank floor of a total, which only depends on the Gram. The quotient unit (labels mod rad, same Gram +and decoder) has window r = d - rho <= h - 2 rho for every realisation with window h = d + rho. Certified exponents X +are non-increasing in the window and in the copy cost, so X_deg(s) <= X(s, r/k, cst_quotient/v) for every s. + +This script checks, with exact ranks: +* the one degenerate paired cube (p = 3: 8 ports in F_2^6, 16 helper registers, six totals each of dimension 3; the + instance is data/toys/sol_p3_cone_R16.json): d 4, rho 2, copy cost 18 when read at 0, 16 for the best schedule of cuts + over all flags of rad, 12 for the quotient (= the pairing-rank bound); window 6 against 2 for the quotient, which has + only 2 distinct labels; +* the paired cubes p = 3..12 (ports chi_S: three index pairs, one of two bits each, form 9I - J on the bit side): only the + complex p = 3 is degenerate, the bit side never; +* E8 (120 root lines in doubled standard coordinates, nine F_2 label coordinates, e8units.standard_e8) decorated by one + isotropic direction e on the bit side (nine star totals) and by weight-2 isotropic directions on the complex side + (eight coordinate totals of 78 lines): the best schedule recovers exactly the quotient cost 72 at a larger window + (10 against 8; 11/13/15 against 9); a decoration aligned with two totals needs 73; +* 737 random instances (337 with extra-coordinate radicals, 400 with intrinsic radicals; Python's seeded Mersenne + Twister, seeds 1 and 7): no schedule beats the quotient; +* the certified exponents X (hi of skel.X at the best odd s in 3..13) of the quotient against the degenerate realisation, + with the references of the punctured units (bit 111/63, complex 112/64). +Run: python3 c4_quotient.py +""" +import itertools +import random +import time + +import numpy as np + +from report import check, finish, DATA, load_json +from e8units import (standard_e8, e8_labels, f2_rank, rank_q, row_basis_idx_q, gram_rank_q, dot2, span_basis, + rows_to_int) +from skel import X as skel_X +from fractions import Fraction as Fr + +# --------------------------------------------------------------------------------------------------------------- +# expected values (from the recorded runs) +EXPECTED = { + 'toy': dict(d=4, rho=2, r=2, n_totals=6, dim_f=3, cst0=18, cstE2=16, cstQ=12, window_deg=6, window_quot=2, + n_labels=2, rad=[0b110011, 0b001111, 0b111100]), + # paired cubes p = 3..12: number of ports and complex (d, rho); the bit side is nondegenerate + 'paired_v': {3: 8, 4: 32, 5: 80, 6: 160, 7: 280, 8: 448, 9: 672, 10: 960, 11: 1320, 12: 1760}, + # E8 rows: (d, rho, r, window of the degenerate realisation, cst0, cstE2, cstQ, cstC) + 'e8_bit_e': (9, 1, 8, 10, 81, 72, 72, 72), + 'e8_cx': {1: (10, 1, 9, 11, 80, 72, 72, 72), 2: (11, 2, 9, 13, 88, 72, 72, 72), 3: (12, 3, 9, 15, 96, 72, 72, 72)}, + 'e8_cx_aligned': (11, 2, 9, 13, 86, 73, 72, 72), + # random instances: 'tests', no violation of cst_E2' >= cst_quotient, and how often a cut schedule beats reading at 0 + # ('gain') / equals the quotient ('equal'). The recorded run (gain 257 of 337, gain 376 and equal 374 of 400) + # minimised only over the flags built from subsets of the radical basis ('coordinate flags'); the optimum over ALL + # flags of the radical (this script's best_e2) is slightly better: gain_all 275, 379, equal_all 391. + 'random_extra': dict(tests=337, violations=0, gain=257, gain_all=275), # extra-coordinate radicals + 'random_intrinsic': dict(tests=400, violations=0, gain=376, equal=374, gain_all=379, equal_all=391), + # certified X (hi, best s over 3,5,...,13) printed to 5 significant digits; None = deficit D <= 0 at every s + 'X': {'toy_deg': None, 'toy_quot': None, + 'bit_quot': '5.1598e-03', 'bit_e_deg': '4.0462e-03', + 'cx_quot': '4.5356e-03', 'cx_rho1': '3.6522e-03', 'cx_rho2': '3.0570e-03', 'cx_rho3': '2.6286e-03', + 'bit_punct': '6.0005e-03', 'cx_punct': '5.1863e-03', + 'bit_star_floor': '8.0093e-03', 'cx_star_floor': '7.0728e-03'}, +} + + +# --------------------------------------------------------------------------------------------------------------- +# F_2 helpers on integer bit vectors +def f2_rad(basis): + """basis (echelon, decreasing) of W cap W^perp for W = span(basis): solve A c = 0 with A_ij = b_i . b_j.""" + n = len(basis) + A = [[dot2(basis[i], basis[j]) for j in range(n)] for i in range(n)] + rows = [sum(A[i][j] << j for j in range(n)) for i in range(n)] + red = [] + for r in rows: + for (p, rr) in red: + if r >> p & 1: + r ^= rr + if r: + p = r.bit_length() - 1 + red = [(pp, (rr ^ r) if rr >> p & 1 else rr) for pp, rr in red] + red.append((p, r)) + pivs = {p for p, _ in red} + ker = [] + for fj in [j for j in range(n) if j not in pivs]: + c = 1 << fj + for p, rr in red: + if rr & c: + c |= 1 << p + x = 0 + for i in range(n): + if c >> i & 1: + x ^= basis[i] + assert all(dot2(x, b) == 0 for b in basis) + ker.append(x) + return span_basis(ker) + + +def f2_dim(vecs): + return len(span_basis(vecs)) + + +def in_span(x, basis): + for b in basis: + if x ^ b < x: + x ^= b + return x == 0 + + +def quot_dim(f, rad): + """dim pi(f) = dim (f + rad) - dim rad.""" + return f2_dim(list(f) + list(rad)) - len(rad) + + +def subspaces_of(rad, all_subspaces): + """subspaces of rad as echelon-basis tuples. all_subspaces=False: only spans of subsets of the given basis (the + coordinate subspaces); True: every subspace (spans of subsets of the nonzero vectors, deduplicated).""" + rho = len(rad) + pool = [x for x in _span_elems(rad) if x] if all_subspaces else list(rad) + subs = {} + for k in range(rho + 1): + for comb in itertools.combinations(pool, k): + sp = tuple(sorted(span_basis(list(comb)), reverse=True)) + subs[sp] = len(sp) + return list(subs) + + +def best_e2(fs, rad, all_subspaces=True): + """minimum over complete flags 0 < g_1 < ... < g_rho = rad of sum_j min_{g in flag} d(f_j, g), exactly.""" + rho = len(rad) + subs = subspaces_of(rad, all_subspaces) + + def dcut(f, g): + cap = f2_dim([x for x in _span_elems(g) if in_span(x, f)]) + return len(f) + len(g) - 2 * cap + + def inside(a, b): + return f2_dim(list(a) + list(b)) == len(b) + cache = {} + + def cost(fi, g): + key = (fi, g) + if key not in cache: + cache[key] = dcut(fs[fi], g) + return cache[key] + best = None + + def flags(cur, chain): + nonlocal best + if len(cur) == rho: + c = sum(min(cost(i, g) for g in chain) for i in range(len(fs))) + best = c if best is None else min(best, c) + return + for g in subs: + if len(g) == len(cur) + 1 and inside(cur, g): + flags(g, chain + [g]) + flags((), [()]) + return best + + +def _span_elems(g): + """all vectors of the span of the basis g.""" + out = [0] + for b in g: + out += [x ^ b for x in out] + return out + + +# --------------------------------------------------------------------------------------------------------------- +def toy_p3(): + t0 = time.time() + c = load_json(DATA / 'toys' / 'sol_p3_cone_R16.json') + ports, fr = c['ports'], c['frames'] + tot = [fr[f] for _, _, f in c['ret']] # the six total frames f_j (bit vectors in F_2^6) + W = span_basis(ports) + d = len(W) + rad = f2_rad(W) + rho = len(rad) + Ex = EXPECTED['toy'] + cst0 = sum(len(span_basis(f)) for f in tot) + cstE2 = best_e2([tuple(span_basis(f)) for f in tot], rad) + cstE2_coord = best_e2([tuple(span_basis(f)) for f in tot], rad, all_subspaces=False) + cstQ = sum(quot_dim(span_basis(f), rad) for f in tot) + # pairing rank between the span of a total's source labels and the readers' span (all ports) + cstC = sum(f2_rank(np.array([[dot2(a, b) for b in ports] for a in span_basis(f)])) for f in tot) + rad_vecs = sorted(x for x in _span_elems(rad) if x) + check('C4.toy_p3.degeneracy', (d, rho, d - rho) == (Ex['d'], Ex['rho'], Ex['r']) and rad_vecs == sorted(Ex['rad']), + 'port span d = %d, radical rho = %d (nonzero radical vectors %s), Gram rank %d' + % (d, rho, [format(x, '06b') for x in rad_vecs], d - rho)) + check('C4.toy_p3.costs', (len(tot), cst0, cstE2, cstE2_coord, cstQ, cstC) == (Ex['n_totals'], Ex['cst0'], Ex['cstE2'], Ex['cstE2'], + Ex['cstQ'], Ex['cstQ']) + and all(len(span_basis(f)) == Ex['dim_f'] for f in tot), + '%d totals of dim %d: copy cost at 0 = %d, best cut schedule over all flags of rad = %d (coordinate flags: %d), ' + 'quotient = %d = pairing-rank bound %d' % (len(tot), Ex['dim_f'], cst0, cstE2, cstE2_coord, cstQ, cstC)) + labels = {reduce_mod(p, rad) for p in ports} + check('C4.toy_p3.window_labels', (c['h'], d - rho, len(labels)) == (Ex['window_deg'], Ex['window_quot'], Ex['n_labels']), + 'window of the degenerate realisation %d, of the quotient %d; quotient has %d distinct labels (ports mod rad)' + % (c['h'], d - rho, len(labels))) + print(' (toy_p3: %.1f s)' % (time.time() - t0), flush=True) + return dict(d=d, rho=rho, cstE2=cstE2, cstQ=cstQ, window=c['h'], v=c['v']) + + +def reduce_mod(x, rad): + for b in rad: + if x ^ b < x: + x ^= b + return x + + +# --------------------------------------------------------------------------------------------------------------- +def paired_cube(p, tau=3): + ports = [] + for I in itertools.combinations(range(p), tau): + for bb in itertools.product((0, 1), repeat=tau): + x = [0] * (2 * p) + for i, b in zip(I, bb): + x[2 * i + b] = 1 + ports.append(x) + return ports + + +def paired_cubes(): + t0 = time.time() + degenerate_cx, degenerate_bit, vs, bit_dims = [], [], {}, {} + for p in range(3, 13): + P = paired_cube(p) + vs[p] = len(P) + ints = [rows_to_int(r) for r in P] + B = span_basis(ints) + rho = len(f2_rad(B)) + # bit side: Q labels with form 9 I - J; window = rank_Q of the Gram L Q L^T/9, d = rank_Q L + h = 2 * p + Q = [[(9 if i == j else 0) - 1 for j in range(h)] for i in range(h)] + dq = len(row_basis_idx_q(P)) + rq = gram_rank_q(P, Q, range(len(P))) + if rho: + degenerate_cx.append((p, len(B), rho)) + if dq != rq: + degenerate_bit.append((p, dq, rq)) + bit_dims[p] = (dq, rq) + check('C4.paired.complex', vs == EXPECTED['paired_v'] and degenerate_cx == [(3, 4, 2)], + 'ports %s; complex labels degenerate only for (p, d, rho) = %s' % (list(vs.values()), degenerate_cx)) + check('C4.paired.bit', not degenerate_bit and all(bit_dims[p] == ((4, 4) if p == 3 else (2 * p, 2 * p)) for p in bit_dims), + 'bit labels (form 9I - J): rank of the labels = rank of the Gram for every p = 3..12 (p: rank) %s; degenerate: %s' + % ({p: bit_dims[p][0] for p in bit_dims}, degenerate_bit)) + print(' (paired cubes: %.1f s)' % (time.time() - t0), flush=True) + + +# --------------------------------------------------------------------------------------------------------------- +def e8_rows(rng): + """the E8 decorations. The random draws are made in a fixed order (bit decorations, then complex ones) so that the + generator state after this function is deterministic; it is passed on to the random tests.""" + L, G = standard_e8() + v = 120 + U9 = e8_labels(G) + bit_tots = [np.nonzero(U9[:, j])[0] for j in range(9)] # 9 totals S_j = ports with label bit j set + cx_tots = [(np.nonzero(L[:, a])[0], np.nonzero(L[:, a])[0]) for a in range(8)] # coordinate a: readers = sources + rows = {} + + def bit_decorated(cvec, key): + P = np.hstack([L, np.array(cvec, dtype=np.int64).reshape(-1, 1), np.zeros((v, 1), dtype=np.int64)]) + e = np.zeros(10, dtype=np.int64) + e[8] = 1 + d, r = rank_q(P.tolist()), 8 + rho = d - r + c0 = cE2 = cQ = cC = 0 + for Sj in bit_tots: + F = P[Sj] + df = rank_q(F.tolist()) + e_in = rank_q(np.vstack([F, e]).tolist()) == df + c0 += df + cE2 += min(df, df + 1 - 2 * int(e_in)) if rho == 1 else df # cuts: 0 or the line + cQ += rank_q(L[Sj].tolist()) + cC += rank_q(G[np.ix_(Sj, Sj)].tolist()) + rows[key] = (d, rho, r, d + rho, c0, cE2, cQ, cC) + + bit_decorated([0] * v, 'bit_control') + bit_decorated([1] * v, 'bit_e_all') + bit_decorated([rng.randint(0, 1) for _ in range(v)], 'bit_e_random') + bit_decorated([rng.randint(-3, 3) for _ in range(v)], 'bit_e_lifts') + + u = [rows_to_int(U9[t]) for t in range(v)] + assert all(dot2(u[s], u[t]) == ((1 + G[s, t]) % 2) for s in range(v) for t in range(v)) + + def cx_decorated(cmat, rho_max, key): + E = [(1 << (9 + 2 * i)) | (1 << (10 + 2 * i)) for i in range(rho_max)] # weight-2 isotropic directions + q = [] + for t in range(v): + x = u[t] + for i in range(rho_max): + if cmat[t] >> i & 1: + x ^= E[i] + q.append(x) + assert all(dot2(q[s], q[t]) == dot2(u[s], u[t]) for s in range(v) for t in range(v)) # Gram unchanged + B = span_basis(q) + rad = f2_rad(B) + d, rho = len(B), len(rad) + r = d - rho + c0 = cQ = cC = 0 + fs = [] + for R, A in cx_tots: + f = span_basis([q[t] for t in A]) + fs.append(tuple(f)) + c0 += len(f) + cQ += quot_dim(f, rad) + cC += f2_rank(np.array([[dot2(q[a], q[b]) for b in R] for a in A])) + cE2 = best_e2(fs, rad) if rho >= 1 else c0 + cE2c = best_e2(fs, rad, all_subspaces=False) if rho >= 1 else c0 + rows[key] = (d, rho, r, d + rho, c0, cE2, cQ, cC) + rows[key + '_coordflags'] = cE2c + + cx_decorated([0] * v, 0, 'cx_control') + cx_decorated([1] * v, 1, 'cx_e_all') + for rho_max in (1, 2, 3): + cx_decorated([rng.randrange(1 << rho_max) for _ in range(v)], rho_max, 'cx_rho%d' % rho_max) + cx_decorated([(1 if t in set(cx_tots[0][1].tolist()) else 0) | (2 if t in set(cx_tots[1][1].tolist()) else 0) for t in range(v)], + 2, 'cx_aligned') + return rows + + +def check_e8(rows): + Ex = EXPECTED + names = ('d', 'rho', 'r', 'window', 'cst0', 'cstE2', 'cstQ', 'cstPair') + + def fmt(t): + return ', '.join('%s %d' % (n, x) for n, x in zip(names, t)) + check('C4.e8_bit_e.controls', rows['bit_control'] == (8, 0, 8, 8, 72, 72, 72, 72), + 'undecorated bit E8: ' + fmt(rows['bit_control'])) + ok = all(rows[k] == Ex['e8_bit_e'] for k in ('bit_e_all', 'bit_e_random', 'bit_e_lifts')) + check('C4.e8_bit_e', ok, 'E8 bit + isotropic e (every port / random ports / lifts n_T e): ' + fmt(rows['bit_e_all']) + + '; window 10 vs quotient 8') + check('C4.e8_cx.controls', rows['cx_control'] == (9, 0, 9, 9, 72, 72, 72, 72) and rows['cx_e_all'][1] == 0, + 'undecorated complex E8: ' + fmt(rows['cx_control'])) + for k in (1, 2, 3): + t = rows['cx_rho%d' % k] + check('C4.e8_cx.rho%d' % k, t == Ex['e8_cx'][k], 'E8 complex + weight-2 isotropic decoration rho=%d: %s ' + '(coordinate-flag optimum %d)' % (k, fmt(t), rows['cx_rho%d_coordflags' % k])) + t = rows['cx_aligned'] + check('C4.e8_cx.aligned', t == Ex['e8_cx_aligned'], + 'aligned decoration (e1 on total-0 sources, e2 on total-1 sources): %s (coordinate-flag optimum %d)' + % (fmt(t), rows['cx_aligned_coordflags'])) + return rows + + +# --------------------------------------------------------------------------------------------------------------- +def random_extra(rng): + """random instances with extra-coordinate radicals: odd-weight vectors in F_2^n decorated by weight-2 isotropic + directions in extra coordinates.""" + tests = viol = viol_c = gain = gain_c = 0 + for trial in range(400): + n = rng.randint(6, 9) + rho_t = rng.randint(1, 3) + E = [(1 << (n + 2 * i)) | (1 << (n + 1 + 2 * i)) for i in range(rho_t)] + nports = rng.randint(6, 14) + ports = [] + while len(ports) < nports: + x = rng.randrange(1, 1 << n) + if bin(x).count('1') % 2 == 1: + for i in range(rho_t): + if rng.random() < 0.5: + x ^= E[i] + ports.append(x) + B = span_basis(ports) + rad = f2_rad(B) + if not rad: + continue + ntot = rng.randint(2, 6) + fs = [tuple(span_basis(rng.sample(ports, rng.randint(2, len(ports))))) for _ in range(ntot)] + cE2 = best_e2(fs, rad) + cE2c = best_e2(fs, rad, all_subspaces=False) + cQ = sum(quot_dim(f, rad) for f in fs) + c0 = sum(len(f) for f in fs) + tests += 1 + viol += cE2 < cQ + viol_c += cE2c < cQ + gain += cE2 < c0 + gain_c += cE2c < c0 + return dict(tests=tests, violations=viol, violations_coord=viol_c, gain=gain_c, gain_all=gain) + + +def random_intrinsic(): + """W a random subspace of F_2^n (n 6..10) with a nonzero radical; ports = odd-norm vectors of W.""" + rng = random.Random(7) + tests = viol = viol_c = gain = gain_c = eq = eq_c = 0 + while tests < 400: + n = rng.randint(6, 10) + k = rng.randint(3, n - 1) + gens = [rng.randrange(1, 1 << n) for _ in range(k)] + B = span_basis(gens) + rad = f2_rad(B) + if not rad or len(rad) > 3: + continue + span = [0] + for b in B: + span += [x ^ b for x in span] + odd = [x for x in span if dot2(x, x) == 1] + if len(odd) < 6: + continue + ports = rng.sample(odd, min(len(odd), rng.randint(6, 16))) + Bp = span_basis(ports) + radp = f2_rad(Bp) + if not radp or len(radp) > 3: + continue + fs = [tuple(span_basis(rng.sample(ports, rng.randint(2, len(ports))))) for _ in range(rng.randint(2, 6))] + cE2 = best_e2(fs, radp) + cE2c = best_e2(fs, radp, all_subspaces=False) + cQ = sum(quot_dim(f, radp) for f in fs) + c0 = sum(len(f) for f in fs) + tests += 1 + viol += cE2 < cQ + viol_c += cE2c < cQ + gain += cE2 < c0 + gain_c += cE2c < c0 + eq += cE2 == cQ + eq_c += cE2c == cQ + return dict(tests=tests, violations=viol, violations_coord=viol_c, gain=gain_c, gain_all=gain, equal=eq_c, equal_all=eq) + + +# --------------------------------------------------------------------------------------------------------------- +def certified_X(h, k, cst, v): + """(best hi over s = 3,5,..,13, s) of the certified skeleton exponent, or (None, None) if D <= 0 at every s.""" + x = Fr(cst, v) + best = (None, None) + for s in (3, 5, 7, 9, 11, 13): + r = skel_X(s, h, k, x) + if r is not None: + hi = float(r['hi']) + if best[0] is None or hi > best[0]: + best = (hi, s) + return best + + +def check_X(toy, rows): + t0 = time.time() + cases = { # key: (h, cst, v) + 'toy_deg': (toy['window'], toy['cstE2'], toy['v']), + 'toy_quot': (toy['d'] - toy['rho'], toy['cstQ'], toy['v']), + 'bit_quot': (rows['bit_e_all'][2], rows['bit_e_all'][6], 120), + 'bit_e_deg': (rows['bit_e_all'][3], rows['bit_e_all'][5], 120), + 'cx_quot': (rows['cx_rho1'][2], rows['cx_rho1'][6], 120), + 'cx_rho1': (rows['cx_rho1'][3], rows['cx_rho1'][5], 120), + 'cx_rho2': (rows['cx_rho2'][3], rows['cx_rho2'][5], 120), + 'cx_rho3': (rows['cx_rho3'][3], rows['cx_rho3'][5], 120), + 'bit_punct': (8, 63, 111), # punctured references: 111 ports, copy cost 63 + 'cx_punct': (9, 64, 112), # 112 ports, copy cost 64 (the binding reference) + 'bit_star_floor': (8, 64, 120), # 64/120: unattained star-scope bounds for repeated labels + 'cx_star_floor': (9, 64, 120), + } + got = {} + for key, (h, cst, v) in cases.items(): + hi, s = certified_X(h, 1, cst, v) + got[key] = None if hi is None else '%.4e' % hi + print(' %-15s h=%2d cst/v=%d/%d X* = %s (s=%s)' % (key, h, cst, v, got[key] or 'D<=0 at every s', s), flush=True) + Ex = EXPECTED['X'] + check('C4.X.toy', got['toy_deg'] == Ex['toy_deg'] and got['toy_quot'] == Ex['toy_quot'], + 'toy_p3: deficit D <= 0 at every s for the degenerate (h=6, 16/8) and the quotient (h=2, 12/8) unit') + check('C4.X.bit', (got['bit_quot'], got['bit_e_deg']) == (Ex['bit_quot'], Ex['bit_e_deg']), + 'bit E8: quotient (h=8, 72/120) X* = %s versus +e realisation (h=10, 72/120) X* = %s' % (got['bit_quot'], got['bit_e_deg'])) + check('C4.X.cx', tuple(got[k] for k in ('cx_quot', 'cx_rho1', 'cx_rho2', 'cx_rho3')) + == tuple(Ex[k] for k in ('cx_quot', 'cx_rho1', 'cx_rho2', 'cx_rho3')), + 'complex E8: quotient (h=9) X* = %s versus rho=1/2/3 realisations (h=11/13/15) %s / %s / %s' + % (got['cx_quot'], got['cx_rho1'], got['cx_rho2'], got['cx_rho3'])) + check('C4.X.references', (got['bit_punct'], got['cx_punct']) == (Ex['bit_punct'], Ex['cx_punct']), + 'punctured references: bit 111/63 (h=8) X* = %s, complex 112/64 (h=9) X* = %s (binding)' % (got['bit_punct'], got['cx_punct'])) + check('C4.X.star_floor', (got['bit_star_floor'], got['cx_star_floor']) == (Ex['bit_star_floor'], Ex['cx_star_floor']), + 'star-scope floor cst/v = 64/120 (unattained bound for repeated labels): bit %s, complex %s' + % (got['bit_star_floor'], got['cx_star_floor'])) + print(' (certified X: %.1f s)' % (time.time() - t0), flush=True) + + +def main(): + t_start = time.time() + toy = toy_p3() + paired_cubes() + t0 = time.time() + rng = random.Random(1) + rows = e8_rows(rng) + check_e8(rows) + print(' (E8 decorations: %.1f s)' % (time.time() - t0), flush=True) + t0 = time.time() + re = random_extra(rng) + Ex = EXPECTED['random_extra'] + check('C4.random_extra', (re['tests'], re['violations'], re['violations_coord'], re['gain'], re['gain_all']) + == (Ex['tests'], 0, 0, Ex['gain'], Ex['gain_all']), + '%d instances with extra-coordinate radicals: violations of cst_E2\' >= cst_quotient: %d (coordinate flags: %d); ' + 'a cut schedule beats reading at 0 in %d (coordinate flags: %d)' + % (re['tests'], re['violations'], re['violations_coord'], re['gain_all'], re['gain'])) + ri = random_intrinsic() + Ex = EXPECTED['random_intrinsic'] + check('C4.random_intrinsic', (ri['tests'], ri['violations'], ri['violations_coord'], ri['gain'], ri['gain_all'], ri['equal'], ri['equal_all']) + == (Ex['tests'], 0, 0, Ex['gain'], Ex['gain_all'], Ex['equal'], Ex['equal_all']), + '%d instances with intrinsic radicals: violations %d (coordinate flags: %d); schedule beats reading at 0 in %d ' + '(coordinate flags: %d), equals the quotient in %d (coordinate flags: %d)' + % (ri['tests'], ri['violations'], ri['violations_coord'], ri['gain_all'], ri['gain'], ri['equal_all'], ri['equal'])) + print(' (random tests: %.1f s)' % (time.time() - t0), flush=True) + check_X(toy, rows) + print('total %.1f s' % (time.time() - t_start)) + finish() + + +if __name__ == '__main__': + main() diff --git a/research/format-limits/scripts/c5_lambda.py b/research/format-limits/scripts/c5_lambda.py new file mode 100644 index 0000000000..4eb6c0bd57 --- /dev/null +++ b/research/format-limits/scripts/c5_lambda.py @@ -0,0 +1,291 @@ +"""Lemma lambda: a phase-frame butterfly needs sqrt(-1); a different payload ring cannot remove the butterfly layer. + +The complex interface (upstream/build/sections/03-motifs.tex:568-640, 05-layers.tex:1-100) is built from the +butterfly kernel C = aI + bX on one address bit (X the bit swap), H0 = D1 C D2 with D1, D2 diagonal, and the +phase frames q_U = wt(P_U x) mod 4. The checks below are exact (Fractions, integers mod M, sympy), no floats. + +T1 length-8 synthetic (Nussbaumer) transform over R = Q[y]/(y^4+1), root y of order 8 + (upstream/build/sections/06-transforms.tex, synthetic DIF): three levels 'H0 on one address bit, then a + twiddle y^(-k)'. The product of the six factors equals B_8 F_8^- exactly. The three twiddle factors are signed + permutations (monomial, bit-legal controlled shifts); the three H0 factors are not monomial (row support 2). +T2 Fermat specialisation y -> 2, R -> Z/17: the same program is the bit-reversed length-8 NTT with root 2. +T3 Fourier mass: monomials preserve row support and a 2-point butterfly level at most doubles it; the target has + row support 8 = 2^3 blocks, so at least 3 butterfly levels are needed, the same as the complex DFT_8. +T4 Kernel lemma over Z/M (M odd): D1 (aI+bX) D2 = u [[1,1],[1,-1]] with D1, D2 invertible diagonal, u a unit, + aI+bX invertible, is solvable iff -1 is a square mod M, and in every solution + lambda = (a-b)/(a+b) satisfies lambda^2 = -1 (an order-4 Walsh phase). Brute force over the listed M. +T5 Directional-kernel lemma: for a 2x2 matrix K, conjugation of K (x) I by the address map x1 += x2 fixes K (x) I + iff K commutes with X, i.e. K = aI + bX. +T6 The complex identities hold verbatim in Z/17 with i = 4: C^2 = X and H0 = b S C S, lambda = +-i. +T7 A REAL butterfly D1 H D2 never lies in span(I, X) (a complex d1 is needed with e1 = +-i e2). +T8 In the phase calculus a residual vector v of weight 2 mod 4 gives H^m diag(i^(wt(v) [v.x])) H^m / 2^m a + permutation (a translation, rank 0); weights 3 and 1 do not. +real T7 and T8 together: real kernels have no subspace-labelled directional version, and only weight-2 mod 4 + directions are free translations. +Run: python3 c5_lambda.py +""" +import itertools +import time +from fractions import Fraction as Fr +from math import gcd + +import numpy as np +import sympy as sp + +from report import check, finish + +# --------------------------------------------------------------------------------------------------------------- +# expected values (from the recorded runs) +EXPECTED = { + 'T1_factors': (3, 3), # (monomial twiddle factors, non-monomial H0 factors) + 'T3_support': 8, # row support (ring blocks) of the length-8 transform; levels = 3 + 'T3_levels': 3, + # M -> number of solutions (a, b, e1, e2) of the kernel equation; nonzero iff -1 is a square mod M + 'T4': {3: 0, 5: 32, 7: 0, 9: 0, 11: 0, 13: 288, 15: 0, 17: 512, 21: 0, 25: 800, 29: 1568, 33: 0, 37: 2592, + 41: 3200, 45: 0, 65: 9216}, + 'T6_lambda': 4, # (a-b)/(a+b) in Z/17 with i = 4 + 'T8': {(1, 1, 0): True, (1, 1, 1): False, (1, 0, 0): False}, +} + +R = 4 # y^R = -1 : the ring Q[y]/(y^4+1) +T = 8 # transform length, root w = y of order 2R = 8 + + +# --------------------------------------------------------------------------------------------------------------- +def ymul_matrix(a): + """R x R matrix of multiplication by y^a on coefficient vectors (a signed permutation).""" + M = [[0] * R for _ in range(R)] + for k in range(R): + e = (k + a) % (2 * R) + sign = 1 + if e >= R: + e -= R + sign = -1 + M[e][k] = sign + return M + + +def zeros(n): + return [[Fr(0)] * n for _ in range(n)] + + +def matmul(A, B): + n, m, p = len(A), len(B), len(B[0]) + C = [[Fr(0)] * p for _ in range(n)] + for i in range(n): + Ai = A[i] + for k in range(m): + if Ai[k] != 0: + Bk, a = B[k], Ai[k] + for j in range(p): + if Bk[j] != 0: + C[i][j] += a * Bk[j] + return C + + +def put_block(i, j, M, big): + for r in range(R): + for c in range(R): + big[i * R + r][j * R + c] = Fr(M[r][c]) + + +def is_monomial(A): + return all(sum(1 for x in row if x != 0) == 1 for row in A) and all(sum(1 for x in col if x != 0) == 1 for col in zip(*A)) + + +def max_row_support_blocks(A): + """row support counted in ring blocks (an R x R block is nonzero iff some entry is).""" + n = len(A) // R + return max(sum(1 for j in range(n) if any(A[i * R + r][j * R + c] != 0 for r in range(R) for c in range(R))) + for i in range(n)) + + +def rev3(j): + return int(format(j, '03b')[::-1], 2) + + +def build_T1(): + target = zeros(T * R) # bit-reversed length-8 transform: (F^- z)_j = (1/8) sum_k w^(-jk) z_k at slot rev3(j) + for j in range(T): + for k in range(T): + put_block(rev3(j), k, [[Fr(x, T) for x in row] for row in ymul_matrix(-(j * k) % (2 * R))], target) + + def butterfly_level(bit): + A = zeros(T * R) + h = Fr(1, 2) + for s in range(T): + if (s >> bit) & 1: + continue + p = s | (1 << bit) + for (o, i, sg) in ((s, s, 1), (s, p, 1), (p, s, 1), (p, p, -1)): + put_block(o, i, [[h * sg * (1 if r == c else 0) for c in range(R)] for r in range(R)], A) + return A + + def twiddle_level(bit, t): + A = zeros(T * R) + wt_exp = (2 * R) // t # w_t = y^(2R/t) + for s in range(T): + b = (s >> bit) & 1 + k = s & ((1 << bit) - 1) + put_block(s, s, ymul_matrix((-(wt_exp * k * b)) % (2 * R)), A) + return A + + factors = [] # first element is applied first + for lev, bit in enumerate((2, 1, 0)): + t = T >> lev + factors.append(('H0 on bit %d' % bit, butterfly_level(bit))) + factors.append(('twiddle w_%d^-k on bit %d' % (t, bit), twiddle_level(bit, t))) + prog = None + for name, Fm in factors: + prog = Fm if prog is None else matmul(Fm, prog) + return target, factors, prog, rev3 + + +def reduce_mod_17(M): + """evaluate an R-block matrix at y = 2 (ring Q[y]/(y^4+1) -> Z/17).""" + P = 17 + n = len(M) // R + out = [[0] * n for _ in range(n)] + for i in range(n): + for j in range(n): + val = sum(M[i * R + r][j * R + 0] * (2 ** r) for r in range(R)) + out[i][j] = (val.numerator * pow(val.denominator, -1, P)) % P + return out + + +def kernel_lemma(M): + """number of solutions (a, b, e1, e2) of D1 (aI+bX) D2 = u H over Z/M and whether lambda^2 = -1 in all.""" + U = np.array([x for x in range(M) if gcd(x, M) == 1]) + isunit = np.zeros(M, dtype=bool) + isunit[U] = True + sols = 0 + lam_ok = True + A = np.arange(M) + for a in range(M): + for b in range(M): + if not isunit[(a * a - b * b) % M]: + continue + # row 1: (a e1, b e2) equal and a unit; row 2: (b e1, a e2) sums to 0 and first entry a unit + r10 = (a * U) % M + r11 = (b * U) % M + r20 = (b * U) % M + r21 = (a * U) % M + ok = (r10[:, None] == r11[None, :]) & isunit[r10][:, None] \ + & (((r20[:, None] + r21[None, :]) % M) == 0) & isunit[r20][:, None] + cnt = int(ok.sum()) + if cnt: + sols += cnt + s = (a + b) % M + if isunit[s]: + lam = ((a - b) * pow(s, -1, M)) % M + if (lam * lam) % M != M - 1: + lam_ok = False + else: + lam_ok = False + return sols, lam_ok + + +def main(): + t_all = time.time() + # ---- T1, T2, T3 + t0 = time.time() + target, factors, prog, _ = build_T1() + n_mono = sum(1 for _, F in factors if is_monomial(F)) + n_non = len(factors) - n_mono + pattern = [(name, is_monomial(F), max_row_support_blocks(F)) for name, F in factors] + ok_pat = all(m == name.startswith('twiddle') and s == (1 if m else 2) for name, m, s in pattern) + check('C5.T1', prog == target and (n_mono, n_non) == EXPECTED['T1_factors'] and ok_pat, + 'product of 6 factors == B_8 F_8^- exactly; monomial twiddles %d, non-monomial H0 factors %d (row supports %s)' + % (n_mono, n_non, [s for _, _, s in pattern])) + P = 17 + ntt = [[0] * T for _ in range(T)] + inv8 = pow(8, -1, P) + for j in range(T): + for k in range(T): + ntt[rev3(j)][k] = (pow(2, (-(j * k)) % 16, P) * inv8) % P + check('C5.T2', reduce_mod_17(prog) == ntt, 'program at y = 2 == bit-reversed length-8 NTT over Z/17 with root 2') + sup_target = max_row_support_blocks(target) + sup_ntt = max(sum(1 for x in row if x) for row in ntt) + dft = np.exp(-2j * np.pi * np.outer(range(T), range(T)) / T) / T # complex DFT_8: every entry has modulus 1/8 + sup_dft = int(max((np.abs(row) > 1e-12).sum() for row in dft)) + levels = (sup_target - 1).bit_length() + check('C5.T3', sup_target == sup_ntt == sup_dft == EXPECTED['T3_support'] and levels == EXPECTED['T3_levels'], + 'row support of F_8 over R: %d blocks, of the Z/17 NTT: %d, of the complex DFT_8: %d -> >= %d butterfly levels' + % (sup_target, sup_ntt, sup_dft, levels)) + print(' (T1-T3: %.1f s)' % (time.time() - t0), flush=True) + + # ---- T4 + t0 = time.time() + got, allok = {}, True + for M in EXPECTED['T4']: + sols, lam_ok = kernel_lemma(M) + sq = any((x * x) % M == M - 1 for x in range(M)) + got[M] = sols + good = ((sols > 0) == sq) and (sols == 0 or lam_ok) + allok &= good + print(' M=%3d: -1 square=%-5s solutions=%6d lambda^2=-1 in all=%s' % (M, sq, sols, lam_ok if sols else '-'), flush=True) + check('C5.T4', got == EXPECTED['T4'] and allok, + 'solutions exist iff -1 is a square mod M and lambda^2 = -1 in every solution, M in %s' % sorted(got)) + print(' (T4: %.1f s)' % (time.time() - t0), flush=True) + + # ---- T5 + a_, b_, c_, d_ = sp.symbols('a b c d') + K = sp.Matrix([[a_, b_], [c_, d_]]) + KI = sp.kronecker_product(K, sp.eye(2)) + Pn = sp.zeros(4, 4) + for x1 in (0, 1): + for x2 in (0, 1): + Pn[2 * (x1 ^ x2) + x2, 2 * x1 + x2] = 1 + cond = sp.simplify(Pn * KI * Pn.inv() - KI) + sol = sp.solve(list(cond), [c_, d_], dict=True) + check('C5.T5', sol == [{c_: b_, d_: a_}], 'conjugation by x1 += x2 fixes K (x) I <=> %s, i.e. K = aI + bX' % sol) + + # ---- T6: Z/17 with i = 4 + M, i = 17, 4 + assert (i * i) % M == M - 1 + h = pow(2, -1, M) + a = ((1 + i) * h) % M + b = ((1 - i) * h) % M + + def mm(A, B): + return [[sum(A[r][k] * B[k][c] for k in range(2)) % M for c in range(2)] for r in range(2)] + C = [[a, b], [b, a]] + X = [[0, 1], [1, 0]] + S = [[1, 0], [0, i]] + H0 = [[h, h], [h, (-h) % M]] + bSCS = [[(b * x) % M for x in row] for row in mm(mm(S, C), S)] + lam = ((a - b) * pow((a + b) % M, -1, M)) % M + check('C5.T6', mm(C, C) == X and bSCS == H0 and lam == EXPECTED['T6_lambda'] and lam in {i, M - i}, + 'Z/17, i=4: C^2 == X, H0 == b S C S, lambda = (a-b)/(a+b) = %d (i or -i: %s)' % (lam, sorted({i, M - i}))) + + # ---- T7, T8 (real kernels; weight of the residual vector) + d1, d2, e1, e2 = sp.symbols('d1 d2 e1 e2', real=True, nonzero=True) + H = sp.Matrix([[1, 1], [1, -1]]) + Kr = sp.diag(d1, d2) * H * sp.diag(e1, e2) + real_sol = sp.solve([sp.Eq(Kr[0, 0], Kr[1, 1]), sp.Eq(Kr[0, 1], Kr[1, 0])], [d1, e1], dict=True) + z1 = sp.symbols('z1') + Kc = sp.diag(z1, 1) * H * sp.diag(e1, e2) + cplx_sol = sp.solve([Kc[0, 0] - Kc[1, 1], Kc[0, 1] - Kc[1, 0]], [z1, e1], dict=True) + ok7 = (real_sol == [] and cplx_sol == []) + check('C5.T7', ok7, 'real solutions of D1 H D2 in span(I,X): %s; with complex d1 (needs e1 = +-i e2): %s' % (real_sol, cplx_sol)) + m = 3 + Hm = sp.Matrix([[1]]) + for _ in range(m): + Hm = sp.kronecker_product(Hm, H) + xs = list(itertools.product((0, 1), repeat=m)) + t8 = {} + for v in EXPECTED['T8']: + w = sum(v) + ph = sp.diag(*[sp.I ** ((w % 4) * (sum(p * q for p, q in zip(v, x)) % 2)) for x in xs]) + Op = (Hm * ph * Hm) / 2 ** m + t8[v] = all(sum(1 for t in Op.row(r) if t != 0) == 1 for r in range(2 ** m)) + check('C5.T8', t8 == EXPECTED['T8'], + 'H^m diag(i^(wt*[v.x])) H^m / 2^m is a permutation: ' + ', '.join('v=%s wt=%d: %s' % (v, sum(v), t8[v]) for v in t8)) + check('C5.real', ok7 and t8 == EXPECTED['T8'], + 'no real subspace-labelled butterfly (T7); only weight 2 mod 4 residuals are free translations (T8)') + print('total %.1f s' % (time.time() - t_all)) + finish() + + +if __name__ == '__main__': + main() diff --git a/research/format-limits/scripts/c6_asym.py b/research/format-limits/scripts/c6_asym.py new file mode 100644 index 0000000000..cf0dd58777 --- /dev/null +++ b/research/format-limits/scripts/c6_asym.py @@ -0,0 +1,311 @@ +"""Asymmetric pairing uniqueness: at the E8-linear decoder pattern the F_2 pairing is unique up to gauge at window <= 9. + +Setting. A cross-field unit on v ports has a decoder N over Q and an F_2 pairing B = W U^T, B_TS = , between +sink words w_T and source labels u_S in F_2^c. Lossless correction of the garbage N_TS x_S needs B_TS = 0, exactness +needs N o B = diag (N_TT != 0, B_TT = 1). The window is the F_2 rank of B and the copy cost of a total j is +rank B[readers_j, sources_j]. The pair (U, W) -> (U P, W P^-T) with P in GL(c, F_2) leaves B fixed, so 'W != U' is +meaningful only through B != B^T or B != B0 = (J + G) mod 2, the pairing of the symmetric unit. + +The E8-linear decoder pattern. Ports are root lines of E8 (e8units.hamming_e8: 8 coordinate lines and the 112 code +lines, G = L L^T / 2); the decoder is the sum of the 8 coordinate terms N = G/2, so N_TS != 0 exactly where +G_TS != 0. B must therefore vanish on every ordered pair with G_TS != 0 (T != S) and have unit diagonal. P120 is the +set of all 120 lines, P112 the 112 code lines. Gauge: K = a set of 8 pairwise non-orthogonal lines has B[K,K] = I +forced, which brings U_K = W_K to [I_8 | 0] (valid for every c >= 8). + +Checks (Cadical 1.5.3 through pysat; minrank_sat.py): +* c = 9 on P112: a non-symmetric B is UNSAT; B != B0 on some free pair is UNSAT; with U fixed to the E8 labels and W free, + a non-symmetric B is UNSAT; the unconstrained problem is SAT and every solution has B = B0 of rank 9. +* c = 9 on P120: non-symmetric B UNSAT; B != B0 UNSAT. +* c = 8 on the pattern of all 120 lines: UNSAT for symmetric and for non-symmetric words (and on P112). + c = 9 with non-symmetric words is SAT (calibration of the encoding). +* an exact asymmetric 2-target unit on P112: relaxing two targets (sinks w_t != u_t) with one shared extra total of 24 + sources and copy cost 1 gives B != B0 at copy cost 8*8 + 1 = 65 and window 9, N' o B = diag on all 112 x 112 entries + (rationals), the relaxed targets need 28 corrections instead of 52. +* an exact asymmetric toy: the directed 3-cycle with B non-symmetric of rank 2, decoded exactly on all 343 inputs. +Run: python3 c6_asym.py +""" +import itertools +import time +from fractions import Fraction as Fr + +import numpy as np + +from report import check, finish +from e8units import hamming_e8, e8_labels, f2_rank, first_clique, rank_q +from minrank_sat import solve_minrank, solve_asym + +# --------------------------------------------------------------------------------------------------------------- +# expected values (from the recorded runs) +EXPECTED = { + 'ncl': 8, # size of the gauge clique + 'sat': { # (set, c, mode) -> result + ('P112', 9, 'nonsym'): 'UNSAT', ('P112', 9, 'neB0'): 'UNSAT', ('P112', 9, 'fixU_nonsym'): 'UNSAT', + ('P120', 9, 'nonsym'): 'UNSAT', ('P120', 9, 'neB0'): 'UNSAT', + ('P112', 9, 'none'): 'SAT', ('P112', 9, 'fixU_none'): 'SAT', + }, + 'minrank': {('P120', 8, 'sym'): 'UNSAT', ('P120', 8, 'nonsym'): 'UNSAT', ('P120', 9, 'nonsym'): 'SAT'}, + 'rankB0': 9, + 'unit': { # d0 -> (relaxed lines, sinks, extra-total sources, corrections of a relaxed target) + 75: ([75, 76], [329, 438]), 76: ([75, 76], [329, 438]), 77: ([74, 77], [378, 389])}, + 'unit_common': dict(Qsize=24, beta_alpha=1, beta_coord=[8] * 8, rankB=9, corr_relaxed=28, corr_E8=52, copy_cost=65), + 'toy3': dict(betas=[2, 2], cst=4, rankB=2, rankN=2, inputs=343, D=(-6, -12)), +} + + +# --------------------------------------------------------------------------------------------------------------- +class Geometry: + def __init__(self): + self.L, self.G = hamming_e8() + self.U9 = e8_labels(self.G) # 120 x 9, U9 U9^T = (J + G) mod 2 + self.M0 = ((1 + self.G) % 2).astype(np.uint8) + + def port_set(self, name): + return list(range(8, 120)) if name == 'P112' else list(range(120)) + + def pattern(self, P): + """ordered pairs (i, j), i != j, of positions in P with G != 0.""" + return {(i, j) for i in range(len(P)) for j in range(len(P)) if i != j and self.G[P[i], P[j]] != 0} + + def clique(self, P, size=8): + adj = [[i != j and self.G[P[i], P[j]] != 0 for j in range(len(P))] for i in range(len(P))] + return first_clique(adj, size), first_clique(adj, size + 1) + + +def sat_checks(geo): + t_all = time.time() + got = {} + for name in ('P112', 'P120'): + P = geo.port_set(name) + pat = geo.pattern(P) + K, K9 = geo.clique(P, EXPECTED['ncl']) + assert K is not None and K9 is None, 'the largest set of pairwise non-orthogonal lines has 8 elements' + B0 = geo.M0[np.ix_(P, P)] + U = geo.U9[P] + runs = [(9, 'nonsym', None), (9, 'neB0', None)] + if name == 'P112': + runs += [(9, 'none', None), (9, 'nonsym', U), (9, 'none', U)] + for c, mode, Ufix in runs: + r = solve_asym(len(P), pat, B0, c, mode, K, U_fixed=Ufix) + key = (name, c, ('fixU_' + mode) if Ufix is not None else mode) + got[key] = r + extra = '' + if r['result'] == 'SAT': + Bm = r['B'] + extra = '; B == B0: %s, rank_F2 B = %d' % (bool((Bm == B0).all()), f2_rank(Bm.astype(np.uint8))) + print(' %s c=%d %-12s %s (%.1f s, %d vars, %d clauses)%s' % (name, c, key[2], r['result'], r['sec'], r['vars'], + r['clauses'], extra), flush=True) + ok_all = True + for key, want in EXPECTED['sat'].items(): + ok_all &= got[key]['result'] == want + names = {('P112', 9, 'nonsym'): 'C6.P112.c9.nonsym', ('P112', 9, 'neB0'): 'C6.P112.c9.neB0', + ('P112', 9, 'fixU_nonsym'): 'C6.P112.c9.fixU_nonsym', ('P120', 9, 'nonsym'): 'C6.P120.c9.nonsym', + ('P120', 9, 'neB0'): 'C6.P120.c9.neB0'} + text = {'nonsym': 'no non-symmetric pairing (B_TS != B_ST on some free pair)', + 'neB0': 'no pairing with B != B0 = (J+G) mod 2 on a free pair', + 'fixU_nonsym': 'U fixed to the E8 labels, W free: no non-symmetric pairing'} + for key, cid in names.items(): + r = got[key] + check(cid, r['result'] == EXPECTED['sat'][key], + '%s at window 9 under the E8-linear pattern: %s -> %s (%.1f s)' % (key[0], text[key[2]], r['result'], r['sec'])) + # calibration: unconstrained is SAT and forces B = B0 + P = geo.port_set('P112') + B0 = geo.M0[np.ix_(P, P)] + cal = {} + for key in (('P112', 9, 'none'), ('P112', 9, 'fixU_none')): + r = got[key] + okB = r['result'] == 'SAT' and bool((r['B'] == B0).all()) and f2_rank(r['B'].astype(np.uint8)) == EXPECTED['rankB0'] + cal[key] = okB + check('C6.P112.c9.calibration', all(cal.values()) and all(got[k]['result'] == 'SAT' for k in cal), + 'unconstrained c=9 (and with U fixed) is SAT; the solution has B = B0 = (J+G) mod 2 of F_2 rank %d' % EXPECTED['rankB0']) + print(' (window-9 SAT runs: %.1f s)' % (time.time() - t_all), flush=True) + return got + + +def window8_checks(geo): + t0 = time.time() + P = geo.port_set('P120') + off = sorted((i, j) for (i, j) in geo.pattern(P) if i < j) + K, _ = geo.clique(P, 8) + res = {} + for c, sym in ((8, True), (8, False), (9, False)): + r = solve_minrank(len(P), off, c, sym, K) + res[(c, sym)] = r + print(' pattern of 120 lines, c=%d %s: %s (%.1f s, %d vars, %d clauses)' % (c, 'sym' if sym else 'nonsym', r[0], r[1], r[2], r[3]), + flush=True) + check('C6.P120.c8.sym', res[(8, True)][0] == EXPECTED['minrank'][('P120', 8, 'sym')], + '120-line pattern (%d off-cap pairs), symmetric words at window 8: %s' % (len(off), res[(8, True)][0])) + check('C6.P120.c8.nonsym', res[(8, False)][0] == EXPECTED['minrank'][('P120', 8, 'nonsym')], + '120-line pattern, non-symmetric words at window 8: %s' % res[(8, False)][0]) + check('C6.P120.c9.minrank_calibration', res[(9, False)][0] == EXPECTED['minrank'][('P120', 9, 'nonsym')], + '120-line pattern, non-symmetric words at window 9 (the encoding finds solutions): %s (%.1f s)' + % (res[(9, False)][0], res[(9, False)][1])) + P112 = geo.port_set('P112') + off112 = sorted((i, j) for (i, j) in geo.pattern(P112) if i < j) + K112, _ = geo.clique(P112, 8) + r112 = solve_minrank(len(P112), off112, 8, False, K112) + print(' pattern of the 112 code lines, c=8 nonsym: %s (%.1f s)' % (r112[0], r112[1]), flush=True) + check('C6.P112.c8.nonsym', r112[0] == 'UNSAT', '112-line pattern, non-symmetric words at window 8: %s' % r112[0]) + print(' (window-8 runs: %.1f s)' % (time.time() - t0), flush=True) + + +# --------------------------------------------------------------------------------------------------------------- +def bits9(w): + return [(w >> (8 - j)) & 1 for j in range(9)] + + +def int9(row): + return int(''.join(str(int(x)) for x in row), 2) + + +def asym_unit(geo): + """exact 2-target asymmetric P112 unit. Relax two targets: sink w_t != u_t, decoder row n_t = L d + mu L e + alpha + where d, e are root vectors and alpha is the extra total with sources Q (coefficients -).""" + t0 = time.time() + L, G, U9 = geo.L, geo.G, geo.U9 + P = geo.port_set('P112') + v = len(P) + Lp = L[P].astype(np.int64) + u = np.array([[int(x) for x in U9[p]] for p in P], dtype=np.int64) + Wall = np.array([bits9(w) for w in range(512)], dtype=np.int64) + Zall = (Wall @ u.T) % 2 == 1 # Zall[w, t]: = 1 + uint = [int9(U9[p]) for p in P] + + def nodes_of(e, d0): + pe, pd = Lp @ e, Lp @ d0 + eperp = pe == 0 + V2 = eperp & (pd == 0) + out = [] + for t in range(v): + for w in range(1, 512): + if not Zall[w, t] or w == uint[t]: + continue + Z = Zall[w].copy() + Z[t] = False + o = np.nonzero(Z & ~eperp)[0] + mus = {Fr(-int(pd[S]), int(pe[S])) for S in o} + if len(mus) > 1: + continue + if mus: + mu = mus.pop() + else: + mu = next(Fr(k) for k in range(1, 50) if pd[t] + k * pe[t] != 0) if (pd[t] or pe[t]) else None + if mu is None or pd[t] + mu * pe[t] == 0: + continue + E = Z & eperp & ~V2 + if E[t]: + continue + out.append((t, w, mu, E)) + return out + + res = {} + for (ei, di) in [(0, 75), (0, 76), (0, 77)]: + e, d0 = L[ei], L[di] + ns = nodes_of(e, d0) + pair = None + for a in range(len(ns)): + for b in range(a + 1, len(ns)): + ta, wa, ma, Ea = ns[a] + tb, wb, mb, Eb = ns[b] + if ta != tb and not (Ea & ~Zall[wb]).any() and not (Eb & ~Zall[wa]).any() and not Ea[tb] and not Eb[ta]: + pair = (ns[a], ns[b]) + break + if pair: + break + (ta, wa, ma, Ea), (tb, wb, mb, Eb) = pair + Q = Ea | Eb + sinks = {t: uint[t] for t in range(v)} + sinks[ta], sinks[tb] = wa, wb + Wm = np.array([bits9(sinks[t]) for t in range(v)], dtype=np.int64) + B = (Wm @ u.T) % 2 + alpha = {S: -Fr(int(Lp[S] @ d0)) for S in np.nonzero(Q)[0]} + N = [[Fr(int(G[P[t], P[S]]), 2) for S in range(v)] for t in range(v)] + for t, mu in ((ta, ma), (tb, mb)): + vec = [Fr(int(d0[i])) + mu * Fr(int(e[i])) for i in range(8)] + N[t] = [sum(Fr(int(Lp[S][i])) * vec[i] for i in range(8)) + alpha.get(S, 0) for S in range(v)] + exact = all((t == S) or N[t][S] == 0 or B[t, S] == 0 for t in range(v) for S in range(v)) and \ + all(B[t, t] == 1 and N[t][t] != 0 for t in range(v)) + Qi = list(np.nonzero(Q)[0]) + beta_alpha = f2_rank(B[np.ix_([ta, tb], Qi)].astype(np.uint8)) + beta_coord = [f2_rank(B[np.ix_([t for t in range(v) if Lp[t, a] != 0 or t in (ta, tb)], + [S for S in range(v) if Lp[S, a] != 0])].astype(np.uint8)) for a in range(8)] + corr = {int(t): sum(1 for S in range(v) if S != t and N[t][S] != 0) for t in (ta, tb)} + B0 = geo.M0[np.ix_(P, P)] + res[di] = dict(relaxed=[int(P[ta]), int(P[tb])], sinks=[int(wa), int(wb)], mu=[str(ma), str(mb)], exact=bool(exact), + Qsize=len(Qi), beta_alpha=int(beta_alpha), beta_coord=[int(b) for b in beta_coord], + rankB=int(f2_rank(B.astype(np.uint8))), corr=corr, differs_from_B0=int((B != B0).sum()), + asym_pairs=int((B != B.T).sum() // 2), nodes=len(ns)) + print(' pencil e=line %d, d0=line %d: %s' % (ei, di, res[di]), flush=True) + Ex, Exc = EXPECTED['unit'], EXPECTED['unit_common'] + ok = True + for di, r in res.items(): + ok &= r['relaxed'] == Ex[di][0] and r['sinks'] == Ex[di][1] and r['exact'] and r['Qsize'] == Exc['Qsize'] \ + and r['beta_alpha'] == Exc['beta_alpha'] and r['beta_coord'] == Exc['beta_coord'] and r['rankB'] == Exc['rankB'] \ + and set(r['corr'].values()) == {Exc['corr_relaxed']} and r['differs_from_B0'] > 0 + r = res[75] + cost = sum(r['beta_coord']) + r['beta_alpha'] + check('C6.unit.exact', ok and cost == Exc['copy_cost'], + '2-target asymmetric P112 unit (3 pencils): N\' o B = diag on 112 x 112 rational entries; relaxed lines %s with sinks %s ' + '(w != u); extra total: %d sources, copy cost %d; 8 coordinate totals at 8 each; copy cost %d total, window rank B = %d' + % (r['relaxed'], r['sinks'], r['Qsize'], r['beta_alpha'], cost, r['rankB'])) + check('C6.unit.corrections', all(set(r['corr'].values()) == {Exc['corr_relaxed']} for r in res.values()), + 'relaxed targets need %s corrections instead of %d' % (sorted({c for r in res.values() for c in r['corr'].values()}), + Exc['corr_E8'])) + check('C6.unit.not_gauge', all(r['differs_from_B0'] > 0 and r['asym_pairs'] > 0 for r in res.values()), + 'B differs from B0 in %s entries and is non-symmetric on %s pairs (not a gauge of the symmetric unit)' + % ([r['differs_from_B0'] for r in res.values()], [r['asym_pairs'] for r in res.values()])) + print(' (2-target unit: %.1f s)' % (time.time() - t0), flush=True) + + +# --------------------------------------------------------------------------------------------------------------- +def toy3(): + """directed 3-cycle: U = (10,01,11), W = (10,11,01) in F_2^2, B = W U^T non-symmetric; decoder N of rank 2.""" + U = [(1, 0), (0, 1), (1, 1)] + W = [(1, 0), (1, 1), (0, 1)] + B = [[sum(a * b for a, b in zip(W[t], U[s])) % 2 for s in range(3)] for t in range(3)] + N = [[Fr(1), Fr(1), Fr(0)], [Fr(0), Fr(1), Fr(1)], [Fr(-1), Fr(0), Fr(1)]] + ok_pattern = all((N[t][s] == 0) or (t == s) or B[t][s] == 0 for t in range(3) for s in range(3)) and \ + all(B[t][t] == 1 and N[t][t] != 0 for t in range(3)) + # totals tau1 = x1 + x2 (read by ports 1, 3), tau2 = x2 + x3 (read by ports 2, 3) + terms = [({0, 2}, {0, 1}, {0: Fr(1), 2: Fr(-1)}, {0: Fr(1), 1: Fr(1)}), + ({1, 2}, {1, 2}, {1: Fr(1), 2: Fr(1)}, {1: Fr(1), 2: Fr(1)})] # (readers, sources, reader coeffs, source coeffs) + Nr = [[sum(rd.get(t, 0) * ad.get(s, 0) for _, _, rd, ad in terms) for s in range(3)] for t in range(3)] + n_inputs = 0 + exact = Nr == N + for x in itertools.product(range(-3, 4), repeat=3): + x = [Fr(a) for a in x] + tau = [sum(ad[s] * x[s] for s in ad) for _, _, _, ad in terms] + for t in range(3): + y = sum(rd.get(t, 0) * tau[j] for j, (_, _, rd, _) in enumerate(terms)) + for s in range(3): + if s != t and N[t][s] != 0: + if B[t][s] != 0: + exact = False # a lossless correction is only legal on pairs with B_TS = 0 + y -= N[t][s] * x[s] + exact &= (y == N[t][t] * x[t]) + n_inputs += 1 + betas = [f2_rank(np.array([[B[t][s] for s in sorted(A)] for t in sorted(R)], dtype=np.uint8)) for R, A, _, _ in terms] + Ex = EXPECTED['toy3'] + sym = B == [list(r) for r in zip(*B)] + rankB = f2_rank(np.array(B, dtype=np.uint8)) + rankN = rank_q([[int(x) for x in row] for row in N]) + D = (2 * 3 - 3 * sum(betas), 8 * 3 - 9 * sum(betas)) + check('C6.toy3', ok_pattern and exact and not sym and n_inputs == Ex['inputs'] and betas == Ex['betas'] + and sum(betas) == Ex['cst'] and rankB == Ex['rankB'] and rankN == Ex['rankN'] and D == Ex['D'], + 'directed 3-cycle: B = %s non-symmetric, rank_F2 B = %d, rank_Q N = %d; decoded exactly on all %d integer inputs ' + '(corrections only where B_TS = 0); betas %s, cst %d, D at s=3,9: %s (<0: formalism toy)' + % (B, rankB, rankN, n_inputs, betas, sum(betas), D)) + + +def main(): + t_start = time.time() + geo = Geometry() + sat_checks(geo) + window8_checks(geo) + asym_unit(geo) + toy3() + print('total %.1f s' % (time.time() - t_start)) + finish() + + +if __name__ == '__main__': + main() diff --git a/research/format-limits/scripts/c7_descent_price.py b/research/format-limits/scripts/c7_descent_price.py new file mode 100644 index 0000000000..419479f9ef --- /dev/null +++ b/research/format-limits/scripts/c7_descent_price.py @@ -0,0 +1,28 @@ +"""C. Per-call pricing of a paid descent on the frontier pipelines. + +c7_descent_toy.py shows exactly that a rank-1 descent of a register followed by its re-climb costs two extra rank-1 +children (2 units of data mass) in the local word. The local word runs in every call (#555: s T = 288 stage-replicas; +#568: 72 calls), so by the deficit identity m W - mass = D each descent per local word lowers D by 2 per call and adds +two rank-1 children per call. This script prices that on the published global profiles with the certified pressure +root, hosting held fixed, for one and two rank-1 descents per local word. +""" +from fractions import Fraction as Fr + +from cert import root_bracket +from report import VENDOR, check, finish, load_json, near + +EXPECTED = {('#555', 1): -5.630, ('#555', 2): -11.257, ('#568', 1): -5.646, ('#568', 2): -11.290} +e555 = load_json(VENDOR / 'pr555' / 'EXPECTED.json') +p568 = load_json(VENDOR / 'pr568' / 'PLAN.json.gz') +WORDS = {'#555': ({int(k): z for k, z in e555['nine']['profile'].items()}, 64, 10272, 288), + '#568': ({int(k): z for k, z in p568['profile'].items()}, 73, 2560, 72)} +for name, (P, m, D, calls) in WORDS.items(): + _, h0 = root_bracket(P, m, D, hi=Fr(1, 100)) + for n in (1, 2): + Q = dict(P) + Q[1] = Q.get(1, 0) + 2 * n * calls + _, h = root_bracket(Q, m, D - 2 * n * calls, hi=Fr(1, 100)) + pct = 100 * float(h / h0 - 1) + check('C7.price.%s.%d' % (name, n), near(pct, EXPECTED[name, n], 3), + '%d rank-1 descent(s) per local word, paid in all %d calls: %+.3f%% of the supplier value' % (n, calls, pct)) +finish() diff --git a/research/format-limits/scripts/c7_descent_toy.py b/research/format-limits/scripts/c7_descent_toy.py new file mode 100644 index 0000000000..5831b87179 --- /dev/null +++ b/research/format-limits/scripts/c7_descent_toy.py @@ -0,0 +1,429 @@ +"""Exact toy: one reader reads three terms at their point stations, paying for descents between the reads (n = 5). + +Setting. Labels live in F2^5 with the standard dot product. Three term anchors u_a, u_b, u_c are odd and pairwise +orthogonal; a "double" reader t has an odd label u_t orthogonal to all three anchors and reads all three terms; for each +term j a "single" reader r_j reads only term j. Term j has a source x_j stored at a 2-dimensional nondegenerate frame +f_j = containing u_j. The natural readers of term j are all odd vectors orthogonal to u_j; they span u_j^perp, +so the only frame orthogonal to all of them is : the only common read frame of term j is the point station +(checked by enumeration). The operator (identical in all networks) is + y_r -> T_{u_r^perp} ( l_y + sum_j q_rj l_{x_j} ), x_j -> T_FULL l_{x_j}, +with fixed dyadic Gaussian coefficients q_rj, on the 7 logical registers (x_a, x_b, x_c, y_t, y_a, y_b, y_c). +T_F is the canonical Clifford frame operator of a subspace F, d(A, B) = dim A + dim B - 2 dim(A cap B) the frame +distance; the charged rank of a step A -> B is the Clifford rank of T_B T_A^{-1} (clifford_exact.lower_child). + +Networks (copy C_j of x_j is dropped f_j -> at cost 1 each, except in STD where noted): + STD : nesting kept, only term a is read at a point station; C_b, C_c drop to 0 (cost 2 each); t and the single + readers r_b, r_c read b, c at 0 first, then t climbs 0 -> (reads a) -> u_t^perp. + CLONE : nesting kept by clones: t climbs 0 -> [a] -> [b, read from a clone of C_b raised + -> ] -> [c, from a clone raised -> ] -> u_t^perp. + DESC : t reads at the point stations in turn: 0 -> [a] -> (one NON-comparable step, rank 2) [b] -> + (rank 2) [c] -> u_t^perp. + DESC0 : as DESC but each descent goes through 0: -> 0 -> -> 0 -> . +Checks: the whole linear map is exactly equal to the intended operator (exact Gaussian-dyadic arithmetic) on all +7 * 32 = 224 basis columns, in every network; every transition has Clifford child rank == d(A, B); the charged mass +by role is compared with the closed forms (+2 per descent between two lines, 2(k-1) for k point stations, STD vs DESC). +""" +import itertools +import random +import sys + +import numpy as np + +from report import check, finish, main_file + +sys.path.insert(0, str(main_file('research/direct-delivery504-global/vendor/clifford/clifford_exact.py').parent)) +import clifford_exact as ce # noqa: E402 (the repository's exact Clifford compiler, unmodified) + +# ---------------------------------------------------------------------------------------------------------------- +# Expected values (from the exact run of the toy; every one is re-derived below and compared). +EXPECTED = { + 'instance': dict(anchors=[0b00111, 0b01011, 0b01110], u_t=0b01101, singles=[0b01000, 0b00100, 0b00001], + frames=[[0b00111, 0b01000], [0b01011, 0b00100], [0b01110, 0b00001]]), + 'readers_per_term': 8, # odd vectors orthogonal to u_j + 'common_frames': [[0, 7], [0, 11], [0, 14]], # {z : z.r = 0 for all natural readers r} = {0, u_j} + 'columns': 224, # 7 registers x 32 basis vectors + # transitions (role, dim A, dim B, child rank, distance d), source-register steps omitted (3 steps 2 -> 5, rank 3) + 'transitions': { + 'STD': [('copy-drop', 2, 1, 1, 1), ('copy-drop', 2, 0, 2, 2), ('copy-drop', 2, 0, 2, 2), ('t', 0, 1, 1, 1), + ('t', 1, 4, 3, 3), ('single', 0, 4, 4, 4), ('single', 0, 4, 4, 4), ('single', 0, 1, 1, 1), + ('single', 1, 4, 3, 3)], + 'CLONE': [('copy-drop', 2, 1, 1, 1), ('copy-drop', 2, 1, 1, 1), ('copy-drop', 2, 1, 1, 1), ('clone', 1, 2, 1, 1), + ('clone', 1, 3, 2, 2), ('t', 0, 1, 1, 1), ('t', 1, 2, 1, 1), ('t', 2, 3, 1, 1), ('t', 3, 4, 1, 1), + ('single', 0, 1, 1, 1), ('single', 1, 4, 3, 3), ('single', 0, 1, 1, 1), ('single', 1, 4, 3, 3), + ('single', 0, 1, 1, 1), ('single', 1, 4, 3, 3)], + 'DESC': [('copy-drop', 2, 1, 1, 1), ('copy-drop', 2, 1, 1, 1), ('copy-drop', 2, 1, 1, 1), ('t', 0, 1, 1, 1), + ('t-DESCENT', 1, 1, 2, 2), ('t-DESCENT', 1, 1, 2, 2), ('t', 1, 4, 3, 3), ('single', 0, 1, 1, 1), + ('single', 1, 4, 3, 3), ('single', 0, 1, 1, 1), ('single', 1, 4, 3, 3), ('single', 0, 1, 1, 1), + ('single', 1, 4, 3, 3)], + 'DESC0': [('copy-drop', 2, 1, 1, 1), ('copy-drop', 2, 1, 1, 1), ('copy-drop', 2, 1, 1, 1), ('t', 0, 1, 1, 1), + ('t-DESCENT', 1, 0, 1, 1), ('t-reclimb', 0, 1, 1, 1), ('t-DESCENT', 1, 0, 1, 1), + ('t-reclimb', 0, 1, 1, 1), ('t', 1, 4, 3, 3), ('single', 0, 1, 1, 1), ('single', 1, 4, 3, 3), + ('single', 0, 1, 1, 1), ('single', 1, 4, 3, 3), ('single', 0, 1, 1, 1), ('single', 1, 4, 3, 3)], + }, + # charged mass by role, and total + 'mass': { + 'STD': ({'copy-drop': 5, 't': 4, 'single': 12, 'source': 9}, 30), + 'CLONE': ({'copy-drop': 3, 'clone': 3, 't': 4, 'single': 12, 'source': 9}, 31), + 'DESC': ({'copy-drop': 3, 't': 4, 't-DESCENT': 4, 'single': 12, 'source': 9}, 32), + 'DESC0': ({'copy-drop': 3, 't': 4, 't-DESCENT': 2, 't-reclimb': 2, 'single': 12, 'source': 9}, 32), + }, + 'reader_mass': {'STD': 4, 'CLONE': 4, 'DESC': 8, 'DESC0': 8}, # all roles starting with 't' + 'nested_minimum': 4, # dim u_t^perp = n - 1: path 0 -> -> u_t^perp + 'descent_mass_each': 2, # one descent between two lines: direct or via 0 + 'k_stations_extra': {1: 0, 2: 2, 3: 4}, # reader at k point stations pays 2(k-1) + 'desc_vs_std': dict(cst=-2, reader=4, net=2), +} + +n = 5 +N = 1 << n + + +def par(x): + return bin(x).count('1') & 1 + + +def span(vs): + out = {0} + for v in vs: + out |= {o ^ v for o in out} + return out + + +def perp(vs): + return [z for z in range(N) if all(par(z & v) == 0 for v in vs)] + + +def basis(vs): + b = [] + for v in vs: + if ce.rank(b + [v]) > len(b): + b.append(v) + return b + + +def dim(A): + return len(basis(A)) + + +def meet(A, B): + sA = span(basis(A)) + return [z for z in span(basis(B)) if z in sA] + + +def dist(A, B): + """frame distance d(A,B) = dim A + dim B - 2 dim(A cap B).""" + return dim(A) + dim(B) - 2 * dim(meet(A, B)) + + +def nondeg(F): + b = basis(F) + G = [[par(x & y) for y in b] for x in b] + rows = [sum(G[i][j] << j for j in range(len(b))) for i in range(len(b))] + return ce.rank(rows) == len(b) + + +def T(F): + return ce.canonical_frame(n, tuple(basis(F))) + + +# ---- exact Gaussian-dyadic matrices: (Re int array, Im int array, exponent e), value = (Re + i Im) / 2^e +def mat(op): + E = 0 + raw = [] + for bidx in range(N): + vec, den = op.zero_state(bidx) + raw.append((vec, den)) + E = max(E, den) + R = np.zeros((N, N), dtype=object) + I = np.zeros((N, N), dtype=object) + for j, (vec, den) in enumerate(raw): + s = 1 << (E - den) + for i, (a, c) in enumerate(vec): + R[i, j] = a * s + I[i, j] = c * s + return (R, I, E) + + +def norm(M): + R, I, E = M + while E > 0 and all(int(v) % 2 == 0 for v in R.flat) and all(int(v) % 2 == 0 for v in I.flat): + R = R // 2 + I = I // 2 + E -= 1 + return (R, I, E) + + +def mm(A, B): + (a, b, e), (c, d, f) = A, B + return norm((a.dot(c) - b.dot(d), a.dot(d) + b.dot(c), e + f)) + + +def madd(A, B): + (a, b, e), (c, d, f) = A, B + E = max(e, f) + return norm((a * (1 << (E - e)) + c * (1 << (E - f)), b * (1 << (E - e)) + d * (1 << (E - f)), E)) + + +def scal(q, A): # q = (re, im, exp) is the dyadic Gaussian number (re + i im) / 2^exp + (qr, qi, qe) = q + (a, b, e) = A + return norm((qr * a - qi * b, qr * b + qi * a, e + qe)) + + +def eq(A, B): + A = norm(A) + B = norm(B) + return A[2] == B[2] and (A[0] == B[0]).all() and (A[1] == B[1]).all() + + +ZERO = (np.zeros((N, N), dtype=object), np.zeros((N, N), dtype=object), 0) +ID = (np.eye(N, dtype=object) * 1, np.zeros((N, N), dtype=object), 0) +cache = {} + + +def Tm(F): + k = tuple(sorted(span(basis(F)))) + if k not in cache: + cache[k] = (mat(T(F)), mat(T(F).adjoint())) + return cache[k] + + +# ---- instance search (deterministic, seeded): anchors odd and pairwise orthogonal, u_t odd orthogonal to all of them, +# one single reader per term, f_j = nondegenerate +odd = [v for v in range(1, N) if par(v)] +inst = None +rng = random.Random(24) +trip = list(itertools.combinations(odd, 3)) +rng.shuffle(trip) +for ua, ub, uc in trip: + if any(bin(u).count('1') == 1 for u in (ua, ub, uc)): + continue # avoid coordinate anchors + if par(ua & ub) or par(ua & uc) or par(ub & uc): + continue + ts = [t for t in odd if not par(t & ua) and not par(t & ub) and not par(t & uc)] + if not ts: + continue + ut = ts[0] + + def single(u, others): + c = [r for r in odd if not par(r & u) and all(par(r & o) for o in others)] + return c[0] if c else None + ra, rb, rc = single(ua, [ub, uc]), single(ub, [ua, uc]), single(uc, [ua, ub]) + if None in (ra, rb, rc): + continue + ok = True + for u in (ua, ub, uc): + R = [r for r in odd if not par(r & u)] # the full natural reader set of the term + if set(perp(R)) != {0, u}: + ok = False + if not ok: + continue + fs = [] + for u in (ua, ub, uc): + w = [w for w in range(1, N) if w not in span([ua, ub, uc]) and nondeg([u, w]) and dim([u, w]) == 2] + fs.append([u, w[0]]) + inst = (ua, ub, uc, ut, ra, rb, rc, fs) + break +assert inst, 'no instance found' +ua, ub, uc, ut, ra, rb, rc, fs = inst +U = [ua, ub, uc] +FULL = list(range(1, N)) + +# ---- C7.c0-station +common = [] +n_read = [] +for u in U: + R = [r for r in odd if not par(r & u)] + n_read.append(len(R)) + common.append(sorted(perp(R))) +inst_got = dict(anchors=U, u_t=ut, singles=[ra, rb, rc], frames=fs) +frames_ok = all(nondeg(F) for F in [[ua], [ub], [uc], [ua, ub], [ua, ub, uc], perp([ut])] + fs) +orth_ok = (all(par(u) == 1 for u in U + [ut]) and not par(ua & ub) and not par(ua & uc) and not par(ub & uc) + and all(not par(ut & u) for u in U)) +check('C7.c0-station', + common == EXPECTED['common_frames'] and n_read == [EXPECTED['readers_per_term']] * 3 and orth_ok and frames_ok + and inst_got == EXPECTED['instance'], + 'anchors %s, u_t %s; each term has %d natural readers whose common read frames are %s = {0, u_j}: only the point ' + 'station ' % ([format(u, '05b') for u in U], format(ut, '05b'), n_read[0], common)) + +# ---- the four networks +regs = ['x0', 'x1', 'x2', 'yt', 'ya', 'yb', 'yc'] +reads = {'yt': [0, 1, 2], 'ya': [0], 'yb': [1], 'yc': [2]} +ulab = {'yt': ut, 'ya': ra, 'yb': rb, 'yc': rc} +Q = {('yt', 0): (1, 0, 0), ('yt', 1): (-1, 0, 0), ('yt', 2): (1, 1, 1), ('ya', 0): (3, 0, 1), ('yb', 1): (0, 1, 0), + ('yc', 2): (-1, 2, 2)} # dyadic Gaussian q = (re + i im) / 2^e + + +def run(mode): + log = [] + # state: register -> {input register: matrix}; physical x_j = T_{f_j} l_{x_j}; targets start at frame 0 + S = {r: {} for r in regs} + for j in range(3): + S['x%d' % j] = {'x%d' % j: Tm(fs[j])[0]} + for y in ('yt', 'ya', 'yb', 'yc'): + S[y] = {y: ID} + + def step(reg, A, B, who): + if basis(A) == basis(B) and span(basis(A)) == span(basis(B)): + return + op = T(A).adjoint().then(T(B)) if basis(A) else T(B) + r = ce.lower_child(op)[0]['rank'] + dd = dist(A, B) + log.append((who, dim(A), dim(B), r, dd)) + M = mm(Tm(B)[0], Tm(A)[1]) + S[reg] = {k: mm(M, v) for k, v in S[reg].items()} + + def addto(dst, src, q): + for k, v in S[src].items(): + S[dst][k] = madd(S[dst].get(k, ZERO), scal(q, v)) + + def copy(new, src): + S[new] = {k: v for k, v in S[src].items()} + + for j in range(3): + copy('cp%d' % j, 'x%d' % j) + if mode == 'STD': + step('cp0', fs[0], [ua], 'copy-drop') # term a cheap + step('cp1', fs[1], [], 'copy-drop') + step('cp2', fs[2], [], 'copy-drop') + addto('yt', 'cp1', Q[('yt', 1)]) # reads of b, c at frame 0 + addto('yt', 'cp2', Q[('yt', 2)]) + addto('yb', 'cp1', Q[('yb', 1)]) + addto('yc', 'cp2', Q[('yc', 2)]) + step('yt', [], [ua], 't') + addto('yt', 'cp0', Q[('yt', 0)]) + step('yt', [ua], perp([ut]), 't') + step('yb', [], perp([rb]), 'single') + step('yc', [], perp([rc]), 'single') + else: + for j, u in enumerate(U): + step('cp%d' % j, fs[j], [u], 'copy-drop') + if mode in ('DESC', 'DESC0'): + step('yt', [], [ua], 't') + addto('yt', 'cp0', Q[('yt', 0)]) + if mode == 'DESC': + step('yt', [ua], [ub], 't-DESCENT') + else: + step('yt', [ua], [], 't-DESCENT') + step('yt', [], [ub], 't-reclimb') + addto('yt', 'cp1', Q[('yt', 1)]) + if mode == 'DESC': + step('yt', [ub], [uc], 't-DESCENT') + else: + step('yt', [ub], [], 't-DESCENT') + step('yt', [], [uc], 't-reclimb') + addto('yt', 'cp2', Q[('yt', 2)]) + step('yt', [uc], perp([ut]), 't') + elif mode == 'CLONE': + copy('K1', 'cp1') + copy('K2', 'cp2') # clones at the bottom frame (equal-frame copy, no drop) + step('K1', [ub], [ua, ub], 'clone') + step('K2', [uc], [ua, ub, uc], 'clone') + step('yt', [], [ua], 't') + addto('yt', 'cp0', Q[('yt', 0)]) + step('yt', [ua], [ua, ub], 't') + addto('yt', 'K1', Q[('yt', 1)]) + step('yt', [ua, ub], [ua, ub, uc], 't') + addto('yt', 'K2', Q[('yt', 2)]) + step('yt', [ua, ub, uc], perp([ut]), 't') + step('yb', [], [ub], 'single') + addto('yb', 'cp1', Q[('yb', 1)]) + step('yb', [ub], perp([rb]), 'single') + step('yc', [], [uc], 'single') + addto('yc', 'cp2', Q[('yc', 2)]) + step('yc', [uc], perp([rc]), 'single') + step('ya', [], [ua], 'single') + addto('ya', 'cp0', Q[('ya', 0)]) + step('ya', [ua], perp([ra]), 'single') + for j in range(3): + step('x%d' % j, fs[j], FULL, 'source') + return S, log + + +def expected(): + E = {} + for j in range(3): + E['x%d' % j] = {'x%d' % j: Tm(FULL)[0]} + for y, js in reads.items(): + P = Tm(perp([ulab[y]]))[0] + dct = {y: P} + for j in js: + dct['x%d' % j] = scal(Q[(y, j)], P) + E[y] = dct + return E + + +EXP = expected() +res = {} +for mode in ('STD', 'CLONE', 'DESC', 'DESC0'): + S, log = run(mode) + # column (input register k, basis vector) is exact iff every output register's block for k matches the intended + # operator (blocks absent from the intended operator must be zero) + ncols = 0 + for k in regs: + if all(eq(S[r].get(k, ZERO), EXP[r].get(k, ZERO)) for r in regs): + ncols += N + by = {} + for who, a, b, r, dd in log: + by[who] = by.get(who, 0) + r + res[mode] = dict(cols=ncols, log=log, by=by, total=sum(by.values()), + shown=[x for x in log if x[0] != 'source'], + reader=sum(v for k, v in by.items() if k.startswith('t'))) + check('C7.exact-%s' % mode.lower(), ncols == EXPECTED['columns'], + '%s: whole operator equals the intended one on %d/%d basis columns of the 7 logical registers' + % (mode, ncols, EXPECTED['columns'])) + +all_eq = all(r == dd for m in res.values() for _, _, _, r, dd in m['log']) +shown_ok = all(m['shown'] == EXPECTED['transitions'][mode] for mode, m in res.items()) +n_trans = sum(len(m['log']) for m in res.values()) +# the two descents of DESC are steps between two distinct lines -> -> : non-comparable, child rank 2 +noncomp = [x for x in res['DESC']['log'] if x[0] == 't-DESCENT'] +lines_meet_zero = all(len(meet([a], [b])) == 1 for a, b in ((ua, ub), (ub, uc))) +check('C7.child-eq-d', all_eq and shown_ok and len(noncomp) == 2 and lines_meet_zero + and all((a, b, r, dd) == (1, 1, 2, 2) for _, a, b, r, dd in noncomp), + 'lower_child rank == d(A,B) on all %d transitions of the four networks, including the %d non-comparable ' + 'line-to-line descents (dim 1 -> 1, meet 0) of rank 2' % (n_trans, len(noncomp))) + +# ---- descents: +2 per line-to-line descent, direct or through 0 +per_desc = {'DESC': sum(r for w_, a, b, r, dd in res['DESC']['log'] if w_ == 't-DESCENT') // 2, + 'DESC0': sum(r for w_, a, b, r, dd in res['DESC0']['log'] if w_ in ('t-DESCENT', 't-reclimb')) // 2} +check('C7.descent-mass', + per_desc == {'DESC': EXPECTED['descent_mass_each'], 'DESC0': EXPECTED['descent_mass_each']}, + '+%d data mass per rank-1 descent -> : direct %d (one rank-2 step), via 0 %d (two rank-1 steps), identical' + % (EXPECTED['descent_mass_each'], per_desc['DESC'], per_desc['DESC0'])) + + +# ---- a reader at k point stations pays 2(k-1) beyond the nested minimum +def path_mass(stations): + tot = 0 + for A, B in zip(stations, stations[1:]): + op = T(A).adjoint().then(T(B)) if basis(A) else T(B) + r = ce.lower_child(op)[0]['rank'] + assert r == dist(A, B) + tot += r + return tot + + +extra = {} +for k in (1, 2, 3): + path = [[]] + [[u] for u in U[:k]] + [perp([ut])] + extra[k] = path_mass(path) - EXPECTED['nested_minimum'] +check('C7.reader-stations', + extra == EXPECTED['k_stations_extra'] and res['DESC']['reader'] == EXPECTED['nested_minimum'] + extra[3] + and all(extra[k] == 2 * (k - 1) for k in extra), + 'reader path 0 -> -> ... -> -> u_t^perp costs %d + 2(k-1): extra %s for k = 1, 2, 3 (DESC: %d = %d + 4)' + % (EXPECTED['nested_minimum'], extra, res['DESC']['reader'], EXPECTED['nested_minimum'])) + +# ---- charged mass by role; DESC vs STD +mass_ok = all((m['by'], m['total']) == EXPECTED['mass'][mode] for mode, m in res.items()) +reader_ok = {mode: m['reader'] for mode, m in res.items()} == EXPECTED['reader_mass'] +cst = res['DESC']['by']['copy-drop'] - res['STD']['by']['copy-drop'] +rd = res['DESC']['reader'] - res['STD']['reader'] +check('C7.desc-vs-std', + mass_ok and reader_ok and (cst, rd, cst + rd) == tuple(EXPECTED['desc_vs_std'][k] for k in ('cst', 'reader', 'net')), + 'charged totals STD %d, CLONE %d, DESC %d, DESC0 %d; reader path mass %s; DESC vs STD: copy-drop mass %+d, reader ' + '%+d, net %+d (2 cheap terms earned, 2 descents paid)' + % (res['STD']['total'], res['CLONE']['total'], res['DESC']['total'], res['DESC0']['total'], + {m: v['reader'] for m, v in res.items()}, cst, rd, cst + rd)) + +finish() diff --git a/research/format-limits/scripts/cert.py b/research/format-limits/scripts/cert.py new file mode 100644 index 0000000000..351fbd107c --- /dev/null +++ b/research/format-limits/scripts/cert.py @@ -0,0 +1,133 @@ +"""Certified (rational interval) pressure-root tools. +g_q(r) = r*((m/r)^q - 1). Root of sum_i z_i g_q(r_i) = D (increasing in q). +All transcendental values are enclosed by rational intervals (Fraction endpoints, outward rounding).""" +from fractions import Fraction as Fr +import math + +DEN = 10**45 # outward rounding grid + + +def rd(x): # round down to grid + return Fr(math.floor(x * DEN), DEN) + + +def ru(x): + return Fr(math.ceil(x * DEN), DEN) + + +def ln_iv(x): + """enclosure of ln(x), x = Fraction > 0, via ln x = 2 atanh(z), z=(x-1)/(x+1); reduce by powers of 2.""" + x = Fr(x) + assert x > 0 + if x == 1: + return (Fr(0), Fr(0)) + # reduce: x = 2^e * y, y in [1/sqrt2, sqrt2] (use [2/3, 4/3]-ish via loop) + e = 0 + while x > Fr(4, 3): + x /= 2; e += 1 + while x < Fr(2, 3): + x *= 2; e -= 1 + lo2, hi2 = LN2 + lo, hi = _atanh2(x) + return (rd(lo + e * (lo2 if e >= 0 else hi2)), ru(hi + e * (hi2 if e >= 0 else lo2))) + + +def _atanh2(x): + z = (x - 1) / (x + 1) + az = abs(z) + s = Fr(0); t = z; k = 0 + N = 40 + for k in range(N): + s += t / (2 * k + 1) + t *= z * z + # tail bound: |sum_{k>=N} z^(2k+1)/(2k+1)| <= |z|^(2N+1)/((2N+1)(1-z^2)) + tail = az ** (2 * N + 1) / ((2 * N + 1) * (1 - z * z)) + s = 2 * s; tail = 2 * tail + return (rd(s - tail), ru(s + tail)) + + +def _ln2(): + # ln 2 = 2 atanh(1/3) + z = Fr(1, 3); s = Fr(0); t = z + N = 60 + for k in range(N): + s += t / (2 * k + 1); t *= z * z + tail = z ** (2 * N + 1) / ((2 * N + 1) * (1 - z * z)) + return (rd(2 * s - 2 * tail), ru(2 * s + 2 * tail)) + + +LN2 = _ln2() + + +def expm1_iv(lo, hi): + """enclosure of e^y - 1 for y in [lo,hi], 0 <= lo <= hi <= 1.""" + assert 0 <= lo <= hi <= 2 + N = 30 + + def ser(y): + s = Fr(0); t = Fr(1) + for i in range(1, N + 1): + t = t * y / i; s += t + return s + slo = ser(lo) # all terms >= 0 -> lower bound + shi = ser(hi) + 8 * hi ** (N + 1) / math.factorial(N + 1) # tail <= y^(N+1)/(N+1)! * e^y, e^y<=8 (y<=2) + return (rd(slo), ru(shi)) + + +def g_iv(r, m, q): + """enclosure of r*((m/r)^q - 1), q Fraction >= 0.""" + if r >= m: + return (Fr(0), Fr(0)) if r == m else None + l_lo, l_hi = ln_iv(Fr(m) / r) + lo, hi = expm1_iv(q * l_lo, q * l_hi) + return (r * lo, r * hi) + + +def F_iv(prof, m, q): + """prof: dict rank -> count (Fractions/ints allowed), returns enclosure of sum z g_q(r).""" + lo = Fr(0); hi = Fr(0) + for r, z in prof.items(): + if z == 0: + continue + a, b = g_iv(Fr(r), Fr(m), Fr(q)) + lo += z * a; hi += z * b + return (rd(lo), ru(hi)) + + +def root_bracket(prof, m, D, lo=Fr(0), hi=Fr(1, 10), iters=60): + """returns (qlo, qhi) with F(qlo) < D (certified via upper enclosure) and F(qhi) >= D (via lower enclosure): + the true root lies in [qlo, qhi]. qhi is a certified UPPER bound of the root.""" + D = Fr(D) + assert F_iv(prof, m, hi)[0] >= D, 'hi too small' + for _ in range(iters): + mid = rd((lo + hi) / 2) + flo, fhi = F_iv(prof, m, mid) + if flo >= D: + hi = mid + elif fhi < D: + lo = mid + else: + break + return lo, hi + + +def cost_iv(prof, m): + """enclosure of Cost = sum z r ln(m/r).""" + lo = Fr(0); hi = Fr(0) + for r, z in prof.items(): + if z == 0 or r >= m: + continue + a, b = ln_iv(Fr(m) / r) + lo += z * r * a; hi += z * r * b + return lo, hi + + +if __name__ == '__main__': + # self-test against floats + for x in (Fr(64, 7), Fr(73, 8), Fr(100, 3), Fr(3, 2), Fr(1, 5)): + lo, hi = ln_iv(x) + assert lo <= Fr(math.log(x)) + Fr(1, 10**12) and hi >= Fr(math.log(x)) - Fr(1, 10**12), x + assert hi - lo < Fr(1, 10**40), (x, float(hi - lo)) + print('ln ok', float(LN2[1] - LN2[0])) + lo, hi = expm1_iv(Fr(1, 100), Fr(1, 100)) + print('expm1(0.01)', float(lo), math.expm1(0.01), float(hi - lo)) diff --git a/research/format-limits/scripts/d1_cover_numbers.py b/research/format-limits/scripts/d1_cover_numbers.py new file mode 100644 index 0000000000..406a1012f8 --- /dev/null +++ b/research/format-limits/scripts/d1_cover_numbers.py @@ -0,0 +1,371 @@ +"""D1. Design-free cover numbers at the two ends of a register chain (complex P112 and bit E8), and the helper counts they force. + +Setting. A register carries a monotone chain of frames (subspaces) and a gate acts on registers that stand in a common +frame. In a lossless transfer x_s -> y_q between a source s and a terminal q with s orthogonal to q, the information crosses +every level d exactly once, inside one register's climb from a frame a (dim a < d) into a frame b (dim b >= d); such a +climb contributes a rank-one term with support (sources lying in a) x (terminals lying in b^perp). + +* Source end (level 2). A source s starts in the one-dimensional frame . A register standing at and climbing + into a frame b = serves the readers t of s (t perp s) with t . w = 0: a "hyperplane section" + {t perp s, t.w = 0} of the set T(s) of readers of s. Every reader of s is served by some crossing register, so the + sections of the crossing registers cover T(s); the cover number of s is the least number of proper sections covering T(s). +* Terminal end (level h-1 -> h). The same statement for the sources lying in q^perp, covered by hyperplane sections of q^perp. + +Each source (terminal) is served by its own register x_s (y_q) and by other registers, so with cover number c every source +needs c-1 foreign crossing registers. Data registers (x and y) can supply only a few of these; the rest are helpers. + +This script computes the cover numbers exactly on the two units + * complex P112 (PR #531): readers = the 52 ports orthogonal to a port; frames = F_2-subspaces of F_2^9; + * bit E8 (PR #555): readers of a source = the 63 lines of E7 orthogonal to it; frames = subspaces of Q^8; the sections are + the hyperplane flats of E7 (all lines of E7 inside a hyperplane of the 7-space), +and derives the lower bounds on the number of helper registers that follow from them. Upstream: PR #531 (complex word, +geometry), PR #555 (bit word). +""" +import sys + +sys.dont_write_bytecode = True + +from collections import Counter, defaultdict +from fractions import Fraction as Fr + +import numpy as np +from scipy.sparse import csr_matrix +from scipy.sparse.csgraph import connected_components + +import d_geometry as dg +from report import check, finish + +# --------------------------------------------------------------------------------------------------------------- +# Expected values (hard-coded; each is the exact outcome of the computation below) +# --------------------------------------------------------------------------------------------------------------- +EXPECTED = dict( + # complex P112 (F_2^9, 112 ports, 52 readers per port) + cx_readers=52, + cx_max_section=32, # largest section {t perp s, t.w = 0} over all sources and all w (any frame) + cx_closed_max_section=26, # same when the frame must be spanned by ports (w or u_s + w a port) + cx_cover2=112, # sources whose 52 readers are covered by two proper sections (any frames and closed frames) + # census of the word of PR #531 + c531_crossings_per_source={3: 112}, # every source has exactly 3 level-2 crossings + c531_class_mix={'h': 224, 'x': 112}, # = X_s plus two helpers per source (224 helper crossings) + c531_frame_dims={2: 336}, # all 336 post-crossing frames are 2-dimensional + c531_section=20, # each such frame serves 20 readers + c531_level8={'h': 266, 'y': 112, 'x': 14}, # entries into 8-dimensional frames by register class + c531_nonport_rank1_frames=7, # frames of dim >= 2 are all port-spanned; 7 rank-1 frames are not ports + # drop-free demand of the word of PR #531 + demand_nnz=5824, demand_minus_half=3920, demand_plus_half=1904, + # bit E8 (63 readers of a source) + bit_flats_sampled=2258, # 60000 random 6-subsets (seed 1) of E7: legacy sampled census + bit_flats_sampled_hist={10: 232, 13: 589, 16: 683, 21: 663, 30: 63, 36: 28}, + bit_flats=8821, # exhaustive census of the hyperplane flats of E7 + bit_flats_hist={10: 5040, 13: 2016, 16: 1008, 21: 666, 30: 63, 36: 28}, + bit_union2=52, # largest union of two hyperplane flats + bit_leftover=11, # 63 - 52 lines left uncovered by the best two sections + bit_cover_number=3, + bit_term_live_leftover_min=6, # worst live-source leftover after any two sections, over all 120 terminals + # shipped word of PR #555 + bit555_level2={'x': 111, 'y': 10, 'h': 285}, + bit555_per_source_min=3, + bit555_per_source_hist={3: 78, 4: 6, 5: 17, 6: 7, 7: 3}, + bit555_level7={'x': 108, 'y': 111, 'h': 252}, + bit555_R=439, + # forced helper counts + bit_sink=212, bit_strict=138, cx_sink=107, cx_sink_closed=108, cx_strict=0, +) +# The seven maximal parabolic types J of E7 (delete one node of the Dynkin diagram): the flat of rank 6 is the root subsystem of +# type J; its connected components (lines joined when not orthogonal) have the listed numbers of lines. There are +# |W(E7)| / (2 |W_J|) flats of type J (the number of parabolic subgroups conjugate to W_J is |W(E7)| / |N(W_J)|, and |N(W_J)| = 2 |W_J| for all seven types, as the +# census below confirms). +W_E7 = 2903040 # order of the Weyl group of E7 +TYPES = { # component sizes (in lines) -> (name, |W_J|) + (36,): ('E6', 51840), (30,): ('D6', 23040), (21,): ('A6', 5040), (20, 1): ('D5+A1', 1920 * 2), + (15, 1): ('A5+A1', 720 * 2), (10, 3): ('A4+A2', 120 * 6), (6, 3, 1): ('A3+A2+A1', 24 * 6 * 2), +} + + +# --------------------------------------------------------------------------------------------------------------- +# Complex P112: census of the word of PR #531 +# --------------------------------------------------------------------------------------------------------------- +def census531(): + """replay the frame assignments of the word of PR #531 (phases precondition, A, cut, B, cleanup). A register moved + from a one-dimensional frame into a frame F of dimension >= 2 is a level-2 crossing for the source s; its section is + the set of readers t of s with t perp F. Also records entries into 8-dimensional frames and the types of all frames.""" + d = dg.word531() + V = 112 + ports = d['ports'] + red, inspan, pd = dg.f2_reduce, dg.f2_inspan, dg.pdot + cls = lambda r: 'x' if r < V else 'y' if r < 2 * V else 'h' + cur = {int(k): red(v) for k, v in d['start_extra'].items()} + for s in range(V): + cur[s] = [ports[s]] + port_spanned = lambda B: len(red([p for p in ports if inspan(p, B)])) == len(B) + cnt = defaultdict(list) + c8 = Counter() + for ph in ['precondition', 'A', 'cut', 'B', 'cleanup']: + for e in d.get(ph, []): + regs = (e[1], e[2]) if e[0] == 'add' else (e[1],) + F = e[-1] + if F is None: + continue + Fb = red(F) + for r in regs: + c = cur.get(r, []) + if len(c) == 1 < len(Fb): + s = next((i for i in range(V) if inspan(ports[i], c)), None) + if s is not None: + sec = sum(1 for t in range(V) if not pd(ports[t], ports[s]) and all(not pd(ports[t], f) for f in Fb)) + cnt[s].append((cls(r), len(Fb), sec, port_spanned(Fb))) + if len(c) < 8 <= len(Fb) and len(Fb) == 8: + c8[cls(r)] += 1 + cur[r] = Fb + frames = set() + for ph in ['precondition', 'A', 'B', 'cleanup']: + for e in d.get(ph, []): + if e[-1] is not None: + frames.add(tuple(sorted(e[-1]))) + for v in d['start_extra'].values(): + frames.add(tuple(sorted(v))) + ftype = Counter() + for F in frames: + B = red(list(F)) + ftype[(len(B), port_spanned(B))] += 1 + return cnt, c8, ftype + + +def demand531(): + """the drop-free demand of the word of PR #531: replay the additions and scalings of the phases precondition, A, B, + cleanup over Q with the decoder copies (phase cut) removed, tracking for each register its coefficient vector on the + 112 sources. Returns the matrix L[t][s] = coefficient of x_s in y_t and the replay sanity flags.""" + d = dg.word531() + V = 112 + n = 2 * V + d['report']['R'] + ports = d['ports'] + val = [{} for _ in range(n)] + for s in range(V): + val[s] = {s: Fr(1)} + for ph in ['precondition', 'A', 'B', 'cleanup']: + for e in d.get(ph, []): + if e[0] == 'add': + _, a, b, num, den, _F = e + c = Fr(num, den) + for k, x in val[b].items(): + nv = val[a].get(k, 0) + c * x + if nv: + val[a][k] = nv + else: + val[a].pop(k, None) + elif e[0] == 'scale': + _, r, num, den, _F = e + c = Fr(num, den) + val[r] = {k: x * c for k, x in val[r].items()} + else: + raise ValueError(e) + L = {t: val[V + t] for t in range(V)} + restored = all(val[s] == {s: Fr(1)} for s in range(V)) + helpers_free = all(not val[r] for r in range(2 * V, n)) + return L, ports, restored, helpers_free + + +# --------------------------------------------------------------------------------------------------------------- +# Bit E8: hyperplane flats, covers, terminal side, shipped word +# --------------------------------------------------------------------------------------------------------------- +def sampled_flats(): + """the earlier sampled census: 60000 random 6-subsets of the 63 lines (numpy default_rng(1)); each independent + 6-subset together with s0 spans a hyperplane of R^8 whose E7 lines form a flat. Returns the set of line-id frozensets.""" + E = dg.e8() + s0 = E['srcid'][0] + LN = E['L'].astype(float) + P = np.array(dg.e7_of(s0)) + rng = np.random.default_rng(1) + secs = set() + for _ in range(60000): + S = rng.choice(P, 6, replace=False) + M = np.vstack([LN[S], LN[s0][None, :]]) + if np.linalg.matrix_rank(M) < 7: + continue + _, _, Vt = np.linalg.svd(M) + nv = Vt[-1] + secs.add(frozenset(int(x) for x in P[np.abs(LN[P] @ nv) < 1e-7])) + return secs + + +def terminal_side(hyp): + """for every one of the 120 lines q (111 targets and the 9 pseudo-targets at the punctured lines): the live sources + lying in q^perp (the live lines orthogonal to q) and the least number of them left uncovered by any two hyperplane + flats of E7(q). The flats of E7(q) are the images of the flats of E7(s0) under an E8 symmetry taking s0 to q.""" + E = dg.e8() + idx, _ = dg.e7_flats() + s0, g, perms = dg.e8_isometries() + F0 = np.zeros((len(hyp), 120), dtype=bool) + for fi, m in enumerate(hyp): + for k in range(63): + if m >> k & 1: + F0[fi, idx[k]] = True + isl = E['isl'] + G = E['G'] + worst, nlive_min = 10 ** 9, 10 ** 9 + for q in range(120): + Fq = np.zeros_like(F0) + Fq[:, perms[q]] = F0 + perp = G[q] == 0 + assert (Fq <= perp[None, :]).all() + nlive = int((perp & isl).sum()) + pad = np.zeros((len(hyp), 128), dtype=bool) + pad[:, :120] = Fq & isl[None, :] + words = np.packbits(pad, axis=1, bitorder='little').view(np.uint64) + left = nlive - dg.max_pair_union(words) + worst = min(worst, left) + nlive_min = min(nlive_min, nlive) + return worst, nlive_min + + +def bit555_crossings(): + """level-2 crossings (rank-1 frame -> rank >= 2) and level-7 crossings (rank < 7 -> rank-7 frame) of the registers of the + word of PR #555, by register class (x sources, y targets, h helpers), and the number of level-2 crossings per source.""" + w, dims, path = dg.bit_paths() + v = w['v'] + cls = lambda r: 'x' if r < v else 'y' if r < 2 * v else 'h' + c2, c7, per = Counter(), Counter(), Counter() + for r, p in enumerate(path): + for a, b in zip(p, p[1:]): + if dims[a] < 2 <= dims[b] and dims[a] == 1: + c2[cls(r)] += 1 + per[tuple(w['frames'][a])] += 1 + if dims[a] < 7 <= dims[b] and dims[b] == 7: + c7[cls(r)] += 1 + return dict(c2), dict(c7), per, w + + +def main(): + # ===== complex P112: covers of the readers of a source ====================================================== + cv = dg.cx_section_covers() + ok = (cv['readers'] == {EXPECTED['cx_readers']} and cv['cover2_all'] == EXPECTED['cx_cover2'] and cv['left_all'] == 0 + and cv['max_all'] == EXPECTED['cx_max_section'] and cv['improper_all'] == 0) + check('D1.cx-cover2', ok, + 'every source (%d/112): its %d readers are covered by two proper sections {t perp s, t.w = 0} (no single section covers them: ' + 'cover number 2); largest single section %d' % (cv['cover2_all'], EXPECTED['cx_readers'], cv['max_all'])) + ok = (cv['cover2_closed'] == EXPECTED['cx_cover2'] and cv['left_closed'] == 0 and cv['max_closed'] == EXPECTED['cx_closed_max_section'] + and cv['improper_closed'] == 0) + check('D1.cx-cover2-closed', ok, + 'frames spanned by ports only: largest section %d, two sections still cover %d/112 sources' + % (cv['max_closed'], cv['cover2_closed'])) + + # ===== complex P112: the word of PR #531 ==================================================================== + cnt, c8, ftype = census531() + per_src = Counter(len(v) for v in cnt.values()) + mix = Counter(c for v in cnt.values() for c, _, _, _ in v) + dims = Counter(dm for v in cnt.values() for _, dm, _, _ in v) + secs = Counter(sc for v in cnt.values() for _, _, sc, _ in v) + allclosed = all(ps for v in cnt.values() for _, _, _, ps in v) + ok = (len(cnt) == 112 and dict(per_src) == EXPECTED['c531_crossings_per_source'] and dict(mix) == EXPECTED['c531_class_mix'] + and dict(dims) == EXPECTED['c531_frame_dims'] and dict(secs) == {EXPECTED['c531_section']: 336} and allclosed) + check('D1.cx531-level2', ok, + 'every source has exactly 3 level-2 crossings (X_s + 2 helpers: %d helper crossings), all into 2-dim port-spanned frames ' + 'with section %d (3 x %d = %d >= 52 > 2 x %d)' % (mix['h'], EXPECTED['c531_section'], EXPECTED['c531_section'], + 3 * EXPECTED['c531_section'], EXPECTED['c531_section'])) + check('D1.cx531-level8', dict(c8) == EXPECTED['c531_level8'], + 'entries into 8-dimensional frames: helpers %d, targets %d, sources %d' % (c8['h'], c8['y'], c8['x'])) + nonport = ftype[(1, False)] + ok = all(ps for (dm, ps) in ftype if dm >= 2) and nonport == EXPECTED['c531_nonport_rank1_frames'] + check('D1.cx531-frame-types', ok, + 'every frame of dimension >= 2 used by the word is port-spanned (closed); %d rank-1 entrance frames are not ports' % nonport) + sec531 = EXPECTED['c531_section'] + ok = 2 * sec531 < EXPECTED['cx_readers'] <= 3 * sec531 and cv['cover2_all'] == 112 and cv['cover2_closed'] == 112 + check('D1.cx-224-is-section-type', ok, + "the 224 helper crossings (2 per source) are forced by #531's section type (2 x %d = %d < 52 <= %d), not by the format: " + 'sections of size up to %d (%d with closed frames) let two sections cover every source' + % (sec531, 2 * sec531, 3 * sec531, cv['max_all'], cv['max_closed'])) + + L, ports, restored, helpers_free = demand531() + nz = sum(len(r) for r in L.values()) + northo = sum(1 for t in range(112) for s in range(112) if not dg.pdot(ports[s], ports[t])) + bad = sum(1 for t in L for s in L[t] if dg.pdot(ports[s], ports[t])) + hist = Counter(str(x) for r in L.values() for x in r.values()) + ok = (restored and helpers_free and nz == northo == EXPECTED['demand_nnz'] and bad == 0 + and hist == Counter({'-1/2': EXPECTED['demand_minus_half'], '1/2': EXPECTED['demand_plus_half']})) + check('D1.cx-demand', ok, + 'drop-free demand: %d nonzeros = all %d orthogonal (source, target) pairs, values -1/2 x %d and +1/2 x %d, %d entries on non-orthogonal pairs' + % (nz, northo, hist['-1/2'], hist['1/2'], bad)) + + # ===== bit E8: hyperplane flats of E7 ======================================================================= + E = dg.e8() + idx, lattice = dg.e7_flats() + hyp = lattice[6] + pos = {l: i for i, l in enumerate(idx)} + sampled = sampled_flats() + hs = Counter(len(x) for x in sampled) + hyp_set = set(hyp) + sampled_masks = {sum(1 << pos[l] for l in x) for x in sampled} + ok = len(sampled) == EXPECTED['bit_flats_sampled'] and dict(hs) == EXPECTED['bit_flats_sampled_hist'] and sampled_masks <= hyp_set + check('D1.bit-flats-sampled', ok, + 'earlier census by sampling 60000 random 6-subsets (seed 1): %d flats %s, all genuine hyperplane flats of E7 but an ' + 'undercount of the small types' % (len(sampled), dict(sorted(hs.items())))) + hh = Counter(m.bit_count() for m in hyp) + certified = dg.certify_hyperplane_flats(hyp) + G7 = E['G'][np.ix_(idx, idx)] + by_type = Counter() + for m in hyp: + members = [i for i in range(63) if m >> i & 1] + sub = (G7[np.ix_(members, members)] != 0) & ~np.eye(len(members), dtype=bool) + _, lab = connected_components(csr_matrix(sub), directed=False) + by_type[tuple(sorted(Counter(lab.tolist()).values(), reverse=True))] += 1 + orb_ok = (set(by_type) == set(TYPES) + and all(by_type[k] == W_E7 // (2 * TYPES[k][1]) for k in TYPES)) + ok = len(hyp) == EXPECTED['bit_flats'] == certified and dict(hh) == EXPECTED['bit_flats_hist'] and orb_ok + check('D1.bit-flats', ok, + 'exhaustive census of the hyperplane flats of E7 (closure of flats, each certified in integers): %d flats, by size %s; by type %s ' + '(= |W(E7)| / (2 |W_J|) for each of the 7 maximal parabolic types)' + % (len(hyp), dict(sorted(hh.items())), {TYPES[k][0]: by_type[k] for k in sorted(TYPES, key=lambda k: -sum(k))})) + + words = dg.masks_to_words(hyp, 63) + full = dg.masks_to_words([(1 << 63) - 1], 63)[0] + u2 = dg.max_pair_union(words) + big = dg.masks_to_words([m for m in hyp if m.bit_count() >= 21], 63) # a witness may be searched among the large flats + triple = dg.find_triple_cover(big, full) + ok = u2 == EXPECTED['bit_union2'] and 63 - u2 == EXPECTED['bit_leftover'] and triple is not None + check('D1.bit-cover3', ok, + 'readers of a source (63 lines of E7): two hyperplane flats cover at most %d, leaving >= %d lines; three flats of size %s ' + 'cover all 63, so the cover number is %d' % (u2, 63 - u2, [int(dg.popcount(big[i]).sum()) for i in triple], EXPECTED['bit_cover_number'])) + + worst, nlive_min = terminal_side(hyp) + ok = worst == EXPECTED['bit_term_live_leftover_min'] and worst >= (63 - u2) - 9 + check('D1.bit-terminal-cover3', ok, + 'terminal side: for each of the 120 terminals q, the live sources in q^perp (at least %d) are not covered by two flats of E7(q): at ' + 'least %d live sources remain (>= 11 - 9 punctured lines = 2), so the cover number is 3' % (nlive_min, worst)) + + c2, c7, per, w = bit555_crossings() + ph = Counter(per.values()) + ok = c2 == EXPECTED['bit555_level2'] and len(per) == 111 and min(ph) == EXPECTED['bit555_per_source_min'] and dict(ph) == EXPECTED['bit555_per_source_hist'] + check('D1.bit555-level2', ok, + 'shipped #555 word: level-2 crossings from rank-1 frames x %d / y %d / h %d; every source has >= %d (histogram %s)' + % (c2['x'], c2['y'], c2['h'], min(ph), dict(sorted(ph.items())))) + check('D1.bit555-level7', c7 == EXPECTED['bit555_level7'], + 'shipped #555 word: level-7 crossings into rank-7 frames x %d / y %d / h %d' % (c7['x'], c7['y'], c7['h'])) + + # ===== derived helper counts ================================================================================ + nsrc = w['v'] + nterm = 120 + cmin_bit = EXPECTED['bit_cover_number'] + leftover_bit = 63 - u2 + sink_bit, strict_bit = dg.single_cut_bounds(nsrc, nterm, leftover_bit, cmin_bit) + ok = (sink_bit == EXPECTED['bit_sink'] and strict_bit == EXPECTED['bit_strict'] and w['R'] == EXPECTED['bit555_R'] + and triple is not None and u2 < 63) + check('D1.bit-bounds', ok, + 'bit (cover number %d, leftover %d): sink-only targets R >= 2 x %d - floor(%d/%d) = %d; strict R >= max(3 x %d - %d, 3 x %d - %d) = %d ' + '(shipped %d: %.1f%%)' % (cmin_bit, leftover_bit, nsrc, nsrc, leftover_bit, sink_bit, nsrc, 2 * nsrc, nterm, 2 * nsrc, strict_bit, + w['R'], 100.0 * strict_bit / w['R'])) + V = 112 + cmin_cx = 2 if cv['cover2_all'] == V else None + left_cx = EXPECTED['cx_readers'] - cv['max_all'] + left_cl = EXPECTED['cx_readers'] - cv['max_closed'] + sink_cx, strict_cx = dg.single_cut_bounds(V, V, left_cx, cmin_cx) + sink_cl, strict_cl = dg.single_cut_bounds(V, V, left_cl, cmin_cx) + R531 = dg.word531()['report']['R'] + ok = (sink_cx == EXPECTED['cx_sink'] and sink_cl == EXPECTED['cx_sink_closed'] and strict_cx == strict_cl == EXPECTED['cx_strict'] + and R531 == 504 and len(dg.word531()['centers']) == 8) + check('D1.cx-bounds', ok, + 'complex (cover number %d, symmetric ends): sink-only targets R >= 112 - floor(112/%d) = %d (%d with closed frames, section %d); ' + 'strict R >= 2 x 112 - 224 = %d (shipped %d helpers + 8 centres)' % (cmin_cx, left_cx, sink_cx, sink_cl, cv['max_closed'], strict_cx, R531 - 8)) + + +main() +finish() diff --git a/research/format-limits/scripts/d2_coupled_count.py b/research/format-limits/scripts/d2_coupled_count.py new file mode 100644 index 0000000000..29459e249b --- /dev/null +++ b/research/format-limits/scripts/d2_coupled_count.py @@ -0,0 +1,521 @@ +"""D2. The coupled (cross-level) cover relaxation with a helper-count objective, on the complex unit P112 and the bit unit E8. + +D1 bounds the number of helper registers by a cut at one level of the frame lattice (the source end or the terminal +end). Here the cuts at all levels are coupled through the register chains. + +Model. Let K be the top rank (K = 9 for P112 inside F_2^9, K = 8 for E8). A register has a class and a chain of frame ranks: + X (source) : starts at rank 1, ends at rank K, stops at any subset of {2, ..., K-1}; + Y (target) : starts at rank 0, ends at rank K-1 (the frame u_q^perp), stops at any subset of {1, ..., K-2}; + H (helper) : starts at a rank s0 (0 for the bit unit, 0..5 for the complex unit) and ends at rank K, stops at any subset of + {s0+1, ..., K-1}. +A hop from rank a to rank b > a carries one rank-one term of the transfer matrix, supported on (sources in a) x (terminals in +b^perp); it serves every level d with a < d <= b. Its area (number of orthogonal source/terminal pairs it can serve) is at +most ell_a * ell_{K-b}, where ell_i is the largest number of source points in a subspace of rank i, further capped by + * the exact unit rectangle cap M(d) of every level d spanned by the hop (largest area of a product (P cap a) x (P cap c) of + mutually orthogonal subspaces a, c with dim a = d-1, dim c = K-d), + * the largest section of a source's readers by a hyperplane (hops leaving rank 1) and of a terminal's sources (hops entering rank K-1), + * for sink-only targets (a target is only a destination, its hop serves one terminal column) ell_a x 1. +Variables x_t >= 0 (integer for the ILP): the number of registers of type t = (class, chain). Constraints: + (level) for every level d = 2..K-1: sum_t area_t(d) x_t >= P, where P is the number of orthogonal pairs; + (counts) exactly nX registers of class X, exactly nY of class Y; + (source) every source is served by a multiset of registers standing at its rank-1 frame whose first hops are sections of its + readers: at least cmin sections, any two cover at most u2 readers, together at least NT_src readers (patterns of + registers, linked to the first hops of the chains by >= ); + (terminal) the same for the sources of every terminal q at the rank-(K-1) end: the registers entering the frame q^perp + must cover the sources in q^perp by sections of q^perp (the target's own register contributes the section of + the hop that enters its final frame; pseudo-targets at the 9 punctured lines of the bit unit have no own register). +Objective: the number of helper registers. Scope: this is a relaxation under the assumptions above (frames are arbitrary +subspaces, the only drops are the decoder copies, the areas are exact maxima over the geometry); it is not a proof about every +conceivable design. The shipped words of PR #555 (bit) and PR #569 (complex) are feasible points of it. + +Claims. (a) the optimum of the coupled model equals the single-cut bounds of D1 (212, 138 for the bit unit; 107, 0 for the +complex unit): coupling adds nothing; (b) exact geometry inputs (ell_k and unit caps of P112); (c) the shipped words are +feasible; (d) with the data ladders of the shipped words fixed, the least number of helpers is 222 (bit) and 146 / 123 (complex). +Upstream: PR #555 (bit word), PR #531 (P112 and its word), PR #569 (complex word with width-7 gauges). +""" +import sys + +sys.dont_write_bytecode = True + +import itertools +from collections import Counter + +import numpy as np +from scipy.optimize import LinearConstraint, Bounds, milp +from scipy.sparse import lil_matrix, csr_matrix + +import d_geometry as dg +from report import VENDOR, check, finish, load_json + +# --------------------------------------------------------------------------------------------------------------- +# Expected values (hard-coded; each is the exact outcome of the computation below) +# --------------------------------------------------------------------------------------------------------------- +EXPECTED = dict( + cx_ell=[1, 2, 4, 8, 12, 20, 36, 64, 112], # ell_k, k = 1..9: most ports in a k-dimensional subspace of F_2^9 + cx_nsubspaces=8283458, # all subspaces of F_2^9 (sum of the Gaussian binomials) + cx_unitcaps={2: 32, 3: 32, 4: 32, 5: 36, 6: 32, 7: 32, 8: 32}, # exact rectangle cap per level d = 2..8 + bit_ell=[1, 3, 6, 12, 20, 36, 63], # ell_i, i = 1..7: lines in the largest rank-i flat (A1, A2, A3, D4, D5, E6, E7) + bit_sink=212, bit_strict=138, cx_sink=107, cx_strict=0, # integer optima of the coupled model (helpers) + lp=dict(bit_sink=2331 / 11, bit_strict=138.0, cx_sink=106.4, cx_strict=0.0), # their LP relaxations + ship_fixed_bit=222, ship_fixed_bit_strict=222, ship_fixed_cx_sink=146, ship_fixed_cx_strict=123, +) +TIME_LIMIT = 300 # seconds per ILP; every ILP below proves optimality in well under a second + + +# --------------------------------------------------------------------------------------------------------------- +# Exact geometry of P112: ell_k and the unit rectangle caps (exhaustive over all subspaces of F_2^9) +# --------------------------------------------------------------------------------------------------------------- +def cx_ell(orth_lo, orth_hi): + """ell_k = largest number of ports in a k-dimensional subspace u of F_2^9, k = 0..9: u = W^perp for the (9-k)-dimensional + subspace W spanned by the rows of an echelon basis; the ports in u are those orthogonal to every row. Returns (ell, + number of subspaces enumerated).""" + ell, total = {}, 0 + for m in range(0, 10): + best = 0 + for rows in dg.subspace_bases(m): + total += len(rows) + if m == 0: + best = len(dg.cx_ports()) + continue + lo = np.bitwise_and.reduce(orth_lo[rows], axis=1) + hi = np.bitwise_and.reduce(orth_hi[rows], axis=1) + best = max(best, int((dg.popcount(lo) + dg.popcount(hi)).max())) + ell[9 - m] = best + return [ell[k] for k in range(1, 10)], total + + +def cx_unit_caps(orth_lo, orth_hi): + """M(d) for d = 2..8: the largest |P cap a| * |P cap c| over orthogonal subspaces a, c of F_2^9 with dim a = d-1, + dim c = 9-d, c a hyperplane of a^perp. Enumerated exhaustively over a for dim a = 1..4 (d = 2..5); the pair (a, c) is + symmetric in its two members (a is a hyperplane of c^perp as well), so M(d) = M(10-d) gives d = 6, 7, 8.""" + ports = dg.cx_ports() + isport = np.zeros(512, dtype=bool) + isport[list(ports)] = True + lo1, hi1 = orth_lo[1:], orth_hi[1:] # for w = 1..511 + caps = {} + for p in (1, 2, 3, 4): + best = 0 + for rows in dg.subspace_bases(p, chunk=1 << 13): + n = len(rows) + vs = np.zeros((n, 1), dtype=np.int64) # all vectors of the span of each basis (column 0 is the zero vector) + for i in range(p): + vs = np.concatenate([vs, vs ^ rows[:, i:i + 1]], axis=1) + na = isport[vs[:, 1:]].sum(axis=1) # ports in a + olo = np.bitwise_and.reduce(orth_lo[rows], axis=1) + ohi = np.bitwise_and.reduce(orth_hi[rows], axis=1) # ports in a^perp + cc = dg.popcount(lo1[None, :] & olo[:, None]) + dg.popcount(hi1[None, :] & ohi[:, None]) # ports in a^perp cap w^perp + np.put_along_axis(cc, vs[:, 1:] - 1, 0, axis=1) # w in span(a) does not cut a hyperplane out of a^perp + best = max(best, int((na * cc.max(axis=1)).max())) + caps[p + 1] = best + for d in (6, 7, 8): + caps[d] = caps[10 - d] + return caps + + +# --------------------------------------------------------------------------------------------------------------- +# The coupled cover model +# --------------------------------------------------------------------------------------------------------------- +class Spec: + """parameters of one unit: K, ell[0..K], hstarts, capS(j), capT(i), ymode ('sink' or 'strict'), cmin, u2, NT_src, NT_top, + P, nX, nY, nsrc, nterm and optionally unitcap {level: cap}.""" + + +def feasible(caps, cmin, NT, u2): + """can sections with these caps serve NT readers: at least cmin of them, and (if u2 is given) any two cover at most u2.""" + caps = sorted(caps, reverse=True) + if len(caps) < cmin: + return False + top2 = sum(caps[:2]) + if u2 is not None: + top2 = min(top2, u2) + return top2 + sum(caps[2:]) >= NT + + +def minimal_patterns(fixed, itemcaps, cmin, NT, u2, maxextra=4): + """minimal multisets of optional items (key -> cap) that, together with the fixed items, can serve NT readers; minimal + means removing any optional item destroys feasibility. Returns the list of item tuples.""" + keys = [k for k in itemcaps if itemcaps[k] > 0] + base = [c for _, c in fixed] + out = [] + for r in range(0, maxextra + 1): + for combo in itertools.combinations_with_replacement(keys, r): + if not feasible(base + [itemcaps[k] for k in combo], cmin, NT, u2): + continue + if all(not feasible(base + [itemcaps[k] for t, k in enumerate(combo) if t != idx], cmin, NT, u2) for idx in range(len(combo))): + out.append(combo) + return out + + +class CoverModel: + def __init__(self, spec): + self.sp = sp = spec + K = sp.K + self.types = [] + for ch in self._chains(1, K, 2, K - 1): + self.types.append(('X', ch)) + for ch in self._chains(0, K - 1, 1, K - 2): + self.types.append(('Y', ch)) + for s0 in sp.hstarts: + for ch in self._chains(s0, K, s0 + 1, K - 1): + self.types.append(('H', ch)) + self.index = {t: i for i, t in enumerate(self.types)} + + @staticmethod + def _chains(start, end, lo, hi): + mids = list(range(lo, hi + 1)) + return [(start,) + T + (end,) for k in range(len(mids) + 1) for T in itertools.combinations(mids, k)] + + def area(self, cls, ch, d): + """largest number of orthogonal pairs the hop of the chain that spans level d can serve (0 if no hop spans d).""" + sp = self.sp + K = sp.K + for a, b in zip(ch, ch[1:]): + if a < d <= b: + if a == 0: + return 0.0 + if cls == 'Y' and sp.ymode == 'sink': + return float(min(sp.ell[a], sp.capT(a))) + ar = sp.ell[a] * sp.ell[K - b] + uc = getattr(sp, 'unitcap', None) + if uc: + ar = min(ar, min(uc.get(dd, 0) for dd in range(a + 1, b + 1))) + if a == 1: + ar = min(ar, sp.capS(b)) + if b == K - 1: + ar = min(ar, sp.capT(a)) + return float(ar) + return 0.0 + + @staticmethod + def first_hop(ch): + """target rank of the hop leaving rank 1 (None if the chain has no rank-1 stop).""" + return ch[ch.index(1) + 1] if 1 in ch[:-1] else None + + def last_hop(self, ch): + """source rank of the hop entering rank K-1 (None if the chain never enters rank K-1).""" + K = self.sp.K + return ch[ch.index(K - 1) - 1] if (K - 1) in ch[1:] else None + + def build(self, xfix=None): + sp = self.sp + K, T = sp.K, self.types + n = len(T) + j1 = [self.first_hop(ch) for _, ch in T] + i7 = [self.last_hop(ch) for _, ch in T] + srcpat = [] # (rank of X's hop, tuple of other (class, hop rank) items) + for jX in range(2, K + 1): + capX = sp.capS(jX) if jX < K else 0 + if sp.ymode == 'sink': + hk = [('H', j) for j in range(2, K)] + for r in range(0, 4): + for combo in itertools.combinations_with_replacement(hk, r): + caps = [capX] + [sp.capS(j) for _, j in combo] + for a in range(0, int(sp.NT_src) + 1): # a sink-only targets, one reader each + if not feasible(caps + [1] * a, sp.cmin, sp.NT_src, sp.u2): + continue + ok = not (a > 0 and feasible(caps + [1] * (a - 1), sp.cmin, sp.NT_src, sp.u2)) + for idx in range(len(combo)): + c2 = [capX] + [sp.capS(j) for t_, (_, j) in enumerate(combo) if t_ != idx] + [1] * a + if feasible(c2, sp.cmin, sp.NT_src, sp.u2): + ok = False + break + if ok: + srcpat.append((jX, tuple(combo) + tuple([('Y', 2)] * a))) + break + else: + optional = {} + for j in range(2, K + 1): + optional[('H', j)] = sp.capS(j) if j < K else 0 + optional[('Y', j)] = sp.capS(j) if j < K else 0 + for combo in minimal_patterns([(('X', jX), capX)], optional, sp.cmin, sp.NT_src, sp.u2): + srcpat.append((jX, combo)) + toppat = [] # (has own target section, its rank, tuple of other (class, hop rank) items) + for hasY in (True, False): + for iY in (range(0, K - 1) if hasY else [None]): + fixed = [(('Y', iY), sp.capT(iY))] if hasY else [] + optional = {} + for i in range(1, K - 1): + optional[('H', i)] = sp.capT(i) + optional[('X', i)] = sp.capT(i) + for combo in minimal_patterns(fixed, optional, sp.cmin, sp.NT_top, sp.u2): + toppat.append((hasY, iY, combo)) + ns, nt = len(srcpat), len(toppat) + N = n + ns + nt + c = np.zeros(N) + for t, (cls, ch) in enumerate(T): + c[t] = 1.0 if cls == 'H' else 0.0 + rows, lo, hi = [], [], [] + + def addrow(coefs, l, h): + rows.append(coefs) + lo.append(l) + hi.append(h) + + for d in range(2, K): # level covers + addrow({t: self.area(cls, ch, d) for t, (cls, ch) in enumerate(T) if self.area(cls, ch, d) > 0}, sp.P, np.inf) + addrow({t: 1.0 for t, (cls, _) in enumerate(T) if cls == 'X'}, sp.nX, sp.nX) + addrow({t: 1.0 for t, (cls, _) in enumerate(T) if cls == 'Y'}, sp.nY, sp.nY) + addrow({n + k: 1.0 for k in range(ns)}, sp.nsrc, sp.nsrc) # source patterns + for cls in ('X', 'H', 'Y'): + for j in range(2, K + 1): + co = {t: 1.0 for t, (cl, _) in enumerate(T) if cl == cls and j1[t] == j} + for k, (jX, combo) in enumerate(srcpat): + m = (1 if (cls == 'X' and jX == j) else 0) + sum(1 for it in combo if it == (cls, j)) + if m: + co[n + k] = co.get(n + k, 0) - m + addrow(co, 0, np.inf) + addrow({n + ns + k: 1.0 for k in range(nt) if toppat[k][0]}, sp.nY, sp.nY) # terminal patterns + addrow({n + ns + k: 1.0 for k in range(nt) if not toppat[k][0]}, sp.nterm - sp.nY, sp.nterm - sp.nY) + for cls in ('X', 'H', 'Y'): + for i in range(0, K - 1): + co = {t: 1.0 for t, (cl, _) in enumerate(T) if cl == cls and i7[t] == i} + for k, (hasY, iY, combo) in enumerate(toppat): + m = (1 if (cls == 'Y' and hasY and iY == i) else 0) + sum(1 for it in combo if it == (cls, i)) + if m: + co[n + ns + k] = co.get(n + ns + k, 0) - m + if co: + addrow(co, 0, np.inf) + if xfix: + for t, v in xfix.items(): + addrow({t: 1.0}, v, v) + A = lil_matrix((len(rows), N)) + for r, co in enumerate(rows): + for k, v in co.items(): + A[r, k] = v + self.c, self.A, self.lo, self.hi, self.N = c, csr_matrix(A), np.array(lo, float), np.array(hi, float), N + self.npat = (ns, nt) + + def solve(self, integer, xfix=None): + """minimise the number of helpers; returns scipy's result. Integer solves use mip_rel_gap = 0.""" + self.build(xfix) + cons = LinearConstraint(self.A, lb=self.lo, ub=self.hi) + integ = np.ones(self.N) if integer else np.zeros(self.N) + return milp(self.c, constraints=[cons], integrality=integ, bounds=Bounds(0, np.inf), + options=dict(time_limit=TIME_LIMIT, disp=False, mip_rel_gap=0.0)) + + +def optimum(model, integer, xfix=None): + """(value, proved) where proved says: solver status 'optimal', and for an ILP the dual bound equals the objective.""" + res = model.solve(integer, xfix) + if res.status != 0 or res.x is None: + return None, False + proved = True + if integer: + proved = abs(res.fun - round(res.fun)) < 1e-6 and (res.mip_dual_bound is None or res.mip_dual_bound >= res.fun - 1e-6) + return float(res.fun), proved + + +# --------------------------------------------------------------------------------------------------------------- +# Unit specifications +# --------------------------------------------------------------------------------------------------------------- +def e8_geometry(): + """cover parameters of the bit unit from the exact E7 flat lattice: ell_i = lines in the largest rank-i flat of E7 (for + i <= 7 the largest rank-i flats of E8 have the same sizes: the largest root subsystems of rank i are A1, A2, A3, D4, D5, E6, E7 + by the classification of parabolic subsystems; only the E7 part is enumerated here), u2 = largest union of two hyperplane + flats, cmin = cover number.""" + idx, lattice = dg.e7_flats() + ell = [max(m.bit_count() for m in lattice[k]) for k in range(1, 7)] + [63] + hyp = lattice[6] + words = dg.masks_to_words(hyp, 63) + full = dg.masks_to_words([(1 << 63) - 1], 63)[0] + u2 = dg.max_pair_union(words) + big = dg.masks_to_words([m for m in hyp if m.bit_count() >= 21], 63) + cmin = 2 if u2 == 63 else (3 if dg.find_triple_cover(big, full) is not None else None) + return ell, u2, cmin + + +def bit_spec(ymode, ell, u2, cmin): + E = dg.e8() + K = 8 + G, isl = E['G'], E['isl'] + nlive_perp = [int(((G[q] == 0) & isl).sum()) for q in range(120)] + sp = Spec() + sp.K, sp.ell = K, [0] + list(ell) + [120] + sp.hstarts = [0] + sp.capS = lambda j: sp.ell[K - j] + sp.capT = lambda i: sp.ell[i] + sp.ymode, sp.cmin, sp.u2 = ymode, cmin, u2 + sp.NT_src, sp.NT_top = 63, min(nlive_perp) + sp.P = sum(int((G[E['srcid'][i]] == 0).sum()) for i in range(111)) + sp.nX = sp.nY = sp.nsrc = 111 + sp.nterm = 120 + return sp + + +def cx_spec(ymode, ell, unitcap, sec, cmin): + K = 9 + sp = Spec() + sp.K, sp.ell = K, [0] + list(ell) + sp.hstarts = [0, 1, 2, 3, 4, 5] + sp.capS = lambda j: min(sp.ell[K - j], sec) if j < K else 0 + sp.capT = lambda i: min(sp.ell[i], sec) + sp.unitcap = unitcap + sp.ymode, sp.cmin, sp.u2 = ymode, cmin, None + sp.NT_src = sp.NT_top = 52 + sp.P = 112 * 52 + sp.nX = sp.nY = sp.nsrc = sp.nterm = 112 + return sp + + +# --------------------------------------------------------------------------------------------------------------- +# Chains of the shipped words +# --------------------------------------------------------------------------------------------------------------- +def bit_ship_counts(): + """(class, chain of frame ranks) -> number of registers, for the 661 registers of the word of PR #555.""" + w, dims, path = dg.bit_paths() + v = w['v'] + cnt = Counter() + for r, p in enumerate(path): + cnt[('X' if r < v else 'Y' if r < 2 * v else 'H', tuple(dims[f] for f in p))] += 1 + return cnt, w['R'] + + +def cx_ship_counts(): + """the same for the word of PR #569 with width-7 gauges, without its 8 rank-8 centres (which are not helpers of the + model). The chain of a register is the sequence of ranks of the frames it occupies during the phases A and B and at the + end (sources and helpers end in the full frame, targets in the perp of their port).""" + g = dg.gk() + word = load_json(VENDOR / 'pr569' / 'width7-eight-FULL-WORD.json.gz') + V = 112 + R = word['report']['R'] + n = 2 * V + R + P = word['ports'] + start_extra = word.get('start_extra', {}) + cur = {} + for r in range(n): + if r < V: + cur[r] = (P[r],) + elif r >= 2 * V: + cur[r] = g.basis(start_extra[str(r)]) if str(r) in start_extra else () + else: + cur[r] = () + chain = {r: [len(cur[r])] for r in range(n)} + + def climb(r, F): + F = g.basis(F) + if cur[r] != F: + chain[r].append(len(F)) + cur[r] = F + + for seq in (word['A'], word['B']): + for e in seq: + for r in [e[1]] + ([e[2]] if e[0] == 'add' else []): + climb(r, e[-1]) + for r in range(n): + climb(r, g.FULL if (r < V or r >= 2 * V) else g.perp(P[r - V])) + ctr = set(word['centers']) + cnt = Counter() + for r in range(n): + if r in ctr: + continue + cnt[('X' if r < V else 'Y' if r < 2 * V else 'H', tuple(chain[r]))] += 1 + return cnt, R, len(ctr) + + +def fix_all(model, cnt): + """x fixed to the counts cnt for every type (missing types are fixed to 0); returns (xfix, types of cnt not in the model).""" + missing = [k for k in cnt if k not in model.index] + return {i: cnt.get(t, 0) for i, t in enumerate(model.types)}, missing + + +def fix_data(model, cnt): + """x fixed to the shipped counts for the data classes X and Y only.""" + missing = [k for k in cnt if k[0] in 'XY' and k not in model.index] + return {i: cnt.get(t, 0) for i, t in enumerate(model.types) if t[0] in 'XY'}, missing + + +def main(): + ports = dg.cx_ports() + _, olo, ohi = dg.orth_tables(ports) + + # ===== exact geometry of P112 ============================================================================== + ell, nsub = cx_ell(olo, ohi) + check('D2.cx-ell', ell == EXPECTED['cx_ell'] and nsub == EXPECTED['cx_nsubspaces'], + 'largest number of ports in a k-dimensional subspace of F_2^9, k = 1..9: %s (all %d subspaces enumerated)' % (ell, nsub)) + caps = cx_unit_caps(olo, ohi) + check('D2.cx-unitcaps', caps == EXPECTED['cx_unitcaps'], + 'exact rectangle cap per level d = 2..8: %s (the product ell_(d-1) x ell_(9-d) = 8 x 8 = 64 at d = 5 is not attained)' + % [caps[d] for d in range(2, 9)]) + cv = dg.cx_section_covers() + sec, cmin_cx = cv['max_all'], (2 if cv['cover2_all'] == 112 else None) + assert sec == caps[2] == 32 and cmin_cx == 2 + + ell_bit, u2, cmin_bit = e8_geometry() + check('D2.bit-ell', ell_bit == EXPECTED['bit_ell'], + 'largest flat of rank i = 1..7 of E7 (exhaustive census of the flats): %s lines = A1, A2, A3, D4, D5, E6, E7; E8 itself has 120 lines; ' + 'two hyperplane flats cover at most %d of 63 lines, cover number %d' % (ell_bit, u2, cmin_bit)) + + # ===== the coupled model: count-minimal helpers ============================================================= + results = {} + for name, mk in (('bit', lambda y: bit_spec(y, ell_bit, u2, cmin_bit)), ('cx', lambda y: cx_spec(y, ell, caps, sec, cmin_cx))): + for ymode in ('sink', 'strict'): + m = CoverModel(mk(ymode)) + lp, _ = optimum(m, False) + ip, proved = optimum(m, True) + results[(name, ymode)] = (lp, ip, proved, m) + leftover_bit, leftover_cx = 63 - u2, 52 - sec + sc = {('bit', 'sink'): None, ('bit', 'strict'): None, ('cx', 'sink'): None, ('cx', 'strict'): None} + s, t = dg.single_cut_bounds(111, 120, leftover_bit, cmin_bit) + sc[('bit', 'sink')], sc[('bit', 'strict')] = s, t + s, t = dg.single_cut_bounds(112, 112, leftover_cx, cmin_cx) + sc[('cx', 'sink')], sc[('cx', 'strict')] = s, t + labels = {('bit', 'sink'): ('D2.bit-sink', 'bit_sink', 'bit unit, sink-only targets'), + ('bit', 'strict'): ('D2.bit-strict', 'bit_strict', 'bit unit, strict targets'), + ('cx', 'sink'): ('D2.cx-sink', 'cx_sink', 'complex unit, sink-only targets'), + ('cx', 'strict'): ('D2.cx-strict', 'cx_strict', 'complex unit, strict targets')} + for key, (cid, exp, text) in labels.items(): + lp, ip, proved, m = results[key] + ok = (proved and round(ip) == EXPECTED[exp] == sc[key] and abs(lp - EXPECTED['lp'][exp]) < 1e-6 + and int(np.ceil(lp - 1e-9)) == round(ip)) + check(cid, ok, '%s: coupled optimum %d helpers (ILP %s, LP relaxation %.4f, %d types, %d+%d patterns) = single-cut bound %d: ' + 'coupling adds %d' % (text, round(ip), 'proved optimal' if proved else 'NOT proved optimal', lp, len(m.types), m.npat[0], + m.npat[1], sc[key], round(ip) - sc[key])) + + # ===== the shipped words are feasible points ================================================================ + bit_cnt, bit_R = bit_ship_counts() + stat = [] + for ymode in ('sink', 'strict'): + m = results[('bit', ymode)][3] + xf, missing = fix_all(m, bit_cnt) + val, proved = optimum(m, False, xf) + stat.append((ymode, val, missing)) + ok = all(v is not None and not mi for _, v, mi in stat) and sum(bit_cnt.values()) == 661 and bit_R == 439 + check('D2.bit-ship-feasible', ok, 'the word of PR #555 (111 x + 111 y + %d helpers) is feasible in the model, sink-only and strict' % bit_R) + cx_cnt, cx_R, ncent = cx_ship_counts() + stat = [] + for ymode in ('sink', 'strict'): + m = results[('cx', ymode)][3] + xf, missing = fix_all(m, cx_cnt) + val, proved = optimum(m, False, xf) + stat.append((ymode, val, missing)) + ok = all(v is not None and not mi for _, v, mi in stat) and sum(cx_cnt.values()) == 112 + 112 + 496 and ncent == 8 + check('D2.cx-ship-feasible', ok, 'the word of PR #569 (width 7) without its %d centres (112 x + 112 y + %d helpers) is feasible in the model, ' + 'sink-only and strict' % (ncent, cx_R - ncent)) + + # ===== data ladders fixed to the shipped words ================================================================ + fixed_bit = {} + for ymode in ('sink', 'strict'): + m = results[('bit', ymode)][3] + xf, missing = fix_data(m, bit_cnt) + fixed_bit[ymode] = optimum(m, True, xf) + (missing,) + ok = (all(p and not mi for _, p, mi in fixed_bit.values()) and round(fixed_bit['sink'][0]) == EXPECTED['ship_fixed_bit'] + and round(fixed_bit['strict'][0]) == EXPECTED['ship_fixed_bit_strict']) + check('D2.bit-fixed-data', ok, + 'bit unit, data ladders fixed to the shipped word (X: 1,2,4,6,7,8 ...; Y: 0,2,7 ...): at least %d helpers sink-only, %d strict ' + '(= the 222 carriers, 2 per source; the free optima are 212 and 138)' + % (round(fixed_bit['sink'][0]), round(fixed_bit['strict'][0]))) + vals = {} + for ymode in ('sink', 'strict'): + m = results[('cx', ymode)][3] + xf, missing = fix_data(m, cx_cnt) + vals[ymode] = optimum(m, True, xf) + (missing,) + ok = (all(p and not mi for _, p, mi in vals.values()) and round(vals['sink'][0]) == EXPECTED['ship_fixed_cx_sink'] + and round(vals['strict'][0]) == EXPECTED['ship_fixed_cx_strict']) + check('D2.cx-fixed-data', ok, + 'complex unit, data ladders fixed to the shipped word: at least %d helpers sink-only, %d strict (free optima 107 and 0; the shipped X ladders ' + 'have no hop across ranks 5-8, so helpers must supply the large rectangles there; shipped 496)' + % (round(vals['sink'][0]), round(vals['strict'][0]))) + + +main() +finish() diff --git a/research/format-limits/scripts/d3_carrier_ledger.py b/research/format-limits/scripts/d3_carrier_ledger.py new file mode 100644 index 0000000000..0493cbbf6a --- /dev/null +++ b/research/format-limits/scripts/d3_carrier_ledger.py @@ -0,0 +1,297 @@ +"""D3. The register ledger of PR #531's 504 helpers, rebuilt from tile objects on the geometry P112, and the carrier-neutrality identity. + +Geometry (PR #531). The 112 sources are the root lines 8..119 of the file inputs/hamming/PROTOTYPE.json; two sources are +adjacent (a reader t "reads" a source s) when their root lines have inner product +-2; Gm[t, s] = (root_t . root_s)/2 in {0, +-1} +is the signed adjacency; every reader reads 52 sources. The sources fall into 14 blocks of 8 (the support of the root +line), each block into 2 tetrads of 4 (parity of the number of minus signs). The F_2 label of a source is its port in F_2^9. + +Tile objects. A "face" is a set B of 4 sources of one block together with a sign pattern; its reach is the number of readers +t that read all four sources with that pattern (Gm[t, B] proportional to the pattern). A "key" is a pair of faces of two +different blocks, disjoint in sources, that have at least 3 readers in common with a consistent relative sign. For a key with +reader set T and source set S (8 sources) two readers are mates if their common neighbourhood with consistent sign has at least +|S| + 8 sources; mu is the size of a maximum matching of mates among T. + +Ledger. The helper registers needed by a hierarchy of faces and keys are counted by births = sum over object instances of +(uses - 1): + carriers 2 N2 - 112 + waste (N2 faces, each built from two edge objects, "waste" = edges used with only one sign variant), + face copies 2 K - N2 (K keys, each face used by two keys), + M copies R - K (R = key hosts = sum over keys of (|T| - mu), minus the 56 slots borrowed from the output pairs), +so that B = N2 + K + R - 112 + waste. For PR #531 the objects are the 168 block-local planes (faces of reach 8 whose four ports +add to 0), their 168 tetrad edges, and 168 keys; the ledger gives 224 + 168 + 112 = 504 = the helper count R of the word. +A butterfly (an edge object with both sign variants) is register-neutral, so the ledger depends on the edge cover only through +`waste`: rebuilt from edges joining the two tetrads of a block the same planes cost 168 more registers (12 wasted single-variant +butterflies per block), while the tetrad edges waste nothing. Upstream: PR #531 (word and geometry). +""" +import sys + +sys.dont_write_bytecode = True + +import itertools +from collections import Counter, defaultdict + +from ortools.sat.python import cp_model + +import d_geometry as dg +from report import check, finish + +# --------------------------------------------------------------------------------------------------------------- +# Expected values (hard-coded; each is the exact outcome of the computation below) +# --------------------------------------------------------------------------------------------------------------- +EXPECTED = dict( + faces=168, faces_per_block=12, face_reach=8, face_rank=3, # block-local planes + edges=168, edge_degree=3, carriers=224, # tetrad edges, each source in 3, births of the sources + keys=168, key_readers={4: 168}, face_uses={2: 168}, key_rank={5: 168}, + nested_width={2: 168}, face_copies=168, # consumer frames of the two keys of a face are never nested + mate_matching={2: 168}, borrowed=56, hosts=280, m_copies=112, + total=504, classes=(224, 168, 112), + cross_block_best=(28, 12, 12, 16, 12), # per block: (births, wasted butterflies, edges, births_0, births_1) + cross_total=392, cross_extra=168, cross_waste=12, + waste_min=0, candidate_edges=392, +) + + +# --------------------------------------------------------------------------------------------------------------- +# Faces, keys +# --------------------------------------------------------------------------------------------------------------- +def block_local_faces(P): + """all faces of reach 8 inside one block: for each 4-subset B of a block (sources in increasing order) and each sign + pattern (signs of Gm[t, B] normalised so that the first is +1) count the readers t with that pattern. Returns the list of + faces (B, pattern, {reader: sign at B[0]}), sorted, and the census {(reach): number of (B, pattern)}.""" + Gm, ADJ, blk = P['Gm'], P['ADJ'], P['blk'] + faces, census = [], Counter() + for b in range(P['nblocks']): + srcs = [s for s in range(P['n']) if blk[s] == b] + for B in itertools.combinations(srcs, 4): + readers = [t for t in range(P['n']) if all(ADJ[t, s] for s in B)] + groups = defaultdict(dict) + for t in readers: + v = [int(Gm[t, s]) for s in B] + groups[tuple(x * v[0] for x in v)][t] = v[0] + for pat, rd in groups.items(): + census[len(rd)] += 1 + if len(rd) == EXPECTED['face_reach']: + faces.append((B, pat, rd)) + return sorted(faces, key=lambda f: (f[0], f[1])), census + + +def keys_of(faces, P): + """keys = pairs of faces of different blocks without common source, with relative sign rho = +-1 on at least 3 common + readers. Returns [(i, j, rho, sorted tuple of the common readers with that relative sign)].""" + blk = P['blk'] + out = [] + for i, j in itertools.combinations(range(len(faces)), 2): + Bi, _, Ri = faces[i] + Bj, _, Rj = faces[j] + if blk[Bi[0]] == blk[Bj[0]] or set(Bi) & set(Bj): + continue + common = set(Ri) & set(Rj) + for rho in (1, -1): + rd = [t for t in common if Ri[t] * Rj[t] == rho] + if len(rd) >= 3: + out.append((i, j, rho, tuple(sorted(rd)))) + return out + + +def f2_rank(vs): + return len(dg.f2_reduce(vs)) + + +def mate_matching(P, S8, T): + """size (0, 1 or 2) of a maximum matching of mates among the (four) readers T of a key with source set S8.""" + Gm, ADJ, n = P['Gm'], P['ADJ'], P['n'] + + def mate(t1, t2): + r = Gm[t1, S8[0]] * Gm[t2, S8[0]] + a = [s for s in range(n) if ADJ[t1, s] and ADJ[t2, s] and Gm[t1, s] * Gm[t2, s] == r] + return len(a) >= len(S8) + 8 + + G = [(a, b) for a, b in itertools.combinations(T, 2) if mate(a, b)] + if any(len(set(p) | set(q)) == 4 for p, q in itertools.combinations(G, 2)): + return 2 + return 1 if G else 0 + + +# --------------------------------------------------------------------------------------------------------------- +# Edge covers and waste +# --------------------------------------------------------------------------------------------------------------- +def block_births(options): + """options[f] = list of candidate splits of face f into two edge-variants; a split is [(edge, variant), (edge, variant)]. + For every choice of one split per face (exhaustively) compute: waste = number of edges used with exactly one sign variant, + births_0 = sum over sources of (number of distinct edges at the source - 1), births_1 = sum over used edge variants of + (number of faces using it - 1). Returns the list of (births, waste, edges, births_0, births_1) over all choices.""" + res = [] + for ch in itertools.product(*[range(len(o)) for o in options]): + used = defaultdict(set) + cnt = Counter() + for o, c in zip(options, ch): + for e, v in o[c]: + used[e].add(v) + cnt[(e, v)] += 1 + deg = Counter(x for e in used for x in e) + b0 = sum(d - 1 for d in deg.values()) + b1 = sum(u - 1 for u in cnt.values()) + res.append((b0 + b1, sum(1 for v in used.values() if len(v) == 1), len(used), b0, b1)) + return res + + +def face_splits(P, faces, kind): + """per block: for each face of the block, its splits into two edge-variants, where an edge is a pair of sources and its + variant the product of the face signs at its two ends. kind 'tetrad': the one split into two pairs inside the two + tetrads of the face; kind 'cross': the two splits whose pairs join the two tetrads.""" + tet = P['tet'] + byblk = defaultdict(list) + for B, pat, rd in faces: + s = dict(zip(B, pat)) + a = [x for x in B if tet[x] == tet[B[0]]] + c = [x for x in B if tet[x] != tet[B[0]]] + assert len(a) == 2 and len(c) == 2 + if kind == 'tetrad': + splits = [[(a[0], a[1]), (c[0], c[1])]] + else: + splits = [[(a[0], c[0]), (a[1], c[1])], [(a[0], c[1]), (a[1], c[0])]] + byblk[int(P['blk'][B[0]])].append([[(tuple(sorted(e)), s[e[0]] * s[e[1]]) for e in sp] for sp in splits]) + return byblk + + +def waste_min_all_splits(faces, tl=120): + """CP-SAT: choose one of the 3 splits of every face into two pairs (the tetrad split and the two cross splits) so that the + number of edges used with exactly one sign variant is minimal. Returns (optimum, status name, number of candidate edges).""" + m = cp_model.CpModel() + var = {0: {}, 1: {}} # variant +1 -> 0, -1 -> 1 : edge -> Bool "variant used" + + def v(variant, e): + d = var[0 if variant == 1 else 1] + if e not in d: + d[e] = m.NewBoolVar('') + return d[e] + + for B, pat, rd in faces: + s = dict(zip(B, pat)) + opts = [] + for part in ((0, 1), (0, 2), (0, 3)): + e1 = tuple(sorted((B[part[0]], B[part[1]]))) + rest = [i for i in range(4) if i not in part] + e2 = tuple(sorted((B[rest[0]], B[rest[1]]))) + z = m.NewBoolVar('') + opts.append(z) + for e in (e1, e2): + m.AddImplication(z, v(s[e[0]] * s[e[1]], e)) + m.AddExactlyOne(opts) + edges = set(var[0]) | set(var[1]) + obj = [] + for e in edges: + a, b = v(1, e), v(-1, e) + both = m.NewBoolVar('') + m.Add(both <= a) + m.Add(both <= b) + obj.append(a + b - 2 * both) + m.Minimize(sum(obj)) + solver = cp_model.CpSolver() + solver.parameters.max_time_in_seconds = tl + solver.parameters.num_workers = 4 + solver.parameters.random_seed = 1 + status = solver.Solve(m) + return int(round(solver.ObjectiveValue())), solver.StatusName(status), len(edges) + + +def main(): + P = dg.p112() + n, ports, Gm, ADJ, blk, tet = P['n'], P['ports'], P['Gm'], P['ADJ'], P['blk'], P['tet'] + word = dg.word531() + ok = (all(int(ADJ[t].sum()) == 52 for t in range(n)) and Counter(blk.tolist()) == Counter({b: 8 for b in range(14)}) + and Counter(tet.tolist()) == Counter({x: 4 for x in range(28)}) + and all((bin(ports[s] & ports[t]).count('1') % 2 == 0) == bool(ADJ[t, s]) for s in range(n) for t in range(n) if s != t)) + check('D3.geometry', ok, '112 sources in 14 blocks of 8 (28 tetrads of 4), 52 readers each; two sources are adjacent iff their ports are orthogonal in F_2^9') + + # ===== faces: the block-local planes ======================================================================== + faces, census = block_local_faces(P) + per_block = Counter(int(blk[f[0][0]]) for f in faces) + ranks = Counter(f2_rank([ports[s] for s in f[0]]) for f in faces) + tet_shape = Counter(tuple(sorted(Counter(int(tet[s]) for s in f[0]).values())) for f in faces) + plane = all(ports[f[0][0]] ^ ports[f[0][1]] ^ ports[f[0][2]] ^ ports[f[0][3]] == 0 for f in faces) + ok = (len(faces) == EXPECTED['faces'] and per_block == Counter({b: EXPECTED['faces_per_block'] for b in range(14)}) + and ranks == Counter({EXPECTED['face_rank']: 168}) and tet_shape == Counter({(2, 2): 168}) and plane + and max(census) == EXPECTED['face_reach']) + check('D3.faces', ok, + '%d block-local faces of reach %d (%d per block; no block-local 4-set has a larger reach), four ports adding to 0 (rank %d), each made ' + 'of 2 sources in each tetrad of its block' % (len(faces), EXPECTED['face_reach'], EXPECTED['faces_per_block'], EXPECTED['face_rank'])) + + edges = {tuple(sorted(s for s in f[0] if tet[s] == t)) for f in faces for t in {int(tet[s]) for s in f[0]}} + deg = Counter(s for e in edges for s in e) + births0 = sum(d - 1 for d in deg.values()) + ok = (len(edges) == EXPECTED['edges'] and set(deg.values()) == {EXPECTED['edge_degree']} and len(deg) == 112 and births0 == EXPECTED['carriers']) + check('D3.edges', ok, '%d tetrad edges, every source in %d of them: births of the sources %d = 2 x %d - 112' + % (len(edges), EXPECTED['edge_degree'], births0, len(edges))) + + # ===== keys ================================================================================================ + kl = keys_of(faces, P) + keys = [(tuple(sorted(faces[i][0] + faces[j][0])), i, j, rho, T) for i, j, rho, T in kl] + uses = Counter() + for _, i, j, _, _ in keys: + uses[i] += 1 + uses[j] += 1 + kr = Counter(len(T) for *_, T in keys) + krank = Counter(f2_rank([ports[s] for s in S8]) for S8, *_ in keys) + ok = len(keys) == EXPECTED['keys'] and dict(kr) == EXPECTED['key_readers'] and Counter(uses.values()) == Counter(EXPECTED['face_uses']) \ + and len(uses) == 168 and dict(krank) == EXPECTED['key_rank'] + check('D3.keys', ok, '%d keys = pairs of planes of two blocks with 4 common readers (the only plane pairs with >= 3 common readers); ' + 'every plane lies in exactly 2 keys; key frames have rank 5' % len(keys)) + + def contains(Fa, Fb): + return f2_rank(Fb + Fa) == f2_rank(Fb) + + cons = defaultdict(list) + for S8, i, j, rho, T in keys: + fr8 = [ports[s] for s in S8] + cons[i].append(fr8) + cons[j].append(fr8) + width = Counter() + for i, (a, b) in ((i, l[:2]) for i, l in cons.items()): + width[2 if not (contains(a, b) or contains(b, a)) else 1] += 1 + copies = sum(w - 1 for w, c in width.items() for _ in range(c)) + ok = dict(width) == EXPECTED['nested_width'] and copies == EXPECTED['face_copies'] + check('D3.face-copies', ok, 'the frames of the two keys using a plane are not nested for %d/168 planes: %d face copies (= 2K - N2 = 2 x %d - %d)' + % (width[2], copies, len(keys), len(faces))) + + mu = Counter(mate_matching(P, S8, list(T)) for S8, i, j, rho, T in keys) + hosts = sum(len(T) - mate_matching(P, S8, list(T)) for S8, i, j, rho, T in keys) - word['report']['borrowed_output_pairs'] + ok = dict(mu) == EXPECTED['mate_matching'] and word['report']['borrowed_output_pairs'] == EXPECTED['borrowed'] and hosts == EXPECTED['hosts'] + check('D3.hosts', ok, 'the mates of the 4 readers of a key form a matching of size 2 for %d/168 keys: hosts R = sum(|T| - mu) - %d = %d' + % (mu[2], EXPECTED['borrowed'], hosts)) + + # ===== the ledger ========================================================================================== + N2, K, R, waste = len(faces), len(keys), hosts, 0 + scratch, face, mcop = 2 * N2 - 112 + waste, 2 * K - N2, R - K + B = N2 + K + R - 112 + waste + ok = ((scratch, face, mcop) == EXPECTED['classes'] and B == scratch + face + mcop == EXPECTED['total'] == word['report']['R'] + and scratch == births0 and face == copies and len(edges) * 2 - 112 == scratch) + check('D3.ledger', ok, 'B = N2 + K + R - 112 + waste = %d + %d + %d - 112 + %d = %d = helper count of the word of PR #531; classes: %d scratch ' + '(carriers), %d face copies, %d M copies; the identity agrees with the direct counts %d (source births) and %d (face copies)' + % (N2, K, R, waste, B, scratch, face, mcop, births0, copies)) + + # ===== carriers are register-neutral ========================================================================== + tet_b = face_splits(P, faces, 'tetrad') + cross_b = face_splits(P, faces, 'cross') + base = [block_births(tet_b[b]) for b in range(14)] + best_tet = [min(r) for r in base] + cross = [block_births(cross_b[b]) for b in range(14)] + best = [min(r) for r in cross] + identity = all(r[0] == 2 * 12 - 8 + r[1] for blk_res in (base + cross) for r in blk_res) # births = 2 N2 - 8 + waste, per block + total_tet = sum(x[0] for x in best_tet) + total_cross = sum(x[0] for x in best) + ok = (tuple(best[0]) == EXPECTED['cross_block_best'] and len(set(best)) == 1 and total_tet == EXPECTED['carriers'] + and total_cross == EXPECTED['cross_total'] and total_cross - total_tet == EXPECTED['cross_extra'] + and all(x[1] == 0 for x in best_tet) and sum(x[1] for x in best) == 14 * EXPECTED['cross_waste'] and identity) + check('D3.carriers-neutral', ok, + 'the same 168 planes rebuilt from edges joining the two tetrads of a block cost %d registers instead of %d: +%d (%d wasted single-variant ' + 'butterflies per block, optimal over all 2^12 choices of split); for every choice births = 2 N2 - 112 + waste, tetrad edges waste 0' + % (total_cross, total_tet, total_cross - total_tet, best[0][1])) + + opt, status, nedges = waste_min_all_splits(faces) + check('D3.waste-min', (opt, status, nedges) == (EXPECTED['waste_min'], 'OPTIMAL', EXPECTED['candidate_edges']), + 'CP-SAT over all 3 splits of every plane (%d candidate edges): minimum waste %d, status %s (the tetrad splits attain it)' + % (nedges, opt, status)) + + +main() +finish() diff --git a/research/format-limits/scripts/d_geometry.py b/research/format-limits/scripts/d_geometry.py new file mode 100644 index 0000000000..8dd30ebc39 --- /dev/null +++ b/research/format-limits/scripts/d_geometry.py @@ -0,0 +1,452 @@ +"""Geometry shared by the design-free lower-bound checks d1_cover_numbers.py, d2_coupled_count.py, d3_carrier_ledger.py. + +Two incidence geometries are used. + +* E8 (bit side, PR #555). The 240 roots of E8 are scaled to norm 4 and live in {-2..2}^8 with even pairwise inner + products; a "line" is a pair {r, -r}, so there are 120 lines. The word of PR #555 uses 111 of them as sources; the other 9 + are punctured. Two lines are orthogonal iff their inner product is 0. For a line s the 63 lines orthogonal to s form an + E7 line system of rank 7; its flats of rank 6 are the "hyperplane flats". +* P112 (complex side, PR #531). The 112 ports are vectors of F_2^9 (odd weight), two ports are orthogonal iff their + F_2 dot product is 0. The root lines of PR #531's geometry file give a second description of the same 112 points, used + by d3 (blocks, tetrads, signed adjacency). + +All arithmetic is exact (integers); floating point is used only to group vectors by direction and every grouping result is +re-verified with integer arithmetic where it matters. +""" +import functools +import itertools +import sys + +sys.dont_write_bytecode = True # never write __pycache__ next to files of main + +import numpy as np + +from report import VENDOR, load_json, main_file + +# --------------------------------------------------------------------------------------------------------------- +# F_2 helpers +# --------------------------------------------------------------------------------------------------------------- + + +def pdot(a, b): + """F_2 dot product of two bit vectors stored as ints.""" + return (a & b).bit_count() & 1 + + +def f2_reduce(vs): + """a basis (list of ints) of the F_2 span of vs, built by xor-min reduction.""" + b = [] + for v in vs: + for x in b: + v = min(v, v ^ x) + if v: + b.append(v) + return b + + +def f2_inspan(v, basis): + for x in basis: + v = min(v, v ^ x) + return v == 0 + + +if hasattr(np, 'bitwise_count'): # numpy >= 2.0 + def popcount(x): + """number of set bits of every element of an unsigned integer array.""" + return np.bitwise_count(x).astype(np.int64) +else: + def popcount(x): + x = np.ascontiguousarray(x) + return np.unpackbits(x.view(np.uint8)).reshape(x.shape + (8 * x.dtype.itemsize,)).sum(axis=-1).astype(np.int64) + + +def orth_tables(ports, nbits=9): + """ORTH[w] (w in 0..2^nbits-1) = bit mask over the ports i with ports[i] . w = 0, plus the two 64-bit halves as + uint64 arrays (the 112 ports need 112 bits).""" + orth = [sum(1 << i for i, p in enumerate(ports) if not pdot(p, w)) for w in range(1 << nbits)] + m64 = (1 << 64) - 1 + lo = np.array([o & m64 for o in orth], dtype=np.uint64) + hi = np.array([o >> 64 for o in orth], dtype=np.uint64) + return orth, lo, hi + + +def subspace_bases(m, nbits=9, chunk=1 << 14): + """yield int64 arrays of shape (n, m); each row of an array is the reduced-row-echelon basis of one m-dimensional + subspace of F_2^nbits, and every subspace occurs exactly once (the number of subspaces is the Gaussian binomial).""" + if m == 0: + yield np.zeros((1, 0), dtype=np.int64) + return + for piv in itertools.combinations(range(nbits), m): # leading-bit positions of the m rows + pivset = set(piv) + free = [[b for b in range(p) if b not in pivset] for p in piv] + tot = sum(len(f) for f in free) + for start in range(0, 1 << tot, chunk): + bits = np.arange(start, min(start + chunk, 1 << tot), dtype=np.int64) + rows = np.empty((len(bits), m), dtype=np.int64) + sh = 0 + for i in range(m): + v = np.full(len(bits), 1 << piv[i], dtype=np.int64) + for k, b in enumerate(free[i]): + v |= ((bits >> (sh + k)) & 1) << b + sh += len(free[i]) + rows[:, i] = v + yield rows + + +# --------------------------------------------------------------------------------------------------------------- +# P112 (PR #531) +# --------------------------------------------------------------------------------------------------------------- +WORD531 = 'research/direct-delivery504-global/evidence/local/FULL-WORD.json.gz' +PROTO531 = 'research/direct-delivery504-global/inputs/hamming/PROTOTYPE.json' +GK_WORD = 'research/direct-delivery504-global/vendor/gk/gk_word.py' +GK_COMMON = 'research/direct-delivery504-global/vendor/gk/gk_common.py' + + +@functools.lru_cache(None) +def word531(): + """the complex word of PR #531 (a file of main).""" + return load_json(main_file(WORD531)) + + +@functools.lru_cache(None) +def cx_ports(): + """the 112 ports of P112 as 9-bit integers (orthogonality = even F_2 dot product).""" + return tuple(word531()['ports']) + + +@functools.lru_cache(None) +def p112(): + """P112 with its root-line description (PR #531's geometry file, lines 8..119): returns a dict with + ports, root lines RL, the signed adjacency Gm[t,s] = (RL_t . RL_s)/2, ADJ = (Gm != 0), the 14 blocks (supports of + the root lines), the parity par (number of minus signs mod 2) and the tetrad id tet = 2*block + par (28 tetrads).""" + D = load_json(main_file(PROTO531)) + ports = [D['binary_ports'][i] for i in range(8, 120)] + RL = [D['root_lines'][i] for i in range(8, 120)] + assert tuple(ports) == cx_ports() and RL == word531()['root_lines'] + n = 112 + Gm = np.array([[sum(x * y for x, y in zip(RL[t], RL[s])) // 2 for s in range(n)] for t in range(n)], dtype=np.int8) + np.fill_diagonal(Gm, 0) + ADJ = Gm != 0 + assert (ADJ == ADJ.T).all() + supp = [tuple(i for i in range(8) if RL[s][i]) for s in range(n)] + blocks = sorted(set(supp)) + blk = np.array([blocks.index(supp[s]) for s in range(n)]) + par = np.array([sum(RL[s][i] < 0 for i in supp[s]) % 2 for s in range(n)]) + tet = blk * 2 + par + return dict(n=n, ports=ports, RL=RL, Gm=Gm, ADJ=ADJ, blk=blk, par=par, tet=tet, nblocks=len(blocks)) + + +@functools.lru_cache(None) +def gk(): + """PR #531's word helpers (gk_word.py / gk_common.py of main): basis(), perp(), FULL.""" + p = main_file(GK_WORD) + main_file(GK_COMMON) # imported by gk_word.py from the same directory + sys.path.insert(0, str(p.parent)) + import gk_word + return gk_word + + +def cx_section_covers(): + """covers of the readers of a source by sections, for every source s of P112. The readers are T(s) = the ports orthogonal + to u_s (52 of them); a section is T(s) & {t : t.w = 0} for a vector w outside (a frame of dimension 2); it + is proper if it is not all of T(s). "all" uses every w in F_2^9, "closed" only those w for which w or u_s + w is a port + (frames spanned by ports). For each family returns the largest section size over all sources, the number of sources + whose readers are covered by two proper sections, the largest leftover (uncovered readers) after the best two, and the + number of improper sections (w outside whose section is all of T(s); zero means the readers of s span u_s^perp, so no + single frame serves them all and the cover number is exactly 2). The orthogonality relation of P112 is symmetric, so the + same numbers hold for the sources lying in the perp of a terminal.""" + ports = cx_ports() + orth, _, _ = orth_tables(ports) + out = dict(readers=set(), max_all=0, max_closed=0, cover2_all=0, cover2_closed=0, left_all=0, left_closed=0, + improper_all=0, improper_closed=0) + m64 = (1 << 64) - 1 + + def words(masks): + return np.array([[m & m64, m >> 64] for m in masks], dtype=np.uint64) + + for s, us in enumerate(ports): + T = orth[us] + out['readers'].add(T.bit_count()) + secs = {T & orth[w] for w in range(1, 512) if w != us} + closed = {T & orth[w] for w in ({p for p in ports if p != us} | {p ^ us for p in ports if p != us})} + for key, fam in (('all', secs), ('closed', closed)): + out['improper_' + key] += int(T in fam) + fam = sorted(fam - {T}) # proper sections only + out['max_' + key] = max(out['max_' + key], max(x.bit_count() for x in fam)) + best = max_pair_union(words(fam)) + out['left_' + key] = max(out['left_' + key], T.bit_count() - best) + out['cover2_' + key] += (best == T.bit_count()) + return out + + +# --------------------------------------------------------------------------------------------------------------- +# E8 (PR #555) +# --------------------------------------------------------------------------------------------------------------- +BIT_WORD = VENDOR / 'pr555' / 'bitE8mut-p111.json.gz' + + +@functools.lru_cache(None) +def bit_word(): + return load_json(BIT_WORD) + + +@functools.lru_cache(None) +def e8(): + """the 120 lines of E8 (integer vectors of norm 4, one representative per +-pair, sorted), the 111 live source lines of + PR #555's word (srcid[i] = line index of the i-th source) and the inner-product matrix G. + + The lines are the norm-4 vectors with entries in {-2..2} that have even inner product with every live line; the + candidates are the vectors (+-2, 0, ..., 0) and (+-1, +-1, +-1, +-1, 0, 0, 0, 0).""" + live = np.array(bit_word()['lines'], dtype=np.int64) + cands = [] + for i in range(8): + for s in (2, -2): + v = [0] * 8 + v[i] = s + cands.append(v) + for pos in itertools.combinations(range(8), 4): + for sg in itertools.product((1, -1), repeat=4): + v = [0] * 8 + for p, s in zip(pos, sg): + v[p] = s + cands.append(v) + C = np.array(cands, dtype=np.int64) + roots = C[((C @ live.T) % 2 == 0).all(axis=1)] + canon = lambda a: max(tuple(a), tuple(-x for x in a)) + L = np.array(sorted({canon(r) for r in roots.tolist()}), dtype=np.int64) + assert len(roots) == 240 and len(L) == 120 + index = {tuple(l): i for i, l in enumerate(L.tolist())} + srcid = [index[canon(a)] for a in live.tolist()] + assert len(set(srcid)) == 111 + isl = np.zeros(120, dtype=bool) + isl[srcid] = True + G = L @ L.T + return dict(L=L, srcid=srcid, isl=isl, G=G, index=index, canon=canon) + + +def e8_line_index(v): + E = e8() + return E['index'][E['canon'](tuple(int(x) for x in v))] + + +def e7_of(q): + """indices of the 63 lines orthogonal to line q.""" + G = e8()['G'] + return [j for j in range(120) if G[q, j] == 0] + + +@functools.lru_cache(None) +def e8_isometries(): + """for every line q a matrix g_q (8x8, float with exact binary entries) of the E8 symmetry group generated by the + reflections in the roots, with g_q(line s0) = +-(line q), where s0 = line of the first live source; and the induced + permutation perm_q of the 120 lines (line i is sent to line perm_q[i]).""" + E = e8() + L = E['L'].astype(float) + s0 = E['srcid'][0] + refl = [np.eye(8) - np.outer(L[i], L[i]) / 2.0 for i in range(120)] + g = {s0: np.eye(8)} + todo = [s0] + while todo: + a = todo.pop() + for r in range(120): + m = refl[r] @ g[a] + b = e8_line_index(np.rint(m @ L[s0]).astype(int)) + if b not in g: + g[b] = m + todo.append(b) + assert len(g) == 120 + perms = {} + for q, m in g.items(): + img = np.rint(L @ m.T).astype(int) + perms[q] = np.array([e8_line_index(v) for v in img]) + assert sorted(perms[q]) == list(range(120)) + return s0, g, perms + + +# --- flats of a vector configuration -------------------------------------------------------------------------- + + +def flat_lattice(V, top_rank): + """all flats of the matroid of the rows of V (float array, rows are vectors) of rank 1..top_rank, as int bit masks. + A flat of rank k+1 is the closure of a flat F of rank k together with one more vector, so level k+1 is found by + grouping the vectors outside F by the direction of their component orthogonal to span(F). + returns {rank: sorted list of masks}.""" + n = len(V) + levels = {} + level = {0} + for k in range(1, top_rank + 1): + nxt = set() + for mask in level: + idx = [i for i in range(n) if mask >> i & 1] + if idx: + U, S, _ = np.linalg.svd(V[idx].T, full_matrices=False) + Q = U[:, S > 1e-9] + Rr = V - (V @ Q) @ Q.T + else: + Rr = V.copy() + nrm = np.linalg.norm(Rr, axis=1) + out = [i for i in range(n) if nrm[i] > 1e-9] + Rn = Rr[out] / nrm[out][:, None] + same = np.abs(Rn @ Rn.T) > 1 - 1e-9 + seen = set() + for a in range(len(out)): + if a in seen: + continue + grp = np.nonzero(same[a])[0] + seen.update(grp.tolist()) + m = mask + for g_ in grp: + m |= 1 << out[g_] + nxt.add(m) + level = nxt + levels[k] = sorted(level) + return levels + + +@functools.lru_cache(None) +def e7_flats(): + """flats of ranks 1..6 of the E7 line system E7(s0) (the 63 lines orthogonal to the line s0 of the first live source). + returns (E7 line indices, {rank: masks over the 63 positions}); the rank-6 flats are the hyperplane flats.""" + E = e8() + s0 = E['srcid'][0] + idx = e7_of(s0) + assert len(idx) == 63 + V = E['L'][idx].astype(float) + assert np.linalg.matrix_rank(V) == 7 + return idx, flat_lattice(V, 6) + + +def rank_mod_p(rows, p=1000003, want=None): + """rank of an integer matrix modulo the prime p (stops early once the rank reaches want). For the 8-column matrices of + norm-4 vectors used here every nonzero 6x6 minor is at most 2^6 < p in absolute value, so the rank mod p equals the + rank over the rationals as long as the rank is at most 6.""" + rows = [[x % p for x in r] for r in rows] + rk = 0 + ncol = len(rows[0]) + for c in range(ncol): + piv = next((i for i in range(rk, len(rows)) if rows[i][c]), None) + if piv is None: + continue + rows[rk], rows[piv] = rows[piv], rows[rk] + inv = pow(rows[rk][c], -1, p) + rows[rk] = [(x * inv) % p for x in rows[rk]] + for i in range(len(rows)): + if i != rk and rows[i][c]: + f = rows[i][c] + rows[i] = [(x - f * y) % p for x, y in zip(rows[i], rows[rk])] + rk += 1 + if want is not None and rk >= want: + break + return rk + + +def certify_hyperplane_flats(masks): + """exact (integer) certificate that every mask is a hyperplane flat of E7(s0). For a mask with member lines l_1..l_k the + vector s0 and the members span a hyperplane n^perp of R^8; n is found numerically, scaled to an integer vector and then + checked EXACTLY: (i) the lines of E7(s0) with n . l = 0 are precisely the members (so the mask is closed under taking + the span), and (ii) the members have rank 6 (computed modulo a prime larger than every nonzero 6x6 minor).""" + E = e8() + idx, _ = e7_flats() + E7 = E['L'][idx] + sv = E['L'][E['srcid'][0]] + for m in masks: + members = [i for i in range(63) if m >> i & 1] + A = np.vstack([E7[members], sv[None, :]]).astype(float) + _, S, Vt = np.linalg.svd(A) + assert S[6] > 1e-6 and S[7] < 1e-9 + nv = Vt[7] + nz = np.abs(nv) > 1e-9 + nn = nv / np.abs(nv[nz]).min() + for t in range(1, 49): + if np.abs(t * nn - np.rint(t * nn)).max() < 1e-7: + break + else: + raise AssertionError('normal vector is not rational with small denominator') + n = np.rint(t * nn).astype(np.int64) + assert (E7 @ n == 0).tolist() == [bool(m >> i & 1) for i in range(63)], 'flat is not closed' + assert int(sv @ n) == 0 + assert rank_mod_p(E7[members].tolist(), want=6) == 6 + return len(masks) + + +def max_pair_union(words): + """maximum, over unordered pairs (a, b) of rows (a = b allowed), of the number of set bits in the union of two rows; + words: (n, k) uint64 array (the bit sets). Exact; pairs are pruned with |a|+|b| <= current best.""" + sizes = popcount(words).sum(axis=1) + order = np.argsort(-sizes, kind='stable') + W, S = words[order], sizes[order] + best = 0 + neg = -S + for i in range(len(W)): + if 2 * S[i] <= best: + break + cnt = int(np.searchsorted(neg, -(best - S[i]), side='left')) # rows j with S[j] > best - S[i] + cnt = max(cnt, i + 1) + u = popcount(W[i][None, :] | W[i:cnt]).sum(axis=1) + best = max(best, int(u.max())) + return best + + +def masks_to_words(masks, nbits): + """list of int bit masks -> (n, ceil(nbits/64)) uint64 array.""" + k = (nbits + 63) // 64 + m64 = (1 << 64) - 1 + return np.array([[(m >> (64 * j)) & m64 for j in range(k)] for m in masks], dtype=np.uint64) + + +def find_triple_cover(words, universe): + """indices (i, j, k) of three rows of words (an (n, w) uint64 array) whose union is the universe (a (w,) uint64 row), or + None. For every pair (i, j) the rows containing the leftover are searched.""" + n = len(words) + for i in range(n): + for j in range(i, n): + left = universe & ~(words[i] | words[j]) + ok = np.nonzero(((words & left) == left).all(axis=1))[0] + if len(ok): + return i, j, int(ok[0]) + return None + + +def single_cut_bounds(nsrc, nterm, leftover, cmin): + """lower bounds on the number of helper registers from the cover numbers at the two ends of the chains. + nsrc sources (and nsrc targets, so 2 nsrc data registers), nterm terminals; cmin = cover number (every source and every + terminal needs cmin crossing registers at its end); leftover = number of readers that the best (cmin-1) sections leave + uncovered (the readers a further register must serve). + * strict (every data register may serve as a crossing register, each crossing a level at most once): the cmin x nsrc + crossings needed at the source end and cmin x nterm at the terminal end are supplied by at most 2 nsrc data registers, + so R >= max(cmin nsrc - 2 nsrc, cmin nterm - 2 nsrc, 0). + * sink-only (a target register is only a destination, so it can serve a single reader of a source): a source can do + with one helper fewer only if `leftover` targets serve its leftover readers one by one; nsrc targets allow that for at + most floor(nsrc / leftover) sources, so R >= (cmin - 1) nsrc - floor(nsrc / leftover). + returns (sink-only bound, strict bound).""" + data = 2 * nsrc + sink = (cmin - 1) * nsrc - nsrc // leftover + strict = max(cmin * nsrc - data, cmin * nterm - data, 0) + return sink, strict + + +# --- paths of the registers of the word of PR #555 -------------------------------------------------------------- + + +@functools.lru_cache(None) +def bit_paths(): + """replay the frame changes of PR #555's word. Each register r (0..110 sources x, 111..221 targets y, then 439 + helpers) starts in frame w['start'][r]; every gate (phases D, A, B, C in this order) [f, dst, srcs] moves dst and the + srcs into frame f; finally every register is moved to its final frame. Returns (word, dims, path) where + dims[f] = rank of frame f (span of its lines) and path[r] = list of frame indices visited by r.""" + w = bit_word() + lines = np.array(w['lines'], dtype=float) + dims = [int(np.linalg.matrix_rank(lines[F])) if F else 0 for F in w['frames']] + cur = list(w['start']) + path = [[f] for f in cur] + for ph in 'DABC': + for f, dst, srcs in w[ph]: + for r in [dst] + list(srcs): + if cur[r] != f: + cur[r] = f + path[r].append(f) + for r in range(len(cur)): + if cur[r] != w['final'][r]: + path[r].append(w['final'][r]) + cur[r] = w['final'][r] + return w, dims, path diff --git a/research/format-limits/scripts/e1_run_census.py b/research/format-limits/scripts/e1_run_census.py new file mode 100644 index 0000000000..bab27fb79b --- /dev/null +++ b/research/format-limits/scripts/e1_run_census.py @@ -0,0 +1,234 @@ +"""E. Moment-cost census by register class on the frontier words #555 (bit) and #569 (complex). + +Each register's chain of frame dimensions is replayed from the published word. A run is one paid child (one change +of frame); its moment cost at the word's own root q is phi(r) = r((m/r)^q - 1), and the published global profile has +sum phi = D at q. For each class the script prints the number of registers, runs, mean run rank, interior stops per +register, its share of sum phi (F-share), and the 'all-single-run ceiling': the supplier value if every register of the +class climbed in one child (one run of rank = its total climb), with hosting held proportional (the idle remainder +absorbs the change of hosted helper mass), i.e. the most any re-scheduling of that class's stops could give. +#555: s = 9, T = 32 (288 stage-replicas), m = 64, D = 10272; #569: 72 calls, m = 73, D = 2560, with the width-7 +provider word used for all 72 calls in the class shares (the 9 four-cold calls differ in a handful of runs). +""" +from collections import Counter, defaultdict +import sys + +import numpy as np + +from report import DATA, VENDOR, check, finish, load_json, main_file, near_all + + +def root(P, m, D, hi=0.05): + f = lambda q: sum(z * r * ((m / r) ** q - 1) for r, z in P.items() if z) - D + lo = 0.0 + for _ in range(100): + mid = (lo + hi) / 2 + if f(mid) < 0: + lo = mid + else: + hi = mid + return hi + + +def runs_of(ch): + return [abs(b - a) for a, b in zip(ch, ch[1:])] + + +def hist_of(pats): + h = Counter() + for ch, z in pats.items(): + for r in runs_of(ch): + h[r] += z + return h + + +def proportional(m, D, base, rem, hel_new, hmass_new, hmass0): + """root of base + hel_new + rem rescaled so the total hosted-plus-remainder mass stays fixed""" + remmass = sum(r * z for r, z in rem.items()) + scale = (remmass + hmass0 - hmass_new) / remmass + P = Counter() + for h in (base, hel_new): + for r, z in h.items(): + P[r] += z + for r, z in rem.items(): + P[r] += z * scale + return root(P, m, D) + + +# ------------------------------------------------------------------ #555 (bit) +EXP555 = { # class: (n, runs, mean run, stops per register, F-share %, all-single-run ceiling %) + 'A line-born carriers (0,1,2,..)': (205, 1120, 1.46, 4.46, 29.5, 14.75), + 'B cell-born (0,2,..)': (153, 686, 1.78, 3.48, 21.2, 9.60), + 'C line -> quad/D6 keepers and totals (0,1,4|6,..)': (80, 349, 1.83, 3.36, 10.7, 4.35), + 'D quad-born (0,4,..)': (1, 4, 2.00, 3.00, 0.1, 0.05), +} +EXP555_DATA = {'x': (539, 1.44, 14.1), 'y': (213, 3.65, 10.4), 'copies': 0.7, 'remainder': 13.4} +w = load_json(VENDOR / 'pr555' / 'bitE8mut-p111.json.gz') +e555 = load_json(VENDOR / 'pr555' / 'EXPECTED.json') +lines = np.array(w['lines'], float) +dims = [int(np.linalg.matrix_rank(lines[F])) if F else 0 for F in w['frames']] +cur = list(w['start']) +chain = {r: [dims[cur[r]]] for r in range(len(cur))} +for ph in 'DABC': + for f, dst, srcs in w[ph]: + for r in [dst] + list(srcs): + if cur[r] != f: + chain[r].append(dims[f]) + cur[r] = f +for r in range(len(cur)): + if cur[r] != w['final'][r]: + chain[r].append(dims[w['final'][r]]) +v, R, S, m, D = 111, w['R'], 288, 64, 10272 +real = {int(r): z for r, z in e555['nine']['profile'].items()} +pats = Counter(tuple(chain[r]) for r in range(2 * v, 2 * v + R)) +hel0 = hist_of(pats) +dat = hist_of(Counter(tuple(chain[r]) for r in range(2 * v))) +rem = Counter(real) +for h in (dat, hel0, {7: 9}): + for r, z in h.items(): + rem[r] -= z * S +rem = {r: z for r, z in rem.items() if z} +base = Counter({r: z * S for r, z in dat.items()}) +base[7] += 9 * S +c0 = root(real, m, D) +phi = lambda r: r * ((m / r) ** c0 - 1) +F = lambda h, k=S: sum(z * k * phi(r) for r, z in h.items()) +hmass0 = S * sum(r * z for r, z in hel0.items()) +check('E1.#555.root', abs(c0 / 1.733052825e-3 - 1) < 1e-8 and abs(F(real, 1) - D) < 1e-6 * D, + 'root of the published profile %.9e; sum phi = D' % c0) +classes = { + 'A line-born carriers (0,1,2,..)': lambda p: p[1] == 1 and p[2] == 2, + 'B cell-born (0,2,..)': lambda p: p[1] == 2, + 'C line -> quad/D6 keepers and totals (0,1,4|6,..)': lambda p: p[1] == 1 and p[2] != 2, + 'D quad-born (0,4,..)': lambda p: p[1] == 4, +} +for name, fn in classes.items(): + sub = Counter({p: z for p, z in pats.items() if fn(p)}) + h = hist_of(sub) + n, nr, mass = sum(sub.values()), sum(h.values()), sum(r * z for r, z in h.items()) + hel = Counter(hel0) + for r, z in h.items(): + hel[r] -= z + hel[8] += n + c1 = proportional(m, D, base, rem, {r: z * S for r, z in hel.items()}, S * sum(r * z for r, z in hel.items()), hmass0) + got = (n, nr, mass / nr, (nr - n) / n, 100 * F(h) / D, 100 * (c1 / c0 - 1)) + check('E1.#555.' + name.split()[0], near_all(got, EXP555[name], (None, None, 2, 2, 1, 2)), + '%s: n %d, runs %d, mean run %.2f, stops/register %.2f, F-share %.1f%%, all-single-run ceiling %+.2f%% c' + % ((name,) + got)) +for k, rng in (('x', range(v)), ('y', range(v, 2 * v))): + h = hist_of(Counter(tuple(chain[r]) for r in rng)) + nr, mass = sum(h.values()), sum(r * z for r, z in h.items()) + got = (nr, mass / nr, 100 * F(h) / D) + check('E1.#555.data-' + k, near_all(got, EXP555_DATA[k], (None, 2, 1)), 'data %s: runs %d, mean run %.2f, F-share %.1f%%' % ((k,) + got)) +got = (100 * F({7: 9}) / D, 100 * F(rem, 1) / D) +check('E1.#555.copies-remainder', near_all(got, (EXP555_DATA['copies'], EXP555_DATA['remainder']), (1, 1)), + 'copies %.1f%%, idle remainder %.1f%% of sum phi' % got) + +# ------------------------------------------------------------------ #569 (complex) +EXP569 = {'factory': (216, 1304, 1.49, 5.04, 32.5, 16.4), 'face': (168, 696, 1.98, 3.14, 21.9, 9.1), + 'M': (112, 504, 1.43, 3.50, 12.4, 4.9)} +EXP569_OTHER = {'data': 28.3, 'centres': 1.8, 'remainder': 3.0} +sys.path.insert(0, str(main_file('research/direct-delivery504-global/vendor/gk/gk_word.py').parent)) +import gk_common as co # noqa: E402 +import gk_word as g # noqa: E402 +g.V = co.V = 112 +co.DATA = g.DATA = 7168 +V = 112 + + +def chains_gk(word): + """frame-dimension chain per register and the paid runs per register (gk_word.profile semantics: a centre's copy + descents to its served levels are paid at the cut and its later climbs start again from the centre frame).""" + R = word['report']['R'] + n = 2 * V + R + P = word['ports'] + cur = {} + for r in range(n): + if r < V: + cur[r] = (P[r],) + elif r >= 2 * V and str(r) in word.get('start_extra', {}): + cur[r] = g.basis(word['start_extra'][str(r)]) + else: + cur[r] = () + ch = {r: [len(cur[r])] for r in range(n)} + paid = {r: [] for r in range(n)} + + def climb(r, F): + F = g.basis(F) + o = cur[r] + assert g.basis(list(o) + list(F)) == F, 'frame violation' + if o != F: + ch[r].append(len(F)) + paid[r].append(len(F) - len(o)) + cur[r] = F + lev = {int(t): g.basis(E) for t, E in word.get('target_levels', {}).items()} + for seq, part in ((word['A'], 'A'), (word['B'], 'B')): + if part == 'B': + for r, f in zip(word['centers'], word['center_frames']): + o = g.basis(f) + assert cur[r] == o, 'centre frame' + served = sorted({lev.get(e[1] - V, ()) for e in word['cut'] if e[2] == r}, key=lambda E: -len(E)) + for Lv in served: + if Lv != o: + paid[r].append(len(o) - len(Lv)) + o = Lv + for e in seq: + for r in [e[1]] + ([e[2]] if e[0] == 'add' else []): + climb(r, e[-1]) + for r in range(n): + climb(r, g.FULL if (r < V or r >= 2 * V) else g.perp(P[r - V])) + return ch, paid, R, set(word['centers']) + + +ch7, paid7, R, ctr = chains_gk(load_json(VENDOR / 'pr569' / 'width7-eight-FULL-WORD.json.gz')) +chF, paidF, RF, ctrF = chains_gk(load_json(VENDOR / 'pr569' / 'fourcold-eight-FULL-WORD.json.gz')) +runs = lambda paid, regs: Counter(x for r in regs for x in paid[r]) +m, D, CALLS = 73, 2560, 72 +dat = hist_of(Counter(tuple(ch7[r]) for r in range(2 * V))) +cen = runs(paid7, ctr) +hel = hist_of(Counter(tuple(ch7[r]) for r in range(2 * V, 2 * V + R) if r not in ctr)) +locF = runs(paidF, range(2 * V + RF)) +loc7 = runs(paid7, range(2 * V + R)) +calls = load_json(DATA / 'prices' / 'pr569-provider-calls.json')['calls'] # #569's per-call dispatch +n7 = sum(1 for _, prov in calls if prov == 'width7_eight') +assert len(calls) == CALLS and n7 == 63 +rem = {3: 1200, 7: 48, 48: 1808} # #569's idle remainder (its hosting control is recomputed in e2_helper_prices.py) +P = Counter() +for r, z in loc7.items(): + P[r] += z * n7 +for r, z in locF.items(): + P[r] += z * (CALLS - n7) +for r, z in rem.items(): + P[r] += z +PUB = {1: 169056, 2: 41184, 3: 25968, 4: 13824, 5: 4032, 6: 4032, 7: 48, 8: 576, 48: 1808} +check('E1.#569.profile', dict(P) == PUB and 73 * 7168 - sum(r * z for r, z in PUB.items()) == D, + '63 width-7 calls + 9 four-cold calls + idle remainder = the published global profile; D = %d' % D) +b0 = root(PUB, m, D) +phi = lambda r: r * ((m / r) ** b0 - 1) +F = lambda h, k=CALLS: sum(z * k * phi(r) for r, z in h.items()) +helall = Counter({r: z * CALLS for r, z in hel.items()}) +hmass0 = sum(r * z for r, z in helall.items()) +datg = Counter({r: z * CALLS for r, z in dat.items()}) +ceng = Counter({r: z * CALLS for r, z in cen.items()}) +base_b = proportional(m, D, datg + ceng, rem, helall, hmass0, hmass0) +cls = lambda h: 'factory' if h < 224 else ('face' if h < 392 else 'M') +for k in ('factory', 'face', 'M'): + sub = Counter(tuple(ch7[r]) for r in range(2 * V, 2 * V + R) if r not in ctr and cls(r - 2 * V) == k) + h = hist_of(sub) + n, nr, mass = sum(sub.values()), sum(h.values()), sum(r * z for r, z in h.items()) + single = Counter() + for p, z in sub.items(): + single[p[-1] - p[0]] += z + hel2 = Counter(helall) + for r, z in h.items(): + hel2[r] -= z * CALLS + for r, z in single.items(): + hel2[r] += z * CALLS + b2 = proportional(m, D, datg + ceng, rem, hel2, hmass0, hmass0) + got = (n, nr, mass / nr, (nr - n) / n, 100 * F(h) / D, 100 * (b2 / base_b - 1)) + check('E1.#569.' + k, near_all(got, EXP569[k], (None, None, 2, 2, 1, 1)), + '%s: n %d, runs %d, mean run %.2f, stops/register %.2f, F-share %.1f%%, all-single-run ceiling %+.1f%% b' + % ((k,) + got)) +got = (100 * F(dat) / D, 100 * F(cen) / D, 100 * F(rem, 1) / D) +check('E1.#569.data-centres-remainder', near_all(got, tuple(EXP569_OTHER.values()), (1, 1, 1)), + 'data %.1f%%, centres %.1f%%, idle remainder %.1f%% of sum phi' % got) +finish() diff --git a/research/format-limits/scripts/e2_helper_prices.py b/research/format-limits/scripts/e2_helper_prices.py new file mode 100644 index 0000000000..6608d37354 --- /dev/null +++ b/research/format-limits/scripts/e2_helper_prices.py @@ -0,0 +1,362 @@ +"""E2. What a complex helper costs, by class, on the word of PR #569 (DominikScholz). + +Question: by how many percent does the pressure root b of PR #569's global word (published conservative +b = 1.60263181098e-3) improve when ONE helper register of a given class is deleted, in all 72 calls, and the hosting is +re-optimised? The answer is the price of a helper of that class; the published word has 504 helpers per call. + +Data (all derived here; nothing is copied from the original measurement except the checked constants of EXPECTED): + * the two local words of PR #569 (data/vendor/pr569: width7_eight, fourcold_eight; PR #433's FULL-WORD format with the + start_extra / target_levels extension keys that gk_word.py reads) and PR #569's per-call dispatch + (data/prices/pr569-provider-calls.json: 63 calls use width7_eight, 9 use fourcold_eight); + * the word helpers gk_word.py / gk_common.py and PR #523's ASYNC112 hosting windows, files of main. +Per local word, replaying the frame chains register by register gives (i) the paid climbs of every register, hence the +per-call local profile LOC, and (ii) the width of every helper, 9 minus the dimension of its entrance frame, hence the +per-call demand DEM of helper widths to be hosted. For every non-centre helper the local mass (sum of its paid climbs) +equals its width, so a deleted helper takes its width out of the demand together with the same mass out of the local +profile and the total paid mass stays 520704 (D = 73 * 7168 - 520704 = 2560). + +Model of a deletion (hypothetical removal, no dependent registers): helper h leaves every call (in a call of word type +nm its climbs leave LOC[nm] and its width leaves DEM[nm]); the hosting linear programme (e_hosting.py: the relaxation of +the whole-owner hosting model of PR #523's 112-port unit) is re-solved at q = the published b; the new paid profile is the +72 local profiles plus the LP's idle remainders and its b is the moment root b_float (W = 7168). Price = percent change +of b per deleted helper. + +Cells are (role, width in the width7_eight word); the role is told by index range (h < 224 factory scratch registers, +h < 392 face copies, else M copies: 14 blocks of 16 / 12 / 8). k = 1 deletes the single helper of the cell whose local +fee is closest to the cell mean. The recorded values are reproduced to the last printed digit (HiGHS through scipy +1.17; the dual simplex and the interior-point method returned the same idle remainders and b on the cells F9, P8, P7, +M5, M4 when tried once). + +Claims: E2.inputs, E2.cells, E2.control (zero deletion), E2.k1. for the seven cells, E2.width-law, E2.table. +The eight LPs (control and seven cells) run in parallel in at most 6 processes. +""" +import multiprocessing as mp +import os +import sys +import time +from collections import Counter + +os.environ.setdefault('PYTHONDONTWRITEBYTECODE', '1') +sys.dont_write_bytecode = True + +from report import DATA, VENDOR, check, finish, load_json, main_file # noqa: E402 +import e_hosting as hosting # noqa: E402 + +V = 112 # data registers per side; helpers are registers 2 V + h +N_HELPERS = 504 # helpers per local word (h = 0..503) +N_FACTORY, N_FACE = 224, 392 # h < 224 factory scratch, h < 392 face copies, else M copies (14 blocks of 16 / 12 / 8) +M = hosting.M +CALLS = hosting.CALLS +Q_PICK = 1.6026e-3 # q at which the representative helper of a cell is chosen +PROVIDER_FILES = {'width7_eight': 'width7-eight-FULL-WORD.json.gz', 'fourcold_eight': 'fourcold-eight-FULL-WORD.json.gz'} +GK_DIR = 'research/direct-delivery504-global/vendor/gk/gk_word.py' +CELL_ORDER = ['F9', 'P9', 'P8', 'P7', 'M8', 'M5', 'M4'] + +# ---- expected values --------------------------------------------------------------------------------------------- +EXPECTED = { + # per-call local profile (rank: number of paid climbs) of each local word, from the register-by-register replay + 'LOC': {'width7_eight': {1: 2352, 2: 592, 3: 328, 4: 192, 5: 56, 6: 56, 8: 8}, + 'fourcold_eight': {1: 2320, 2: 432, 3: 456, 4: 192, 5: 56, 6: 56, 8: 8}}, + # per-call demand of helper widths (width: number of helpers) + 'DEM': {'width7_eight': {4: 8, 5: 48, 7: 32, 8: 128, 9: 288}, 'fourcold_eight': {4: 8, 5: 48, 8: 160, 9: 288}}, + 'DISPATCH': {'width7_eight': 63, 'fourcold_eight': 9}, + # PR #569's published global profile, W = 7168, and its idle remainder (rank: count) + 'PUB': {1: 169056, 2: 41184, 3: 25968, 4: 13824, 5: 4032, 6: 4032, 7: 48, 8: 576, 48: 1808}, + 'REM': {3: 1200, 7: 48, 48: 1808}, + 'MASS': 520704, 'D': 2560, + 'B_PUBLISHED': 1.602631810982070e-3, # conservative certified b of the published profile (PR #569) + # zero-deletion LP: objective (to 1e-4), idle remainder, b_float printed 1.602631811e-3 + 'CONTROL': {'obj': 78.05857, 'b': 1.602631811e-3}, + # cells: size (non-centre helpers), recorded k = 1 LP objective, b_float, and the price in percent b per helper + # (3 decimals) ; 'share' = LP objective increase / local fee saved, in percent (1 decimal) + 'CELLS': { + 'F9': dict(size=216, obj=78.30161, b=0.0016048723869705752, pct=0.140, share=6.3), + 'P9': dict(size=64, obj=78.30161, b=0.0016047716270276408, pct=0.134, share=6.6), + 'P8': dict(size=72, obj=78.64326, b=0.0016043095787383996, pct=0.105, share=17.9), + 'P7': dict(size=32, obj=78.94317, b=0.001603832889308516, pct=0.075, share=31.5), + 'M8': dict(size=56, obj=78.64326, b=0.0016044102805557185, pct=0.111, share=17.0), + 'M5': dict(size=48, obj=79.39080, b=0.0016032504527875107, pct=0.039, share=57.4), + 'M4': dict(size=8, obj=78.96089, b=0.0016033092416224345, pct=0.042, share=45.4), + }, + # offset share bands in percent by width (k = 1): w9 about 6, w8 about 18, w7 31-55, w5 57-65, w4 about 45 + 'SHARE_BANDS': {9: (5.0, 7.5), 8: (16.0, 19.0), 7: (31.0, 55.0), 5: (57.0, 65.0), 4: (43.0, 47.0)}, +} +TOL_OBJ = 1e-4 # LP objective +TOL_B_REL = 1e-9 # b against the recorded value (LP solutions agree to ~1e-14 across scipy 1.17-1.18) +TOL_TABLE_OBJ = 1e-6 # recorded table against recomputation +TOL_MASS = 1e-6 # rank mass of an LP-derived profile (floating sum of fractional remainders) +TOL_PRINTED = 6e-4 # a value printed with 3 decimals: half a unit of the last digit plus slack +TOL_SHARE = 0.06 # a share printed with 1 decimal +SHARE_ORDER = [9, 8, 7, 5] # offset share increases as the width falls through 9, 8, 7, 5 (width 4 sits between 7 and 5) + + +# ---- word replay -------------------------------------------------------------------------------------------------- +def load_gk(): + """import gk_word / gk_common of main and set them to the 112-port unit (V = 112, DATA = 7168)""" + d = main_file(GK_DIR).parent + sys.path.insert(0, str(d)) + import gk_word as g + import gk_common as co + g.V = co.V = V + g.DATA = co.DATA = hosting.W_DATA + return g + + +def per_register_climbs(word, g): + """paid climbs of every register of a gk word: {register: [climb sizes in order]}. A climb is a change of the + register's frame; its size is the rank of the paid child. This is gk_word.profile with per-register attribution + (the centre registers also pay the descents through the served target levels).""" + R = word['report']['R'] + n = 2 * V + R + P = word['ports'] + cur = {} + for r in range(n): + if r < V: + cur[r] = (P[r],) + elif r >= 2 * V: + cur[r] = g.basis(word['start_extra'][str(r)]) if str(r) in word.get('start_extra', {}) else () + else: + cur[r] = () + paid = {r: [] for r in range(n)} + + def climb(r, F): + F = g.basis(F) + o = cur[r] + assert g.basis(list(o) + list(F)) == F, ('frame violation', r, o, F) + if o != F: + paid[r].append(len(F) - len(o)) + cur[r] = F + lev = {int(t): g.basis(E) for t, E in word.get('target_levels', {}).items()} + for seq, part in ((word['A'], 'A'), (word['B'], 'B')): + if part == 'B': + for r, f in zip(word['centers'], word['center_frames']): + assert cur[r] == g.basis(f), 'centre frame' + served = sorted({lev.get(e[1] - V, ()) for e in word['cut'] if e[2] == r}, key=lambda E: -len(E)) + o = g.basis(f) + for Lv in served: + if Lv != o: + paid[r].append(len(o) - len(Lv)) + o = Lv + for e in seq: + for r in [e[1]] + ([e[2]] if e[0] == 'add' else []): + climb(r, e[-1]) + for r in range(n): + climb(r, g.FULL if (r < V or r >= 2 * V) else g.perp(P[r - V])) + return paid + + +def replay(name, g): + """paid climbs, local profile, helper widths and centres of one provider word""" + word = load_json(VENDOR / 'pr569' / PROVIDER_FILES[name]) + assert word['report']['R'] == N_HELPERS + paid = per_register_climbs(word, g) + loc = Counter() + for r, l in paid.items(): + for c in l: + loc[c] += 1 + se = word.get('start_extra', {}) + width = [9 - len(se[str(2 * V + h)]) if str(2 * V + h) in se else 9 for h in range(N_HELPERS)] + centres = {c - 2 * V for c in word['centers']} + dem = dict(sorted(Counter(width).items())) + return dict(paid=paid, loc=dict(sorted(loc.items())), width=width, dem=dem, centres=centres, + profile_g=dict(sorted(g.profile(word).items())), widths_g=g.widths_of(word)) + + +def role(h, centres): + if h in centres: + return 'C' + return 'F' if h < N_FACTORY else ('P' if h < N_FACE else 'M') + + +def fee(climbs, q): + """pressure fee of a list of paid climbs: sum r ((m / r)^q - 1)""" + return sum(c * ((M / c) ** q - 1) for c in climbs) + + +def pick_representative(cell_helpers, w7): + """the helper of the cell whose local fee (width7_eight word, q = Q_PICK) is closest to the cell mean""" + fees = {h: fee(w7['paid'][2 * V + h], Q_PICK) for h in cell_helpers} + mean = sum(fees.values()) / len(fees) + return min(cell_helpers, key=lambda h: abs(fees[h] - mean)) + + +# ---- scenarios ---------------------------------------------------------------------------------------------------- +def deleted_models(words, prov, dele): + """per-call local profiles and demands when helpers `dele` are deleted in all 72 calls (word type per call)""" + out_l, out_d = [], [] + for t in range(CALLS): + w = words[prov[t]] + l = dict(w['loc']) + d = dict(w['dem']) + for h in dele: + for c in w['paid'][2 * V + h]: + l[c] -= 1 + d[w['width'][h]] -= 1 + assert all(z >= 0 for z in l.values()) and all(z >= 0 for z in d.values()) + out_l.append({r: z for r, z in l.items() if z}) + out_d.append({x: z for x, z in d.items() if z}) + return out_l, out_d + + +def local_fee_saved(words, prov, dele, q): + tot = 0 + for t in range(CALLS): + for h in dele: + tot += fee(words[prov[t]]['paid'][2 * V + h], q) + return tot + + +def rel(a, b): + return abs(a / b - 1) + + +def main(): + t_start = time.time() + ex = EXPECTED + g = load_gk() + + # ---- E2.inputs -------------------------------------------------------------------------------------------- + pc = load_json(DATA / 'prices' / 'pr569-provider-calls.json') + calls = pc['calls'] + assert [c[0] for c in calls] == list(range(CALLS)) + prov = [c[1] for c in calls] + words = {nm: replay(nm, g) for nm in PROVIDER_FILES} + disp = dict(Counter(prov)) + loc_ok = all(words[nm]['loc'] == ex['LOC'][nm] and words[nm]['profile_g'] == ex['LOC'][nm] for nm in words) + dem = {nm: words[nm]['dem'] for nm in words} + dem_ok = all(dem[nm] == ex['DEM'][nm] and words[nm]['widths_g'] == words[nm]['width'] for nm in words) + ident_ok = all(sum(words[nm]['paid'][2 * V + h]) == words[nm]['width'][h] + for nm in words for h in range(N_HELPERS) if h not in words[nm]['centres']) + comp = Counter() + for nm in words: + for r, z in words[nm]['loc'].items(): + comp[r] += z * disp[nm] + for r, z in ex['REM'].items(): + comp[r] += z + comp = dict(sorted(comp.items())) + mass_pub = hosting.mass(comp) + check('E2.inputs', + disp == ex['DISPATCH'] and loc_ok and dem_ok and ident_ok and comp == ex['PUB'] + and mass_pub == ex['MASS'] and hosting.M * hosting.W_DATA - mass_pub == ex['D'], + 'dispatch %s; local profile width7_eight %s, fourcold_eight %s (replay = gk_word.profile); demand %s / %s; ' + 'local mass = width for the 496 non-centre helpers of each word; 63 x width7_eight + 9 x fourcold_eight local ' + 'profiles + idle remainder %s = published profile %s; mass %d, D %d' + % (disp, words['width7_eight']['loc'], words['fourcold_eight']['loc'], dem['width7_eight'], + dem['fourcold_eight'], ex['REM'], comp, mass_pub, hosting.M * hosting.W_DATA - mass_pub)) + + # ---- E2.cells --------------------------------------------------------------------------------------------- + w7 = words['width7_eight'] + cells = {} + for h in range(N_HELPERS): + cells.setdefault((role(h, w7['centres']), w7['width'][h]), []).append(h) + specs = load_json(DATA / 'prices' / 'k1-specs.json')['cells'] + dele, sizes_ok, pick_ok = {}, True, True + for c in CELL_ORDER: + key = (specs[c]['role'], specs[c]['width']) + members = cells[key] + dele[c] = specs[c]['deleted_helpers'] + sizes_ok &= len(members) == ex['CELLS'][c]['size'] and len(dele[c]) == 1 and dele[c][0] in members + pick_ok &= pick_representative(members, w7) == dele[c][0] + check('E2.cells', sizes_ok and pick_ok and len(cells[('F', 9)]) == 216 and len(w7['centres']) == 8, + 'cell sizes %s (+ 8 centre accumulators, not priced); k = 1 helper per cell %s is the helper with the local ' + 'fee closest to the cell mean' + % ({c: ex['CELLS'][c]['size'] for c in CELL_ORDER}, {c: dele[c][0] for c in CELL_ORDER})) + + # ---- the eight LPs in parallel ------------------------------------------------------------------------- + jobs = [] + for name, dl in [('ctl', [])] + [(c, dele[c]) for c in CELL_ORDER]: + out_l, out_d = deleted_models(words, prov, dl) + jobs.append((name, out_l, out_d, ex['B_PUBLISHED'])) + nproc = min(6, len(jobs)) + t_lp = time.time() + with mp.get_context('spawn').Pool(nproc) as pool: + res = {r['name']: r for r in pool.imap_unordered(hosting.run_job, jobs)} + wall_lp = time.time() - t_lp + bad = [n for n, r in res.items() if r['status'] != 0] + if bad: + check('E2.control', False, 'LP failed for %s: %s' % (bad, [res[n]['status'] for n in bad])) + finish() + return + + # ---- E2.control --------------------------------------------------------------------------------------------- + c0 = res['ctl'] + b0, obj0 = c0['b'], c0['obj'] + rem_ok = set(c0['rem']) == set(ex['REM']) and all(abs(c0['rem'][r] - z) < 1e-6 for r, z in ex['REM'].items()) + p_pub = {r: float(z) for r, z in ex['PUB'].items()} + b_pub_profile = hosting.b_float(p_pub, hosting.W_DATA) + ctl_ok = (abs(obj0 - ex['CONTROL']['obj']) < TOL_OBJ and rem_ok and rel(b0, ex['CONTROL']['b']) < 1e-9 + and rel(b0, ex['B_PUBLISHED']) < 1e-12 and rel(b_pub_profile, b0) < 1e-12 and abs(c0['mass'] - ex['MASS']) < TOL_MASS) + check('E2.control', ctl_ok, + 'zero-deletion LP objective %.5f, idle remainder %s, b_float %.9e = published conservative b %.12e ' + '(relative difference %.1e); b_float of the published profile %.9e; (%.0f s)' + % (obj0, {r: round(z, 6) for r, z in c0['rem'].items()}, b0, ex['B_PUBLISHED'], rel(b0, ex['B_PUBLISHED']), + b_pub_profile, c0['secs'])) + + # ---- E2.k1. ------------------------------------------------------------------------------------------- + shares, lfs = {}, {} + for c in CELL_ORDER: + r, e = res[c], ex['CELLS'][c] + pct = 100 * (r['b'] / b0 - 1) + lf = local_fee_saved(words, prov, dele[c], b0) + lfs[c] = lf + shares[c] = 100 * (r['obj'] - obj0) / lf + ok = (abs(r['obj'] - e['obj']) < TOL_OBJ and rel(r['b'], e['b']) < TOL_B_REL and abs(pct - e['pct']) < TOL_PRINTED + and abs(r['mass'] - ex['MASS']) < TOL_MASS) + check('E2.k1.' + c, ok, + 'delete helper %d (cell of %d): %.4f%% b per helper (printed %.3f); LP objective %.5f (+%.5f); b %.12e ' + '(recorded %.12e, relative difference %.1e); (%.0f s)' + % (dele[c][0], e['size'], pct, e['pct'], r['obj'], r['obj'] - obj0, r['b'], e['b'], rel(r['b'], e['b']), + r['secs'])) + + # ---- E2.width-law ------------------------------------------------------------------------------------------- + width_of = {c: specs[c]['width'] for c in CELL_ORDER} + dobj = {c: res[c]['obj'] - obj0 for c in CELL_ORDER} + same = all(abs(dobj[a] - dobj[b]) < 1e-9 for a in CELL_ORDER for b in CELL_ORDER if width_of[a] == width_of[b]) + bands = all(ex['SHARE_BANDS'][width_of[c]][0] <= shares[c] <= ex['SHARE_BANDS'][width_of[c]][1] + for c in CELL_ORDER) + printed = all(abs(shares[c] - ex['CELLS'][c]['share']) < TOL_SHARE for c in CELL_ORDER) + by_w = {} + for c in CELL_ORDER: + by_w.setdefault(width_of[c], []).append(shares[c]) + mono = all(max(by_w[SHARE_ORDER[i]]) < min(by_w[SHARE_ORDER[i + 1]]) for i in range(len(SHARE_ORDER) - 1)) + # price in fee units per percent of b: (local fee saved - LP objective increase) / (percent b) + conv = {c: (lfs[c] - dobj[c]) / (100 * (res[c]['b'] / b0 - 1)) for c in CELL_ORDER} + conv_ok = max(conv.values()) / min(conv.values()) < 1.02 + check('E2.width-law', same and bands and printed and mono and conv_ok, + 'hosting offset = LP objective increase / local fee saved at k = 1 (percent): ' + + ', '.join('%s (w%d) %.1f' % (c, width_of[c], shares[c]) for c in CELL_ORDER) + + '; equal widths give an identical LP objective increase (%s); the share rises as the width falls through ' + '9, 8, 7, 5 with width 4 (45) between 7 and 5; fee saved less offset per percent b (one conversion constant ' + 'for all cells, within 2%%): %.2f..%.2f' + % (', '.join('w%d %.5f' % (w, dobj[next(c for c in CELL_ORDER if width_of[c] == w)]) for w in (9, 8)), + min(conv.values()), max(conv.values()))) + + # ---- E2.table ----------------------------------------------------------------------------------------------- + tab = load_json(DATA / 'prices' / 'helper-price-table.json') + rows = {r['name']: r for r in tab['rows']} + rc = tab['control'] + tab_ok = (abs(rc['lp_objective'] - obj0) < TOL_TABLE_OBJ and rel(rc['b'], b0) < TOL_B_REL + and all(abs(rc['idle_remainder'][str(r)] - z) < 1e-6 for r, z in c0['rem'].items())) + for c in CELL_ORDER: + rw, r = rows[c + '_k1'], res[c] + tab_ok &= (rw['status'] == tab['status_recomputed'] and rw['deleted_helpers'] == dele[c] + and abs(rw['lp_objective'] - r['obj']) < TOL_TABLE_OBJ and rel(rw['b'], r['b']) < TOL_B_REL + and abs(rw['mass'] - r['mass']) < TOL_MASS and set(map(int, rw['idle_remainder'])) == set(r['rem']) + and all(abs(rw['idle_remainder'][str(k)] - z) < 1e-6 for k, z in r['rem'].items())) + tab_ok &= all(rw['status'] == (tab['status_recomputed'] if rw['name'] in {c + '_k1' for c in CELL_ORDER} + else tab['status_recorded']) for rw in tab['rows']) + tab_ok &= all(rel(100 * (rw['b'] / rc['b'] - 1) / rw['k'], rw['pct_b_per_helper']) < 1e-9 if rw['k'] else True + for rw in tab['rows']) + check('E2.table', tab_ok, + '%d recorded rows in data/prices/helper-price-table.json (all cells at k = 1, 8, 14, whole cell; mixed classes; ' + 'sub-classes): the control and the seven k = 1 rows equal the recomputation, the other rows are marked ' + '"%s", every row\'s percent per helper is consistent with its b' + % (len(tab['rows']), tab['status_recorded'])) + + print('total %.1f s (hosting LPs %.1f s wall in %d processes; machine load %s)' + % (time.time() - t_start, wall_lp, nproc, ' '.join('%.1f' % x for x in os.getloadavg()))) + finish() + + +if __name__ == '__main__': + main() diff --git a/research/format-limits/scripts/e8units.py b/research/format-limits/scripts/e8units.py new file mode 100644 index 0000000000..c4c1053bd1 --- /dev/null +++ b/research/format-limits/scripts/e8units.py @@ -0,0 +1,275 @@ +"""Geometry shared by the E8 / paired-cube checks of research/format-limits: exact linear algebra over F_2 and Q, +the 120 root lines of E8, and the 9-bit labels of a complex or bit unit. Everything is generated here; nothing is +read from a file. + +Conventions. A 'unit' on v ports has an integer Gram matrix G (diagonal 2 for E8) and F_2 labels u_T in F_2^c with +U U^T = (J + G) mod 2, i.e. = 1 + G_ST (mod 2). Vectors of F_2^n are Python integers (bit j = coordinate j) +or 0/1 numpy rows; both representations are used below and the function names say which. +Two coordinate systems of the same 120 E8 root lines are provided. standard_e8(): doubled standard coordinates +(roots +-2e_i +-2e_j and the half-integer (+-1)^8 with an even number of minus signs, G = L L^T/4); the totals +'coordinate a' used for the complex decoder are the 78 lines with a nonzero a-th coordinate. hamming_e8(): lines +numbered as in the Hamming-code construction: + lines 0..7 : the coordinate lines 2 e_a, + lines 8..119 : the 112 'code lines', for each of the 14 weight-4 words of the extended Hamming code [8,4,4] + (support i0 integer with bit j = row[j].""" + return sum(int(x) << j for j, x in enumerate(row)) + + +# --------------------------------------------------------------------------------------------------------------- +# exact rank over Q +def _reduce_int(r, pc, rr): + """r - (r[pc]/rr[pc]) rr, scaled to stay integral and divided by the content.""" + f, g = r[pc], rr[pc] + r = [g * x - f * y for x, y in zip(r, rr)] + c = math.gcd(*r) + return [x // c for x in r] if c > 1 else r + + +def rank_q(rows): + """exact rank over Q of a list of integer rows (fraction-free elimination).""" + red = [] # (pivot column, row) + for r in rows: + r = [int(x) for x in r] + for pc, rr in red: + if r[pc] != 0: + r = _reduce_int(r, pc, rr) + pc = next((j for j, x in enumerate(r) if x != 0), None) + if pc is not None: + red.append((pc, r)) + return len(red) + + +def row_basis_idx_q(rows): + """indices of a maximal independent set of rows over Q (greedy, in order).""" + chosen, red = [], [] + for i, r in enumerate(rows): + r = [int(x) for x in r] + for pc, rr in red: + if r[pc] != 0: + r = _reduce_int(r, pc, rr) + pc = next((j for j, x in enumerate(r) if x != 0), None) + if pc is not None: + red.append((pc, r)) + chosen.append(i) + return chosen + + +def gram_rank_q(L, Q, S): + """rank over Q of the Gram block L_S Q L_S^T (L integer v x h, Q integer h x h, S index list), computed on a + row basis B of L_S: rank(L_S Q L_S^T) = rank(B Q B^T) because L_S = C B with C of full column rank.""" + LS = [[int(x) for x in L[s]] for s in S] + idx = row_basis_idx_q(LS) + if not idx: + return 0 + B = [LS[i] for i in idx] + Qm = [[int(x) for x in row] for row in Q] + BQ = [[sum(b[k] * Qm[k][j] for k in range(len(b))) for j in range(len(Qm[0]))] for b in B] + BQBt = [[sum(x * y for x, y in zip(bq, b2)) for b2 in B] for bq in BQ] + return rank_q(BQBt) + + +# --------------------------------------------------------------------------------------------------------------- +# E8 +def hamming_e8(): + """(L, G): L = 120 x 8 integer root lines (norm 4), G = L L^T / 2 (diagonal 2, off-diagonal 0, +-1).""" + gen = np.array([[1, 1, 1, 1, 0, 0, 0, 0], [0, 0, 1, 1, 1, 1, 0, 0], [0, 0, 0, 0, 1, 1, 1, 1], + [0, 1, 0, 1, 0, 1, 0, 1]]) + code = {tuple((np.array(c) @ gen) % 2) for c in itertools.product([0, 1], repeat=4)} + w4 = sorted(c for c in code if sum(c) == 4) + assert len(w4) == 14 + L = [tuple(2 * (np.arange(8) == a).astype(int)) for a in range(8)] + for c in w4: + idx = [i for i in range(8) if c[i]] + for sg in itertools.product([1, -1], repeat=3): + x = np.zeros(8, dtype=int) + x[idx[0]] = 1 + for k in range(3): + x[idx[k + 1]] = sg[k] + L.append(tuple(x)) + L = np.array(L, dtype=np.int64) + G = L @ L.T + assert (G % 2 == 0).all() + G = G // 2 + assert len(L) == 120 and set(np.diag(G)) == {2} and set(G[~np.eye(120, dtype=bool)].tolist()) == {-1, 0, 1} + return L, G + + +def standard_e8(): + """(L, G): the 120 root lines of E8 in doubled standard coordinates (norm 8), G = L L^T / 4. Lines 0..55 are + +-2e_i +-2e_j (i column + nvar = 0 + ylist = [] # (g, pattern, total width, cost, column) + for gi, (orp, i, r, ts) in enumerate(G): + ws, pats = patterns(r, widths, len(ts)) + for p in pats: + size = sum(c * w for c, w in zip(p, ws)) + ylist.append((gi, p, size, f_cost(r - size, q), nvar)) + nvar += 1 + for gi, (orp, i, r, ts) in enumerate(G): + for t in ts: + for w in widths: + xvars[gi, t, w] = nvar + nvar += 1 + c = np.zeros(nvar) + for gi, p, size, co, col in ylist: + c[col] = co + rows, cs, vs, beq = [], [], [], [] + nr = 0 + ybyg = defaultdict(list) + for gi, p, size, co, col in ylist: + ybyg[gi].append((p, col)) + for gi in range(len(G)): # sum_pattern y = P + for p, col in ybyg[gi]: + rows.append(nr); cs.append(col); vs.append(1.0) + beq.append(float(PORTS)); nr += 1 + for gi, (orp, i, r, ts) in enumerate(G): # hosted helpers of width w = helpers the patterns provide + for w in widths: + for t in ts: + rows.append(nr); cs.append(xvars[gi, t, w]); vs.append(1.0) + for p, col in ybyg[gi]: + n = p[widx[w]] + if n: + rows.append(nr); cs.append(col); vs.append(-float(n)) + beq.append(0.0); nr += 1 + gt = defaultdict(list) + for gi, (orp, i, r, ts) in enumerate(G): + for t in ts: + gt[t].append(gi) + for t in range(CALLS): # demand + for w in widths: + for gi in gt[t]: + rows.append(nr); cs.append(xvars[gi, t, w]); vs.append(1.0) + beq.append(float(dem_t[t].get(w, 0))); nr += 1 + Aeq = sp.csr_matrix((vs, (rows, cs)), shape=(nr, nvar)) + rows, cs, vs, bub = [], [], [], [] + nu = 0 + for gi, (orp, i, r, ts) in enumerate(G): # one helper per port family and call + for t in ts: + for w in widths: + rows.append(nu); cs.append(xvars[gi, t, w]); vs.append(1.0) + bub.append(float(PORTS)); nu += 1 + Aub = sp.csr_matrix((vs, (rows, cs)), shape=(nu, nvar)) + t0 = time.time() + res = linprog(c, A_ub=Aub, b_ub=bub, A_eq=Aeq, b_eq=beq, bounds=(0, None), method='highs', + options=dict(time_limit=time_limit)) + out = dict(status=int(res.status), obj=res.fun, secs=time.time() - t0) + if res.status == 0: + rem = defaultdict(float) + for gi, p, size, co, col in ylist: + v = res.x[col] + if v > 1e-9 and G[gi][2] - size > 0: + rem[G[gi][2] - size] += v + out['rem'] = dict(sorted(rem.items())) + return out + + +def mass(p): + return sum(r * z for r, z in p.items()) + + +def b_float(p, W): + lo, hi = 0., .01 + for _ in range(100): + q = (lo + hi) / 2 + mom = (sum(r * z / (M * W) * (M / r) ** q for r, z in p.items()) + + 32 * M * sum(p.values()) / (10 ** 16 * W) * M ** q) + if mom < 1: + lo = q + else: + hi = q + return (lo + hi) / 2 + + +def profile_with_remainder(local_profiles, rem): + """paid profile = sum of the per-call local profiles + the LP's idle remainders (rank -> count)""" + p = defaultdict(float) + for l in local_profiles: + for r, z in l.items(): + p[r] += z + for r, z in rem.items(): + p[int(r)] += float(z) + return {r: z for r, z in p.items() if z > 1e-12} + + +def run_job(job): + """worker for a process pool: job = (name, local profiles per call, demands per call, q); solves the hosting LP, + builds the paid profile and its b""" + name, out_l, out_d, q = job + o = solve_lp(out_d, q) + if o['status'] != 0: + return dict(name=name, status=o['status'], secs=o['secs']) + p = profile_with_remainder(out_l, o['rem']) + return dict(name=name, status=0, obj=o['obj'], rem={int(k): float(v) for k, v in o['rem'].items()}, + b=b_float(p, W_DATA), mass=mass(p), secs=o['secs']) diff --git a/research/format-limits/scripts/minrank_sat.py b/research/format-limits/scripts/minrank_sat.py new file mode 100644 index 0000000000..99a4a8c833 --- /dev/null +++ b/research/format-limits/scripts/minrank_sat.py @@ -0,0 +1,133 @@ +"""SAT encodings for the F_2 pairing of a decoder pattern. + +A pattern is a set of unordered 'off-cap' pairs {S, T} of ports and the question is whether there are words +u_T (and, in the asymmetric case, v_T) in F_2^c with + = 1 (unit diagonal) + = = 0 for every off-cap pair {S, T} +(symmetric case v = u). Such words exist iff the pairing matrix B = V U^T (B_TS = ) has unit diagonal, +vanishes on the pattern, and has F_2 rank at most c; the smallest c is the F_2 'minrank' of the pattern. + +Symmetry breaking. A set K of pairwise off-cap ports has B[K,K] = I forced, so U_K and V_K are invertible; the action +(U, V) -> (U P, V P^-T) of GL(c, F_2) leaves B fixed and can bring U_K to [I_|K| | 0] and V_K to [I_|K| | 0] +(for |K| = c this is U_K = V_K = I; for |K| < c the last columns of V_K are cleared with the remaining freedom of P). +In the symmetric case O(c, F_2) is transitive on orthonormal bases, so U_K = V_K = [I | 0] as well. + +solve_minrank() is the plain minrank question (gauge only when |K| = c). solve_asym() adds the gauge for any +|K| <= c and the extra constraints 'B is not symmetric' (some free pair has B_TS != B_ST) or 'B differs from B0 = +(J + G) mod 2' (some free pair), and optionally fixes U to given labels. The solver is Cadical 1.5.3 through pysat. +""" +import time + +import numpy as np +from pysat.solvers import Cadical153 + + +class CNF: + def __init__(self): + self.n = 0 + self.cl = [] + + def new(self): + self.n += 1 + return self.n + + def AND(self, a, b): + z = self.new() + self.cl += [[-z, a], [-z, b], [z, -a, -b]] + return z + + def XOR(self, a, b): + z = self.new() + self.cl += [[-z, a, b], [-z, -a, -b], [z, -a, b], [z, a, -b]] + return z + + def xorlist(self, lits): + acc = lits[0] + for l in lits[1:]: + acc = self.XOR(acc, l) + return acc + + def parity(self, lits, val): + """add the constraint xor(lits) = val.""" + if not lits: + if val: + self.cl.append([]) + return + acc = self.xorlist(lits) + self.cl.append([acc] if val else [-acc]) + + +def build(v, offpairs, c, sym, clique): + f = CNF() + U = [[f.new() for _ in range(c)] for _ in range(v)] + V = U if sym else [[f.new() for _ in range(c)] for _ in range(v)] + for T in range(v): + f.parity([f.AND(U[T][j], V[T][j]) for j in range(c)], 1) + for (S, T) in offpairs: + f.parity([f.AND(U[S][j], V[T][j]) for j in range(c)], 0) + if not sym: + f.parity([f.AND(U[T][j], V[S][j]) for j in range(c)], 0) + if clique and len(clique) == c: # U_K V_K^T = I => WLOG U_K = V_K = I + for i, T in enumerate(clique): + for j in range(c): + f.cl.append([U[T][j]] if j == i else [-U[T][j]]) + if not sym: + f.cl.append([V[T][j]] if j == i else [-V[T][j]]) + return f + + +def solve_minrank(v, offpairs, c, sym, clique): + """('SAT'|'UNSAT', seconds, variables, clauses) for words in F_2^c on the pattern.""" + f = build(v, offpairs, c, sym, clique) + s = Cadical153(bootstrap_with=f.cl) + t = time.time() + r = s.solve() + return ('SAT' if r else 'UNSAT'), round(time.time() - t, 1), f.n, len(f.cl) + + +def solve_asym(v, pattern, B0, c, mode, K, U_fixed=None): + """Existence of source labels U and sink labels W (v x c over F_2), B = W U^T, with B_TT = 1, B_TS = 0 on the + ordered pairs of `pattern`, gauge U_K = W_K = [I | 0] on the pairwise off-cap set K, and + mode 'none' : no further condition, + mode 'nonsym' : B_TS != B_ST for some free pair (T, S), + mode 'neB0' : B_TS != B0_TS for some free pair. + U_fixed (v x c 0/1 array) additionally fixes U (then K's gauge is dropped). Returns a dict with the result and, + when satisfiable, the pairing B = W U^T and its F_2 rank.""" + f = CNF() + U = [[f.new() for _ in range(c)] for _ in range(v)] + W = [[f.new() for _ in range(c)] for _ in range(v)] + Bvar = {} + + def B(T, S): + if (T, S) not in Bvar: + Bvar[(T, S)] = f.xorlist([f.AND(W[T][j], U[S][j]) for j in range(c)]) + return Bvar[(T, S)] + for T in range(v): + f.cl.append([B(T, T)]) + for (T, S) in pattern: + f.cl.append([-B(T, S)]) + if U_fixed is not None: + for T in range(v): + for j in range(c): + f.cl.append([U[T][j]] if U_fixed[T][j] else [-U[T][j]]) + else: + for i, T in enumerate(K): + for j in range(c): + f.cl.append([U[T][j]] if j == i else [-U[T][j]]) + f.cl.append([W[T][j]] if j == i else [-W[T][j]]) + free = [(T, S) for T in range(v) for S in range(v) if T != S and (T, S) not in pattern] + if mode == 'nonsym': + f.cl.append([f.XOR(B(T, S), B(S, T)) for (T, S) in free if T < S]) + elif mode == 'neB0': + f.cl.append([B(T, S) if B0[T][S] == 0 else -B(T, S) for (T, S) in free]) + s = Cadical153(bootstrap_with=f.cl) + t = time.time() + r = s.solve() + out = dict(c=c, mode=mode, result='SAT' if r else 'UNSAT', sec=round(time.time() - t, 1), vars=f.n, clauses=len(f.cl)) + if r: + m = set(x for x in s.get_model() if x > 0) + Uv = np.array([[1 if U[T][j] in m else 0 for j in range(c)] for T in range(v)], dtype=np.int64) + Wv = np.array([[1 if W[T][j] in m else 0 for j in range(c)] for T in range(v)], dtype=np.int64) + out['B'] = (Wv @ Uv.T) % 2 + out['U'], out['W'] = Uv, Wv + return out diff --git a/research/format-limits/scripts/provenance.py b/research/format-limits/scripts/provenance.py new file mode 100644 index 0000000000..8a43afd4dc --- /dev/null +++ b/research/format-limits/scripts/provenance.py @@ -0,0 +1,62 @@ +"""Step 0 of verify.sh: the package's files, its vendored inputs and the files of main it reads. + +MANIFEST.sha256 every file of the package (except MANIFEST.sha256 itself) with its sha256; a mismatch, a missing file + or an unlisted file fails. +SOURCES.json every file under data/vendor/ with the PR, author, commit and path it was copied from and the git blob + id of the original file (for a .gz copy: of the decompressed content); the blob id is recomputed here, + so anyone can compare it with `git rev-parse :`. +DEPENDS.json the files of main the checks read, by sha256. +""" +import gzip +import hashlib +import json +import os +import sys + +from report import PKG, REPO, check, finish + + +def sha256(p): + return hashlib.sha256(p.read_bytes()).hexdigest() + + +def blob_id(data): + return hashlib.sha1(b'blob %d\0' % len(data) + data).hexdigest() + + +listed, bad = set(), [] +for line in (PKG / 'MANIFEST.sha256').read_text().splitlines(): + h, rel = line.split(' ', 1) + listed.add(rel) + p = PKG / rel + if not p.is_file() or sha256(p) != h: + bad.append(rel) +present = set() +for root, dirs, files in os.walk(PKG): + dirs[:] = [d for d in dirs if d != '__pycache__'] + for f in files: + rel = os.path.relpath(os.path.join(root, f), PKG) + if rel != 'MANIFEST.sha256': + present.add(rel) +unlisted = sorted(present - listed) +check('0.manifest', not bad and not unlisted, + '%d files match MANIFEST.sha256' % len(listed) if not bad and not unlisted + else 'mismatch or missing: %s; unlisted: %s' % (bad, unlisted)) + +src = json.loads((PKG / 'SOURCES.json').read_text()) +vend = sorted(os.path.relpath(os.path.join(r, f), PKG) for r, _, fs in os.walk(PKG / 'data' / 'vendor') for f in fs) +bad = [] +for e in src['files']: + data = (PKG / e['file']).read_bytes() + if e['file'].endswith('.gz') and not e['source_path'].endswith('.gz'): + data = gzip.decompress(data) + if blob_id(data) != e['blob']: + bad.append(e['file']) +check('0.sources', not bad and sorted(e['file'] for e in src['files']) == vend, + '%d vendored files match the git blob ids of their PR commits' % len(vend) if not bad else 'blob mismatch: %s' % bad) + +dep = json.loads((PKG / 'DEPENDS.json').read_text()) +bad = [d['path'] for d in dep['files'] if not (REPO / d['path']).is_file() or sha256(REPO / d['path']) != d['sha256']] +check('0.depends', not bad, '%d files of main match DEPENDS.json (repository root %s)' % (len(dep['files']), REPO) + if not bad else 'missing or changed: %s' % bad) +finish() diff --git a/research/format-limits/scripts/report.py b/research/format-limits/scripts/report.py new file mode 100644 index 0000000000..5593062527 --- /dev/null +++ b/research/format-limits/scripts/report.py @@ -0,0 +1,60 @@ +"""Shared paths and PASS/FAIL reporting for the checks of research/format-limits. + +Every check prints one line per claim: 'PASS ' or 'FAIL ', and finish() exits +with status 1 if any claim failed. Paths: PKG is this package, DATA its data/ directory, REPO the repository root +(the package lives at REPO/research/format-limits; files of main are read from REPO and pinned in DEPENDS.json). +""" +import gzip +import json +import os +import pathlib +import sys + +HERE = pathlib.Path(__file__).resolve().parent +PKG = HERE.parent +DATA = PKG / 'data' +VENDOR = DATA / 'vendor' +REPO = pathlib.Path(os.environ.get('FORMAT_LIMITS_REPO', PKG.parent.parent)).resolve() + +_failed = [] + + +def check(cid, ok, detail=''): + print(('PASS ' if ok else 'FAIL ') + cid + (' ' + str(detail) if detail != '' else ''), flush=True) + if not ok: + _failed.append(cid) + return ok + + +def near(x, expected, places): + """x agrees with a value printed to `places` decimals: |x - expected| <= 0.6 * 10^-places (half a unit of the last + printed digit plus slack, so a float computed with a different library build cannot flip a rounding boundary).""" + return abs(float(x) - float(expected)) <= 0.6 * 10.0 ** (-places) + + +def near_all(xs, expected, places): + xs, expected = list(xs), list(expected) + return len(xs) == len(expected) and all( + (x == e) if isinstance(e, int) and not isinstance(e, bool) and p is None else near(x, e, p) + for x, e, p in zip(xs, expected, places)) + + +def finish(): + if _failed: + print('FAILED CLAIMS: ' + ' '.join(_failed), flush=True) + sys.exit(1) + + +def load_json(path): + path = pathlib.Path(path) + if path.suffix == '.gz': + return json.loads(gzip.decompress(path.read_bytes())) + return json.loads(path.read_text()) + + +def main_file(rel): + """a file of the repository's main branch (pinned by sha256 in DEPENDS.json).""" + p = REPO / rel + if not p.exists(): + sys.exit('missing file of main: %s (run from a checkout of the whole repository)' % rel) + return p diff --git a/research/format-limits/scripts/run_all.py b/research/format-limits/scripts/run_all.py new file mode 100644 index 0000000000..9482f0749b --- /dev/null +++ b/research/format-limits/scripts/run_all.py @@ -0,0 +1,78 @@ +"""Run every check of research/format-limits, a few at a time, and print PASS/FAIL per claim in a fixed order. + +usage: run_all.py OUTPUT_DIR [JOBS] +Each check script runs in its own process with its output in OUTPUT_DIR/logs/