From 6077fa76950facbce11ec2f04dfbc93493114a44 Mon Sep 17 00:00:00 2001 From: higherbeing Date: Sun, 11 Oct 2026 19:51:21 -0400 Subject: [PATCH] Research note: open asynchronous whole-owner allocator and a delivery-layer map of the complex word Tools: aggregate CP-SAT allocator with lazy nomination cuts, literal nominator, profile composer; validated against PR505's published owner map and PR512's endpoint. Analysis: exact symbolic replay of the PR433/PR484/PR507 complex words (helper roles, duplicates, consumers, target frame chains, merge test) and a correction of a plane-section lower-bound argument. Co-Authored-By: Claude Fable 5.1 --- .../LICENSE | 201 ++++++++++++ .../MANIFEST.json | 19 ++ .../README.md | 101 ++++++ .../code/delivery_layer.py | 94 ++++++ .../code/plane_cover.py | 85 +++++ .../allocator-pr507-widths-offset6.json | 310 ++++++++++++++++++ .../allocator-pr507-widths-timing-free.json | 310 ++++++++++++++++++ .../results/allocator-validation-pr505.json | 310 ++++++++++++++++++ .../results/delivery-layer-433.json | 51 +++ .../results/delivery-layer-484.json | 54 +++ .../results/delivery-layer-507.json | 54 +++ .../results/plane-cover-433.json | 23 ++ .../results/plane-cover-484.json | 23 ++ .../results/plane-cover-507.json | 23 ++ .../tools/aggregate_milp.py | 58 ++++ .../tools/compose_profile.py | 29 ++ .../tools/loop.py | 95 ++++++ .../tools/nominate2.py | 32 ++ 18 files changed, 1872 insertions(+) create mode 100644 research/complex-delivery-map-open-allocator/LICENSE create mode 100644 research/complex-delivery-map-open-allocator/MANIFEST.json create mode 100644 research/complex-delivery-map-open-allocator/README.md create mode 100644 research/complex-delivery-map-open-allocator/code/delivery_layer.py create mode 100644 research/complex-delivery-map-open-allocator/code/plane_cover.py create mode 100644 research/complex-delivery-map-open-allocator/results/allocator-pr507-widths-offset6.json create mode 100644 research/complex-delivery-map-open-allocator/results/allocator-pr507-widths-timing-free.json create mode 100644 research/complex-delivery-map-open-allocator/results/allocator-validation-pr505.json create mode 100644 research/complex-delivery-map-open-allocator/results/delivery-layer-433.json create mode 100644 research/complex-delivery-map-open-allocator/results/delivery-layer-484.json create mode 100644 research/complex-delivery-map-open-allocator/results/delivery-layer-507.json create mode 100644 research/complex-delivery-map-open-allocator/results/plane-cover-433.json create mode 100644 research/complex-delivery-map-open-allocator/results/plane-cover-484.json create mode 100644 research/complex-delivery-map-open-allocator/results/plane-cover-507.json create mode 100644 research/complex-delivery-map-open-allocator/tools/aggregate_milp.py create mode 100644 research/complex-delivery-map-open-allocator/tools/compose_profile.py create mode 100644 research/complex-delivery-map-open-allocator/tools/loop.py create mode 100644 research/complex-delivery-map-open-allocator/tools/nominate2.py diff --git a/research/complex-delivery-map-open-allocator/LICENSE b/research/complex-delivery-map-open-allocator/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/research/complex-delivery-map-open-allocator/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/complex-delivery-map-open-allocator/MANIFEST.json b/research/complex-delivery-map-open-allocator/MANIFEST.json new file mode 100644 index 0000000000..955680117a --- /dev/null +++ b/research/complex-delivery-map-open-allocator/MANIFEST.json @@ -0,0 +1,19 @@ +{ + "LICENSE": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4", + "README.md": "071f863c61d2ad456ab92518a355fa38adb640c7a1505690f3284e1a78a5a3fe", + "code/delivery_layer.py": "b48f498185e0c0d28a7a88dd37255bd18fe6648ed0c006d17444c925d304ac21", + "code/plane_cover.py": "dbc3929ce5ec595b280cf60e90797829e416d260fd1ee8600e7560ebee563a58", + "results/allocator-pr507-widths-offset6.json": "b63f53ca79055fed4a036f42e0f4cc66e4a8f71a0fa9771602170ddd2a885a91", + "results/allocator-pr507-widths-timing-free.json": "c216cbf2aea3594885c9edc0948aa1bdcec85337bf6ed356714e4325ce12029c", + "results/allocator-validation-pr505.json": "1c8255bd5b39796c1637f2a2c88ff61f5dfa8c8de01e7411caf703c90d6a78bd", + "results/delivery-layer-433.json": "0e2767ff70444dc3aafc660764c892680513bff1a8e9b71af9f2321883880b5d", + "results/delivery-layer-484.json": "b9f321af2f55e989716c7ccde5345673b8e0c89eed85ac4f88969b6dbc606712", + "results/delivery-layer-507.json": "b27912d3be0ccf131967338f3b63bcd910ae5da7bb49b5ad99bf22b8eb6fceb2", + "results/plane-cover-433.json": "d4c7013756d4b8861ae07d0fa8b34ee5de666343268a37c93fe74a531b8817b1", + "results/plane-cover-484.json": "f6bdacb2799faaec8bcf9e6d0fe3aa5699eee120edfa3356ce8eac1c2e2728ab", + "results/plane-cover-507.json": "30391112018c3867b628674524eb7ed59740ce2e5eb3cabd7e412bbee876d974", + "tools/aggregate_milp.py": "b3a814b69953d1dd9fffeb8797dc7a0e8e229dc1b8de9487f1bfee7fdc8db7f9", + "tools/compose_profile.py": "75527659062ee1389c664dec59542d6c8d96c6d8ca913d40e9657c1093a1d4e9", + "tools/loop.py": "dad9c7c99e8647fdfbf391fb4ab25fd63e8e8cffc24a4747f484d50cd41311a1", + "tools/nominate2.py": "c73a7f375bb8a330e8cd378c4c35c61a41cd2eec4c0cba13254ac00519a967e0" +} diff --git a/research/complex-delivery-map-open-allocator/README.md b/research/complex-delivery-map-open-allocator/README.md new file mode 100644 index 0000000000..addc24a99f --- /dev/null +++ b/research/complex-delivery-map-open-allocator/README.md @@ -0,0 +1,101 @@ +# Open whole-owner allocator and a delivery-layer map of the complex word + +Research note and tools. Prepared by **higherbeing** with Claude Code (Anthropic) assistance. Apache-2.0. + +Two things the κ race has so far kept as frozen witnesses or as prose are released here as runnable, checked code: + +1. **An open asynchronous whole-owner allocator** (`tools/`): the optimisation that turns a local complex word's + residual widths and a replica call order into a literal owner map of the kind #505, #508, #512, #514 and #517 ship as + `ASYNC-OWNERS.json.gz`. Validated: on #505's own call order and widths it returns #505's published remainder profile + exactly as the optimum (`results/allocator-validation-pr505.json`); on #507's widths under #508's offset-six order it + reaches the timing-free ceiling, and the resulting profile gives #512's complex endpoint `b = 0.001389978441615608` through the pinned moment engine. +2. **A map of where the 560 complex helpers go** (`code/`, `results/`): exact symbolic replay of the #433, #484 and #507 + words, classifying every helper by role, duplicate structure and consumer, with the frame-chain structure of the + targets, a merge-legality test, and one correction to a tempting lower-bound argument. + +## 1. The allocator + +`tools/aggregate_milp.py` is the aggregate model (CP-SAT): variables `x[t,i,ρ,w]` = number of width-`w` helpers of the +call at time `t` hosted on idle child `i` of owner replica `ρ`, and `y[i,ρ,P]` = number of that child's 120 ports tiled by +the width multiset `P`. Constraints: every call places all its helpers inside the windows of the plan; at most one helper +per call per port (whole owner); the pattern counts cover the 120 ports; Hall conditions for the literal nomination. +Objective: Σ ports `g(remainder)` with `g(r) = r·ln(73/r)`, which is the exact first-order objective because the rank +mass is fixed (`Σ r(73/r)^c = mass + c·Σ r ln(73/r) + O(c²)`). + +`tools/loop.py` adds lazy cuts: after each aggregate solve it tries the literal per-child nomination (a 0/1 model per +child copy) and, for every child that fails, adds the violated top-k Hall cut +`Σ_{t ∈ top-k calls} Σ_{w∈S} x[t,w] ≤ Σ_P y_P · min(k, #S-slots of P)`, then re-solves with the previous solution as hint. +`tools/nominate2.py` writes the literal roles in #505's `ASYNC-OWNERS` format (chronological offsets, whole owners), +`tools/compose_profile.py` recounts the remainders from the roles alone and writes the plan file that #508's +width-generic `emit_async.py` / `audit_async.py` check against. + +| run | widths | order | result | note | +| --- | --- | --- | --- | --- | +| validation | #484 `{9:438,8:104,7:14,5:4}` | #505 offset 3 | OPTIMAL 123,686.5; remainders `{1:136,3:3432,5:3501,7:2137,16:1,48:408}` | identical to #505's published profile | +| #507 word | `{9:396,8:146,7:14,5:4}` | #508 offset 6 | 99,625.66 (bound 99,616.7) | aggregate stage with Hall cuts; the literal nomination loop was still adding top-k cuts when #512 published the same endpoint, and was stopped | +| ceiling | same | all windows open | OPTIMAL 99,625.66 | equals the offset-6 value: that order is at the timing-free ceiling for this inventory, as #517 also states | + +Requires `ortools` (CP-SAT). Inputs are a plan file in #505's format (`order`, `windows`, `sides`, `pairs`, +`baseline_widths`) and the width histogram; see the docstrings. Solve times on a laptop: 1–10 minutes per aggregate +solve, seconds per child nomination. + +Why release it: every published asynchronous result ships only its frozen witness. Anyone changing the local word +(new gauges, new kernels, new factories) needs a fresh allocation; this gives the community a checked one. + +## 2. Where the complex helpers go + +`code/delivery_layer.py` replays phases precondition+A over Q and classifies the 560 helpers. All three words agree on +the packet layer; they differ in how packets reach the targets. + +| class (after phase A) | #433 | #484 | #507 | +| --- | ---: | ---: | ---: | +| single-source copies (feed the block shears 476×, packet builders 343–371×, centres 21×) | 84 | 84 | 84 | +| 4-source packets (span 3) | 269 | 269 | 269 | +| 8-source packets (span 5) | 204 | 204 | 204 | +| 56-source centre sums | 3 | 3 | 3 | +| identical copies of a packet (same support, same coefficients) | 90 | 90 | 90 | +| sign variants of a packet | 84 | 84 | 84 | +| helpers whose first gate copies a source | 389 | 380 | 283 | +| helpers built only from other helpers | 171 | 171 | 255 | +| identical pairs each read by exactly one target, mergeable post hoc at the readers' shared 7-frame | 0 of 45 | 0 of 22 | 0 of 22 | + +Coefficients are ±1 and ±1/2 only. The duplicates are not waste in the packet algebra; they exist because the same packet +is read by two targets whose caps `u_t^⊥` are different 8-dimensional frames, so one register cannot serve both at the +cap. Merging a pair at the readers' shared 7-dimensional frame `u_t^⊥ ∩ u_e^⊥` fails for every pair in all three words +for the same reason: each reader has other pre-cap reads at frames that do not lie inside that meet. The merge would +have to be designed together with the readers' frame chains, not applied afterwards. + +`code/plane_cover.py` records the chain structure that governs this (`results/plane-cover-*.json`): + +- every target's cap is exactly `u_t^⊥`, and the sources it can be served are exactly the 56 with `u_s·u_t = 0`; +- in #433 and #484 every target reads along `0 → F₅ → F₆ → cap`; its 5-frame is private (112 distinct 5-frames for 112 + targets) and its 6-frame is shared with exactly one other target (56 pairs). #507 shifts this to `0 → F₆ → F₇ → cap` + with the same private/paired pattern; +- so a packet is delivered either once per target (private frame or cap) or once per pair (shared 6-frame). No frame + below the cap is shared by more than two targets in any of the three words. + +**A correction.** One might try to bound carriers by plane sections: a register of `s` above its line sits on a plane +`span(u_s, v)` and can serve the readers with `u_t·v = 0`; the largest such section has 32 of the 56 readers. But two +sections always cover all 56: every port has odd weight, so the all-ones functional is 1 on every port and the planes +spanned with `v` and `v+1` are complementary on the readers. Plane sections therefore give no bound (we had written one +down privately and withdraw it). The binding structure is the nested frame chains above: a delivery frame must be a +chain frame of every target it serves. + +## 3. The direction this points to + +The complex word's cost is 72 % helpers, and the helpers are mostly delivery: the same 269 + 204 packets as #425, copied +and re-signed until every target can read them inside its own chain. The chain design in use shares frames only in +pairs. A design that groups `k` targets on a shared `(9−k)`-dimensional frame delivers a packet once per group instead +of once per pair, at the price of one extra climb step per target per shared level (≈ 3 Φ at `m = 73`, against ≈ 25 Φ +per helper removed). The question is combinatorial and exact: choose chains `0 ⊂ G₁ ⊂ … ⊂ u_t^⊥` for the 120 targets +and frames for the 473 packets so that every packet's port span lies in the chain frame of each of its readers, and +minimise paid climbs. Nothing in #446's rigidity results (labelling, caps, copy loss, m) constrains this layer. We have +not solved it; the data above is what a solver would have to beat. + +## Credits + +#425 (icekylinx) and #433 for the packet word; #484 for the gauge/kernel word; #507 (eumemic) for the partial-own +factory; #505 and #508 (Dugongue) for the asynchronous schedule and the width-generic emitter/audit used to check the +emitted maps; #512 and #517 (chafreaky) for the independently published endpoint and ceiling that the allocator +reproduces; #446 (DreamingOfClouds) for the rigidity results; #496 (eumemic) and #442 (DaysSky) for whole-owner hosting. +All scripts here were written by higherbeing with Claude Code assistance. diff --git a/research/complex-delivery-map-open-allocator/code/delivery_layer.py b/research/complex-delivery-map-open-allocator/code/delivery_layer.py new file mode 100644 index 0000000000..014e0a0de6 --- /dev/null +++ b/research/complex-delivery-map-open-allocator/code/delivery_layer.py @@ -0,0 +1,94 @@ +"""Where the excess carriers of a split-centre complex word sit, and whether identical packet copies can be merged. +Usage: delivery_layer.py CANONICAL-WORD.json.gz [OUT.json] +Prepared by higherbeing with Claude Code assistance; Apache-2.0. + +Symbolic replay over Q of phases precondition+A: each helper's content as a signed rational combination of sources. +Reports helper roles (support sizes), duplicate supports (identical copies vs sign variants), what each class feeds, +and the legality of merging each identical pair at the two readers' shared 7-dim frame given the readers' other reads. +""" +import gzip,json,sys +from fractions import Fraction as Fr +from collections import Counter,defaultdict +w=json.loads(gzip.decompress(open(sys.argv[1],'rb').read()));V=120;ports=w['ports'];frames=w['frames'];centres=set(w['centers']) +NR=2*V+w['report']['R']if'report'in w else 800 +dot=lambda a,b:bin(a&b).count('1')&1 +def span(vs): + b=[] + for v in vs: + for x in b:v=min(v,v^x) + if v:b.append(v) + return b +def inspan(v,b): + for x in b:v=min(v,v^x) + return v==0 +le=lambda A,B:all(inspan(a,B)for a in A) +def meet(A,B): + out=[] + for mask in range(1,1<>i&1:v^=a + if inspan(v,B):out.append(v) + return span(out) +val={i:{i:Fr(1)}for i in range(V)};val.update({i:{}for i in range(V,NR)}) +uses=defaultdict(list);treads=defaultdict(list);first={} +def apply(g,ph): + if g[0]=='add': + dst,src,q,den,fr=g[1],g[2],Fr(g[3]),g[4],g[5];c=Fr(q,den)if den else q + kind='target'if V<=dst<2*V else('source'if dst=2*V for g in w['A']if g[1]==h and g[0]=='add')] +out['helpers_built_only_from_helpers']=dict(count=len(sums),support_sizes=dict(sorted(Counter(len(snap[h])for h in sums).items())),consumers=dict(Counter(k for h in sums for ph,k in uses[h]))) +caps={t:span(frames[w['final'][V+t]])for t in range(V)} +pairs=[] +for S,hs in ident: + if len(hs)!=2:continue + r=[[t for t in range(V)if any(src==h for src,_ in treads[t])]for h in hs] + if len(r[0])==1 and len(r[1])==1 and r[0]!=r[1]:pairs.append((hs,r[0][0],r[1][0],S)) +ok=0;reasons=Counter() +for hs,t1,t2,S in pairs: + F=meet(caps[t1],caps[t2]);psp=span([ports[s]for s in S]) + if len(F)!=7:reasons['meet dim '+str(len(F))]+=1;continue + if not le(psp,F):reasons['packet span not inside the meet']+=1;continue + good=True + for t,h in((t1,hs[0]),(t2,hs[1])): + others=[fb for src,fb in treads[t]if src!=h and len(fb)<8] + if not all(le(fb,F)for fb in others):good=False;reasons['another pre-cap read of a reader is outside the meet']+=1;break + ok+=good +out['identical_pairs_each_read_by_one_distinct_target']=len(pairs) +out['mergeable_at_shared_7frame_without_other_changes']=ok;out['merge_blockers']=dict(reasons) +out['target_pre_cap_read_dims']=dict(sorted(Counter(len(fb)for t in range(V)for src,fb in treads[t]if len(fb)<8).items())) +print(json.dumps(out,indent=1)) +if len(sys.argv)>2:json.dump(out,open(sys.argv[2],'w'),indent=1) diff --git a/research/complex-delivery-map-open-allocator/code/plane_cover.py b/research/complex-delivery-map-open-allocator/code/plane_cover.py new file mode 100644 index 0000000000..f335cc4e4b --- /dev/null +++ b/research/complex-delivery-map-open-allocator/code/plane_cover.py @@ -0,0 +1,85 @@ +"""Plane-section covers and target frame chains of a verified split-centre complex word (PR433 lineage). +Usage: plane_cover.py CANONICAL-WORD.json.gz [OUT.json] +Prepared by higherbeing with Claude Code assistance; Apache-2.0. + +Facts checked exactly on the word: + * ports u_t in F_2^9; every target's final frame (cap) is u_t^perp (dim 8); + * the readers of source s (targets whose decoder row contains s) are exactly the 56 targets with u_t . u_s = 0; + * a register holding source s that has climbed above its own line sits on a plane span(u_s, v); it can be read by + target t only if that plane lies in cap(t), i.e. u_t . v = 0. So a 'branch' of s serves the readers in one + hyperplane section {t in N(s): u_t . v = 0} of its reader set. + * exhaustive over all 255 planes through u_s, for all 120 sources: the largest section has 32 readers, and TWO + planes always cover all 56: every port has odd weight, so the all-ones functional is 1 on every port and the + planes spanned with v and v+1 are complementary on the readers. Plane-level delivery is therefore NOT what limits + the carrier count. What limits it is that each target reads along a nested chain of frames (dims 0 -> 5 -> 6 -> 8 + in the #433 word), so a source register can be read by t only when it sits exactly at one of t's chain frames; + the script therefore also reports the chain frames per target and how many targets share each pre-cap frame. +""" +import gzip,json,sys,itertools +from fractions import Fraction as Fr +from collections import Counter,defaultdict +w=json.loads(gzip.decompress(open(sys.argv[1],'rb').read()));V=120;ports=w['ports'];frames=w['frames'] +dot=lambda a,b:bin(a&b).count('1')&1 +def span(vs): + b=[] + for v in vs: + for x in b:v=min(v,v^x) + if v:b.append(v) + return b +def inspan(v,b): + for x in b:v=min(v,v^x) + return v==0 +# decoder rows: replay the word over Q to get each target's source support +val={i:{i:Fr(1)}for i in range(V)};val.update({i:{}for i in range(V,2*V+w['report']['R']if'report'in w else 800)}) +for ph in('precondition','A','cut','B'): + for g in w[ph]: + if g[0]=='add': + dst,src,q,den=g[1],g[2],Fr(g[3]),g[4];c=Fr(q,den)if den else q + for s,v in val[src].items(): + val[dst][s]=val[dst].get(s,0)+c*v + if val[dst][s]==0:del val[dst][s] + elif g[0]=='scale': + dst,q,den=g[1],Fr(g[2]),g[3];c=Fr(q,den)if den else q + for s in list(val[dst]):val[dst][s]*=c +rows={t:{s:c for s,c in val[V+t].items()if s=56-mx],2)) + big=[x for x in secs if len(x)>=19] + three=any(a|b|c==N for a,b,c in itertools.combinations(big,3)) + res.append((mx,two,three)) +summary=dict(sources=V,readers_per_source=56,planes_per_source=255,max_section=Counter(r[0]for r in res),two_cover_exists_for_all_sources=all(r[1]for r in res),all_ports_odd_weight=all(bin(p).count('1')&1 for p in ports)) +# target chains: frames at which each target reads before its cap, and how many targets share each such frame +chains=defaultdict(list) +for ph in('B',): + for g in w[ph]: + if g[0]=='add'and V<=g[1]<2*V and g[5]is not None: + F=tuple(span(frames[g[5]])) + if len(F)<8 and F not in chains[g[1]-V]:chains[g[1]-V].append(F) +share=Counter() +for t,fs in chains.items(): + for F in fs:share[F]+=1 +summary['chain_dims_per_target']={str(k):v for k,v in Counter(tuple(len(F)for F in fs)for fs in chains.values()).items()} +summary['targets_sharing_a_pre_cap_frame']={d:dict(Counter(n for F,n in share.items()if len(F)==d))for d in sorted({len(F)for F in share})} +# how many carriers does the word use (helpers whose first gate copies a source register) +first={} +for ph in('precondition','A'): + for g in w[ph]: + if g[0]=='add'and g[1]>=2*V and g[1]not in first:first[g[1]]=g[2] +summary['carriers_used']=sum(1 for h,src in first.items()if src2:json.dump(summary,open(sys.argv[2],'w'),indent=1,default=str) diff --git a/research/complex-delivery-map-open-allocator/results/allocator-pr507-widths-offset6.json b/research/complex-delivery-map-open-allocator/results/allocator-pr507-widths-offset6.json new file mode 100644 index 0000000000..ce40133bb9 --- /dev/null +++ b/research/complex-delivery-map-open-allocator/results/allocator-pr507-widths-offset6.json @@ -0,0 +1,310 @@ +{ + "status": "FEASIBLE", + "objective": 99625.663, + "bound": 99616.724, + "remainders": { + "3": 3106, + "5": 3551, + "7": 456, + "23": 1, + "48": 734 + }, + "widths": { + "9": 396, + "8": 146, + "7": 14, + "5": 4 + }, + "order": [ + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 0 + ], + [ + 0, + 2 + ], + [ + 1, + 1 + ], + [ + 2, + 0 + ], + [ + 0, + 3 + ], + [ + 1, + 2 + ], + [ + 2, + 1 + ], + [ + 3, + 0 + ], + [ + 0, + 4 + ], + [ + 1, + 3 + ], + [ + 2, + 2 + ], + [ + 3, + 1 + ], + [ + 4, + 0 + ], + [ + 0, + 5 + ], + [ + 1, + 4 + ], + [ + 2, + 3 + ], + [ + 3, + 2 + ], + [ + 4, + 1 + ], + [ + 5, + 0 + ], + [ + 0, + 6 + ], + [ + 1, + 5 + ], + [ + 2, + 4 + ], + [ + 3, + 3 + ], + [ + 4, + 2 + ], + [ + 5, + 1 + ], + [ + 0, + 7 + ], + [ + 6, + 0 + ], + [ + 1, + 6 + ], + [ + 2, + 5 + ], + [ + 3, + 4 + ], + [ + 4, + 3 + ], + [ + 5, + 2 + ], + [ + 6, + 1 + ], + [ + 1, + 7 + ], + [ + 7, + 0 + ], + [ + 2, + 6 + ], + [ + 3, + 5 + ], + [ + 4, + 4 + ], + [ + 5, + 3 + ], + [ + 6, + 2 + ], + [ + 7, + 1 + ], + [ + 2, + 7 + ], + [ + 8, + 0 + ], + [ + 3, + 6 + ], + [ + 4, + 5 + ], + [ + 5, + 4 + ], + [ + 6, + 3 + ], + [ + 7, + 2 + ], + [ + 8, + 1 + ], + [ + 3, + 7 + ], + [ + 4, + 6 + ], + [ + 5, + 5 + ], + [ + 6, + 4 + ], + [ + 7, + 3 + ], + [ + 8, + 2 + ], + [ + 4, + 7 + ], + [ + 5, + 6 + ], + [ + 6, + 5 + ], + [ + 7, + 4 + ], + [ + 8, + 3 + ], + [ + 5, + 7 + ], + [ + 6, + 6 + ], + [ + 7, + 5 + ], + [ + 8, + 4 + ], + [ + 6, + 7 + ], + [ + 7, + 6 + ], + [ + 8, + 5 + ], + [ + 7, + 7 + ], + [ + 8, + 6 + ], + [ + 8, + 7 + ] + ], + "cost": "phi", + "note": "aggregate stage only; x/y columns trimmed for size" +} \ No newline at end of file diff --git a/research/complex-delivery-map-open-allocator/results/allocator-pr507-widths-timing-free.json b/research/complex-delivery-map-open-allocator/results/allocator-pr507-widths-timing-free.json new file mode 100644 index 0000000000..809679fc8e --- /dev/null +++ b/research/complex-delivery-map-open-allocator/results/allocator-pr507-widths-timing-free.json @@ -0,0 +1,310 @@ +{ + "status": "OPTIMAL", + "objective": 99625.663, + "bound": 99625.663, + "remainders": { + "3": 3106, + "5": 3551, + "7": 456, + "23": 1, + "48": 734 + }, + "widths": { + "9": 396, + "8": 146, + "7": 14, + "5": 4 + }, + "order": [ + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 1, + 0 + ], + [ + 0, + 2 + ], + [ + 1, + 1 + ], + [ + 2, + 0 + ], + [ + 0, + 3 + ], + [ + 1, + 2 + ], + [ + 2, + 1 + ], + [ + 3, + 0 + ], + [ + 0, + 4 + ], + [ + 1, + 3 + ], + [ + 2, + 2 + ], + [ + 3, + 1 + ], + [ + 4, + 0 + ], + [ + 0, + 5 + ], + [ + 1, + 4 + ], + [ + 2, + 3 + ], + [ + 3, + 2 + ], + [ + 4, + 1 + ], + [ + 5, + 0 + ], + [ + 0, + 6 + ], + [ + 1, + 5 + ], + [ + 2, + 4 + ], + [ + 3, + 3 + ], + [ + 4, + 2 + ], + [ + 5, + 1 + ], + [ + 0, + 7 + ], + [ + 6, + 0 + ], + [ + 1, + 6 + ], + [ + 2, + 5 + ], + [ + 3, + 4 + ], + [ + 4, + 3 + ], + [ + 5, + 2 + ], + [ + 6, + 1 + ], + [ + 1, + 7 + ], + [ + 7, + 0 + ], + [ + 2, + 6 + ], + [ + 3, + 5 + ], + [ + 4, + 4 + ], + [ + 5, + 3 + ], + [ + 6, + 2 + ], + [ + 7, + 1 + ], + [ + 2, + 7 + ], + [ + 8, + 0 + ], + [ + 3, + 6 + ], + [ + 4, + 5 + ], + [ + 5, + 4 + ], + [ + 6, + 3 + ], + [ + 7, + 2 + ], + [ + 8, + 1 + ], + [ + 3, + 7 + ], + [ + 4, + 6 + ], + [ + 5, + 5 + ], + [ + 6, + 4 + ], + [ + 7, + 3 + ], + [ + 8, + 2 + ], + [ + 4, + 7 + ], + [ + 5, + 6 + ], + [ + 6, + 5 + ], + [ + 7, + 4 + ], + [ + 8, + 3 + ], + [ + 5, + 7 + ], + [ + 6, + 6 + ], + [ + 7, + 5 + ], + [ + 8, + 4 + ], + [ + 6, + 7 + ], + [ + 7, + 6 + ], + [ + 8, + 5 + ], + [ + 7, + 7 + ], + [ + 8, + 6 + ], + [ + 8, + 7 + ] + ], + "cost": "phi", + "note": "aggregate stage only; x/y columns trimmed for size" +} \ No newline at end of file diff --git a/research/complex-delivery-map-open-allocator/results/allocator-validation-pr505.json b/research/complex-delivery-map-open-allocator/results/allocator-validation-pr505.json new file mode 100644 index 0000000000..35f4f3b6f7 --- /dev/null +++ b/research/complex-delivery-map-open-allocator/results/allocator-validation-pr505.json @@ -0,0 +1,310 @@ +{ + "status": "OPTIMAL", + "objective": 123686.499, + "bound": 123686.499, + "remainders": { + "1": 136, + "3": 3432, + "5": 3501, + "7": 2137, + "16": 1, + "48": 408 + }, + "widths": { + "9": 438, + "8": 104, + "7": 14, + "5": 4 + }, + "order": [ + [ + 0, + 0 + ], + [ + 0, + 1 + ], + [ + 0, + 2 + ], + [ + 1, + 0 + ], + [ + 0, + 3 + ], + [ + 1, + 1 + ], + [ + 0, + 4 + ], + [ + 1, + 2 + ], + [ + 2, + 0 + ], + [ + 0, + 5 + ], + [ + 1, + 3 + ], + [ + 2, + 1 + ], + [ + 0, + 6 + ], + [ + 1, + 4 + ], + [ + 2, + 2 + ], + [ + 0, + 7 + ], + [ + 3, + 0 + ], + [ + 1, + 5 + ], + [ + 2, + 3 + ], + [ + 3, + 1 + ], + [ + 1, + 6 + ], + [ + 2, + 4 + ], + [ + 3, + 2 + ], + [ + 1, + 7 + ], + [ + 4, + 0 + ], + [ + 2, + 5 + ], + [ + 3, + 3 + ], + [ + 4, + 1 + ], + [ + 2, + 6 + ], + [ + 3, + 4 + ], + [ + 4, + 2 + ], + [ + 2, + 7 + ], + [ + 5, + 0 + ], + [ + 3, + 5 + ], + [ + 4, + 3 + ], + [ + 5, + 1 + ], + [ + 3, + 6 + ], + [ + 4, + 4 + ], + [ + 5, + 2 + ], + [ + 3, + 7 + ], + [ + 6, + 0 + ], + [ + 4, + 5 + ], + [ + 5, + 3 + ], + [ + 6, + 1 + ], + [ + 4, + 6 + ], + [ + 5, + 4 + ], + [ + 6, + 2 + ], + [ + 4, + 7 + ], + [ + 7, + 0 + ], + [ + 5, + 5 + ], + [ + 6, + 3 + ], + [ + 7, + 1 + ], + [ + 5, + 6 + ], + [ + 6, + 4 + ], + [ + 7, + 2 + ], + [ + 5, + 7 + ], + [ + 8, + 0 + ], + [ + 6, + 5 + ], + [ + 7, + 3 + ], + [ + 8, + 1 + ], + [ + 6, + 6 + ], + [ + 7, + 4 + ], + [ + 8, + 2 + ], + [ + 6, + 7 + ], + [ + 7, + 5 + ], + [ + 8, + 3 + ], + [ + 7, + 6 + ], + [ + 8, + 4 + ], + [ + 7, + 7 + ], + [ + 8, + 5 + ], + [ + 8, + 6 + ], + [ + 8, + 7 + ] + ], + "cost": "phi" +} \ No newline at end of file diff --git a/research/complex-delivery-map-open-allocator/results/delivery-layer-433.json b/research/complex-delivery-map-open-allocator/results/delivery-layer-433.json new file mode 100644 index 0000000000..e797303216 --- /dev/null +++ b/research/complex-delivery-map-open-allocator/results/delivery-layer-433.json @@ -0,0 +1,51 @@ +{ + "helpers_with_content_after_A": 560, + "support_sizes": { + "1": 84, + "4": 269, + "8": 204, + "56": 3 + }, + "coefficients": { + "-1": 228, + "-1/2": 716, + "1": 1532, + "1/2": 484 + }, + "carriers_first_gate_from_source": 389, + "duplicate_supports": { + "partial sign change": 84, + "identical copy": 90 + }, + "identical_groups": 90, + "single_source_copies": { + "count": 84, + "consumers": { + "source": 476, + "helper": 343, + "centre": 21 + } + }, + "helpers_built_only_from_helpers": { + "count": 171, + "support_sizes": { + "4": 85, + "8": 86 + }, + "consumers": { + "target": 112, + "helper": 168, + "source": 18 + } + }, + "identical_pairs_each_read_by_one_distinct_target": 45, + "mergeable_at_shared_7frame_without_other_changes": 0, + "merge_blockers": { + "another pre-cap read of a reader is outside the meet": 45 + }, + "target_pre_cap_read_dims": { + "0": 112, + "5": 112, + "6": 112 + } +} \ No newline at end of file diff --git a/research/complex-delivery-map-open-allocator/results/delivery-layer-484.json b/research/complex-delivery-map-open-allocator/results/delivery-layer-484.json new file mode 100644 index 0000000000..16b8ed26ef --- /dev/null +++ b/research/complex-delivery-map-open-allocator/results/delivery-layer-484.json @@ -0,0 +1,54 @@ +{ + "helpers_with_content_after_A": 560, + "support_sizes": { + "1": 84, + "4": 269, + "8": 204, + "56": 3 + }, + "coefficients": { + "-1": 228, + "-1/2": 716, + "1": 1532, + "1/2": 484 + }, + "carriers_first_gate_from_source": 380, + "duplicate_supports": { + "partial sign change": 84, + "identical copy": 90 + }, + "identical_groups": 90, + "single_source_copies": { + "count": 84, + "consumers": { + "source": 476, + "helper": 343, + "centre": 21 + } + }, + "helpers_built_only_from_helpers": { + "count": 171, + "support_sizes": { + "4": 85, + "8": 86 + }, + "consumers": { + "target": 412, + "helper": 243, + "source": 18 + } + }, + "identical_pairs_each_read_by_one_distinct_target": 22, + "mergeable_at_shared_7frame_without_other_changes": 0, + "merge_blockers": { + "another pre-cap read of a reader is outside the meet": 22 + }, + "target_pre_cap_read_dims": { + "0": 112, + "1": 324, + "2": 80, + "4": 16, + "5": 112, + "6": 112 + } +} \ No newline at end of file diff --git a/research/complex-delivery-map-open-allocator/results/delivery-layer-507.json b/research/complex-delivery-map-open-allocator/results/delivery-layer-507.json new file mode 100644 index 0000000000..ac54370b08 --- /dev/null +++ b/research/complex-delivery-map-open-allocator/results/delivery-layer-507.json @@ -0,0 +1,54 @@ +{ + "helpers_with_content_after_A": 560, + "support_sizes": { + "1": 84, + "4": 269, + "8": 204, + "56": 3 + }, + "coefficients": { + "-1": 228, + "-1/2": 716, + "1": 1532, + "1/2": 484 + }, + "carriers_first_gate_from_source": 283, + "duplicate_supports": { + "partial sign change": 84, + "identical copy": 90 + }, + "identical_groups": 90, + "single_source_copies": { + "count": 84, + "consumers": { + "source": 476, + "helper": 371, + "centre": 21 + } + }, + "helpers_built_only_from_helpers": { + "count": 255, + "support_sizes": { + "4": 169, + "8": 86 + }, + "consumers": { + "helper": 555, + "target": 496, + "source": 18 + } + }, + "identical_pairs_each_read_by_one_distinct_target": 22, + "mergeable_at_shared_7frame_without_other_changes": 0, + "merge_blockers": { + "another pre-cap read of a reader is outside the meet": 22 + }, + "target_pre_cap_read_dims": { + "0": 112, + "1": 324, + "2": 80, + "4": 16, + "6": 112, + "7": 112 + } +} \ No newline at end of file diff --git a/research/complex-delivery-map-open-allocator/results/plane-cover-433.json b/research/complex-delivery-map-open-allocator/results/plane-cover-433.json new file mode 100644 index 0000000000..c61a4d5a3e --- /dev/null +++ b/research/complex-delivery-map-open-allocator/results/plane-cover-433.json @@ -0,0 +1,23 @@ +{ + "sources": 120, + "readers_per_source": 56, + "planes_per_source": 255, + "max_section": { + "32": 120 + }, + "two_cover_exists_for_all_sources": true, + "all_ports_odd_weight": true, + "chain_dims_per_target": { + "(5, 6)": 112 + }, + "targets_sharing_a_pre_cap_frame": { + "5": { + "1": 112 + }, + "6": { + "2": 56 + } + }, + "carriers_used": 389, + "helpers": 560 +} \ No newline at end of file diff --git a/research/complex-delivery-map-open-allocator/results/plane-cover-484.json b/research/complex-delivery-map-open-allocator/results/plane-cover-484.json new file mode 100644 index 0000000000..b680e4ccf7 --- /dev/null +++ b/research/complex-delivery-map-open-allocator/results/plane-cover-484.json @@ -0,0 +1,23 @@ +{ + "sources": 120, + "readers_per_source": 56, + "planes_per_source": 255, + "max_section": { + "32": 120 + }, + "two_cover_exists_for_all_sources": true, + "all_ports_odd_weight": true, + "chain_dims_per_target": { + "(5, 6)": 112 + }, + "targets_sharing_a_pre_cap_frame": { + "5": { + "1": 112 + }, + "6": { + "2": 56 + } + }, + "carriers_used": 380, + "helpers": 560 +} \ No newline at end of file diff --git a/research/complex-delivery-map-open-allocator/results/plane-cover-507.json b/research/complex-delivery-map-open-allocator/results/plane-cover-507.json new file mode 100644 index 0000000000..1e1e5baa7c --- /dev/null +++ b/research/complex-delivery-map-open-allocator/results/plane-cover-507.json @@ -0,0 +1,23 @@ +{ + "sources": 120, + "readers_per_source": 56, + "planes_per_source": 255, + "max_section": { + "32": 120 + }, + "two_cover_exists_for_all_sources": true, + "all_ports_odd_weight": true, + "chain_dims_per_target": { + "(6, 7)": 112 + }, + "targets_sharing_a_pre_cap_frame": { + "6": { + "1": 112 + }, + "7": { + "2": 56 + } + }, + "carriers_used": 283, + "helpers": 560 +} \ No newline at end of file diff --git a/research/complex-delivery-map-open-allocator/tools/aggregate_milp.py b/research/complex-delivery-map-open-allocator/tools/aggregate_milp.py new file mode 100644 index 0000000000..ee882cae5f --- /dev/null +++ b/research/complex-delivery-map-open-allocator/tools/aggregate_milp.py @@ -0,0 +1,58 @@ +"""Aggregate asynchronous whole-owner allocation (stage 1 of PR505's two-stage method, rebuilt). +Usage: aggregate_milp.py PLAN.json WIDTHS(json dict) OUT.json [seconds] [cost=phi|moment:c] +Variables: x[t,i,orp,w] roles of width w at call time t hosted on child (i,orp); y[i,orp,P] ports of (i,orp) +tiled by width multiset P. Each call places all its helpers; at most one role per call per port (whole owner); +patterns cover the 120 ports; cost = sum over ports of g(remainder).""" +import json,math,sys,itertools,time +from collections import Counter,defaultdict +from ortools.sat.python import cp_model +PLAN,WID,OUT=sys.argv[1:4];LIM=float(sys.argv[4])if len(sys.argv)>4 else 300;COST=sys.argv[5]if len(sys.argv)>5 else'phi' +plan=json.load(open(PLAN));nw={int(k):v for k,v in json.loads(WID).items()};M=73 +order=[tuple(x)for x in plan['order']];T=len(order);assert T==72 +win={tuple(map(int,k.split(','))):(v[0],sorted(v[1]))for k,v in plan['windows'].items()} # (orp,i)->(rank,times) +ranks=sorted({r for r,_ in win.values()});W=sorted(nw) +def g(r): + if r==0:return 0 + if COST=='phi':return r*math.log(M/r) + c=float(COST.split(':')[1]);return r*(M/r)**c +def patterns(r): + out=set() + def rec(rem,start,cur): + out.add(tuple(cur)) + for j in range(start,len(W)): + if W[j]<=rem:rec(rem-W[j],j,cur+[W[j]]) + rec(r,0,[]);return sorted(out) +pats={r:patterns(r)for r in ranks};print({r:len(p)for r,p in pats.items()},flush=True) +m=cp_model.CpModel();x={};y={} +for (orp,i),(r,times)in win.items(): + for t in times: + for w in W:x[t,i,orp,w]=m.NewIntVar(0,min(nw[w],120),f'x{t}_{i}_{orp}_{w}') + m.Add(sum(x[t,i,orp,w]for w in W)<=120) + for P in pats[r]:y[i,orp,P]=m.NewIntVar(0,120,f'y{i}_{orp}_{len(P)}_{hash(P)%10**6}') + m.Add(sum(y[i,orp,P]for P in pats[r])==120) + # Hall conditions for the literal nomination: roles of one call must sit in distinct ports, so for every + # subset S of widths the roles of call t with widths in S need that many ports whose pattern meets S. + for k in range(1,len(W)+1): + for Sset in itertools.combinations(W,k): + ports_meeting=sum(y[i,orp,P]for P in pats[r]if any(w in P for w in Sset)) + for t in times:m.Add(sum(x[t,i,orp,w]for w in Sset)<=ports_meeting) + for w in W:m.Add(sum(P.count(w)*y[i,orp,P]for P in pats[r])==sum(x[t,i,orp,w]for t in times)) +for t in range(T): + for w in W: + m.Add(sum(x[t,i,orp,w]for(orp,i),(r,times)in win.items()if t in times)==nw[w]) +SC=1000 +m.Minimize(sum(int(round(SC*g(r-sum(P))))*y[i,orp,P]for(orp,i),(r,times)in win.items()for P in pats[r])) +s=cp_model.CpSolver();s.parameters.max_time_in_seconds=LIM;s.parameters.num_workers=8;t0=time.time();st=s.Solve(m) +print('status',s.StatusName(st),'obj',s.ObjectiveValue()/SC,'bound',s.BestObjectiveBound()/SC,'t',round(time.time()-t0),flush=True) +rem=Counter();X=[];Y=[] +for (orp,i),(r,times)in win.items(): + for P in pats[r]: + v=s.Value(y[i,orp,P]) + if v:rem[r-sum(P)]+=v;Y.append([i,orp,list(P),v]) + for t in times: + for w in W: + v=s.Value(x[t,i,orp,w]) + if v:X.append([t,i,orp,w,v]) +rem={r:n for r,n in sorted(rem.items())if r} +print('remainders',rem,'phi',round(sum(n*r*math.log(M/r)for r,n in rem.items()),1),flush=True) +json.dump(dict(status=s.StatusName(st),objective=s.ObjectiveValue()/SC,bound=s.BestObjectiveBound()/SC,remainders=rem,widths=nw,order=plan['order'],x=X,y=Y,cost=COST),open(OUT,'w')) diff --git a/research/complex-delivery-map-open-allocator/tools/compose_profile.py b/research/complex-delivery-map-open-allocator/tools/compose_profile.py new file mode 100644 index 0000000000..d43ead9837 --- /dev/null +++ b/research/complex-delivery-map-open-allocator/tools/compose_profile.py @@ -0,0 +1,29 @@ +"""Write the plan PR508's emitter checks: profile = sum over the 72 calls of the variant's local paid profile + +one climb per idle remainder (from the nominated owner map, recounted here from the roles alone). +Usage: compose_profile.py PLAN.json OWNERS.json.gz RETIMED_VARIANT OUT-PLAN.json""" +import gzip,json,sys,hashlib +from collections import Counter,defaultdict +PLAN,OWN,RET,OUT=sys.argv[1:5] +read=lambda p:json.loads(gzip.decompress(open(p,'rb').read())if p.endswith('.gz')else open(p,'rb').read()) +plan=read(PLAN);roles=read(OWN);t=read(RET+'/split-centres/GLOBAL-TEMPLATE.json.gz');chk=read(RET+'/split-centres/LOCAL-CHECKS.json');canon=read(RET+'/split-centres/CANONICAL-WORD.json.gz') +R=560;V=120;T=8;M=73;DATA=7680 +widths=[9-len(canon['frames'][canon['start'][240+h]])for h in range(R)];assert widths==plan['baseline_widths'] +children={(z['child'],z['port']):z['rank']for z in t['idle_children']} +used=defaultdict(set) +for z in roles: + i,orp,port,off,w=z['child'],z['owner_replica'],z['port'],z['offset'],z['width'] + assert w==widths[z['helper']]and off+w<=children[i,port] + cells=set(range(off,off+w));assert not used[i,orp,port]&cells;used[i,orp,port]|=cells +rem=Counter() +for (i,port),r in children.items(): + for rep in range(T): + u=used[i,rep,port];assert u==set(range(len(u)));left=r-len(u) + if left:rem[left]+=1 +paid=Counter(rem) +for c in t['calls']: + for r,k in chk[c['orientation']]['paid_profile'].items():paid[int(r)]+=k +mass=sum(r*k for r,k in paid.items());assert M*DATA-mass==2496 and M*M>2*max(paid)**2 +plan['profile']={str(r):k for r,k in sorted(paid.items())};plan['remainders']={str(r):k for r,k in sorted(rem.items())} +plan['local_profile']={str(r):k for r,k in sorted(Counter({int(r):k for r,k in next(iter(chk.values()))['paid_profile'].items()}).items())} +plan['owner_map_sha256']=hashlib.sha256(open(OWN,'rb').read()).hexdigest() +json.dump(plan,open(OUT,'w'),indent=1);print(json.dumps(dict(profile=plan['profile'],remainders=plan['remainders'],local=plan['local_profile'],mass=mass))) diff --git a/research/complex-delivery-map-open-allocator/tools/loop.py b/research/complex-delivery-map-open-allocator/tools/loop.py new file mode 100644 index 0000000000..f41c858af8 --- /dev/null +++ b/research/complex-delivery-map-open-allocator/tools/loop.py @@ -0,0 +1,95 @@ +"""Aggregate allocation with lazy nomination cuts. Usage: loop.py PLAN.json WIDTHS OUT-AGG.json [secs/iter] [max_iters] +Each iteration: solve the aggregate model (with all cuts so far, hinted by the previous solution), then try the literal +per-child nomination; for every child that fails, add top-k Hall cuts: for a width subset S and the k calls with most +S-roles on that child, sum of those roles <= sum_P y_P * min(k, #S-slots of P).""" +import json,math,sys,itertools,time +from collections import Counter,defaultdict +from ortools.sat.python import cp_model +PLAN,WID,OUT=sys.argv[1:4];LIM=float(sys.argv[4])if len(sys.argv)>4 else 300;ITERS=int(sys.argv[5])if len(sys.argv)>5 else 12 +plan=json.load(open(PLAN));nw={int(k):v for k,v in json.loads(WID).items()};M=73 +order=[tuple(x)for x in plan['order']];T=len(order) +win={tuple(map(int,k.split(','))):(v[0],sorted(v[1]))for k,v in plan['windows'].items()} +ranks=sorted({r for r,_ in win.values()});W=sorted(nw);SUBS=[S for k in range(1,len(W)+1)for S in itertools.combinations(W,k)] +g=lambda r:0 if r==0 else r*math.log(M/r) +def patterns(r): + out=set() + def rec(rem,start,cur): + out.add(tuple(cur)) + for j in range(start,len(W)): + if W[j]<=rem:rec(rem-W[j],j,cur+[W[j]]) + rec(r,0,[]);return sorted(out) +pats={r:patterns(r)for r in ranks} +cuts=[] # (i,orp,times,S,k) +def solve(hint): + m=cp_model.CpModel();x={};y={} + for (orp,i),(r,times)in win.items(): + for t in times: + for w in W:x[t,i,orp,w]=m.NewIntVar(0,min(nw[w],120),'') + m.Add(sum(x[t,i,orp,w]for w in W)<=120) + for P in pats[r]:y[i,orp,P]=m.NewIntVar(0,120,'') + m.Add(sum(y[i,orp,P]for P in pats[r])==120) + for w in W:m.Add(sum(P.count(w)*y[i,orp,P]for P in pats[r])==sum(x[t,i,orp,w]for t in times)) + for S in SUBS: + meet=sum(y[i,orp,P]for P in pats[r]if any(w in P for w in S)) + for t in times:m.Add(sum(x[t,i,orp,w]for w in S)<=meet) + for (i,orp,times,S,k)in cuts: + r=win[orp,i][0];m.Add(sum(x[t,i,orp,w]for t in times for w in S)<=sum(min(k,sum(P.count(w)for w in S))*y[i,orp,P]for P in pats[r])) + for t in range(T): + for w in W:m.Add(sum(x[t,i,orp,w]for(orp,i),(r,times)in win.items()if t in times)==nw[w]) + SC=1000;m.Minimize(sum(int(round(SC*g(r-sum(P))))*y[i,orp,P]for(orp,i),(r,times)in win.items()for P in pats[r])) + if hint: + for k,v in hint['x'].items(): + if k in x:m.AddHint(x[k],v) + for k,v in hint['y'].items(): + if k in y:m.AddHint(y[k],v) + s=cp_model.CpSolver();s.parameters.max_time_in_seconds=LIM;s.parameters.num_workers=8;st=s.Solve(m) + assert st in(cp_model.OPTIMAL,cp_model.FEASIBLE),s.StatusName(st) + X={k:s.Value(v)for k,v in x.items()};Y={k:s.Value(v)for k,v in y.items()} + return dict(status=s.StatusName(st),obj=s.ObjectiveValue()/SC,bound=s.BestObjectiveBound()/SC,x=X,y=Y) +def nominate(sol): + """returns dict (i,orp)->port assignment or None, and new cuts""" + fails=[];assign={} + for (orp,i),(r,times)in win.items(): + xs={(t,w):v for (t,ii,oo,w),v in sol['x'].items()if(ii,oo)==(i,orp)and v} + ports=[] + for P in pats[r]:ports+=[P]*sol['y'][i,orp,P] + need=[Counter(P)for P in ports];ts=sorted({t for t,w in xs}) + m=cp_model.CpModel();z={} + for (t,w),v in xs.items(): + for p in range(120): + if need[p][w]:z[t,w,p]=m.NewBoolVar('') + m.Add(sum(z[t,w,p]for p in range(120)if(t,w,p)in z)==v) + for p in range(120): + for w in need[p]:m.Add(sum(z[t,w,p]for t in ts if(t,w,p)in z)==need[p][w]) + for t in ts: + vs=[z[t,w,p]for w in need[p]if(t,w,p)in z] + if len(vs)>1:m.Add(sum(vs)<=1) + s=cp_model.CpSolver();s.parameters.max_time_in_seconds=30;s.parameters.num_workers=8;st=s.Solve(m) + if st in(cp_model.OPTIMAL,cp_model.FEASIBLE): + assign[i,orp]=[(t,w,p)for (t,w,p),var in z.items()if s.Value(var)];continue + # find violated top-k cuts + found=0 + for S in SUBS: + load={t:sum(v for (tt,w),v in xs.items()if tt==t and w in S)for t in ts};tl=sorted(ts,key=lambda t:-load[t]) + slots=[sum(P.count(w)for w in S)for P in ports] + for k in range(1,len(tl)+1): + lhs=sum(load[t]for t in tl[:k]);rhs=sum(min(k,c)for c in slots) + if lhs>rhs:cuts.append((i,orp,tl[:k],S,k));found+=1;break + fails.append((i,orp,found,s.StatusName(st))) + return assign,fails +hint=None +for it in range(ITERS): + t0=time.time();sol=solve(hint);hint=sol + assign,fails=nominate(sol) + rem=Counter() + for (orp,i),(r,times)in win.items(): + for P in pats[r]: + v=sol['y'][i,orp,P] + if v and r-sum(P):rem[r-sum(P)]+=v + print(f"iter {it} {sol['status']} obj {sol['obj']:.1f} bound {sol['bound']:.1f} fails {len(fails)} cuts {len(cuts)} rem {dict(sorted(rem.items()))} t {time.time()-t0:.0f}s",flush=True) + if fails:print(' fails',fails[:8],flush=True) + if not fails or all(f[2]==0 for f in fails):break +out=dict(status=sol['status'],objective=sol['obj'],bound=sol['bound'],remainders={str(k):v for k,v in sorted(rem.items())},widths=nw,order=plan['order'], + x=[[t,i,orp,w,v]for (t,i,orp,w),v in sol['x'].items()if v],y=[[i,orp,list(P),v]for (i,orp,P),v in sol['y'].items()if v], + assign={f'{i},{orp}':a for (i,orp),a in assign.items()},fails=fails,cuts=[[i,orp,ts,list(S),k]for i,orp,ts,S,k in cuts]) +json.dump(out,open(OUT,'w'));print('written',OUT,'unresolved fails',len(fails),flush=True) diff --git a/research/complex-delivery-map-open-allocator/tools/nominate2.py b/research/complex-delivery-map-open-allocator/tools/nominate2.py new file mode 100644 index 0000000000..986b2a55c0 --- /dev/null +++ b/research/complex-delivery-map-open-allocator/tools/nominate2.py @@ -0,0 +1,32 @@ +"""Turn the loop's literal per-child assignment into PR505-format roles (whole owners, chronological offsets). +Usage: nominate2.py LOOP.json PLAN.json RETIMED_VARIANT OUT-OWNERS.json.gz OUT-PLAN.json""" +import gzip,json,sys +from collections import Counter,defaultdict +LOOP,PLAN,RET,OUTO,OUTP=sys.argv[1:6] +agg=json.load(open(LOOP));plan=json.load(open(PLAN));order=[tuple(o)for o in plan['order']] +assert not agg['fails'],agg['fails'] +canon=json.loads(gzip.decompress(open(RET+'/split-centres/CANONICAL-WORD.json.gz','rb').read())) +R=560;V=120;widths=[9-len(canon['frames'][canon['start'][240+h]])for h in range(R)] +assert Counter(widths)=={int(k):v for k,v in agg['widths'].items()} +sides={int(k):v for k,v in plan['sides'].items()};pairs={int(k):v for k,v in plan['pairs'].items()} +bywidth=defaultdict(list) +for h,w in enumerate(widths):bywidth[w].append(h) +pending={t:{w:list(bywidth[w])for w in bywidth}for t in range(72)} +roles=[] +for key,a in sorted(agg['assign'].items()): + i,orp=map(int,key.split(','));perport=defaultdict(list) + for t,w,p in a:perport[p].append((t,w)) + for p,lst in perport.items(): + assert len({t for t,w in lst})==len(lst),'two roles of one call on one port' + off=0 + for t,w in sorted(lst): + h=pending[t][w].pop();stg,rep=order[t] + roles.append(dict(owner_replica=orp,child=i,port=p,time=t,width=w,offset=off,stage=stg,replica=rep,helper=h,copy=orp*V+p,family=orp*960+(2*pairs[i]+sides[i])*V+p));off+=w +assert all(not any(pending[t].values())for t in range(72)),'unplaced helpers' +assert len(roles)==40320 and len({(r['stage'],r['replica'],r['helper'])for r in roles})==40320 +roles.sort(key=lambda r:(r['time'],r['helper'])) +open(OUTO,'wb').write(gzip.compress(json.dumps(roles,separators=(',',':')).encode(),mtime=0)) +out=dict(plan);out['baseline_widths']=widths;out['status']='AGGREGATE_CPSAT_WITH_NOMINATION_CUTS'; +for k in('numeric_saving','solver_status','gap','profile','patterns','stage_assignments','selected','variants','source_local_profile','source_provider'):out.pop(k,None) +out['aggregate']=dict(status=agg['status'],objective=agg['objective'],bound=agg['bound'],cuts=len(agg['cuts'])) +json.dump(out,open(OUTP,'w'),indent=1);print('roles',len(roles),'written; remainders',agg['remainders'])