diff --git a/tracks/qcs/solutions/Genshin_Impact-71/.gitignore b/tracks/qcs/solutions/Genshin_Impact-71/.gitignore new file mode 100644 index 000000000..c5a48d22f --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/.gitignore @@ -0,0 +1,5 @@ +abc.history +__pycache__/ +*.py[cod] +search/c_joint_window_ranking.json +routes/symbolic_hybrid/espresso-berkeley-*.patch diff --git a/tracks/qcs/solutions/Genshin_Impact-71/README.md b/tracks/qcs/solutions/Genshin_Impact-71/README.md new file mode 100644 index 000000000..87a1055a9 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/README.md @@ -0,0 +1,197 @@ +# Exact 347-gate Occam arithmetic circuits + +## Team + +| | | +|---|---| +| Team name | `Genshin_Impact` | +| Members | Kexiang Mao ([@Mao-Kexiang](https://github.com/Mao-Kexiang)) | +| Track | `qcs` | + +Kexiang is discussing a possible future team-up with Zongyue Liu. This submission does not list Zongyue as a member or co-author without mutual confirmation; it credits his prior circuit baseline explicitly below. + +## Challenge + +| | | +|---|---| +| Challenge | Recover and minimize the four hidden arithmetic circuits | +| Catalog issue | [QuantumBFS/quantum.harness issue #71](https://github.com/QuantumBFS/quantum.harness/issues/71) | +| Solution path | `tracks/qcs/solutions/Genshin_Impact-71/` | + +## Bottom line + +This submission identifies all four hidden functions, supplies exact predictions and legal fanin-2 netlists, and reduces the independently audited public 355-gate baseline to **347 gates**: + +| Instance | Recovered function | Baseline | This submission | Reduction | +|---|---|---:|---:|---:| +| A | `x + y` | 37 | 37 | 0 | +| B | `abs(x - y)` | 49 | 49 | 0 | +| C | `x * y` | 156 | **152** | −4 | +| D | `x^2 + y^2` | 113 | **109** | −4 | +| Total | — | 355 | **347** | **−8** | + +All four canonical circuits pass exhaustive direct-formula verification: 87,040 input assignments and 764,928 output bits checked with zero mismatches. No canonical circuit contains a dead gate, constant gate, duplicate gate function, or complementary gate-function pair. + +This is an improvement over the best public baseline we located and froze on 2026-07-30. It is not a proof that 347 is globally optimal. A=37 matches the known `5n - 3` adder bound in the relevant basis; B, C, and D remain open. + +## Prior work and credit + +The 37/49/156/113 = 355 baseline is from **Zongyue Liu's [PR #156](https://github.com/QuantumBFS/quantum.harness/pull/156)**. + +We treated all external PR material as untrusted. We did not execute competitor code or instructions. We extracted only strict plain-text netlists and independently verified them against direct arithmetic formulas. The submitted A37 and B49 netlists retain those verified pure-data circuits. The new circuit contribution here is: + +- C: 156 → 152 gates; +- D: 113 → 109 gates. + +## 1. Verified result + +| Instance | Gates | Assignments | Output bits | Netlist SHA-256 | +|---|---:|---:|---:|---| +| A | 37 | 65,536 | 589,824 | `56c95acceeedb40f54fdae2e5fc2840d1bf58e0989f46a950e6a56742fd20e24` | +| B | 49 | 16,384 | 114,688 | `289b60494a703cf7f74a4aef2f540d225aeff22d86c4d709aaa5b661cc69a0a7` | +| C | 152 | 4,096 | 49,152 | `67540307369fedfffdb2b1a6473eff5e0bbfeb0e4873d03fddbeceb653cd071c` | +| D | 109 | 1,024 | 11,264 | `cd3f317f4a0b88818e54869e40b4550fd67549f8eb4a5eb02c74db4ec6864dbd` | + +The immutable evidence snapshot is in `frontier-347/`; `SHA256SUMS` and `COMPLETE` are emitted only after every audit and manifest stage succeeds. + +The practice instances also pass every input: + +| Practice circuit | Gates | Audit | +|---|---:|---| +| 4-bit addition | 17 | 256/256 exact; official commitment matches | +| 4×4 multiplication | 64 | 256/256 exact; official commitment matches | + +These practice counts are not claimed globally optimal. + +## 2. How the reduction was found + +The successful route is relation-SAT local resynthesis through a pinned eSLIM build, combined with immediate rebasing after each verified improvement. + +The C search is a branching history, not a linear `156 → 154 → 153 → 152` claim: + +```text +C156 +├── size 5, job 43027 task 5 → C154 +│ └── size 7, job 43188 task 3 → C152 +└── size 8, job 43027 task 8 → C153 +``` + +D113 → D109 came from job 42633 at size 6. A second D stage found no further reduction. A third C stage tested sizes 4–8 from C152; all five jobs completed as `VALIDATED_NO_IMPROVEMENT`. + +The main empirical lesson is to run several local scales in parallel and rebase immediately. A small rewrite changes downstream support, candidate divisors, relations, and the next useful windows. Continuing only on the old parent leaves reductions on the table. + +## 3. Every suggested method family was attempted + +| Method family | Actual experiment | Outcome | +|---|---|---| +| Tensor-network/MPS completion | 64 continuous MPS configurations, rank/order audits, Boolean distillation | No full-domain exact learned model; useful structural diagnostic | +| Exact Boolean/GF(2) tensor networks | Exact TT/graph contraction and legal-gate conversion | Exact, but A/B/C/D cost 114/488/4,831/1,132 gates | +| BDD/ZDD learning | Shared BDD, minimum-width/reachable-state MaxSAT, genuine shared ZDD | Completed versions generalize poorly and map to large circuits; three exact jobs remain research-in-progress | +| SAT exact synthesis | MFFC, divisor, and safe multi-root window searches | Many local UNSAT certificates; no additional canonical reduction | +| Independent 0-1 IP/MILP | Explicit ports, phases, gate selection, and truth values in HiGHS | 18 windows: 13 proven infeasible, 5 limits, 0 candidate | +| ABC logic synthesis | EXDC, exact ROBDD→ABC, and direct C152/D109 optimization | Direct frontier run remains 152/109; other exact mappings are larger | +| Espresso | Berkeley 1994 build attempted repeatedly | Infrastructure failure from a modern-libc `strlen` declaration conflict; no scientific result claimed | +| Symbolic/template hybrid | Frozen train-only semantic dispatch into generic arithmetic templates | Exact templates 17/64/37/50/168/127, then relation-SAT supplies the frontier compression | + +The issue's tensor-network hint was therefore taken seriously. Exact ranks reveal useful structure: interleaving inputs lowers A's maximum TT rank from 255 to 3 and gives B rank 5, while C remains around 57–61. The right use of TN here is to rank cuts and windows, not to equate tensor rank with final six-gate cost. + +## 4. Correctness lesson: safe local boundaries + +An early window encoding allowed descendants of target roots as external divisors. That can create cycles and false SAT candidates. Those candidates are invalid and are not shipped. + +A safe replacement window must satisfy all of: + +1. its boundary excludes removed gates; +2. its boundary excludes descendants of every target root, preserving acyclicity; +3. every root is uniquely determined by the boundary on the reachable input space. + +The final safe-v3 finite-budget results are: + +| Parent | Windows | UNSAT | TIMEOUT | Candidates | +|---|---:|---:|---:|---:| +| C156 | 100 | 99 | 1 | 0 | +| C154 | 100 | 97 | 3 | 0 | +| D109 | 79 | 64 | 15 | 0 | + +UNSAT means only the fixed parent hash, roots, boundary, gate library, and budget are impossible. A timeout is not an infeasibility proof, and certificates do not automatically transfer to a new parent. + +## 5. Reproduce + +From the repository root: + +```bash +python tracks/qcs/solutions/Genshin_Impact-71/search/audit_arithmetic_formula.py \ + A tracks/qcs/solutions/Genshin_Impact-71/frontier-347/netlists/mystery-A.txt +python tracks/qcs/solutions/Genshin_Impact-71/search/audit_arithmetic_formula.py \ + B tracks/qcs/solutions/Genshin_Impact-71/frontier-347/netlists/mystery-B.txt +python tracks/qcs/solutions/Genshin_Impact-71/search/audit_arithmetic_formula.py \ + C tracks/qcs/solutions/Genshin_Impact-71/frontier-347/netlists/mystery-C.txt +python tracks/qcs/solutions/Genshin_Impact-71/search/audit_arithmetic_formula.py \ + D tracks/qcs/solutions/Genshin_Impact-71/frontier-347/netlists/mystery-D.txt + +cd tracks/qcs/solutions/Genshin_Impact-71/frontier-347 +sha256sum -c SHA256SUMS +``` + +Core regression suites: + +```bash +python -m unittest discover \ + -s tracks/qcs/solutions/Genshin_Impact-71/search \ + -p 'test_*.py' +python tracks/qcs/solutions/Genshin_Impact-71/routes/exact_tn/test_exact_tn.py -v +python tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/test_routes.py -v +python tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/test_exact_milp.py -v +``` + +Heavy searches were run through Slurm on `t02-server`, with seed 42 as the canonical root seed. The eSLIM source was pinned to commit `51e9f77429627473db623058157b66a1192cbb59`. + +## 6. File map + +| Path | Role | +|---|---| +| `mystery-{A,B,C,D}.txt` | Submission-ready canonical circuits | +| `predictions/mystery-*/test_outputs.csv` | Predicted hidden outputs | +| `frontier-347/` | Netlists, four direct-formula audits, manifest, hashes, completion sentinel | +| `search/audit_arithmetic_formula.py` | Independent exhaustive arithmetic verifier | +| `search/bridge.py` | Strict netlist/BLIF/eSLIM bridge | +| `search/*safe*v3*` | Acyclic, functionally sufficient exact-window workflow | +| `search/ip_milp/` | Independent exact 0-1 MILP route and report | +| `tensor_network/` | Approximate MPS experiments and rank diagnostics | +| `routes/exact_tn/` | Exact Boolean/GF(2) tensor-network route | +| `search/bdd_route/` | BDD/ZDD/MaxSAT route | +| `routes/symbolic_hybrid/` | Arithmetic templates, ABC, and Espresso route | + +Generated row-level results and solver logs remain under the gitignored `results/occam71/` tree on `t02-server`; compact final evidence is tracked here. + +## 7. Issue #71 deliverable audit + +| Required deliverable | Status | Evidence | +|---|---|---| +| `mystery-*.txt` legal circuits | Complete | Four root netlists and `frontier-347/` | +| Predicted `test_outputs.csv` | Complete | `predictions/mystery-*/` | +| Search scripts | Complete | SAT/eSLIM, TN, BDD/ZDD, MILP, ABC/template routes | +| Pitch-style README | Complete | This document | +| Hidden-function beliefs | Complete and commitment-checked | A add, B absolute difference, C multiply, D sum of squares | +| Exact hidden accuracy | Complete | Direct-formula exhaustive audits, zero mismatches | +| Gate-count improvement | Complete | 355 → 347 relative to the frozen public baseline | + +## 8. Claim boundary and next work + +This work does not prove global optimality for B, C, or D; does not turn TIMEOUT into UNSAT; and does not count BDD nodes, ABC internals, PLA cubes, or TN rank as legal gates. + +Most promising next steps: + +1. regenerate safe-v3 windows on C152; +2. add exact-TT separator scores to C window ranking; +3. preserve SAT/MILP UNSAT certificates as a parent-hash-bound cache; +4. repair or replace the Espresso build; +5. continue multi-scale immediate-rebase search for C151 and D108. + +## 9. Suggested reading order + +1. `frontier-347/manifest.json` and the four audits; +2. this README, Sections 2–4; +3. `tensor_network/TN_RESULTS.md`; +4. `routes/exact_tn/README.md`; +5. `search/ip_milp/REPORT.md`; diff --git a/tracks/qcs/solutions/Genshin_Impact-71/frontier-347/COMPLETE b/tracks/qcs/solutions/Genshin_Impact-71/frontier-347/COMPLETE new file mode 100644 index 000000000..fbdc85ed5 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/frontier-347/COMPLETE @@ -0,0 +1,2 @@ +status=FRONTIER_347_COMPLETE +manifest_sha256=d300d5a65830fcca0178950a98beb13c2efe1a5bc51379f1c928a6c0f78dbc77 diff --git a/tracks/qcs/solutions/Genshin_Impact-71/frontier-347/SHA256SUMS b/tracks/qcs/solutions/Genshin_Impact-71/frontier-347/SHA256SUMS new file mode 100644 index 000000000..6b03f9402 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/frontier-347/SHA256SUMS @@ -0,0 +1,9 @@ +56c95acceeedb40f54fdae2e5fc2840d1bf58e0989f46a950e6a56742fd20e24 netlists/mystery-A.txt +289b60494a703cf7f74a4aef2f540d225aeff22d86c4d709aaa5b661cc69a0a7 netlists/mystery-B.txt +67540307369fedfffdb2b1a6473eff5e0bbfeb0e4873d03fddbeceb653cd071c netlists/mystery-C.txt +cd3f317f4a0b88818e54869e40b4550fd67549f8eb4a5eb02c74db4ec6864dbd netlists/mystery-D.txt +3f94c8e5b9a58472573b18f1fc9280ab67dfcd1c60dc1b51d6b2d4dcecba2a39 audit-A.json +035e5ec36e21f5364696192094bf40fdf0293fd0052e055c42e3e9ab2349aa8f audit-B.json +e540f25439453daef23253398c6509b353ae927f1927e137eed4bbb108303195 audit-C.json +df096f3e3b987e11a49d26986efae3f4d14249fb3b573136a5ddb65b3a1cf226 audit-D.json +d300d5a65830fcca0178950a98beb13c2efe1a5bc51379f1c928a6c0f78dbc77 manifest.json diff --git a/tracks/qcs/solutions/Genshin_Impact-71/frontier-347/audit-A.json b/tracks/qcs/solutions/Genshin_Impact-71/frontier-347/audit-A.json new file mode 100644 index 000000000..4551b588e --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/frontier-347/audit-A.json @@ -0,0 +1,19 @@ +{ + "instance": "A", + "candidate": "results/occam71/frontier-347/netlists/mystery-A.txt", + "candidate_sha256": "56c95acceeedb40f54fdae2e5fc2840d1bf58e0989f46a950e6a56742fd20e24", + "inputs": 16, + "outputs": 9, + "gates": 37, + "assignments": 65536, + "output_bits_checked": 589824, + "formula": "x+y", + "equivalent": true, + "mismatches": [], + "structure": { + "dead_gates": [], + "constant_gates": [], + "duplicate_gate_functions": [], + "complement_gate_functions": [] + } +} diff --git a/tracks/qcs/solutions/Genshin_Impact-71/frontier-347/audit-B.json b/tracks/qcs/solutions/Genshin_Impact-71/frontier-347/audit-B.json new file mode 100644 index 000000000..28833ff8f --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/frontier-347/audit-B.json @@ -0,0 +1,19 @@ +{ + "instance": "B", + "candidate": "results/occam71/frontier-347/netlists/mystery-B.txt", + "candidate_sha256": "289b60494a703cf7f74a4aef2f540d225aeff22d86c4d709aaa5b661cc69a0a7", + "inputs": 14, + "outputs": 7, + "gates": 49, + "assignments": 16384, + "output_bits_checked": 114688, + "formula": "abs(x-y)", + "equivalent": true, + "mismatches": [], + "structure": { + "dead_gates": [], + "constant_gates": [], + "duplicate_gate_functions": [], + "complement_gate_functions": [] + } +} diff --git a/tracks/qcs/solutions/Genshin_Impact-71/frontier-347/audit-C.json b/tracks/qcs/solutions/Genshin_Impact-71/frontier-347/audit-C.json new file mode 100644 index 000000000..1899ed824 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/frontier-347/audit-C.json @@ -0,0 +1,19 @@ +{ + "instance": "C", + "candidate": "results/occam71/frontier-347/netlists/mystery-C.txt", + "candidate_sha256": "67540307369fedfffdb2b1a6473eff5e0bbfeb0e4873d03fddbeceb653cd071c", + "inputs": 12, + "outputs": 12, + "gates": 152, + "assignments": 4096, + "output_bits_checked": 49152, + "formula": "x*y", + "equivalent": true, + "mismatches": [], + "structure": { + "dead_gates": [], + "constant_gates": [], + "duplicate_gate_functions": [], + "complement_gate_functions": [] + } +} diff --git a/tracks/qcs/solutions/Genshin_Impact-71/frontier-347/audit-D.json b/tracks/qcs/solutions/Genshin_Impact-71/frontier-347/audit-D.json new file mode 100644 index 000000000..58a819b90 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/frontier-347/audit-D.json @@ -0,0 +1,19 @@ +{ + "instance": "D", + "candidate": "results/occam71/frontier-347/netlists/mystery-D.txt", + "candidate_sha256": "cd3f317f4a0b88818e54869e40b4550fd67549f8eb4a5eb02c74db4ec6864dbd", + "inputs": 10, + "outputs": 11, + "gates": 109, + "assignments": 1024, + "output_bits_checked": 11264, + "formula": "x*x+y*y", + "equivalent": true, + "mismatches": [], + "structure": { + "dead_gates": [], + "constant_gates": [], + "duplicate_gate_functions": [], + "complement_gate_functions": [] + } +} diff --git a/tracks/qcs/solutions/Genshin_Impact-71/frontier-347/manifest.json b/tracks/qcs/solutions/Genshin_Impact-71/frontier-347/manifest.json new file mode 100644 index 000000000..d8c38e860 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/frontier-347/manifest.json @@ -0,0 +1,68 @@ +{ + "all_formula_audits_exact": true, + "challenge": "QuantumBFS/quantum.harness issue 71", + "gate_basis": [ + "AND", + "OR", + "XOR", + "NAND", + "NOR", + "XNOR" + ], + "instances": { + "A": { + "assignments_exhaustively_checked": 65536, + "formula": "x+y", + "formula_audit_sha256": "3f94c8e5b9a58472573b18f1fc9280ab67dfcd1c60dc1b51d6b2d4dcecba2a39", + "gates": 37, + "inputs": 16, + "netlist_sha256": "56c95acceeedb40f54fdae2e5fc2840d1bf58e0989f46a950e6a56742fd20e24", + "output_bits_checked": 589824, + "outputs": 9, + "source": "reference-355/mystery-A.txt", + "synthesis": "37-gate ripple-carry adder; independently audited" + }, + "B": { + "assignments_exhaustively_checked": 16384, + "formula": "abs(x-y)", + "formula_audit_sha256": "035e5ec36e21f5364696192094bf40fdf0293fd0052e055c42e3e9ab2349aa8f", + "gates": 49, + "inputs": 14, + "netlist_sha256": "289b60494a703cf7f74a4aef2f540d225aeff22d86c4d709aaa5b661cc69a0a7", + "output_bits_checked": 114688, + "outputs": 7, + "source": "reference-355/mystery-B.txt", + "synthesis": "49-gate absolute-difference circuit; independently audited" + }, + "C": { + "assignments_exhaustively_checked": 4096, + "formula": "x*y", + "formula_audit_sha256": "e540f25439453daef23253398c6509b353ae927f1927e137eed4bbb108303195", + "gates": 152, + "inputs": 12, + "netlist_sha256": "67540307369fedfffdb2b1a6473eff5e0bbfeb0e4873d03fddbeceb653cd071c", + "output_bits_checked": 49152, + "outputs": 12, + "source": "eSLIM stage2 job 43188 task 3", + "synthesis": "relation-SAT cascade from C154, window size 7, root seed 42" + }, + "D": { + "assignments_exhaustively_checked": 1024, + "formula": "x*x+y*y", + "formula_audit_sha256": "df096f3e3b987e11a49d26986efae3f4d14249fb3b573136a5ddb65b3a1cf226", + "gates": 109, + "inputs": 10, + "netlist_sha256": "cd3f317f4a0b88818e54869e40b4550fd67549f8eb4a5eb02c74db4ec6864dbd", + "output_bits_checked": 11264, + "outputs": 11, + "source": "eSLIM pilot job 42633", + "synthesis": "relation-SAT local resynthesis, size 6, seed 42" + } + }, + "inverters_free": true, + "root_seed": 42, + "total_assignments_exhaustively_checked": 87040, + "total_gates": 347, + "total_output_bits_checked": 764928, + "trust_boundary": "Competitor material was never executed. Pure netlists were parsed as strict ASCII data and all frontier circuits were exhaustively checked directly against independently encoded arithmetic formulas." +} diff --git a/tracks/qcs/solutions/Genshin_Impact-71/frontier-347/netlists/mystery-A.txt b/tracks/qcs/solutions/Genshin_Impact-71/frontier-347/netlists/mystery-A.txt new file mode 100644 index 000000000..f5cdc1664 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/frontier-347/netlists/mystery-A.txt @@ -0,0 +1,39 @@ +INPUTS 16 +w1 = XOR x1 x9 +w2 = AND x1 x9 +w3 = XOR x2 x10 +w4 = XOR w3 w2 +w5 = AND x2 x10 +w6 = AND w3 w2 +w7 = OR w5 w6 +w8 = XOR x3 x11 +w9 = XOR w8 w7 +w10 = AND x3 x11 +w11 = AND w8 w7 +w12 = OR w10 w11 +w13 = XOR x4 x12 +w14 = XOR w13 w12 +w15 = AND x4 x12 +w16 = AND w13 w12 +w17 = OR w15 w16 +w18 = XOR x5 x13 +w19 = XOR w18 w17 +w20 = AND x5 x13 +w21 = AND w18 w17 +w22 = OR w20 w21 +w23 = XOR x6 x14 +w24 = XOR w23 w22 +w25 = AND x6 x14 +w26 = AND w23 w22 +w27 = OR w25 w26 +w28 = XOR x7 x15 +w29 = XOR w28 w27 +w30 = AND x7 x15 +w31 = AND w28 w27 +w32 = OR w30 w31 +w33 = XOR x8 x16 +w34 = XOR w33 w32 +w35 = AND x8 x16 +w36 = AND w33 w32 +w37 = OR w35 w36 +OUTPUTS w1 w4 w9 w14 w19 w24 w29 w34 w37 diff --git a/tracks/qcs/solutions/Genshin_Impact-71/frontier-347/netlists/mystery-B.txt b/tracks/qcs/solutions/Genshin_Impact-71/frontier-347/netlists/mystery-B.txt new file mode 100644 index 000000000..c574016d2 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/frontier-347/netlists/mystery-B.txt @@ -0,0 +1,51 @@ +INPUTS 14 +w1 = XNOR x8 x1 +w2 = NOR ~x8 x1 +w3 = AND w2 x9 +w4 = XNOR w2 ~x9 +w5 = AND w4 ~x2 +w6 = NOR w5 w3 +w7 = NOR w6 x3 +w8 = XNOR w6 ~x3 +w9 = AND w8 x10 +w10 = NOR w9 w7 +w11 = NOR w10 x4 +w12 = XNOR w10 ~x4 +w13 = AND w12 x11 +w14 = NOR w13 w11 +w15 = NOR w14 x5 +w16 = XNOR w14 ~x5 +w17 = AND w16 x12 +w18 = NOR w17 w15 +w19 = NOR w18 ~x13 +w20 = XNOR w18 x13 +w21 = AND w20 ~x6 +w22 = NOR w21 w19 +w23 = AND w22 x7 +w24 = XNOR w22 ~x7 +w25 = AND w24 ~x14 +w26 = NOR w25 w23 +w27 = AND w26 ~w1 +w28 = XNOR w4 x2 +w29 = XNOR w28 w27 +w30 = AND w28 w1 +w31 = NOR w30 ~w26 +w32 = XNOR w8 ~x10 +w33 = XNOR w32 w31 +w34 = AND w32 w30 +w35 = NOR w34 ~w26 +w36 = XNOR w12 ~x11 +w37 = XNOR w36 w35 +w38 = AND w36 w34 +w39 = AND ~w38 w26 +w40 = XNOR w16 ~x12 +w41 = XNOR w40 w39 +w42 = AND w40 w38 +w43 = NOR w42 ~w26 +w44 = XNOR w20 x6 +w45 = XNOR w44 w43 +w46 = NAND w44 w42 +w47 = NAND w46 w26 +w48 = XNOR w24 x14 +w49 = XNOR w48 w47 +OUTPUTS ~w1 w29 w33 w37 w41 w45 w49 diff --git a/tracks/qcs/solutions/Genshin_Impact-71/frontier-347/netlists/mystery-C.txt b/tracks/qcs/solutions/Genshin_Impact-71/frontier-347/netlists/mystery-C.txt new file mode 100644 index 000000000..a502d9bb3 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/frontier-347/netlists/mystery-C.txt @@ -0,0 +1,154 @@ +INPUTS 12 +w1 = AND x6 x7 +w2 = AND x6 x8 +w3 = AND x6 x9 +w4 = AND x6 x10 +w5 = AND x6 x11 +w6 = AND x6 x12 +w7 = AND x5 x8 +w8 = AND x5 x9 +w9 = XOR w8 w2 +w10 = AND x5 x10 +w11 = AND x5 x11 +w12 = XOR w11 w4 +w13 = AND x5 x12 +w14 = XOR w13 w5 +w15 = AND x5 x7 +w16 = AND x4 x7 +w17 = AND x4 x8 +w18 = XOR w17 w15 +w19 = AND w17 w15 +w20 = AND x4 x9 +w21 = AND w20 ~w15 +w22 = XOR w20 w7 +w23 = OR w22 w21 +w24 = AND x4 x10 +w25 = AND x4 x11 +w26 = XOR w25 w10 +w27 = AND x4 x12 +w28 = AND x3 x7 +w29 = AND x3 x8 +w30 = XOR w29 w16 +w31 = AND x3 x9 +w32 = AND x3 x10 +w33 = AND x3 x11 +w34 = XOR w33 w24 +w35 = AND x3 x12 +w36 = AND x2 x9 +w37 = AND x2 x10 +w38 = AND x2 x11 +w39 = AND x2 x12 +w40 = AND x2 x7 +w41 = AND x2 x8 +w42 = AND x1 x9 +w43 = XOR w28 w42 +w44 = XOR w41 w43 +w45 = AND x1 x10 +w46 = XOR w45 w36 +w47 = AND x1 x12 +w48 = XOR w47 w38 +w49 = AND x1 x7 +w50 = AND x1 x11 +w51 = XOR w50 w37 +w52 = AND x1 x8 +w53 = AND w52 w40 +w54 = XOR w53 w44 +w55 = XOR w54 w28 +w56 = OR w43 w55 +w57 = XOR w56 w44 +w58 = XOR w45 w57 +w59 = OR w46 w58 +w60 = XOR w46 w57 +w61 = XOR w29 w60 +w62 = AND w61 ~w30 +w63 = XOR w59 w62 +w64 = XOR w63 w31 +w65 = XOR w50 w64 +w66 = AND w65 ~w51 +w67 = XOR w64 w51 +w68 = XOR w60 w30 +w69 = XOR w59 w60 +w70 = XOR w69 w31 +w71 = OR w63 w70 +w72 = XOR w71 w64 +w73 = XOR w72 w32 +w74 = XOR w71 w66 +w75 = XOR w74 w32 +w76 = XOR w75 w48 +w77 = XOR w47 w75 +w78 = AND w77 ~w48 +w79 = OR w74 w73 +w80 = XOR w79 w75 +w81 = XOR w80 w39 +w82 = XOR w79 w78 +w83 = OR w82 w81 +w84 = XOR w82 w39 +w85 = XOR w19 w84 +w86 = XOR w83 w84 +w87 = XOR w86 w35 +w88 = AND w53 ~w57 +w89 = AND ~w88 w22 +w90 = XOR w89 w19 +w91 = XOR w1 w90 +w92 = XOR w88 w18 +w93 = AND w67 w92 +w94 = XOR w1 w93 +w95 = AND w91 ~w94 +w96 = XOR w23 w95 +w97 = AND w85 ~w96 +w98 = XOR w83 w97 +w99 = OR w98 w87 +w100 = XOR w98 w35 +w101 = XOR w99 w100 +w102 = XOR w101 w27 +w103 = XOR w84 w96 +w104 = XOR w90 w94 +w105 = AND w76 w104 +w106 = XOR w33 w105 +w107 = OR w34 w106 +w108 = XOR w34 w105 +w109 = XOR w8 w108 +w110 = AND w109 ~w9 +w111 = XOR w107 w110 +w112 = XOR w100 w111 +w113 = XOR w26 w112 +w114 = XOR w3 w113 +w115 = XOR w25 w112 +w116 = OR w26 w115 +w117 = XOR w116 w113 +w118 = XOR w108 w9 +w119 = XOR w103 w118 +w120 = AND w103 w118 +w121 = XOR w3 w120 +w122 = AND w114 ~w121 +w123 = XOR w116 w122 +w124 = XOR w113 w121 +w125 = XOR w107 w108 +w126 = XOR w125 w100 +w127 = AND w126 ~w111 +w128 = XOR w99 w127 +w129 = XOR w128 w27 +w130 = XOR w117 w129 +w131 = AND w130 ~w123 +w132 = XOR w129 w123 +w133 = XOR w4 w132 +w134 = OR w12 w133 +w135 = XOR w12 w132 +w136 = XOR w134 w135 +w137 = OR w128 w102 +w138 = XOR w137 w131 +w139 = XOR w138 w136 +w140 = XOR w13 w139 +w141 = AND w140 ~w14 +w142 = XOR w139 w14 +w143 = XOR w137 w129 +w144 = XOR w143 w136 +w145 = OR w138 w144 +w146 = XOR w145 w141 +w147 = OR w11 w146 +w148 = AND w147 w6 +w149 = XOR w6 w146 +w150 = XOR w76 w104 +w151 = XOR w67 w92 +w152 = XOR w52 w40 +OUTPUTS w49 w152 w54 w68 w151 w150 w119 w124 w135 w142 w149 w148 diff --git a/tracks/qcs/solutions/Genshin_Impact-71/frontier-347/netlists/mystery-D.txt b/tracks/qcs/solutions/Genshin_Impact-71/frontier-347/netlists/mystery-D.txt new file mode 100644 index 000000000..01b23da77 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/frontier-347/netlists/mystery-D.txt @@ -0,0 +1,111 @@ +INPUTS 10 +w1 = AND x10 ~x9 +w2 = AND x9 x8 +w3 = AND x10 x8 +w4 = AND x10 x7 +w5 = AND w2 w4 +w6 = XOR w2 w4 +w7 = XOR x8 x7 +w8 = AND x9 x7 +w9 = AND x8 x7 +w10 = AND x7 ~x6 +w11 = AND ~w10 x8 +w12 = AND w7 x6 +w13 = AND x9 x6 +w14 = AND x10 x6 +w15 = AND x5 ~x4 +w16 = AND x4 ~x3 +w17 = AND x5 x3 +w18 = XOR w3 w17 +w19 = AND w3 w17 +w20 = AND x5 x2 +w21 = AND x3 ~x2 +w22 = XOR w21 w13 +w23 = AND w21 ~w13 +w24 = AND ~w23 x3 +w25 = AND w24 w14 +w26 = XOR w24 w14 +w27 = XOR w9 w26 +w28 = AND w9 w26 +w29 = OR w28 w25 +w30 = AND x4 x2 +w31 = XOR x6 x1 +w32 = AND x6 x1 +w33 = AND x2 ~x1 +w34 = XOR w33 w10 +w35 = AND x3 x1 +w36 = XOR w35 w12 +w37 = OR w10 x1 +w38 = AND w37 x2 +w39 = XOR w38 w35 +w40 = AND w36 ~w39 +w41 = XOR w39 w12 +w42 = AND x1 x5 +w43 = AND x4 x1 +w44 = AND w11 w43 +w45 = XOR w11 w43 +w46 = XOR w40 w45 +w47 = XOR w12 w46 +w48 = AND w22 w47 +w49 = XOR w8 w48 +w50 = AND w49 w42 +w51 = XOR w49 w42 +w52 = AND w8 w48 +w53 = XOR w52 w29 +w54 = XOR w53 x9 +w55 = AND w53 x9 +w56 = OR w55 w52 +w57 = XOR w22 w47 +w58 = AND w45 ~w47 +w59 = XOR w58 w44 +w60 = XOR w59 w27 +w61 = XOR w30 w60 +w62 = XOR w51 w61 +w63 = AND w61 w51 +w64 = XOR w50 w63 +w65 = XOR w6 w64 +w66 = AND w65 w20 +w67 = XOR w65 w20 +w68 = AND w54 w67 +w69 = OR w68 w66 +w70 = XOR w54 w67 +w71 = AND w6 w64 +w72 = OR w71 w5 +w73 = AND w60 w30 +w74 = AND w59 w27 +w75 = OR w74 w73 +w76 = XOR w16 w75 +w77 = AND w76 w70 +w78 = XOR w18 w77 +w79 = AND w18 w77 +w80 = OR w79 w19 +w81 = XOR w76 w70 +w82 = AND w16 ~w75 +w83 = AND ~w82 x4 +w84 = AND w69 w83 +w85 = XOR w69 w83 +w86 = AND w85 w72 +w87 = OR w86 w84 +w88 = AND w15 ~w87 +w89 = AND ~w88 x5 +w90 = XOR w15 w87 +w91 = XOR w1 w90 +w92 = AND w1 ~w90 +w93 = AND ~w92 x10 +w94 = XOR w85 w72 +w95 = OR w94 w56 +w96 = XOR w94 w56 +w97 = AND w96 ~w78 +w98 = AND ~w97 w95 +w99 = XOR w91 w98 +w100 = XOR w99 w80 +w101 = AND w99 ~w80 +w102 = OR w91 w98 +w103 = AND ~w101 w102 +w104 = XOR w89 w103 +w105 = AND w104 ~w93 +w106 = XOR w104 w93 +w107 = OR w89 w103 +w108 = AND ~w105 w107 +w109 = XOR w96 w78 +OUTPUTS w31 w32 w34 w41 w57 w62 w81 w109 w100 w106 w108 diff --git a/tracks/qcs/solutions/Genshin_Impact-71/mystery-A.txt b/tracks/qcs/solutions/Genshin_Impact-71/mystery-A.txt new file mode 100644 index 000000000..f5cdc1664 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/mystery-A.txt @@ -0,0 +1,39 @@ +INPUTS 16 +w1 = XOR x1 x9 +w2 = AND x1 x9 +w3 = XOR x2 x10 +w4 = XOR w3 w2 +w5 = AND x2 x10 +w6 = AND w3 w2 +w7 = OR w5 w6 +w8 = XOR x3 x11 +w9 = XOR w8 w7 +w10 = AND x3 x11 +w11 = AND w8 w7 +w12 = OR w10 w11 +w13 = XOR x4 x12 +w14 = XOR w13 w12 +w15 = AND x4 x12 +w16 = AND w13 w12 +w17 = OR w15 w16 +w18 = XOR x5 x13 +w19 = XOR w18 w17 +w20 = AND x5 x13 +w21 = AND w18 w17 +w22 = OR w20 w21 +w23 = XOR x6 x14 +w24 = XOR w23 w22 +w25 = AND x6 x14 +w26 = AND w23 w22 +w27 = OR w25 w26 +w28 = XOR x7 x15 +w29 = XOR w28 w27 +w30 = AND x7 x15 +w31 = AND w28 w27 +w32 = OR w30 w31 +w33 = XOR x8 x16 +w34 = XOR w33 w32 +w35 = AND x8 x16 +w36 = AND w33 w32 +w37 = OR w35 w36 +OUTPUTS w1 w4 w9 w14 w19 w24 w29 w34 w37 diff --git a/tracks/qcs/solutions/Genshin_Impact-71/mystery-B.txt b/tracks/qcs/solutions/Genshin_Impact-71/mystery-B.txt new file mode 100644 index 000000000..c574016d2 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/mystery-B.txt @@ -0,0 +1,51 @@ +INPUTS 14 +w1 = XNOR x8 x1 +w2 = NOR ~x8 x1 +w3 = AND w2 x9 +w4 = XNOR w2 ~x9 +w5 = AND w4 ~x2 +w6 = NOR w5 w3 +w7 = NOR w6 x3 +w8 = XNOR w6 ~x3 +w9 = AND w8 x10 +w10 = NOR w9 w7 +w11 = NOR w10 x4 +w12 = XNOR w10 ~x4 +w13 = AND w12 x11 +w14 = NOR w13 w11 +w15 = NOR w14 x5 +w16 = XNOR w14 ~x5 +w17 = AND w16 x12 +w18 = NOR w17 w15 +w19 = NOR w18 ~x13 +w20 = XNOR w18 x13 +w21 = AND w20 ~x6 +w22 = NOR w21 w19 +w23 = AND w22 x7 +w24 = XNOR w22 ~x7 +w25 = AND w24 ~x14 +w26 = NOR w25 w23 +w27 = AND w26 ~w1 +w28 = XNOR w4 x2 +w29 = XNOR w28 w27 +w30 = AND w28 w1 +w31 = NOR w30 ~w26 +w32 = XNOR w8 ~x10 +w33 = XNOR w32 w31 +w34 = AND w32 w30 +w35 = NOR w34 ~w26 +w36 = XNOR w12 ~x11 +w37 = XNOR w36 w35 +w38 = AND w36 w34 +w39 = AND ~w38 w26 +w40 = XNOR w16 ~x12 +w41 = XNOR w40 w39 +w42 = AND w40 w38 +w43 = NOR w42 ~w26 +w44 = XNOR w20 x6 +w45 = XNOR w44 w43 +w46 = NAND w44 w42 +w47 = NAND w46 w26 +w48 = XNOR w24 x14 +w49 = XNOR w48 w47 +OUTPUTS ~w1 w29 w33 w37 w41 w45 w49 diff --git a/tracks/qcs/solutions/Genshin_Impact-71/mystery-C.txt b/tracks/qcs/solutions/Genshin_Impact-71/mystery-C.txt new file mode 100644 index 000000000..a502d9bb3 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/mystery-C.txt @@ -0,0 +1,154 @@ +INPUTS 12 +w1 = AND x6 x7 +w2 = AND x6 x8 +w3 = AND x6 x9 +w4 = AND x6 x10 +w5 = AND x6 x11 +w6 = AND x6 x12 +w7 = AND x5 x8 +w8 = AND x5 x9 +w9 = XOR w8 w2 +w10 = AND x5 x10 +w11 = AND x5 x11 +w12 = XOR w11 w4 +w13 = AND x5 x12 +w14 = XOR w13 w5 +w15 = AND x5 x7 +w16 = AND x4 x7 +w17 = AND x4 x8 +w18 = XOR w17 w15 +w19 = AND w17 w15 +w20 = AND x4 x9 +w21 = AND w20 ~w15 +w22 = XOR w20 w7 +w23 = OR w22 w21 +w24 = AND x4 x10 +w25 = AND x4 x11 +w26 = XOR w25 w10 +w27 = AND x4 x12 +w28 = AND x3 x7 +w29 = AND x3 x8 +w30 = XOR w29 w16 +w31 = AND x3 x9 +w32 = AND x3 x10 +w33 = AND x3 x11 +w34 = XOR w33 w24 +w35 = AND x3 x12 +w36 = AND x2 x9 +w37 = AND x2 x10 +w38 = AND x2 x11 +w39 = AND x2 x12 +w40 = AND x2 x7 +w41 = AND x2 x8 +w42 = AND x1 x9 +w43 = XOR w28 w42 +w44 = XOR w41 w43 +w45 = AND x1 x10 +w46 = XOR w45 w36 +w47 = AND x1 x12 +w48 = XOR w47 w38 +w49 = AND x1 x7 +w50 = AND x1 x11 +w51 = XOR w50 w37 +w52 = AND x1 x8 +w53 = AND w52 w40 +w54 = XOR w53 w44 +w55 = XOR w54 w28 +w56 = OR w43 w55 +w57 = XOR w56 w44 +w58 = XOR w45 w57 +w59 = OR w46 w58 +w60 = XOR w46 w57 +w61 = XOR w29 w60 +w62 = AND w61 ~w30 +w63 = XOR w59 w62 +w64 = XOR w63 w31 +w65 = XOR w50 w64 +w66 = AND w65 ~w51 +w67 = XOR w64 w51 +w68 = XOR w60 w30 +w69 = XOR w59 w60 +w70 = XOR w69 w31 +w71 = OR w63 w70 +w72 = XOR w71 w64 +w73 = XOR w72 w32 +w74 = XOR w71 w66 +w75 = XOR w74 w32 +w76 = XOR w75 w48 +w77 = XOR w47 w75 +w78 = AND w77 ~w48 +w79 = OR w74 w73 +w80 = XOR w79 w75 +w81 = XOR w80 w39 +w82 = XOR w79 w78 +w83 = OR w82 w81 +w84 = XOR w82 w39 +w85 = XOR w19 w84 +w86 = XOR w83 w84 +w87 = XOR w86 w35 +w88 = AND w53 ~w57 +w89 = AND ~w88 w22 +w90 = XOR w89 w19 +w91 = XOR w1 w90 +w92 = XOR w88 w18 +w93 = AND w67 w92 +w94 = XOR w1 w93 +w95 = AND w91 ~w94 +w96 = XOR w23 w95 +w97 = AND w85 ~w96 +w98 = XOR w83 w97 +w99 = OR w98 w87 +w100 = XOR w98 w35 +w101 = XOR w99 w100 +w102 = XOR w101 w27 +w103 = XOR w84 w96 +w104 = XOR w90 w94 +w105 = AND w76 w104 +w106 = XOR w33 w105 +w107 = OR w34 w106 +w108 = XOR w34 w105 +w109 = XOR w8 w108 +w110 = AND w109 ~w9 +w111 = XOR w107 w110 +w112 = XOR w100 w111 +w113 = XOR w26 w112 +w114 = XOR w3 w113 +w115 = XOR w25 w112 +w116 = OR w26 w115 +w117 = XOR w116 w113 +w118 = XOR w108 w9 +w119 = XOR w103 w118 +w120 = AND w103 w118 +w121 = XOR w3 w120 +w122 = AND w114 ~w121 +w123 = XOR w116 w122 +w124 = XOR w113 w121 +w125 = XOR w107 w108 +w126 = XOR w125 w100 +w127 = AND w126 ~w111 +w128 = XOR w99 w127 +w129 = XOR w128 w27 +w130 = XOR w117 w129 +w131 = AND w130 ~w123 +w132 = XOR w129 w123 +w133 = XOR w4 w132 +w134 = OR w12 w133 +w135 = XOR w12 w132 +w136 = XOR w134 w135 +w137 = OR w128 w102 +w138 = XOR w137 w131 +w139 = XOR w138 w136 +w140 = XOR w13 w139 +w141 = AND w140 ~w14 +w142 = XOR w139 w14 +w143 = XOR w137 w129 +w144 = XOR w143 w136 +w145 = OR w138 w144 +w146 = XOR w145 w141 +w147 = OR w11 w146 +w148 = AND w147 w6 +w149 = XOR w6 w146 +w150 = XOR w76 w104 +w151 = XOR w67 w92 +w152 = XOR w52 w40 +OUTPUTS w49 w152 w54 w68 w151 w150 w119 w124 w135 w142 w149 w148 diff --git a/tracks/qcs/solutions/Genshin_Impact-71/mystery-D.txt b/tracks/qcs/solutions/Genshin_Impact-71/mystery-D.txt new file mode 100644 index 000000000..01b23da77 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/mystery-D.txt @@ -0,0 +1,111 @@ +INPUTS 10 +w1 = AND x10 ~x9 +w2 = AND x9 x8 +w3 = AND x10 x8 +w4 = AND x10 x7 +w5 = AND w2 w4 +w6 = XOR w2 w4 +w7 = XOR x8 x7 +w8 = AND x9 x7 +w9 = AND x8 x7 +w10 = AND x7 ~x6 +w11 = AND ~w10 x8 +w12 = AND w7 x6 +w13 = AND x9 x6 +w14 = AND x10 x6 +w15 = AND x5 ~x4 +w16 = AND x4 ~x3 +w17 = AND x5 x3 +w18 = XOR w3 w17 +w19 = AND w3 w17 +w20 = AND x5 x2 +w21 = AND x3 ~x2 +w22 = XOR w21 w13 +w23 = AND w21 ~w13 +w24 = AND ~w23 x3 +w25 = AND w24 w14 +w26 = XOR w24 w14 +w27 = XOR w9 w26 +w28 = AND w9 w26 +w29 = OR w28 w25 +w30 = AND x4 x2 +w31 = XOR x6 x1 +w32 = AND x6 x1 +w33 = AND x2 ~x1 +w34 = XOR w33 w10 +w35 = AND x3 x1 +w36 = XOR w35 w12 +w37 = OR w10 x1 +w38 = AND w37 x2 +w39 = XOR w38 w35 +w40 = AND w36 ~w39 +w41 = XOR w39 w12 +w42 = AND x1 x5 +w43 = AND x4 x1 +w44 = AND w11 w43 +w45 = XOR w11 w43 +w46 = XOR w40 w45 +w47 = XOR w12 w46 +w48 = AND w22 w47 +w49 = XOR w8 w48 +w50 = AND w49 w42 +w51 = XOR w49 w42 +w52 = AND w8 w48 +w53 = XOR w52 w29 +w54 = XOR w53 x9 +w55 = AND w53 x9 +w56 = OR w55 w52 +w57 = XOR w22 w47 +w58 = AND w45 ~w47 +w59 = XOR w58 w44 +w60 = XOR w59 w27 +w61 = XOR w30 w60 +w62 = XOR w51 w61 +w63 = AND w61 w51 +w64 = XOR w50 w63 +w65 = XOR w6 w64 +w66 = AND w65 w20 +w67 = XOR w65 w20 +w68 = AND w54 w67 +w69 = OR w68 w66 +w70 = XOR w54 w67 +w71 = AND w6 w64 +w72 = OR w71 w5 +w73 = AND w60 w30 +w74 = AND w59 w27 +w75 = OR w74 w73 +w76 = XOR w16 w75 +w77 = AND w76 w70 +w78 = XOR w18 w77 +w79 = AND w18 w77 +w80 = OR w79 w19 +w81 = XOR w76 w70 +w82 = AND w16 ~w75 +w83 = AND ~w82 x4 +w84 = AND w69 w83 +w85 = XOR w69 w83 +w86 = AND w85 w72 +w87 = OR w86 w84 +w88 = AND w15 ~w87 +w89 = AND ~w88 x5 +w90 = XOR w15 w87 +w91 = XOR w1 w90 +w92 = AND w1 ~w90 +w93 = AND ~w92 x10 +w94 = XOR w85 w72 +w95 = OR w94 w56 +w96 = XOR w94 w56 +w97 = AND w96 ~w78 +w98 = AND ~w97 w95 +w99 = XOR w91 w98 +w100 = XOR w99 w80 +w101 = AND w99 ~w80 +w102 = OR w91 w98 +w103 = AND ~w101 w102 +w104 = XOR w89 w103 +w105 = AND w104 ~w93 +w106 = XOR w104 w93 +w107 = OR w89 w103 +w108 = AND ~w105 w107 +w109 = XOR w96 w78 +OUTPUTS w31 w32 w34 w41 w57 w62 w81 w109 w100 w106 w108 diff --git a/tracks/qcs/solutions/Genshin_Impact-71/predictions/mystery-A/test_outputs.csv b/tracks/qcs/solutions/Genshin_Impact-71/predictions/mystery-A/test_outputs.csv new file mode 100644 index 000000000..ec30a6a81 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/predictions/mystery-A/test_outputs.csv @@ -0,0 +1,2001 @@ +input,output +1001001101010110,110011001 +0001000011110111,111011110 +1010010000111101,100001110 +1001001101101010,111110001 +0000011101101111,011010111 +0000101101110001,011110101 +0100010110110110,111100001 +0001001001110110,011011010 +1001101110000110,010111001 +0111001001101010,001001010 +0110001010111100,110000010 +0000100000011011,000101110 +0110000111000111,100101101 +1100111111110001,010000011 +0011100110000110,101111110 +0110001101110110,001011001 +1101010100100010,111101110 +0101110100111011,011010011 +1101100111111000,010111010 +1111100001100011,101001110 +0001100000001101,000100110 +1111011100111010,110100101 +0110000010111010,110001100 +1101010101010111,101010011 +0111111000101000,010010010 +0000001101010111,010101011 +1011111001111111,110111101 +1101000111000111,011101101 +0111110101001001,000010101 +1011001010111111,010100101 +0011001100010101,001011101 +0000110011010011,110111110 +1000100011101010,000101100 +0100010100110101,011100101 +1011111011110000,001100010 +1100100101100011,100110101 +0011001001001100,011111100 +1011110010110110,010101010 +0110001011010001,100010110 +1000110001001101,110001110 +1011110100111000,100110110 +1111000011100100,011011000 +1000100111001111,001000011 +0010100111010101,111111001 +0011000110010101,101011001 +1011001100010111,101011011 +0001000100011011,000001101 +0010000101110000,010010010 +1000000111111100,000000110 +0110100111011000,100011010 +1001001000001110,100111010 +0101001101101011,000001011 +0110110110110011,110000011 +1110100010001111,000100001 +0000000110001111,100011101 +1101001011001001,011110110 +1001011000010111,100010101 +0111011100000001,011101101 +1001111010110111,011001101 +0011011110001111,101110111 +0100110000110001,011111010 +1001110101000100,110110110 +0000100110110001,101110001 +0010101001011000,011101100 +0010111011110011,110000101 +1001101000000010,100110010 +0001111010000010,100111010 +1100010011111000,010000100 +1111100100111100,110110110 +1111001101111010,101101001 +1101011100010000,110011110 +1111010100101111,110001011 +0011101001010011,011001001 +1101100110101110,000010001 +0010110111010001,111111001 +0010000101011111,011111101 +0111100101100100,001000110 +1000100101111011,111101101 +0111010010011100,111001100 +1101101100010011,110001011 +0100010000100100,011000100 +0010010010000101,101000110 +1111011011001011,010000101 +0101101011100110,100000110 +1011110000010101,101001110 +0100110111101101,100101101 +0011101111111000,110111110 +0011110110110110,100101001 +1011111010010110,011001110 +1111110111110101,011101101 +1001111001100001,111111110 +1111010101011011,100100011 +1011101011011001,000111110 +1010110010110111,010001001 +1011101101011100,111010001 +0011010010001011,101111110 +0111010000110001,010111010 +1100101101100100,100111110 +0001101111001101,110100011 +1100001001000111,101001001 +1100111101000000,101011110 +0011010100100100,000010110 +1111101010110000,001101100 +1111110100100111,110001011 +1101110100010001,110000101 +1111011001001001,100000001 +1011101011110101,001100001 +1010100111010010,000001110 +0111111010111101,110111001 +1001000011111100,000100100 +0011100110011010,101011110 +0011110111001001,111100101 +1110101010100100,001111100 +0110100010010110,111111100 +0110111001111001,001010001 +0010011111100100,110100001 +1101101010101011,000011001 +0100001101100011,000100011 +0101010111000100,101100110 +0010100101001100,011000110 +1101101001001011,101101001 +0111010001111101,001101110 +1000011110011010,010111001 +0110000111110111,101011101 +1000000001001101,110011010 +1001111000111110,101011110 +0111001111011001,100101101 +0111000100000001,011100001 +0100001000000100,010001100 +0101011011101010,100000110 +0010011001111011,010000101 +1101111110111011,000110111 +0010100010000111,101011110 +1001110000100110,101110010 +1001010101100010,111101110 +0011011101110001,010111101 +1010101111111101,001010011 +0110100010111001,110011010 +0001011010101011,101111001 +1111101111000110,010000101 +1110000000111111,110000001 +1001010010110111,011010001 +1011100110010001,011001001 +1111000010100111,001011110 +1101010110111111,000101011 +1111010001010101,100110110 +0000101111000001,110010101 +0000111100001010,000000101 +1110110010000000,000111000 +1110101111000110,010111001 +1000011101011111,110110111 +1101100010100101,000000110 +1100001011000010,011000010 +0011011100011000,001000001 +1001010101010111,110010011 +1000010101110010,111101110 +0010001001010011,011100001 +0111110000110100,010101100 +0001101000001010,000101010 +1100011110001100,001010001 +0010111100101001,000100011 +1111011100110110,110110101 +0110001101111010,001001001 +1010011100110000,100011110 +1001001000101001,101110110 +0010001011001010,111010010 +1110100001010100,100000100 +0011101011110101,110100001 +1010011101111010,110000101 +0101100100100101,011111001 +1001101010111000,011011100 +0011100111010111,111000011 +1110100100001111,111000011 +1101010010111101,000101110 +1110101000110011,110001001 +1000111111110000,000000001 +1111100110011011,000111101 +0111111100000001,011111101 +1011000000110000,100110000 +0000110110111100,101101110 +0101100101111101,000110101 +1001111001101100,111101010 +0011010000010100,001010100 +1011111000011111,101011101 +0011000011110010,110110100 +1001110101111001,111010101 +0101001001110001,000110110 +0100000111000100,101001010 +0001110001001111,010101001 +1000101010100101,011011110 +0001001100001101,000111101 +0000010111101111,111010011 +0000101111111001,111101101 +1010011000011001,101111110 +0000100001010101,010111010 +0010010101100010,010101110 +0110101100010010,011110001 +1011101010100101,010000001 +1110101011010101,010000001 +0110001101101001,001110101 +0100010011100100,100100100 +0111010011001011,100000001 +1110111101001001,100100011 +0110100111111101,101010101 +0111110100101011,010010011 +0001001110011111,100000111 +1111011110011011,000100111 +0100111001100011,000111001 +1111010100111100,110101110 +0111000010011110,111000010 +1111010100011111,111001011 +0011011110110011,100111011 +1100001000110110,111101010 +1000101100101100,101000001 +0010111111100011,110111011 +1111111110000110,000001101 +0001111100101011,001100111 +1111111110101110,001011101 +1111010000110010,110111100 +1000111010011010,010100110 +1110110011000011,010111110 +0111001110011101,111000011 +0111100001100101,001000110 +1110000010101110,001111100 +0010111000100001,000111110 +1010100010110001,010001010 +1111111110110010,001100101 +0101001010011100,110000010 +0111111000100010,010000110 +1100011010010000,001101100 +1100000110110010,000010110 +1111011110110011,001111011 +1101110111110110,010101001 +0000100110010110,100111110 +1101110010101101,000011110 +1111111010000000,000000010 +1101010010101101,000001110 +0010010010100110,100100010 +0000000101001111,010011101 +0101100110111101,111010101 +0000111000011001,000100001 +1011000000110111,100111110 +1011101101010000,111001110 +0100101000100010,011010010 +0101111011100000,100000010 +0010001010001010,101010010 +1110100100110001,110001001 +0101011001100111,000010101 +1001001000010110,100011010 +0001110101101000,011100110 +0101000111111001,100101001 +1001001001010011,110010001 +0111100110110000,110101010 +0001111111010110,110001101 +0010001001010100,011101100 +0110101111000000,100110110 +1111000101000011,100010101 +0010100101011111,011100011 +0100010101001011,001011101 +0011011101101001,010000011 +1101011000101100,111110010 +0001001110100000,101100110 +0111010010110101,110110110 +1010111010101010,010100110 +1000011101010011,110101011 +0011100010110101,100100110 +1100000000111111,111111110 +1010001101101111,110111011 +0010001110101011,100110011 +0100000000110100,011101000 +0000010100101100,001010110 +1111100100010011,111001101 +0111011011001100,100001010 +1000000111110111,000011101 +0000001001011001,010110110 +0001111100011110,000011101 +0111001010110010,110110010 +1110110100011001,111100101 +1101001001010100,101011100 +0100000111111011,100001101 +0010101100001011,001001011 +0111110000100101,010001110 +1111010100100010,110011110 +1111100011010000,010101000 +0001000111100000,111100010 +0100000100111000,011110010 +0001001010011111,100000101 +0011011000111111,000101101 +0001001110101011,101110011 +1111111011110111,011101101 +1011110101111000,110110110 +0010001101110000,010010110 +1010101100111010,100011001 +1111111011111010,011110110 +1000101010100100,011011100 +1101110110110100,000101110 +1011101011110100,001100010 +0100110100110001,011111001 +0011100000011010,001011100 +0011101001001110,011100110 +0100101000011010,010101010 +0110100000001110,011000010 +0010000000001101,001011010 +0011011010011001,101000001 +0111101111110000,101101110 +0000011111101110,111010101 +0010000000001011,001010110 +0011111100010011,001000111 +1010010000100110,100100010 +0001000011000011,110100110 +1010110001110001,110000110 +1000100000101111,101000001 +0010000110100101,100101001 +0011010001101000,010000100 +0001101111011100,110010001 +1100100000000100,110011000 +0110101011001100,100100010 +1101000111100010,010010110 +0011001101100101,010011101 +1100111101001011,101000111 +0110111111101001,101100011 +1111101011101001,011011110 +0111010011101000,101000100 +1100111101101111,100101111 +1011110101011000,111010110 +0011010110010000,101011010 +1100000101111011,100001101 +1010000111110110,001011110 +1111100000010000,111001000 +0100000011010110,101101100 +1111010110011001,000100101 +1100111001010001,101111110 +1100101110100111,000111011 +1111100111110001,011101001 +1100000100000010,110000110 +1101111101011000,101010001 +1100110001111000,100010100 +0101101101001101,001100011 +0011001010001000,101110100 +0110100111000101,100111001 +0100111010001111,110001101 +1101000001100001,100010010 +1110101000110001,110001110 +0100111011111100,100011010 +1001100000010010,100001100 +1101110111100010,010000001 +1000011000111001,101111110 +1111001011000100,010011100 +0111010010000000,111101000 +0001010111111111,111001011 +0101010101101101,000001101 +1100010111000010,011001110 +1000111001110111,111110101 +1001110111110000,000100110 +1011000111100001,001010001 +0101100010101100,111100100 +0111100111011000,100111010 +1111000110100100,001011010 +0100100000111110,011100010 +1001101111001101,001100011 +1011001001110101,110111110 +1000111010110001,011111110 +0011011001101100,010001010 +0101111011110010,100100110 +0110110100010101,011110101 +0111010110101011,110000011 +0000111010110100,101110010 +0000011100010111,000100111 +1001010010101101,011110110 +1001110100100101,101110101 +1111101010010010,000101010 +1100000000011101,110111010 +1001000101000010,110100110 +1110001011101100,011111100 +1000010011100110,000100010 +1001000011011000,001001000 +1100101011001111,011000101 +1111000101111000,101101010 +1010010001011001,111111010 +0001011100000101,000100011 +0110011100000111,011000111 +0100010110100001,111001001 +0110111011010101,100001001 +1101000011101000,010001000 +0110110111011010,100010001 +0110110000100001,010111010 +0101010101011110,001001001 +0101101001010101,001000001 +1110010111101100,011110110 +0101001000011110,010000110 +1110011111010001,010011101 +0000101001101000,011001100 +0111111101000111,000001111 +0110111000110110,010001110 +1000000111110110,000011110 +1011111011100110,001001110 +0101100001011000,001011000 +1000101111100010,000110001 +1110111011010111,010001101 +1101111000000111,110110101 +1000111110100011,011011011 +1101100101001011,101101101 +0111100110001010,111101110 +1010101010010100,011111100 +0100100101111111,000010011 +1100111011001011,011000101 +1111000010100101,001011010 +0000101010111011,101101001 +0000101010011010,100101010 +1001110110110000,011000110 +0110011000001101,011010001 +1010100101111101,110010101 +1101010010111110,000101010 +0101011101011101,001001011 +0111000010111100,110100100 +0001101111000110,110111001 +0111011111011011,100100111 +1101100101100010,100001110 +1100010000011000,110111000 +1110010101101101,101110101 +1001010010100000,011101000 +0101110100011110,010011001 +1100111000100000,111011100 +1000100000111111,101100001 +0101111111100011,100000111 +0010001110100101,100101101 +0011000010010011,101010110 +0100101001001111,001000101 +1100010010001000,001011000 +1110011101111110,101001101 +0100011111000001,101001101 +0100100101010001,001110001 +1100010110111010,000000001 +1010001110000110,011001001 +1110101001001101,100100001 +0101000001111101,000100110 +1000000011100111,000101110 +0110001110011110,111111001 +1001110111101011,000010011 +0011111001100100,010001010 +1110001111000000,010100110 +1111000011100101,011011010 +1000010101011100,110110110 +1011101110011100,011010001 +0111110110010001,111000101 +1100111001101100,100101010 +1100111111001010,011000101 +1011001110011100,011000001 +1101000101100101,100011001 +1011011111110111,001110111 +0111001010100111,110011001 +0101111011110011,100100101 +0001101010001001,100101110 +0001010011111010,111000010 +0101101000000001,010110110 +1001001101001111,110111011 +1010001110100000,010100110 +1001001101100000,111100110 +0011101111010010,111001001 +1001010101110110,111010001 +1100100111100101,010111001 +0100010010101011,111011110 +1110000011100111,011101110 +0001111110101101,101101011 +1001010101110001,111011001 +0010111000010101,001110001 +0011001111110001,110110101 +1010001101011011,111110011 +0010010110111011,100000011 +0010001011011010,111110010 +1010111011001111,000101101 +1010000001100110,110101100 +1010000010000100,011001000 +0101110001110010,000100010 +1100110111011001,011100101 +0111001010110101,110111110 +0001101010000010,100110010 +1110001010101100,001111100 +0110000011111000,101001000 +0000110010001111,100001001 +0011000100101000,000001010 +1101101000101111,111100101 +1101101110010111,001000111 +0111011010001111,111110101 +1001010000011011,100000001 +0110000000001011,011010110 +1011101100100011,100001011 +1010000111000011,000100101 +0001100111000001,110110001 +1000111000110000,101111100 +1010111011100101,001110001 +0110011100111110,010001101 +1011111011001000,000010010 +0000001010111011,101110001 +0111001111010001,100110101 +1110111010110111,001001101 +1011010101101010,110000001 +0001100000111011,001011110 +0101101100000000,010110110 +0100000000011100,010111000 +1100111101010101,101110011 +0111101011110111,101100101 +1101011111001000,011111110 +0111100110100111,110000011 +0000111101111010,011100101 +0101011001100110,000010110 +1010000010111011,010001110 +1111010001000010,100011100 +1011100010000110,011111100 +1101101100010010,110001001 +1011100000111010,100111100 +0111101110000000,111110110 +1011010101100011,110011101 +0011110110101110,100011001 +0011010100101101,000001101 +0110000100111101,010000101 +0010101111100100,110111110 +0000001101111001,011110101 +1111011101010101,100110011 +1001111010111010,011010110 +1001001000010101,100011110 +1101001011110011,010110001 +1000100001101110,111000010 +1010110110001010,011000001 +0100101011000111,101011001 +0011001010010110,101011010 +1100110010101111,000101001 +0100010011011010,101111100 +1101000101010010,101010110 +1011001000111110,100100110 +0001100000010010,000001100 +1001001001010000,110010100 +0001010111000111,110100011 +1001001101110110,111011001 +1011100001001001,111101010 +1101100111010001,011001001 +0111011101111010,001100101 +0011011000101010,000000110 +0101011010111010,111000110 +1101001110110010,000110001 +1101110011101001,010010110 +0110000000001111,011011110 +1101100101111111,100110011 +0100010110001101,110010101 +0010100111001001,111001001 +0100110101110000,000000110 +0010100011001010,111001100 +1011111001010011,111000101 +0000011101000100,010000001 +0011011011011110,111001110 +0111111011000010,100000110 +1111001110101010,001001001 +0101101011011010,101011010 +1000110010011111,010101001 +1110101110101101,001100011 +0011000011100010,110010100 +0101000010101110,111111100 +0100101101001101,001000011 +0010011001001111,011010101 +0110110110100111,110110011 +1101111111111011,010110111 +0010110110000110,101010001 +1100111100010011,110111011 +1101101100111011,111011011 +0001000001100000,011100000 +0000011000101101,001010001 +1101001001111000,100101100 +0100011110111101,111110011 +0011101110000010,101110001 +0110100011000100,100111000 +1111100110011110,000110001 +0000000010001110,100011100 +1110011011010010,010011010 +0001011110101001,101111101 +1111011001101101,101001001 +0101111000111111,011011101 +1001010011101010,000000010 +1111011111111001,011100011 +1110001101110110,101011001 +0100100100001111,010000011 +0001001101111111,011000111 +1110111011000111,010110101 +1101111010010101,001001001 +0111010000010000,011011000 +0000000101000111,010001101 +0101011111000111,101100111 +0101110100000111,010110011 +0101011111101000,100000001 +1011110111001110,000011001 +0111101011101010,101011010 +0000010011011101,110110110 +1110001101011010,100001001 +1110011110101111,001110111 +1100000010011011,001110110 +0101110000110001,011000110 +0001101101010010,010001001 +0110111100000110,011010101 +0100001010111001,111110110 +0011100110111001,100111001 +0100011110100000,111001110 +1100010100010111,110100011 +0000100100011100,000100110 +0001111111011001,110010011 +0011100100001011,001101101 +1000100001101111,111000001 +1001101100000001,100110101 +1010101100110101,100000011 +0010100111101000,110101010 +1111111010100110,001001110 +1000111100110010,101111001 +1110010101011101,100001101 +1011111011001010,000010110 +1010000110011010,011110110 +1011010000010101,101010110 +1101001101010111,101011011 +1111010010100011,001011110 +1000111011101000,000100010 +1100100110010111,001111101 +0010010101001100,011010110 +0110000000001101,011011010 +0100100000110001,011110010 +0100100001100100,000111000 +0010010100100011,000101101 +0001110001100001,011111010 +1010001100011010,101110001 +1000110111001000,001000110 +0110101001000010,000110010 +0011011001011001,011000001 +1111001101011001,100101101 +0001010100100111,001100011 +1111010011000101,010010110 +0000000110011010,100110110 +0110111110110100,110001001 +1001000111111111,000100011 +1001001111111101,000100011 +1001001000110110,101011010 +0101100110011100,110010110 +1100011011101111,010110101 +1100000011111110,010000010 +0100001000100010,011000010 +0101010000011100,010001100 +1111010101110011,101111101 +1010000101001111,111011101 +0001010000100110,001100010 +0011100110001111,101100011 +0011000110110101,100111001 +0011001001001000,011110100 +0001110000000101,000110110 +0100011110111001,111111101 +1111000000001110,111111100 +1000111010111110,011101110 +1101100110101010,000011110 +1101101011011100,011010010 +0110100001111001,001011010 +0000000001110010,011100100 +1001100001010100,110000100 +0111100001100110,001000010 +1111111101100001,101000011 +1100111101000110,101010101 +1101110010101110,000011010 +0110100001110000,001001000 +0100101001111000,000011100 +0101101100001000,010101110 +1110111010001110,000101110 +1001000010110101,011011010 +0011111010100101,100001001 +0100111001111001,000010001 +0011101111110101,110100011 +1111000011010110,010111100 +0010001101110111,010011011 +0100000101000101,001001001 +1000011100111000,101111110 +1010101001111001,110011110 +1100110100110011,111111101 +1011010110100000,010011010 +0101111111110001,100100011 +0101011010010100,110010010 +1100001100011110,110111001 +1000101010011111,010100101 +0000111000101000,001000010 +1001100110100110,011111110 +0100110001011010,001100010 +0001011111110111,111010111 +1110000000010001,111100010 +1100100001110010,100001100 +0011110111011010,111010001 +0010100100010001,001110001 +1101000110101110,000000001 +0011010111000011,111101101 +1011111010001101,011101001 +0100000001101101,000111010 +1110000111011111,010000011 +1011111101000001,111111101 +1110101101101010,101101001 +1110110110100001,001111001 +1011110111101101,001011101 +1000011000100111,101000101 +1001000100011001,100001001 +1001010011000101,001100110 +1000111001101001,111000001 +1111011101110011,101111011 +1101110110101111,000011011 +1111010101011100,100101110 +1101011101111110,100101101 +1000001101101001,111010101 +1011001010001010,011110010 +0000011100110011,001101011 +0010111011001111,111001101 +1111000000011101,111000110 +1011110010010100,011001100 +1011101000110001,100101110 +0001000101001010,010110110 +0001001111111110,111000101 +1011000110001000,011110010 +0000110111011100,110101110 +0100001011100110,100101010 +0011101110000011,101110011 +1011010100011000,101000110 +1011000010101100,010000100 +1000111011100000,000111100 +0101110111000001,101111001 +0100111101110100,000001001 +0100111001000100,001010010 +1111101011100101,011000001 +1000010001111110,111110010 +0010111000111000,000010010 +1101000110100101,000011001 +0111000100001110,011111110 +0001101011000011,110110001 +1110010111110101,011010101 +1100011100001001,110011101 +0111110110010100,111001110 +1010100101001110,111000001 +1001111000000000,100111100 +1010000101100111,110101101 +0111001001011000,000101100 +0100000100000100,010001010 +0010001011000100,111001100 +1111111000111101,110111001 +0101001110100100,111101110 +1010111000001010,101000110 +1100001111000101,011001101 +1011010011100000,001011000 +0100011101011111,001110111 +0111001111000000,100010110 +1100110100111001,111100101 +0111110001110110,001101010 +1011010100011110,101001001 +1011111110011110,011011101 +1101100111011101,011010101 +0001000000001001,000110010 +0100100101000101,001011001 +1010100100111001,100011001 +1111011010110100,001110010 +1000000110100001,011000001 +0101101011010011,101001001 +1111011010110110,001110110 +0110000100111000,010001010 +0101001111010111,101011011 +1110001000101000,110110100 +1010010110011011,011111101 +0011010110011100,101001110 +0011010010101110,100001010 +1111000011000101,010011010 +1010111101101110,110101101 +0000011000011010,000111010 +0100101011000010,101010010 +0100100110110011,111110101 +1111101001000101,100000001 +0111101111110100,101100001 +1111110110100110,001001001 +1111110001110000,101100100 +0001111010011000,100010010 +0110011101001101,000110011 +0100101111011011,101101011 +0110100000101011,010101110 +1100000110001001,001010001 +0000101000010100,000111100 +1110010111010100,010010110 +1110110000100111,110110001 +0011111111100110,110001101 +1000000110100100,011001010 +1010001111011011,000001011 +0101010010011110,110001010 +0110011000010001,011101110 +0011001011000101,111101110 +1110000100101100,110111010 +1101100001110001,100101010 +0110010011000011,100101110 +1010010100001010,101011110 +0010011001101000,010111100 +1001101001111011,111011001 +1111001010011101,000100001 +1001000001110110,111011100 +1010001110001001,011010101 +0010100011101110,110100010 +1100010100111110,111110001 +0101110110001101,110101101 +1110000000000000,111000000 +1011110101101000,110010110 +1000110010011101,010101110 +0111000011001110,100000010 +1100001000011001,110110110 +0101010000010100,010010100 +1100010110001010,001011110 +0101011100101110,011110101 +0101011001001011,001111001 +0011010000011010,001000010 +0110001001000100,000101100 +0001101000111001,001011110 +0001011011010001,110011110 +0100111100011000,010100001 +1000001000110111,101101001 +1111100011001101,010010110 +0001111000010101,000001001 +1110101110111011,001011011 +0001011101110110,011010101 +0010011111101000,110111110 +0011001110011001,101001101 +0010110110000100,101010110 +1000111100000100,100010001 +0111001100101001,010001101 +1110100011010001,010001010 +0110100010111110,110010010 +1000010011010000,001101000 +0010111110101100,100101001 +0011001111100011,110010011 +1111101000110100,110100010 +0101101000010010,010001010 +1010000001001000,111010000 +1100111011100110,010110110 +0000101010110010,101110010 +0011110101001011,011100011 +1101011010100010,000011010 +0010000100101110,000111110 +0011100101110010,010101110 +1010111100010000,101111110 +1000010010111111,011110001 +1100101101010010,101110001 +1010001110000011,011000011 +0010100011110101,110000110 +0001001101001111,010111011 +0111101001010110,000100110 +0010100110101000,100101010 +0000001101111110,011111001 +1101000000001100,110111000 +1100001011100110,010101010 +0101001011000000,101100100 +0101001011011100,101000010 +1100101010010000,001110100 +1100001010011100,001111100 +0001101110101101,101100011 +0011001000100100,000011100 +0101000000000001,010100010 +0000100001010111,010111110 +0100101010001110,110000110 +0001110011100000,111111000 +0101001000011001,010001110 +0100010011000000,101001000 +1100000001010100,101101000 +0011110010001011,101100001 +1110101111010010,010001001 +1011111110111100,010111001 +0111111100001000,011100001 +1011010011100100,001010100 +0001100011001001,110101010 +0100011100100111,011000111 +0010010111001011,111011101 +1100111001111101,100011001 +0010110111001000,111000110 +0000111000100111,001010101 +1111110001010101,100101110 +0111001101100100,001011110 +0010111100110010,000000101 +1101000100100110,111101110 +1101110000111010,111010010 +0010100100001001,001001001 +0100110011010011,101111110 +1111001010000000,000010100 +0101110000010100,010001100 +0000011001010110,010100110 +0001111000101001,001100001 +1100101001010100,101111100 +1010100000010110,101111100 +1111101110110101,001100011 +0000000010010101,100101010 +0000111111000110,110010101 +0100110110111010,111100001 +1001010011111010,000100010 +1110110110111000,001010110 +0100100011000100,101011000 +1011101011000011,000001001 +0101111001101001,000010001 +0101011100101000,011111110 +1011000000011101,101000110 +1001100110011010,010011110 +1100010000010101,110100110 +0100111011111001,100010001 +0001011010101111,101110101 +0001111001111011,011010101 +1001100111000010,001110110 +1000011101001000,110011110 +0001001010011011,100001001 +0010101000110110,000000110 +1101100111110010,010101110 +1000011010110001,011101110 +1010111110011010,011100101 +1000011101000111,110000111 +0110011100110010,010011001 +0011110110101010,100010001 +0100010010011111,110110001 +0001001000000001,000100110 +1100001111011100,011111110 +0001001011110110,111011010 +0111010110101110,110001001 +0011001100110000,000110110 +0001100110111000,101011010 +0110000111100001,101100001 +1001011000001000,100111100 +0011011011010110,111010110 +0100111111001100,101001001 +1101110001110111,100101001 +0111101001001111,000010101 +0011101010001111,101100101 +1010000111000110,000101110 +0100000110111101,111111001 +1011000101010001,111010001 +0100110100010000,010111010 +1010001101000000,111000110 +0101000101000000,001100010 +1111001001011001,100101110 +0001000001011001,010001010 +1100111001001001,101000001 +1110000001100111,101101110 +0111101010000001,111110110 +0110010111000100,100100110 +0000011000101110,001010110 +1110100101000011,100110101 +0111001101101011,001001011 +1000111001011110,110101110 +1101010011110010,010111100 +1000000000011100,100111000 +0011111001010110,011001110 +0010111010111000,100010010 +1101010111001111,011110011 +1001101101010110,110000101 +1101101110010010,001001001 +1110001101000000,100100110 +0010000101110100,010011010 +1010011111011110,000001101 +1100011111100101,010100011 +1111100001111010,101111100 +1001111110101100,011101001 +1010000001111111,110000001 +0111101111001101,100010011 +0111100111011101,100110101 +1110011111001001,010111101 +1100111110111110,000011101 +1011100011111101,001110110 +1001010110111100,011001110 +1010010100010100,101100110 +0001001101000100,010101110 +0111110111101000,101010110 +1011111010000010,011111010 +1011110010000101,011110110 +1111111100110101,110101011 +0111111110101110,110011101 +0100010011100001,100101010 +1101001000010001,110010110 +1101001001110110,100111010 +1100000011110111,010011110 +1000001110011000,010110110 +1101101101110111,100100111 +0100000000111010,011110100 +1111110001001000,100010100 +0001100100100111,001111101 +1000110101010001,110111001 +0000001000011100,000111100 +1001100111110001,000101001 +1000000001010111,110101110 +0111101111100000,101001110 +1010111111101100,001101001 +1000100100011110,100100001 +0101101010000101,110111110 +1011001100110000,100110110 +1110010111100000,011101010 +1000010011111100,000001100 +1111000110000000,000010010 +0100010111001000,101011010 +0111010101011110,000101001 +1010101001110111,110000101 +1010010010100110,010100010 +0101110000001001,010100110 +1100001110100110,000101001 +0001101010110111,101000101 +0000001011000111,110001001 +0011010011001101,111110110 +1001001000100110,101101010 +0011001100011110,001000101 +0010001001110110,010011010 +0000000100101010,001010110 +1011010010010111,011010001 +0001100000110101,001000110 +1101101101101001,100011101 +0110000100011000,011110010 +0101010110111010,111000001 +1011011001110001,110111110 +1110001100010001,111100101 +0001011010110110,101010110 +0010001110111100,100000001 +0100110011110100,100001100 +1011111000101101,100011001 +0011011111110000,110111110 +1100011001011100,101110010 +1011100000100010,100001100 +0111101110001110,111100101 +0000111100110111,001110111 +1000000000010000,100100000 +0000111001110010,011111010 +0100010000110010,011101100 +1011011100101100,100001001 +0100101010110010,111110010 +1011010011111011,001100001 +1111011001110010,101111010 +1101000011001101,011111010 +0101110111110001,100100101 +1010001011110100,001011100 +1011110001011100,111011100 +1100111111010010,011111001 +0011111000110100,000101010 +1111010010010010,000111100 +1000000011010110,001101100 +1101011111101101,010001011 +0110010101001011,000111101 +0000101011010110,110111010 +1101001010111100,000100010 +0001100011110110,111000010 +0011100111100100,110000110 +0000101011011000,110101100 +1010101010110101,010000001 +1011111001001100,111101010 +0000011010010011,100101001 +0111111000010001,011000001 +0000011010100000,101001100 +1100101101110111,100000111 +1000111010100110,011010110 +0101101011010111,101000101 +0000011000000000,000001100 +0001000100010110,000011110 +0110011000101011,010111001 +0111101000000110,011111010 +0111011000110111,010110101 +0100001011111010,100001010 +1110010111111000,011000110 +1000011111110111,000010111 +0010011110110010,100011001 +1110101101110100,101000001 +1110000110010100,000011010 +1100111001111111,100011101 +0010011111100111,110100111 +0011001001100001,010010110 +1100111001111000,100010010 +1110010001000111,100100001 +0100101001010011,001110001 +1000101110001010,010001001 +1000000111110100,000011010 +1100111001110100,100001010 +0001101101100110,011111001 +0001111100101010,001100101 +0001011001010101,010010001 +1111011001011011,100100101 +0110010001000100,000100100 +1011000111010001,000110001 +1000001011000100,001001100 +1101110000100001,111111010 +0000101001100000,011010100 +1101101000011001,110011110 +0110101100010101,011111101 +1001010100110111,101010011 +0111101110100000,110001110 +0100011101001100,001010001 +1000010000001000,100011000 +0000001000000100,000001100 +1011001111101011,001001011 +0001011101001011,010111011 +1100000100101111,111011101 +0111001111011111,100100111 +1011010010011100,011001100 +0001100110101110,101100001 +0000000100111001,001110001 +1000011111001011,001011011 +0011111001000100,011110010 +1000111101010011,110111011 +0101101000000010,010110010 +0110111001111100,001011010 +0111110100010011,011000011 +1111110011100101,011001110 +1000000100001100,100011010 +1111101011100011,011001001 +1001100000100111,101111110 +1010000010001001,011010010 +1011101110001111,011100111 +0000010111010100,110100110 +1111001001111001,101101110 +0100011010110100,111100010 +1101001000100001,111100110 +1101011110110101,000110011 +1111111001010110,100101110 +1110100110110110,001000001 +0110010110100100,110100110 +1011110111010101,000101101 +1111011000101101,110001001 +0110011000011010,011111010 +1000000110101111,011011101 +0100110110001000,110000110 +1010011000110111,100010101 +1100010000110001,111101010 +0000011111001111,110010111 +1000001100100111,101001011 +1000110000010111,100110001 +0101111100100010,011111001 +1111100110001011,000011101 +0101111110110110,111001101 +1110001111011010,010001001 +1001101000100100,101111100 +0111100110000101,111111001 +1101100100100011,111110101 +1011110001001100,111101100 +1111101010011000,000111100 +1111011010000100,000010010 +1100111110111111,000011111 +0000001110110000,101100110 +0100000011011111,101111110 +1010101111010010,000001001 +1010001111100000,001100110 +1100101110010101,001111101 +1000100001010001,110110010 +0101001111011001,101001101 +0001100111010101,110000101 +0000001010000101,100001110 +1001110001100001,111111010 +0101111000010111,010001101 +0001001001011111,010000101 +0000100001110101,011111010 +0111001010100011,110010001 +0011011111101010,110000101 +0101011010000011,110101001 +0110010100111010,010000001 +1000011101110111,111100111 +0111001010101010,110001010 +1101111001011101,101011001 +1000101001110101,111111110 +0111111100110101,010101011 +1110100000110110,110000010 +1101000101011000,101001010 +1011101100110110,100100101 +1101100000100111,111111110 +0110101011100111,101111001 +0000001100001111,000011011 +1110111110100011,001111011 +0101101100000100,010111110 +1000011000000001,100001110 +1000111111010111,001110111 +1101100110000110,001111110 +1001001011110010,000110010 +0010110010100111,100110001 +1000010000110100,101100100 +0011000010110101,100111010 +0000011011011101,110110001 +0001110011110111,111001001 +1101001010011010,001001010 +0110100001001100,000100100 +1011110110111100,010111110 +1111001110001110,000000101 +0011111010110011,100100101 +1000001100001010,100010001 +0010011001101011,010111001 +1101110101111001,100110101 +1001001010011001,010001110 +1011001110010100,011011110 +0111101100100010,010001001 +1101011100110010,111011001 +0100011010000001,110001110 +1000100111001110,001000001 +1001010011111100,000101100 +0111011110100111,110010111 +0011011100001100,001110001 +0111000001111111,001100001 +1011110101011110,111011001 +0110101000101110,010100110 +1011011000001100,101110010 +0010100000100001,000110010 +1010010011010111,000010001 +1110100000110111,110000001 +0011001000100011,000010001 +0111111110010010,111000101 +0000011111010110,110100101 +1111101100010001,111001101 +0000011011100101,111000001 +1010101000011011,101101001 +1100001010101101,000111110 +1110011011001000,010111100 +0101001110000110,110101001 +0010000101111100,010000110 +0010111001011010,011100110 +0110111101100011,001111011 +0011010001000101,011100110 +0000100000010001,000110010 +1000110011100110,000110010 +1101011000001111,110110101 +1010001000110000,100010100 +1100111010110010,000000110 +1110100111111010,011011110 +1001000011101001,000001010 +1100111101111100,100011001 +0111000110110101,110111001 +1001111110011010,010010101 +1010011110011000,011111110 +1100111001010111,101110101 +1110011110010000,000011110 +1010010111000001,000101001 +1000101101001001,110001101 +1100111101000100,101010001 +0011010011101111,110001001 +1100001111001000,011010110 +1010100110101110,010100001 +1000101111011001,001101101 +0000111100010011,000111011 +0101011001010001,001011110 +0011101011101111,110010101 +1111110001100000,101000100 +1000100011101100,000100100 +0110101010111110,110010110 +0100111100101000,011000001 +1011010110111010,010100001 +0011110010111000,100110100 +0011111100110000,000100001 +1000001010000100,010001100 +0110101001000001,000110110 +1010000000001110,101011100 +1111010101111010,101100001 +1000101101011110,110100101 +0110010110111110,110001001 +0000000011100010,111000100 +0100011011101110,100110110 +0010000011011110,111111100 +0011011001011110,011001110 +1100110101101011,100100011 +1011101011111010,001111010 +0010100010100010,100110100 +1111101101000011,100001011 +1101101000101101,111100001 +1000111100001000,100000001 +0101101101011001,001011101 +1001011000000101,100100001 +1110010110101101,001110101 +0010111011000000,111011100 +1110101010010011,000001001 +0101101000101110,011100110 +1100010110001100,001010110 +0110111100111010,010010101 +1001100101010010,110001110 +1101111101101010,100010101 +1101111011011111,011011101 +1100011010000110,001000110 +1000000011001001,001010010 +1111110100001001,111100101 +1100010000010010,110101100 +0000101001101110,011000110 +0110101111111101,101010011 +0011000010100111,100011110 +0000000100110111,001101101 +1001110011100110,000001010 +1111111101011001,100110011 +1100111111000000,011011110 +1110100011001000,010101000 +1111110011000111,010001001 +1000111100110011,101111011 +1000111010111111,011101101 +1110100001100100,101111000 +0010000011101111,110111110 +1111111100100010,110000101 +0000110010011000,100100100 +0011010101011100,011001110 +0110110010000010,111011100 +0101110110000011,110111101 +0101110001001101,001101110 +1110101101010110,100000101 +1100011010001011,001011001 +0011001110010010,101010001 +1010001100101010,100110001 +0110110010110001,110000110 +0010100110110010,100001110 +0011100110101000,100011010 +0011010110101111,100001011 +0011011010000011,101101001 +0110010100001010,011011110 +1000011000001000,100011100 +1010110011100011,001111110 +0101101111001111,101100111 +1001001101011001,110001101 +0101001110001110,110111001 +1110101001110011,101001001 +0010101110111010,100011001 +1110000101101001,101110001 +1100011010110110,000010110 +1100111011111010,010010110 +1101111001111000,100110010 +1011110010100110,010001010 +1100010001011010,101111100 +0010001010011100,101111100 +0101000011100000,100010000 +0100110101111110,000011001 +1001001101001001,110110101 +1101000111100100,010011010 +1110101110010000,000001110 +0100011010101101,111010001 +0100011110110011,111101011 +1100100110111100,000010110 +0111000101111110,001100001 +0011101001110001,010101110 +0001001111110111,111011011 +0001110100110110,001001001 +0100001101001100,001011110 +1001001100101101,101111101 +1110100001101111,101100001 +1010010110001001,011011001 +1101001100000000,110100110 +0001010001100111,011100001 +1110000110101111,001111101 +1110100101001010,100101110 +0000111010011111,100101101 +0101000101010111,001011101 +1100010101111110,100001001 +0000101110010011,100110011 +1100000110101100,000111010 +1001000101100100,111101010 +1010011001001110,111010110 +0011101011110001,110101110 +0010110111111001,110010101 +0111110001111111,001111001 +0011010111111110,110101001 +0010110010001011,101000001 +0101011000111110,011001110 +1101110000000101,110110110 +1010101000111000,100011100 +0100001101100001,000100101 +0100011001001010,001011010 +0010101001101100,010100010 +1110100100010011,111110101 +0000111000001011,000000101 +1001010001101110,111110010 +0101100110000110,110111110 +1100111010011001,001100001 +1111001011100110,011011010 +1101111000101100,111101010 +1101011000010110,110010110 +1010110011000011,000111110 +1110011110110000,001011110 +1001011000111111,101001101 +1101110001110101,100101110 +1010111110100001,010111101 +1110110000111000,110010100 +0010100100100000,000110010 +0100111110110110,111110101 +1100000101010101,101101001 +0000111101010001,010111101 +0101000100011011,010001101 +1011101001110001,110101110 +1011101010010011,011001001 +0110100111110111,101000011 +1100111000001101,110001001 +1001000101111100,111000110 +1011111100010100,101001001 +0110010000001000,011011000 +1110000101101110,101111110 +1111111011010010,010100110 +0111010110010001,111011001 +1111000011100010,011010100 +1111001001001101,100000001 +0111000010010001,111010010 +1100001101000111,101001011 +0001010111110000,111011010 +0111010011110001,101111010 +0001010010001000,100111000 +0110101010000000,111010100 +1101100110110100,000100110 +1111011111011110,010101101 +1101010001010110,101010010 +1011110100110010,100100001 +1000111100010111,100110111 +0010001000100001,000100110 +0011111010010101,101001001 +0100000000011001,010110010 +1100111100011011,110100111 +1101011010100000,000011100 +0111110111110110,101101001 +1000101100001111,100000111 +0000011000110011,001101001 +1100001000010101,110101110 +0110011000110111,010010101 +0000000011110100,111101000 +0010111110111000,100010001 +1000011011100010,000101010 +1100111001110001,100000001 +1001100011010111,001000001 +1000011110101110,011010101 +0000101000011011,000101001 +1111001100011010,111001001 +1011111100101000,100010001 +1111111001111011,101110101 +0001100010101001,101101010 +1100101110010110,001111001 +1000001100011011,100110011 +0000101111001110,110000101 +0010111110001001,101000011 +1011101111101110,001010101 +0100010110000001,110001001 +0010010000010101,001100110 +0010111011011001,111100001 +1110100100111000,110011010 +1110101111001101,010100011 +1001100000100011,101110110 +1100011010001001,001011110 +1010001001010010,111100010 +1010000111101101,001111001 +1101001111111001,010101101 +1110111010101011,001100101 +0110011011000110,100100110 +0010001001010101,011101110 +1110011000001000,111011100 +0110111000010101,011110001 +1000111010100010,011011010 +0010100101001010,011001110 +1001011100010100,100010001 +0011111100100001,000000011 +1100100111011100,011100110 +1011100001100111,110000001 +1001110010111000,011010100 +1010001100111110,100000101 +1011110000010010,101000010 +1001011111011110,001001101 +0101111011000011,101111001 +1011000001110110,110111100 +1101000001011000,101001000 +0011110111100100,110001110 +1000111101100001,111011101 +1001110011110001,000100110 +0100010111100000,100101010 +1110001110110110,001011001 +1110001100110111,110011011 +0110101101101100,001100001 +1011100110101111,010010011 +1001000011001011,001110110 +0001000100100001,001100001 +0100110110101111,111001011 +0100101011101101,100100001 +1101100111101000,010011010 +0001011010110101,101010001 +0010111001101111,010101101 +1111111000111001,110110001 +1001011010101110,011110110 +0010100100101101,000100101 +0001100010100101,101111010 +0110010100001001,011011001 +1101001100010001,110010101 +0011101011010011,111001001 +0010111010001111,101001101 +0000101100101101,001000011 +0101101111000010,101110001 +1010011110111001,010000011 +1110100111100101,011111001 +1111011111101011,011000111 +0100100011001100,101000100 +0010101010001011,101001001 +1001000110100001,011100001 +1000010010100101,011000110 +0001111111001101,110101011 +1101001000111000,111001100 +1101101101111101,100110011 +0001001101101101,011111101 +1001010000101000,101111000 +0010001101101110,010111001 +1110011110101101,001110011 +0000001011101111,111011001 +0000110111100010,111011110 +1100001100110100,111101110 +0101000100111111,011000011 +1110010101111111,101001011 +0101000101011100,001000110 +1111001000011100,111000010 +1011010101001011,111111101 +0000111111010010,110111001 +1101000010111001,000101010 +0000101010100011,101010001 +1101110000011010,110010010 +1001011101001101,110110011 +0011101100001010,001101001 +1111110110001011,000010011 +0110001001110000,001010100 +0110001111100011,101100011 +0000011011000010,110001010 +0111001110111010,110101001 +0000011011001000,110011100 +0110001000111101,010000001 +1110010110001111,000110011 +1010000110011111,011111101 +0110001001110110,001011010 +0111011010100101,110010001 +1101011000110010,111011010 +0011111110101101,100011011 +0101001110111010,111001001 +1010101101001000,111001110 +1011111100000000,101111110 +1101101000001111,110100101 +1001011011010001,001011110 +1001010000111010,101000010 +0011011000111100,000101010 +0001111000010000,000000010 +1111100010110010,001101100 +0101101100101000,011101110 +0001010010000001,100101010 +0001111110000011,100111011 +0010001011011110,111111010 +1111001001110101,101111110 +0110001111111001,101001101 +1101001100011011,110001011 +1001111010110001,011000001 +1010110001001011,111000001 +0010000001100011,010100110 +1111011000001100,111110010 +0101011001100011,000011001 +0111110111100101,101001101 +0111001100000110,011101001 +0001000010001101,100111010 +1110100101001011,100101101 +0111000000101000,010001000 +0110101110000101,111011101 +1010001111110010,001010001 +0100000100111111,011111101 +1011010110100110,010010001 +1100000010111111,000000001 +1001101110100100,011111110 +1100011110010110,001100101 +0100000000100010,011000100 +0011111010100010,100000110 +1111001110011000,000101110 +0101000011100100,100011000 +1001100110100010,011110110 +0001101010000011,100110001 +1111110011000011,010000001 +0010100101001011,011001101 +0110010010101100,110110100 +0101001100110010,011010001 +1111101101111010,101111001 +0001101011010111,110000101 +0001001001010011,010010001 +0101011001111011,000100101 +0100011010011100,110110010 +1001010110010101,010010101 +0011101100011001,001011101 +0000111110111010,101100101 +0010011100010101,001100011 +0010001110111001,100001101 +0111111100110011,010100111 +1010110011111011,001010001 +0000001001100010,011000010 +1010000010001000,011010000 +1001011110100000,011101110 +1111100110001010,000011110 +0110110101010000,000000110 +0101111111000011,101111011 +0100000001101010,000110100 +1100111001011000,101100010 +0001111000000111,000110101 +1101100011000000,011110000 +1010011001100001,110101110 +0010100111011110,111100001 +0101010100110101,011010101 +1110110001010000,100000100 +0000111001101001,011000001 +1110000010010111,000011110 +0100101101110001,000001101 +1010111001111001,110010001 +1010101011011001,000011110 +0110011100100100,010100001 +1110010100001011,111011101 +0110111001111010,001010110 +0000001111010000,110100110 +0000111010111011,101100101 +1011100100010101,101000101 +0110110001100100,001110100 +1101111101100001,100000011 +1000001000010100,100101100 +0111011111010010,100111001 +0110001101110001,001010101 +1011010000010110,101010010 +1101001110010000,001010110 +1000111110000001,010011101 +0110010110011100,111110110 +0101110000110010,011000010 +0110100010010010,111110100 +0011101011111011,110111001 +1011100010111001,010111010 +1011001100011001,101001101 +1110101001111010,101011010 +0111001101100011,001010011 +0011001100100100,000011110 +0011001101100100,010011110 +1101010101111010,100100001 +1000101101001100,110000001 +0111010001000111,000010001 +1010011101011110,111110101 +0011110001000000,011111000 +1001101001001101,110100001 +0011010010010100,101010100 +1110001110101111,001111011 +1100101000101010,111001010 +0100100001010110,001111100 +0101110111110011,100100011 +0010100011101000,110101000 +0000111011100000,111011100 +1000001011010111,001101001 +0011011101100100,010010001 +1100011011111101,010001001 +0001110111000100,110110110 +0000100111001111,110000011 +1011110100101100,100011110 +0110010101010010,000011110 +1110011100000101,111000011 +1111011001010010,100111010 +0011100001111010,010111100 +0101001100111010,011001001 +1110100100010100,111111010 +1110010111100010,011101110 +0111110001110011,001100001 +1100000100110011,111100101 +0001100000101010,001101100 +1011101011110010,001101010 +0001011010111001,101000001 +1010001010101101,010111110 +0001110110010111,100001011 +1101111000110001,111000001 +0111110000111110,010111010 +1000101000111001,101101110 +1011111011110010,001100110 +1101001100101111,111111011 +0011001010101101,100000001 +1100000101100101,100101001 +1000101101000001,110010101 +1010100011011101,000010110 +0101001100100010,011100001 +0101000110011001,110001001 +0111010000000001,011101010 +0110000000100010,010100100 +1100101001111000,100011100 +1100010001000101,101000110 +0101110011011100,101011100 +0011100011010000,111001000 +1101011100101011,111111011 +0101001011100001,100010110 +1011100101100010,110001110 +0100101001010100,001111100 +0010110001011000,011100100 +0010000111100010,110100110 +1111011101001101,100001011 +1000010000101011,101011110 +1111001101100000,101010110 +1000111100101010,101000101 +1101011010010001,001011110 +1101000011111101,010100110 +0001111010010000,100000010 +1100100011101101,010100110 +1100000100000111,110001101 +1111101101001010,100011001 +0111001111000010,100010001 +0011100100011010,001011110 +1111010100000110,111100001 +1011000100100100,100011010 +1111110011011011,010110001 +1110100001110010,101001100 +1000101010100011,011010001 +1011100010111111,010110001 +1001111000010001,100000001 +1100110110111011,000010011 +1000010110010010,010101110 +0100101110100111,111011011 +1010011011110010,001011010 +0000100100100000,001010010 +0110100010001100,111000100 +1001011101101100,111110001 +1110010111010101,010010101 +0101010111001100,101110110 +0110101101101111,001100111 +1000111001110010,111111010 +1101001001000110,101101010 +1111011101011110,100101101 +1001101111001110,001100101 +0110001010101100,110111100 +0111001000101010,010001010 +1101111110001110,001101101 +0111011000100100,010010010 +0010000011111011,110001110 +0011100111000110,111111110 +0011101010111000,100111100 +1001000011111000,000101000 +1100000110110100,000011010 +0010111111011101,111101011 +1001000101001111,110111101 +1010001011011011,000001001 +0010111010010001,101111110 +0111000010111010,110101100 +1001101000001000,100101100 +0010000001011100,011111000 +1110100011110001,011001010 +1000001101100011,111000011 +0111000010010000,111010000 +0100101100000111,010011011 +0101100010100100,111111000 +0100001110110001,111100101 +0001111101000110,010110101 +1101001110100000,000010110 +1110110110001011,000100011 +1101000011111000,010101000 +0100000000111001,011110010 +1101101100011100,110010001 +0011001101000101,011101101 +1111110000000000,111111000 +1110001100001101,111011101 +0110000010011000,111110000 +0000011110100110,101000101 +0100000010101100,111011000 +0110000010011100,111111000 +1011001001010000,111010100 +1010011001101001,110111110 +0100100010001001,110001010 +0101000100100110,011101110 +0001000000010001,000010010 +0101000100001001,010110001 +0011001111011010,111001001 +1000101001011100,110100010 +1001001110001011,010110011 +1010000100100010,100100110 +1111100110110100,001100110 +1010110010110000,010000100 +0011010111100100,110010110 +1101100000011001,110011010 +0111010011101100,101001100 +0100100011010101,101111010 +1111100100110100,110100110 +1110010010001011,000111110 +0011010001101111,010001001 +1111011100100011,110011011 +1011111001101111,110011101 +0000010100101000,001011010 +0110111101011000,000010001 +0000100000000100,000011000 +0001111111110010,111000101 +1010110001101001,110100110 +1101010110100101,000010101 +0011111000010111,001001101 +0101100000000101,010111010 +0111000100010011,011010101 +1110000001101011,101110110 +0101011110101111,111110111 +0110010010010101,111100110 +0010000010011101,101111010 +1111001010010001,000110110 +1100010000100111,111000001 +0011011010001001,101111110 +0100110000110000,011111000 +0111100110101001,110011001 +0001010100111000,001000110 +1100101011111010,010011010 +0101010101111001,000100101 +1110110001110101,101001110 +0001000010110100,101011000 +1001111100010101,100001011 +0111101100011110,011010101 +0001111000001010,000100110 +1010101101011100,111100001 +0101010001100101,000010110 +0110100000010100,011111000 +0100101000001000,010001100 +0010010110000100,101000110 +0010011011011001,111111110 +1001011110110101,011010011 +1100101110000110,001011001 +0110001001000110,000101010 +0001010100010101,000010101 +1010101000111100,100010010 +0101011010100011,111101001 +0011100110000010,101110110 +1101110101101110,100011001 +1010011010100100,010100010 +0101010100010000,010011010 +0111110010000000,111111000 +1001100100111111,101010011 +0110100111011010,100011110 +0011100110011101,101010101 +0011100110101101,100010101 +0010110100100100,000110110 +1111011110010011,000111011 +1101100001011110,101010010 +1010001010100000,010100100 +1000101111011101,001100011 +1101011101111001,100100011 +1111100011010111,010100001 +0100100001110011,000001110 +1110110110011111,000011011 +1000111001110000,111111100 +1010000110010000,011100010 +0000000100001110,000011110 +1111111011011110,010111110 +1100101001110111,100000101 +1011011000010110,101010110 +1001100011110011,000101110 +1110001001111101,101000001 +1000110001001110,110001010 +1110111001000000,100111100 +1001010010101000,011111000 +1111110011011010,010110010 +1001110111000001,001111001 +1010010101001101,111010101 +1001101011000011,001110001 +0100000001110101,000011010 +1101000011001010,011110100 +1011011010011101,011001001 +0110111100011100,011101001 +0110111101010111,000001111 +0000010111000000,110001010 +0101010000110001,011011010 +0111111100100111,010001111 +1001011010100101,011100001 +1011000101011101,111000101 +0101110111001100,101101110 +1111011101100100,101010001 +1100000100000011,110000101 +0001000100001111,000111101 +0100110000011000,010100100 +1001000110101010,011110110 +1011010100011001,101000101 +1110001001010101,100011110 +0000011000111101,001110001 +1100111101011001,101100011 +0001000001111111,011000001 +1000010011101101,000110110 +0010000101010010,011100110 +0100110001101011,000100001 +0100011001001100,001010010 +0111100110110001,110101001 +0010111011011100,111101010 +1010100110110111,010000011 +1101110000100110,111110010 +1011000001001101,111111010 +0011010100011010,001000001 +1000101110110011,011110011 +1100011001001000,101011100 +0111000110000101,111101001 +0000001101110100,011101110 +0010001010000110,101001010 +1010101000001010,101001010 +0000000110100100,101001010 +1111100111010110,010100001 +0111101100010111,011000111 +0100110010000101,110010110 +0001001100001111,000111011 +0111001000110111,010111001 +0110101010001010,111001010 +1001111111111110,000111101 +0001110011010100,110001100 +1011111010110011,010100101 +1001000001100010,111100100 +0001010000110100,001010100 +1001111110001001,010100011 +0010010100111011,000000011 +0000011110110010,101101001 +0001100001001111,010100001 +1100001101010101,101101101 +1110110011111011,011010001 +0000001101101111,011011011 +0101110101100010,000000001 +1010110101001010,111000001 +1101100001000000,101110000 +0001101100110101,001000011 +1000001000000001,100000110 +0001001010000110,100101010 +0001000110000111,100101101 +1010111101000101,111010011 +1000100101011101,110100101 +1010011010111011,010000101 +0111001000110001,010110110 +1100110001001100,101001100 +0011111010010010,101000110 +0011100100000100,001111010 +0000010010101110,101010010 +1011101010011111,011010101 +0101101011110010,100101010 +1011101100101010,100011001 +1100111001001010,101000110 +0001110000100111,001110001 +1010110010000011,011011110 +1101101110001101,001100011 +0011111000101001,000010001 +0111000101000011,000010101 +0001101011101110,111100110 +0111000110101101,110000101 +0011111111110001,110100011 +1011111110010110,011001101 +0011111000101110,000011110 +0000001110001110,100011001 +0101011000001010,010111010 +1110101000011000,111101100 +0010111110100101,100110011 +0111110100110101,010101101 +0101000010001101,110111010 +1010000001001010,111010100 +0111011001100010,001011010 +0110111110011110,111101101 +1000100100010000,100110010 +0011101010001100,101100010 +0000001011011000,110110100 +1101011111100110,010010101 +1100000111010110,011101110 +1110100101000001,100110001 +1001100110000101,010111001 +0100010100100011,011001101 +0001110011011100,110011100 +1010110111101010,001100001 +0000100110010111,100111101 +0111111100000000,011111110 +0010000100111111,000000011 +0101000001001101,001111010 +1100111100111110,111101101 +1110011011001011,010111001 +1111010101101111,101001011 +1010111011111110,001011110 +1000000110110100,011101010 +1000111000011010,100100110 +0110111010100100,110110010 +1111010101100001,101011001 +0011110000011110,001011010 +1000001101000111,110001011 +0110011101000001,000101101 +1100100011100011,010110110 +0010010101100101,010100101 +0011001000001110,001111010 +1011110101111110,110111001 +1011101100100001,100001101 +0000101011001011,110001001 +0110001101010101,000011101 +1001110101101001,111100101 +0010000011111111,110000001 +1001000100101110,101111110 +0011111000011111,001011101 +0011100110110111,100100011 +0010001101001010,011010001 +1001011111000101,001100011 +0001111100001100,000101001 +0000111010001111,100001101 +1100100101000000,101010010 +0001011000100001,001101110 +0111001010111100,110100010 +1010010001000111,111000001 +1000010001110011,111101110 +0011100100011111,001010011 +0100001100101001,011010101 +0011011111001100,111110001 +1000111101111110,111101101 +1100100010101100,000100100 +1100001111011000,011110110 +0000101101000101,010011101 +0111010100111101,010101101 +0000110101011111,010101011 +1111000001111101,101100110 +1111001111101111,011000111 +0011101001110000,010101100 +0010100010101101,100100110 +1000110010100000,011011000 +1101010111010011,011011101 +0111101101110010,001101001 +0111000010001101,111111010 +0011001010001011,101110001 +1001000110010110,010011110 +0110110000110000,010000100 +0110111100011011,011100111 +0101101111011110,101010101 +1001110100000011,100111101 +1101001000011111,110000101 +0110000010101010,110110100 +0101110011011001,101010110 +1000100011001011,001001110 +1100111111000101,011010011 +1110010010111001,001000110 +0000001000000010,000000010 +0111101010101101,110010001 +0010001100010110,001101001 +1010100010111000,010011000 +1000110100001101,100001101 +0000111101001111,010001111 diff --git a/tracks/qcs/solutions/Genshin_Impact-71/predictions/mystery-B/test_outputs.csv b/tracks/qcs/solutions/Genshin_Impact-71/predictions/mystery-B/test_outputs.csv new file mode 100644 index 000000000..f6925f1a5 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/predictions/mystery-B/test_outputs.csv @@ -0,0 +1,2001 @@ +input,output +11011111100000,0001111 +01000001011000,1101000 +01001000110100,0010000 +00110100001000,0010010 +01000101111111,1011101 +01001110000100,0100011 +00101011010100,1111110 +11000000111010,1101010 +01110101111010,1000000 +10011100000001,1110000 +11000110010100,1111001 +10000011011010,0010100 +01101010111101,0001000 +00100110011100,0001001 +11011010000100,1101001 +01100000011011,0110011 +01000111110001,1101100 +00011011001001,1111000 +00001101011010,1100000 +11111110111110,1000001 +01001110000010,0100101 +01000111001110,1001010 +01011000001111,0111101 +11110011101111,0011010 +00011001000000,1110100 +00000111111000,1000101 +11101010000111,1001100 +01110010101011,0011100 +11100010110001,1000000 +11010011101110,0000100 +00111000001111,0011101 +10110110010101,1001100 +11000101001111,0110101 +11011001101110,0000010 +00011000100000,0110100 +11100010010101,1011000 +10001011101001,0110000 +01001000100100,0000000 +11000001100011,0000011 +01101100011001,0110100 +11100000111101,1110101 +11000111001111,0110100 +00110001000010,1010100 +11000100100110,1111000 +00000010110110,0101000 +00000100101101,0101110 +11100100101101,1100110 +00000110101101,0110000 +11010001101000,0000000 +10110001110011,0101101 +10011100100011,1001010 +01001011010110,1011100 +10001001001000,0001000 +01100110111110,0001010 +01001011000101,1000000 +00010000010011,0011101 +10001001110011,0110101 +00111100010111,0001110 +00000110011110,0010010 +00010011111000,1001110 +00000000000011,0000011 +01101101000110,1010000 +11011110011111,1000000 +01001100010101,0100010 +11011101000010,0101100 +01101101011011,1110110 +10010110011001,1011100 +00011010100000,0110101 +01110101100000,1101010 +00111110101010,0100101 +01001111010110,1011110 +01101100000110,0110000 +01000001011111,1101111 +00101001101011,1110101 +11110101101110,0011000 +11110111011101,0100100 +00011011011000,1101001 +01010100010110,0101000 +00100101001011,1010001 +00010010101110,0111000 +10001000011010,1101100 +10101000101110,1010010 +11011101011101,0100010 +11111110111100,1000011 +00011110001001,0000110 +01011111001000,1000111 +01000101110001,1010010 +01111101100011,1010010 +00001101101000,1010010 +01001110100000,0000111 +11100000101000,1100000 +10100011011010,0001100 +10000111100111,0100100 +01001001011011,1101101 +01100111100000,1100011 +11000111010010,0111110 +00011000011000,0011000 +10110100100111,1010001 +11010011010100,0110110 +11001000100000,1000100 +00101101110111,1100001 +10101010100011,1011000 +11011110110010,1010101 +00011011011010,1101010 +11000000010001,1000001 +10101001110110,0100010 +01001010110110,0011100 +11101100001100,1111100 +11001001101011,0001101 +11111010000101,1111000 +01000000110011,0010011 +00110010011000,0000001 +11100010101111,1100110 +10110100010100,1001100 +00100000000011,0011101 +11011011010101,0110000 +11011001100111,0001101 +10100001000111,0011011 +00111011010111,1001100 +11011100101111,1111110 +10000110001001,1001100 +10001101100010,0111000 +01001011110111,1010010 +11001000111010,1101100 +11110010001111,1001010 +10100010111011,1001010 +01010011111110,1101000 +00111000000011,0010001 +10010101011000,0011100 +01100010111110,0001000 +11101100110110,1000000 +11100001110101,0000101 +10111011011110,0000010 +10010011111011,0110010 +00111100100100,0101010 +11110110000110,1111110 +10001100110011,1010110 +10110111011101,0000100 +01010011000001,1001000 +01000111111110,1100010 +01100001111000,1001000 +11010101101101,0000110 +01111111100111,1101000 +10011111010000,0010111 +01011111001010,1000101 +10000110110100,1101001 +10000010110000,1101110 +00011101001101,1000010 +11101011111010,0001010 +11100010100011,1101100 +11011101011100,0111100 +00101110010001,0000110 +01010100001010,0100000 +11101010110000,1000101 +00001100111111,0111001 +11010011111101,0010100 +10010001110101,0111001 +11110101100110,0010000 +01101000101101,0010001 +01000111100111,1000100 +11110100011110,1011000 +01000101111101,1011110 +00100000010101,0000101 +10010011101001,0100000 +11101111111010,0001001 +01100100010000,0100010 +11110011101011,0011100 +00111100010000,0001110 +01010000111010,0010010 +01011011011011,1100100 +11100011111000,0001110 +00000001111000,1111000 +01011110001110,0100001 +11101001100001,0011010 +00000000111011,0111011 +10110101011011,0000001 +01001001011001,1101110 +01000101010100,1011000 +01000101011000,1010100 +01111011010001,1001100 +01101101100101,1011100 +11011001010010,0101000 +10010101111111,0110101 +00100001111001,1101001 +10110011100000,0101001 +00010111011100,1101001 +11110110100000,1011011 +10111010010111,1110100 +11110100100010,1011000 +11000010010100,1111010 +00111010100110,0101010 +11011011111110,0011100 +00111100110111,0101110 +11110110001000,1110011 +00100100001110,0010100 +10000000011111,1101111 +11111100100010,1011100 +11101110010010,1100101 +10000100001100,1001000 +00100100010111,0000101 +10100101010100,0000100 +01000111110100,1101001 +11011110011000,1111011 +10110110100000,1101011 +11011001101001,0000110 +10101111110000,0111011 +00110100011100,0000100 +10001000111111,1011011 +01111000000000,0111100 +00010110111000,0101101 +01110011001011,1101100 +11111010011010,1100110 +00110110001010,0010001 +11010000101100,1111000 +10110101110111,0101001 +00101000011101,0001001 +00001100111001,0111100 +01100010001010,0111100 +01011101101111,1000001 +10110101100100,0101100 +10010100011010,1100000 +00111000011101,0000001 +11000000010010,1000010 +10110111001111,0011000 +01011101001100,1000010 +00010011010000,1100001 +00010110100010,0110001 +01001100100101,0000010 +00101001110000,1011000 +11100000100010,1101100 +00111111100001,1001110 +01101101001010,1011000 +10001010101111,1001010 +00011001111101,1110001 +10010011001111,0000110 +11011110001000,1100111 +10001111011011,0010000 +10011011011101,0010000 +10110111101110,0100110 +11000011011000,0110110 +00100100100001,0111100 +10100101100101,0111010 +01001001010001,1100110 +00011001001101,1000001 +11001101001101,0110010 +00010111000010,1110001 +00111000000000,0011100 +00011100000111,0001110 +01000111000011,1000000 +10100101100111,0111001 +10010111010001,0010010 +10111010101110,1100010 +11110110000111,1000000 +11100101000111,0101001 +11001101111001,0011100 +01100110010010,0100001 +01001111110101,1101100 +01101011111010,1110010 +01000000010010,0100010 +01110000101111,0011011 +11000011100110,0000100 +10001001101001,0101110 +00111111101010,1000101 +10000001101010,0101010 +11110010000110,1111100 +10010010000011,1110100 +00101010010001,0000100 +11011110100001,1001110 +11010110000001,1101010 +10101010101100,1101110 +11001001111111,0011011 +00110011011000,1111110 +11100100001111,1000101 +00100110110010,0111110 +01001100101001,0001100 +11101000001111,1000011 +01101011010111,1111100 +00000100101100,0110000 +11100111010010,0100001 +11111011101001,0010100 +11100110000011,1110000 +11101000111001,1110110 +10111101000001,0010000 +01001001111111,1011011 +00000100111101,0111110 +00110111100011,1001000 +10100101001010,0010000 +11100000111111,1110111 +10010100011110,1100100 +10110010111111,1000110 +10100110111111,1001100 +11110000111001,1111110 +01110000010001,0110110 +11010101001111,0111001 +00010111000011,1110000 +11011100110001,1101000 +00011001011110,1010010 +11001101011000,0110010 +11010001011011,0100011 +00100110111010,0110110 +00000101010110,1010100 +11010010110001,1010000 +00100001010010,1000010 +00000001011100,1011100 +11000011110000,0011110 +11110000100101,1100001 +10010101100111,0101001 +00100111110000,1011101 +01010111000001,1001010 +00101101011000,1110010 +10010000101101,1000101 +11000000100100,1111000 +01001011111000,1100001 +11000100001100,1101000 +00000011100111,1100110 +01111001010100,1001000 +11110111000101,0111100 +11000100110110,1100100 +11011101101001,0000100 +10001000100000,1111000 +00110000100010,0110100 +01010111010110,1010110 +00101100011111,0001001 +00101100011000,0001010 +00111100000110,0011000 +10111010001011,1101000 +11011011011001,0111000 +11010110101101,1000100 +00101001101101,1110001 +00010001000100,1001000 +11001110011110,1110110 +11001011101100,0001110 +00000110000011,0000000 +10111011111111,0100010 +11101001110110,0000010 +01001111011000,1010011 +01111100110011,0001010 +00010010011000,0011110 +00110000110010,0101100 +11100001100100,0011000 +01110100011110,0111000 +10011011101010,0111010 +00010000110110,0111010 +00100101011000,1110100 +11100111000010,0110001 +00011111110011,1000100 +01111011110110,1110010 +11100000111000,1110000 +01101111000101,1010010 +00011111100101,1010010 +10111110100011,1101100 +10000101001001,0001010 +00011101110101,1111100 +00000101001111,1001101 +00101001001110,1010010 +00001000011100,0011000 +01110000100000,0011000 +10000001111001,0111001 +01110110001000,0110011 +01011000010011,0101001 +10010100111000,1101100 +01000011110000,1101110 +10111000010010,1110000 +00001111011010,1100001 +10011000011011,1100101 +10101111110001,0111010 +10111101011110,0000000 +00001011010000,1101001 +10111010000001,1011100 +11110110001001,1110010 +10101101100010,0100100 +10011000101001,1000110 +11010100100000,1001010 +01110011010100,1001110 +00101101010100,1111100 +10111010000110,1011010 +10011001010100,0010000 +10111110000001,1011110 +00011111011111,1010000 +01011010100110,0001010 +01001010010001,0111000 +00010101010100,1100100 +00010100101111,0100101 +10111100101001,1011000 +11010010011001,1000000 +01111001011111,1111101 +10101000100000,1100100 +00101000100110,0111100 +10100100111110,1001100 +10001011001000,0001001 +11010000111011,1100011 +10100000101011,1010011 +01111110101010,0010101 +01110111110111,1001000 +10010100010010,1010000 +10100101101011,0110001 +11110011101110,0010100 +10110000000000,1011000 +01100001101101,1010101 +10001110111100,1100101 +01110100001010,0110000 +00100001110000,1100000 +00001001000111,1000011 +01000000111110,0011110 +00000110111001,0100100 +11001111000101,0100010 +10110101001001,0011100 +10010000110110,1011010 +10100000111100,1001100 +00001000001110,0001010 +01111010110000,0001101 +00101011011110,1110100 +00111011100111,1110100 +11100001110000,0000000 +11010110010111,1001000 +00000000001100,0001100 +00011011100111,1101100 +11110001001101,0101001 +00010100101001,0100010 +10111101100111,0110110 +11010101000010,0101000 +01000111101110,1110010 +01101101000011,1101010 +00011100000010,0001100 +11000010001001,1010000 +01110000001111,0101011 +10011000011010,1100100 +00010000001000,0000000 +11101110111100,1001101 +00010010011111,0010110 +11100101001000,0111100 +01101110110011,0000100 +01101110100011,0010100 +11111110101010,1010101 +11001110010101,1111100 +10100101000100,0010100 +00111111011011,1111000 +00000001111001,1111001 +11101001101111,0010011 +01110100000110,0100000 +00000100000000,0000010 +00010101010110,1011000 +10010110111110,1101010 +01010110000100,0101101 +00111101101111,1111110 +11101011111110,0001100 +00100111011110,1110010 +00010100011000,0011100 +11111011100000,0011101 +11111100000100,1111010 +00011100101000,0111010 +00111100011100,0000010 +10101010000101,1010000 +01110011010000,1001001 +10111101110000,0110110 +01100111011010,1001110 +00101000110100,0100000 +01100101011010,1110000 +01100001000011,1101101 +11000010001010,1101100 +11011101010110,0110000 +11100110000100,1110101 +01010110101100,0000101 +01011100101001,0000100 +00011100001101,0000010 +11011010010011,1001000 +00011111010111,1100000 +00001011111001,1000000 +10001101111011,0111110 +01000111001101,1001000 +00110110101111,0111000 +11111100101111,1101110 +10000100111111,1011101 +11110000011111,1011011 +10111110111011,1111000 +10111111011010,0000101 +10101110010000,1000111 +10101011110010,0111010 +00010100100111,0101001 +01001110011000,0110011 +11000001010110,0100110 +10001110101111,1001000 +10101011001110,0011100 +10001010100011,1000100 +00101000111100,0101000 +10000100100010,1000000 +10001101100000,0111010 +10011011101101,0100000 +00111001100110,1110100 +00011011101111,1100010 +01010010010011,0101100 +00111110000011,0011100 +00010010001001,0000000 +11000011110100,0011010 +01011001101001,1000110 +01110111101011,1100000 +01100111111011,1001000 +01111001011010,1111000 +11110110111001,1000010 +00001001100000,1011000 +11000101000110,0111000 +10000100110001,1010010 +00110100100011,0110110 +10010100010111,1101001 +01010111001010,1000001 +01001111110011,1101000 +01011101101100,1111100 +11101010100100,1010001 +00000011010000,1101110 +10111111111101,0111100 +11110011100111,0010010 +11000011001011,0110010 +11000100111010,1101000 +11000110010111,1000100 +11110011000011,0100100 +11101101010001,0111000 +00000110110101,0101000 +10100100100011,1011110 +01010110100001,0001010 +01101110100101,0010010 +10001101101000,0110010 +11110001001000,0110000 +11100100010111,1011001 +01101010100100,0010001 +10101101011000,0001010 +00101011110000,1011001 +10001011011000,0010001 +10100001111110,0101110 +10110101101000,0100010 +11000111011011,0101000 +11000010110111,1100110 +01010011101010,1111100 +01101100111010,0001000 +10101101101011,0110110 +10110111110110,0110110 +01011111011111,1100000 +00010001001000,1000000 +01100100111100,0001000 +10110111000001,0011010 +10001011100011,0100100 +10001000011011,1101101 +10100100101001,1010010 +11001101001010,0101000 +00110011111111,1100110 +01001010000110,0100010 +00010010100110,0110100 +10010101100000,0110010 +11001000010010,1000100 +10101010101111,1010010 +01110111100101,1101100 +10101100001000,1011010 +10101111001010,0011001 +10010111000011,0001000 +10101110111111,1001000 +01010011011110,1011000 +01110101100100,1101100 +01100110110011,0000000 +11010001011000,0100000 +00110101100011,1110110 +11010100101000,1000010 +10111110001111,1010000 +10010000101100,1000100 +10111001111111,0100011 +10100111110101,0111000 +00001011001001,1110000 +01001000011110,0101010 +11110101100111,0010001 +00100110101111,0110100 +10110110001110,1010110 +01010101010011,1101110 +11101001001101,0100001 +11000001000010,0111100 +01111101011010,1000100 +10101111101110,0101110 +10011001110011,0111001 +11110100101101,1101010 +01101100100111,0011110 +10100101101111,0110101 +00111111000101,1101010 +11001011111001,0010000 +01111101100001,1010000 +01001101111010,1100000 +00000101111001,1111010 +10111000000110,1100100 +10101010100000,1100101 +01000010010111,0100110 +01000011011101,1101100 +11111111100010,0011101 +00110110110001,0110010 +10010101110100,0100100 +10100110100011,1100000 +00110001100011,1110101 +10101010111011,1001100 +11111111001000,0110111 +01101010000011,0101000 +00010001110000,1000000 +01010100010001,0101100 +10010110001101,1000100 +11000110100001,1000010 +01100011010011,1111100 +11101010000101,1110000 +01110110111000,0000011 +10000110011000,1010101 +01111100001010,0110100 +00011101110000,1000110 +00010101110011,1111110 +11010110100011,1001000 +00100111100001,1000010 +01010010011111,0100110 +01001101001001,1110100 +11011100010001,1001000 +01011010000010,0101110 +10111011010011,0001000 +11011100011110,1000000 +01100000110110,0000110 +10010101011010,0010000 +00101011011010,1110010 +11001110111000,1010011 +10000011111110,0100000 +01110000101011,0011101 +01111000111000,0000100 +00111001101101,1111110 +01101000101110,0010010 +00010001100000,1010000 +01101110001101,0111100 +01111111101001,1100110 +00010110000100,0001101 +10011110111010,1101001 +11001001110111,0010011 +11001010011100,1110110 +00101000110111,0100011 +11110010000010,1111010 +01111100010011,0110010 +00001011110110,1001100 +00000111000001,1111100 +01010000110101,0011001 +00110001000011,1010101 +10001011000101,0000000 +00100100010010,0000000 +11110000111010,1111100 +01111000100110,0010100 +10001110101110,1110110 +11101011011111,0110010 +11011111111010,0011001 +01111110010010,0101101 +11110111001101,0110100 +11111110000110,1111001 +01001000101110,0001010 +10100101010001,0000010 +10010101110010,0100000 +10101110100010,1100101 +00111110010000,0001111 +11001101010000,0111010 +11100111010111,0111000 +00011001011001,1010110 +10011011001001,0000100 +00010011101111,1100110 +10001011010111,0010010 +01001001110010,1010100 +10111000110110,1001100 +11111101011000,0100110 +11111001010010,0110000 +10010001011111,0010111 +01101001100110,1011100 +10110011110000,0110001 +00100001110010,1100010 +10010101101011,0100001 +10111011101100,0100001 +00111110101011,0100100 +01100100110111,0000101 +00011011110111,1111100 +01011110011000,0111011 +10111101001010,0010100 +00011010101001,0111000 +10010111111100,0101001 +00100000001100,0010100 +01011111000010,1001101 +01000011101001,1001000 +11110000101000,1010000 +11001111110011,0011000 +11100110011001,1101100 +11010010010110,1110100 +00101010110100,0111110 +11110110100111,1100000 +00111011011000,1111001 +00110000100111,0110011 +01111011101010,1100110 +11000001101111,0001111 +00111100001111,0011110 +11101010111010,1001010 +11110010100001,1011000 +00110100100110,0110000 +01100011001111,1100110 +01001110011001,0110010 +01011110000011,0101100 +01101101101101,1010010 +10011110101101,1111100 +00110000000010,0010100 +10110000110101,1001001 +10101011001000,0011001 +10010101010000,0010010 +10110001110100,0101000 +11000001001011,0110011 +00111110110011,0110100 +10101000111010,1001100 +11000101001011,0110001 +10100000101000,1010000 +11110100011100,1100100 +01111110100011,0011100 +11011010001001,1100100 +01110010001000,0110001 +01000111101000,1110101 +00011001100111,1101101 +00111010001010,0010110 +11000001111011,0011011 +10101111111011,0110000 +00010101001010,1000000 +00111000000110,0010100 +10000110101110,1110010 +00110100110111,0101001 +11000110111000,1010101 +11101001011111,0110011 +11010000001110,1011010 +11111101111100,0000010 +00110010101000,0100001 +01111110011001,0100110 +11100001110111,0000111 +10110110000010,1011001 +10100010111001,1001000 +11001110100111,1000000 +00110001000110,1010010 +10110110001011,1010000 +11011100000111,1010110 +11100101010001,0111100 +11000000001101,1010101 +11101101011100,0101100 +01101101001011,1100110 +10111101000110,0011000 +10010101011110,0010100 +11001111111111,0011000 +11101001110100,0000000 +00001010011011,0011100 +00100001010111,1000111 +10101001000111,0011101 +00000111101110,1010010 +10110011000110,0011100 +01111000010010,0110000 +01101100010101,0111100 +00100110100100,0100101 +11011101101000,0000110 +11000010010101,1000100 +01111010010100,0101001 +00100110010100,0000101 +00100100000111,0011001 +10110110010000,1001011 +11000111110011,0010000 +10111110010101,1001010 +01011010010000,0110101 +01101011111111,1001010 +00100011111000,1010110 +10111011000100,0011001 +10100111000000,0010011 +10000010110101,1010100 +10100100010101,1111010 +10010111100001,0110010 +11101000000110,1001100 +01011001010110,1101100 +10101000111110,1001010 +01110110101101,0010100 +01110000000110,0100010 +00010000111110,0110110 +11111111011101,0100010 +01001011010111,1100010 +00111010011101,0000000 +11001100101111,1110001 +10101010010010,1000110 +00001010000001,0000100 +01100101101000,1101100 +01111011111000,1111001 +11010110000000,1101011 +01010100001101,0111010 +10110010001011,1101100 +11010010011111,1000110 +11101011010101,0100000 +00010000110100,0111000 +11011110111101,1011100 +10101011110111,0100010 +00100101010000,1111100 +11110000100111,1100011 +01101111000100,1010011 +10000100011001,1101010 +01010001110110,1011010 +00101101101100,1001100 +01110001111100,1000100 +10000000010100,1100100 +00011100011010,0011000 +10101000110001,1000110 +11110010000111,1000010 +11000000110001,1100001 +01101010000110,0110010 +00001110111101,0100100 +01010111100001,1110010 +01110110011101,0100100 +00010001111110,1110110 +11100010011000,1101110 +01011110110110,0010001 +11110100001010,1110000 +10010111111010,0101110 +11000110100101,1000100 +11110010110101,1110000 +10110100110010,1110000 +01101011000110,1010010 +01110110000100,0111101 +10011101001111,0000001 +11101000110010,1111000 +00000001100101,1100101 +11010101011011,0100001 +01111001100110,1010100 +11010110100111,1110000 +00011001000001,1001010 +10101111011101,0001100 +10011010010000,1010101 +01100110101001,0011100 +10000100010011,1100001 +00001001111001,1111110 +11110001001001,0101110 +10000001111100,0111100 +01011011111011,1010100 +00101010110011,0100100 +00001000110011,0110101 +00011000111110,0110010 +11110101100001,0010100 +10011100110101,1011100 +10000011010101,0010100 +01010000001111,0111011 +10011111011110,0011110 +00100011101100,1001010 +11111000010010,1010000 +11000101010100,0111000 +01001110011110,0110110 +00110101110111,1101001 +10111101010001,0001000 +01100000100001,0011110 +01100001110101,1000101 +00010011000010,1110010 +00101101010101,1000010 +11100111010110,0100110 +11110000110011,1110101 +11011000110111,1101101 +01111010100001,0011100 +01001101001101,1110010 +00001101001011,1001110 +11011111000100,0101011 +10111110011111,1000000 +01010011001000,1000001 +00110100001011,0011110 +10001011111000,0100001 +00011110000111,0001000 +01101011100100,1100001 +01101100000000,0110110 +10100100110010,1000000 +11100000111010,1110010 +00010100100000,0110010 +11110010101000,1010001 +10001001000100,0000000 +11111001101010,0011000 +00110101110010,1010000 +10110011101111,0111010 +01111000001000,0110100 +10100011001011,0010010 +10100011001001,0010000 +11110101111001,0000010 +11100100100110,1101000 +01100101011100,1001000 +00111000111010,0100100 +01111010011100,0100001 +00100110010111,0000100 +10010100011100,1011000 +10011010001000,1000101 +10100101010110,0000100 +00100110011000,0001101 +10000111010000,0011101 +01010101101000,1111100 +00000101011100,1100000 +10110110010011,1001000 +11101101001011,0100110 +01010101110011,1011110 +01011100011111,0100001 +01100101110110,1000100 +10101100000111,1101110 +01001011000100,1000001 +11100001010011,0111101 +00101101101000,1001010 +00010001011010,1010010 +00111100000000,0011110 +01010010110010,0010010 +10100000010001,1111110 +00011110101011,0111000 +10010000111001,1010001 +11111010001110,1110010 +00000110011111,0011100 +10000111101111,0101100 +11100010110011,1111100 +01100110000011,0110000 +10000101110010,0110000 +11011101101111,0000001 +01100101110000,1111100 +11101111110110,0000001 +10010100101111,1000101 +11110001111101,0000101 +10110001110000,0110000 +00011000111010,0110100 +01010001110010,1011100 +11101111011010,0101001 +01000001010010,1100010 +11100100111000,1001100 +00010000111111,0110111 +11100011000101,0101000 +00010010001111,0000110 +01100000011000,0110000 +11011010101110,1000010 +10111010001111,1101100 +01011101001101,1111100 +11000010111000,1010110 +10100001010111,0000111 +10110011100110,0101100 +10110000111110,1000110 +10100101100110,0111000 +11111001100110,0010100 +00001111000101,1111100 +01101110001110,0111110 +11000011101000,0001110 +00001101101011,1101110 +01001001011110,1101010 +11110110100001,1011010 +10000010111101,1011100 +00100001100001,1111110 +10010111000111,0001000 +01000001011011,1101011 +00111011100001,1001100 +01001001010111,1100011 +00110001111000,1100000 +01000010110110,0011000 +01001101110100,1101100 +10010110001010,1000001 +00000101101000,1010100 +10001101111100,0100100 +10000000001101,1110101 +11100011010001,0100000 +00001100101101,0101010 +01000000110110,0010110 +10000111111000,0100101 +11100110000000,1110011 +00100011110010,1011100 +11101011110000,0000101 +10101101001101,0010010 +11011001011111,0100011 +01111000100011,0010001 +10001110101010,1110001 +10110010011110,1000100 +00001101111100,1000100 +01000100001111,0110101 +01000001000000,1000000 +11000100111101,1101110 +11011110000101,1101010 +11010000000101,1010001 +00111100100011,0110010 +11110011001100,0110110 +11000010010000,1111110 +01110111101100,1100101 +11010001101001,0000001 +11011000011101,1000001 +10011011010110,0010010 +10010100010001,1101100 +00011001001010,1000100 +00100010010111,0000110 +11010011011110,0111000 +10100010100001,1100000 +10011011000110,0001010 +10011010101010,1111010 +10101001011101,0001001 +01110001100100,1010000 +10011101011100,0011100 +11111010000010,1111110 +01111000011001,0111010 +11100111011101,0101000 +11100100001100,1111000 +01001010000010,0100110 +10101000001011,1100101 +11110000111101,1111001 +11101101110011,0000110 +01110100101101,0011010 +00010111000110,1110110 +01101110010100,0100011 +00101101100000,1000110 +11111011010111,0110100 +01001001100011,1000101 +01001110111010,0010001 +10101111100000,0100111 +00001011101110,1010100 +11111100011011,1011010 +01111111001000,1010111 +11001111110001,0011010 +00100001110001,1100001 +00000010101010,0110100 +11011000101011,1111001 +11100101010010,0100000 +10100010011111,1110110 +11110101101001,0011100 +10111111001110,0010001 +11111001011001,0111010 +11100001111000,0001000 +01100000010111,0111011 +10111100100010,1101100 +01001111100000,1111011 +00001111000001,1111010 +01000101011001,1101010 +10101111010010,0000101 +11100100110100,1000100 +01000011000110,1000100 +10001001100110,0100010 +00011010110101,0100000 +01010001011110,1100110 +10111110101101,1100010 +01000101011100,1010000 +00010111110010,1000001 +11010011101100,0000110 +11010100011111,1000101 +10000000111000,1011000 +00000111101011,1101000 +00011100100010,0110100 +11010111010010,0110001 +01100110110110,0000110 +01111001001011,1101001 +01000110111000,0010101 +01101000011110,0110010 +10101001110100,0100000 +10110110100001,1101010 +11111000100101,1100110 +11100011110000,0000001 +01100110011111,0110100 +10110111100100,0101101 +11001100001110,1010000 +10001011000111,0000010 +11000011011111,0101110 +00100011101101,1110100 +01111101011100,1000010 +11101011000000,0110101 +01011100011110,0100000 +00111111111100,1011101 +11110111101110,0010110 +01101100000101,0101100 +00110110110101,0110100 +11000011111100,0010010 +11110011111010,0000010 +00110101000011,1010110 +11110011010010,0101010 +01101011110001,1111000 +00010010011101,0010100 +01000000100010,0000010 +11001010001010,1101010 +00011111101110,1011110 +11000101111000,0010100 +01001001011101,1101001 +00101110000111,0010000 +00010001011100,1010100 +01111111000111,1011000 +11100010111101,1110100 +11011110001110,1100001 +00100110110011,0100000 +10001110110011,1101000 +10111000000010,1100000 +11000100111000,1010100 +00000110110010,0101110 +00000111100000,1011101 +00001011010110,1101100 +11110011011110,0100100 +10010000111011,1010011 +00000010001000,0001110 +00000100111110,0111100 +11011011000111,0110100 +11100111111001,0001100 +11100011100011,0011100 +01110010010011,0110100 +00010001010100,1011000 +00000110100100,0111001 +00010110100001,0110010 +11111011111010,0000110 +01111111011001,1000110 +10111100010100,1001010 +11100011100110,0010100 +01111110011110,0100001 +10010111110110,0100110 +10000000011101,1101101 +00000011100101,1100100 +10110110010111,1110000 +10011001011011,0010101 +11010010000000,1101001 +11010010100001,1001000 +10001010011010,1010010 +01111010110111,0001100 +01000000100011,0000011 +00011001110101,1111110 +11100110111011,1110000 +01011010000000,0101101 +01111010100101,0011000 +11100101111110,0001100 +11111000010000,1101100 +01111001100001,1010010 +11100010001011,1000010 +00111110001101,0010010 +10101010110001,1111000 +00010100000010,0001000 +01111110000001,0111110 +10010111100111,0101000 +11110011011101,0111000 +11101000101001,1100110 +00010000001010,0000010 +11110101010101,0110010 +00001001000100,1000000 +11000111110110,0011010 +10010111100000,0110011 +10110000011111,1111011 +00100011000110,1100100 +00001011011001,1100000 +10001010111011,1011100 +00111010010000,0001101 +00101000000011,0011001 +00001110010010,0011001 +10110000010110,1110010 +10111111001100,0010011 +00101010001001,0011000 +10110100011001,1111100 +01001000011010,0101100 +11010001011100,0100100 +10101010001101,1100000 +00000010110101,0110100 +00100001110100,1100100 +01010010111000,0011110 +11000111010110,0111010 +11001000100011,1111001 +11011010011001,1111000 +00001000111000,0100000 +01000100110001,0010010 +10111100111100,1111100 +10101100011001,1110100 +10101111010101,0000010 +01101101101010,1101000 +10101111010001,0000110 +01111000100000,0011100 +11110111011110,0100110 +10110111100011,0101000 +01110111001010,1010001 +00001110011110,0010110 +00001000000000,0000100 +00101001110100,1100000 +11100011001100,0111010 +10010001000111,0001011 +01101011100101,1100000 +01010111010010,1010001 +10011010100100,1110001 +11110101111000,0000010 +00100010000110,0010100 +11110110101010,1010001 +01011001101000,1111000 +00111101011010,1111000 +10011011011110,0011100 +00111101101000,1000110 +01000110101100,0001001 +10100110110110,1111010 +00110110100011,0101000 +10001001000001,0000110 +01111101110100,1110010 +01010010100111,0001010 +10000101101100,0110000 +01010000100101,0001001 +11100101111011,0001001 +11100010111110,1001000 +01001011010011,1100100 +10000110110111,1010100 +00111100100000,0101110 +11010101010011,0101110 +11010000001010,1011100 +11111011101100,0010001 +00001100011001,0011100 +11000000110110,1100110 +10100010001111,1100110 +00010011110111,1111010 +10010101001010,0000000 +10101111001001,0011010 +01100000000001,0101110 +01001001100010,1000100 +10110101000011,0010110 +00000010000010,0000010 +01100100010001,0111100 +11111101101101,0011100 +10100001010110,0000110 +01110010101100,0010110 +10100010011110,1001000 +01001010100001,0000100 +01011001010100,1010000 +11101110100110,1010001 +11101011100000,0010101 +10010011000111,0001010 +01001010001000,0101001 +11100100011010,1010000 +10010010010011,1101100 +01000100110011,0010001 +10011101010100,0010010 +00000110110000,0101101 +01001000001010,0110100 +01100010100110,0010100 +01010010101110,0000100 +00101100010111,0000001 +11001100000110,1100000 +10010100011000,1011100 +01000101110101,1010110 +11011010101011,1111000 +01111110011101,0100010 +01010001101011,1000011 +01110100000011,0100110 +11111010001001,1110100 +00000111001111,1001100 +00111010001000,0010101 +00111110111010,0111001 +01010100111111,0010101 +10101110100101,1100010 +00101001111101,1101001 +11111000100111,1100101 +10110100111101,1000110 +10110011000111,0010010 +10000111101101,0110000 +10100000101101,1010101 +01000100111100,0010000 +10000111001111,0001100 +01111110111110,0000001 +00100101000111,1011001 +11001111111010,0010001 +10101111100110,0100001 +01011001110010,1011000 +10011001101011,0100101 +10100101110001,0100010 +10001101100101,0100010 +01101100010001,0111000 +10100000000011,1101101 +00010000010100,0011000 +00010000011110,0010110 +10100000101001,1010001 +10100000110001,1000001 +00010101000101,1001010 +00010010110101,0111000 +01000110001011,0110000 +00100011010100,1111010 +00110111010110,1110110 +11101000000000,1110100 +01000101010000,1011100 +10111111110011,0110100 +11010110001111,1011000 +01110101100001,1010100 +01100000001001,0100001 +01100000110011,0000011 +11000110101010,1001110 +11111001010011,0110001 +11011010101010,1000110 +00000111010110,1101010 +01110100100001,0010100 +01101110010011,0100100 +11100001001000,0100000 +10110000010010,1110100 +01011101100010,1110100 +10000001101111,0101111 +10011100111100,1101100 +01010010111111,0010110 +11011000000001,1010010 +10000101101000,0110100 +11111000100110,1100100 +11010010001001,1100000 +01010110110111,0011000 +00011011001101,1000000 +01111101010111,1110110 +11111110000101,1111010 +00011110000001,0001110 +10001111000100,0000011 +00110101001100,1100100 +00011010000001,0001100 +00010111010001,1100010 +00000011111111,1111110 +11100011010011,0111100 +11001001011100,0101000 +01001111110100,1101101 +00010101011100,1101000 +11010100111111,1100101 +10111010110101,1110000 +10000111000101,0000100 +01100111010101,1000100 +01001000100010,0000100 +01000110010000,0111101 +10111111010110,0001001 +11100101111010,0001000 +10000101011101,0011110 +00010000100110,0101010 +10001000110000,1101000 +01011110000001,0101110 +11101110000000,1110111 +10011000010110,1101100 +00111011100011,1110000 +10001010011011,1101100 +00000111010101,1101000 +10001111011010,0010001 +00101111010110,1111110 +11011111111111,0010000 +00011010101101,0100000 +10110010100010,1101010 +00010000110011,0111101 +10110001111111,0100111 +10010011001000,0000001 +10111000010000,1001100 +01110001100011,1010101 +11000001101110,0001110 +01110010111111,0000110 +00101001011011,1001101 +01010110011100,0111001 +01110011000100,1011110 +00101001011100,1001000 +01110011001000,1010001 +00101111010010,1111001 +01001111011010,1010001 +10010111111101,0101000 +00000100001100,0001000 +01101110000100,0110011 +00011110110111,0100000 +01001101100101,1000010 +10001000110111,1010011 +01100010100001,0010000 +00010101000000,1110010 +01011001101101,1000001 +01010010101000,0000001 +01011011010110,1010010 +10000110110101,1101000 +00101100101111,0110001 +01101011001011,1100100 +10010111101001,0111100 +01010011100100,1110110 +11010100100011,1110110 +10001000100110,1000010 +10100111111110,0110010 +11100010110000,1000001 +00010001100111,1101011 +00100110111001,0110100 +00000011000000,1111110 +00010110100100,0110101 +11000000101010,1110010 +00010100111010,0110000 +10101100000110,1010000 +11010100011010,1000000 +11010001110010,0011100 +01000010111100,0010010 +10011111111101,0101100 +00101111011100,1110101 +11010101001011,0111110 +01111101000100,1011010 +11010101101110,0000100 +00000011101010,1010100 +10101110101010,1101001 +00101000101101,0110001 +11000101001001,0110010 +11101111100110,0010001 +01001101101100,1110100 +00101010000010,0010110 +01010011011011,1100010 +10111011110101,0110000 +00101101100110,1000000 +11010011001010,0100010 +00011011110100,1000001 +10011010100000,1110101 +11110101110111,0001001 +00110011000101,1010000 +01010101110111,1011001 +10100100101000,1101100 +10000011101001,0101000 +11010100100010,1001000 +10100111100100,0100101 +10111111110000,0110111 +01110111101000,1100011 +01110101000001,1100100 +01010011001001,1000000 +00011000110000,0100100 +00100100110100,0111000 +11101011110101,0000000 +10011110001100,1000011 +11111000000001,1000010 +00001101010111,1010001 +00110100011111,0000101 +10101111000000,0010111 +01011101011010,1011000 +11111100100100,1011010 +01101001111010,1001100 +11011100101010,1000100 +10000111001001,0001100 +11101110010101,1100010 +10011101000101,0001100 +01011011001111,1111100 +01010000011101,0100101 +00101111110100,1011101 +11101001111010,0001100 +00011001101100,1100000 +01100110101110,0011010 +00110011100011,1110100 +10110111011100,0000101 +01110011010010,1001010 +01010000010111,0101011 +01100110101100,0011001 +01000110011110,0110010 +00010101111101,1110110 +10010111001111,0000100 +00101111000100,1100011 +10111101110111,0101110 +00000101011010,1011000 +00111001110110,1101100 +01110001110010,1001100 +10111011011001,0000100 +11001101000101,0111100 +01010111110011,1100000 +00100111111111,1101100 +10010101010011,0011110 +10111101100101,0110100 +01000111011110,1010010 +11111100101010,1010100 +00010010011110,0011000 +00001101001111,1001001 +11111001100000,0011100 +00110101110101,1101010 +11100101100101,0011010 +11001101011110,0101000 +00011011100000,1010101 +10000011111101,0111100 +11000110011110,1110010 +00111101001001,1011000 +11011000000111,1010101 +00100011110101,1100100 +00010110010001,0010010 +10000100001111,1110101 +00110000010110,0001010 +10010000001000,1000000 +00111010001110,0010010 +11001110101000,1001011 +00101101001010,1101000 +11111010110101,1001000 +00110101001111,1011001 +10111110100111,1101000 +11000101000000,0100010 +11000010111001,1101000 +00101110110001,0111010 +10001011111010,0100010 +01000100101101,0001110 +10111110101100,1100011 +10011000001000,1000100 +11010011000001,0101000 +01010110001000,0100011 +11100101010000,0100010 +11001001101101,0001001 +10010110110101,1100100 +01101010111011,0001100 +00000100000101,0000110 +00010010011011,0010010 +01001011011110,1010100 +10011100010011,1101010 +01101010110011,0000100 +11001110011111,1001000 +01101100110001,0000100 +10100010011001,1110000 +00111110001010,0010101 +10001000001110,1110010 +11110010001000,1110001 +10110100001011,1101110 +00000110100000,0111101 +00000001111101,1111101 +00110110010001,0001010 +00100101111100,1010000 +01010101010111,1101001 +10010111001110,0000110 +00010000011011,0010011 +00011000000011,0001001 +11001000010001,1000110 +00110110101000,0100011 +10101101100000,0100110 +00011011000100,1110001 +01010001100011,1001101 +11000111111111,0011100 +00000011101110,1010000 +01110001101100,1011000 +11001011000010,0100110 +01111100011101,0111100 +11111100110101,1110100 +01000100110000,0011100 +01000010001100,0101010 +01010110111111,0010100 +01011011110001,1100100 +10111011111101,0100000 +11000110100000,1000011 +00110111100001,1001010 +11001110010011,1111000 +00110110000001,0011010 +00110011110101,1101000 +10011111100000,0110111 +10010011100001,0110000 +10100101001011,0010001 +01000101000110,1111000 +01101111110110,1111110 +00111000110000,0110100 +01001111111010,1100001 +11111110000011,1111100 +00010000000100,0001000 +10011010000010,1001110 +01100000001000,0100000 +01101101011000,1001010 +11101001010101,0111110 +10111110000100,1011011 +11100001111100,0001100 +11011000000000,1101100 +10110010110001,1110000 +01110100010100,0101100 +10111011010010,0001110 +11110011100011,0010100 +01111110011100,0100011 +10011101000011,0001010 +01111010000011,0100000 +11101111011111,0110000 +10000011011011,0011010 +10000011001100,0001010 +01011010100100,0001001 +10011010111001,1101000 +01011110011110,0111110 +11000111000011,0100000 +10111100101111,1011110 +00000011000001,1000000 +11110101100000,0011010 +10001010111001,1100000 +10010011011110,0011000 +10111110100110,1101001 +00110110011001,0000010 +10100111000001,0010010 +00011101011100,1101100 +01100010101011,0010010 +00111001000110,1010100 +00010111100100,1010101 +11101101011101,0110010 +10111001111000,0111000 +11111001000010,0100000 +11000001010100,0100100 +10010110110110,1100110 +10011001000011,0001001 +10100001100101,0111001 +10101101110111,0100001 +01110001011011,1111101 +01000000010101,0100101 +01100110001110,0111010 +00110001011001,1000001 +11110000110000,1001000 +00101000110010,0100100 +01110001001110,1101010 +00111111010101,1110010 +11101111101110,0011110 +11101100110001,1111000 +00001000100001,0100110 +11100001001011,0100011 +01100000001101,0100101 +01101110010111,0100000 +11101111101111,0010000 +01111001111000,1111000 +00111011011101,1000000 +00110010111110,0111000 +11010100000011,1010110 +11100101100001,0011100 +11111010110111,1110100 +10101110000001,1010110 +01001101011111,1101001 +01000100111010,0011000 +01011011110111,1011100 +00111111111010,1011001 +01010000100001,0001110 +10100110011110,1001010 +10100101111111,0101101 +01000110001101,0101000 +00100111010110,1111010 +11001011111100,0010110 +11000100101100,1001000 +10000011000000,0000001 +00011100111110,0110000 +01111100000110,0111000 +00101101010011,1000110 +11100000001010,1000010 +01101110100000,0010111 +11101001000101,0101110 +00100001101000,1110000 +11000111101101,0001000 +10001101101101,0101010 +11101110101110,1011110 +11111111101011,0010100 +10010010001000,1000001 +00001011101010,1010010 +00010010010110,0010100 +10010100001101,1111010 +00011000000100,0001000 +10100011010111,0000110 +10100000100101,1011001 +01110111111011,1000000 +10100010001110,1011000 +10011111010010,0010101 +00110001010110,1001010 +11010101010110,0101000 +10000001010111,0010111 +00111001010010,1001000 +01001110000101,0100010 +11011111001011,0100100 +00101101100011,1111010 +11001000010110,1000010 +00010101010011,1011110 +10001100110100,1101100 +00011110011011,0011000 +01111001100011,1010001 +10000101001100,0001000 +11001110011010,1110001 +00001011000101,1000000 +01111000111111,0000011 +11110001001011,0101101 +10001101111101,0111010 +11000110101110,1001010 +01100001100000,1100000 +11100011101110,0011000 +01111001100111,1010101 +01100110101111,0010100 +11000100100100,1000100 +00110001100001,1110110 +00100011110100,1011010 +11101100001101,1000010 +01110011110100,1110110 +01101001000010,1101000 +01100101111100,1110000 +01110000101101,0011001 +10001100001011,1110110 +10000110101111,1001100 +00110001011111,1000111 +00101111111011,1010000 +11000110001111,1010100 +00010011101100,1011010 +11011111001000,0100111 +01000110110110,0011010 +10001100001000,1001010 +10001101000100,0000010 +01101101100001,1011000 +00101110010000,0000111 +00110010110101,0101000 +11100010001101,1000100 +11101110110110,1000001 +10011100100110,1110000 +11011110101101,1000010 +11111111001111,0110000 +10010000100111,1001011 +10101011100111,0111100 +11111100101000,1010110 +10110011101110,0100100 +11100000001110,1000110 +01011100000110,0101000 +00001101000111,1000001 +11010000111000,1100000 +10010110110000,1100011 +01111011111110,1111100 +10011101100110,0110000 +11001100011000,1110010 +11101110011000,1101011 +01001011101010,1110010 +01011100101000,0000110 +00111100001110,0010000 +01000010100010,0000010 +10100011110100,0111010 +10100101111011,0101001 +11001110111010,1010001 +11101001111011,0001101 +00110010101011,0111100 +10011001100101,0101110 +10001111001011,0001000 +00011100010001,0011000 +10111111000100,0011011 +11001001011010,0101100 +00101001010011,1000101 +00101100101000,0101010 +00011010011110,0011100 +01100001101100,1010100 +00011001010001,1011010 +10101001000110,0011100 +11010010101000,1000001 +10011000011000,1011000 +10111010000101,1011000 +11110111000010,0111001 +01100100000111,0101001 +11001111001101,0101100 +10010011011010,0011100 +00101111010011,1111000 +00010110101110,0111010 +01101010001010,0111010 +00111000100001,0110010 +01110101000110,1100000 +10001101010010,0011000 +11100001100011,0011101 +11100101110111,0000101 +10111000101000,1100100 +11001011000001,0100100 +11001010101101,1110000 +01001000011101,0101001 +11010011101011,0000010 +01100101011101,1110110 +10001100100101,1000010 +00001011001100,1110110 +01110010011000,0100001 +01101101011110,1110000 +01101111110011,1111000 +00101000010000,0000100 +10000011001000,0001110 +11011011101110,0000010 +01001111000100,1000011 +10101100001110,1100000 +10100010010001,1000000 +11000101011010,0101000 +01000101011101,1101110 +10000011110111,0110110 +00111011000111,1010100 +11011011101000,0000101 +00100110000010,0010001 +10111001110010,0101000 +00000010111110,0100000 +11110111101101,0010100 +11000001110011,0010011 +00101011100001,1000100 +01100000000110,0101010 +11111111010011,0101100 +11111001101111,0011101 +01000110010001,0111100 +01101011111101,1001000 +11001010000001,1100100 +01000101010111,1100101 +00110001110101,1101001 +01111000001110,0101100 +00000001110000,1110000 +10000110110011,1010000 +01000001011010,1101010 +01001011011101,1101000 +10001100110001,1010100 +00000011000100,1111010 +01000100000111,0111001 +11100100000100,1110100 +01011010110011,0011000 +11111011010100,0101001 +10011111010011,0010100 +01010011111111,1010110 +11101010110111,1111100 +00010100011111,0010101 +11011001010000,0110100 +10011000101111,1000011 +01100100010100,0100100 +01000000111001,0011001 +10110010110100,1110110 +01100000001110,0100110 +00111111000011,1101100 +01011111100001,1110110 +01111100100001,0010000 +00111001001100,1100000 +01110101011111,1111001 +10111100100101,1010100 +00000111110011,1110000 +11101110101100,1011101 +10011101110101,0111100 +10011110111000,1101011 +10010010011010,1011100 +10011101011011,0010110 +00010101110101,1111010 +11001101101101,0001010 +10001110000010,1000101 +01011000010111,0101101 +01011101001010,1000100 +00010010110011,0111100 +11101100111011,1110110 +01000110000111,0111000 +11000001010010,0100010 +00010000001101,0000101 +11110001110000,0001000 +11000010101010,1001100 +10010111011011,0010000 +01010000001011,0111101 +11110110010011,1101000 +01111101100010,1101100 +01000111100110,1111010 +01011011100001,1110100 +00010001111001,1110001 +00101111000011,1100100 +11010000011100,1000100 +11010101001000,0100010 +10001100110010,1101000 +10010001110100,0111000 +01001000001000,0101000 +10101010111111,1001010 +11011001100001,0001010 +01101011001000,1011001 +01110110110110,0001110 +00110111000001,1101010 +01100111111101,1110000 +10101000011010,1110100 +01001010000001,0100100 +11010011000111,0110010 +11100011011001,0110000 +00011110111010,0101001 +00101010011010,0001010 +01100001001010,1100010 +10111000011000,1000100 +01111110110001,0001110 +00100000000000,0010000 +11001110001011,1101000 +11000111111001,0010100 +10101111101100,0101101 +11101010001111,1000010 +01111010010110,0101010 +11101110100111,1010000 +10111011000000,0011101 +10100010010011,1111100 +11011001100100,0001000 +00001100010000,0011010 +01100101101001,1010010 +00010101100110,1101000 +00100011001101,1010100 +00111001110100,1010000 +01101110011000,0101011 +00010110101101,0111000 +10011110001000,1000111 +11111101001000,0110110 +11101001100111,0011101 +10001010100110,1111100 +01001000010100,0100000 +10000100110010,1010000 +01011100110100,0010010 +10111011101011,0111000 +11010110010010,1110001 +01000001010011,1100011 +10011100000111,1110110 +10011110100000,1110111 +10010010110010,1100010 +11111101000000,0111110 +01101000101100,0010000 +10010001101101,0100101 +01011100010001,0101000 +10011010001001,1000100 +11100000000011,1001101 +00010110001010,0000001 +01100001010001,1111110 +10111000110000,1110100 +00011110001011,0000100 +01010010011000,0111110 +11000100110101,1100110 +10010110000111,1110000 +01000000001000,0110000 +11100011110111,0000110 +11000110111001,1010100 +01000000011010,0101010 +10001001101110,0101010 +01100100001101,0100110 +10110000000111,1100011 +10010001110110,0111010 +00011011010111,1011100 +10000101110001,0110010 +00110101111000,1011100 +00011010010101,0010000 +11010010101110,1000100 +00001111101010,1010001 +01100011000011,1101100 +01000111010100,1011001 +01100111101110,1101010 +00111001111000,1011000 +01001000010101,0100001 +10101001010110,0000010 +11101101001100,0111100 +10010110011101,1011000 +10000010001000,1001110 +01111111011110,1000001 +00110110111111,0100100 +00111101010110,1110000 +10101010101101,1010000 +10010110101000,1111101 +10111111100000,0101111 +00001111011011,1100000 +11010010110111,1101010 +11101100000001,1001000 +01110101011000,1000010 +10101011101100,0101110 +11001011100011,0000100 +11111000000110,1000100 +00111100011000,0000110 +11101100111001,1110100 +11001101100111,0000001 +11101101001110,0100000 +10001100011010,1010000 +10101010110100,1111110 +11000010111101,1101100 +11011101011010,0111000 +01011111001101,1000010 +11111011101111,0011100 +01011010011100,0111110 +01010111100101,1110100 +01001010011111,0101010 +11000111011000,0110101 +11000110010011,1000000 +11001110100001,1000110 +11000100011001,1001010 +11101101101110,0010000 +11101111001101,0111100 +10100011001010,0011100 +01011110111000,0011011 +01111010101001,0010100 +01011000101000,0000100 +10000001010101,0010101 +10110101101011,0111110 +01000000100001,0000001 +10011001110111,0111101 +00110010001001,0010000 +11001110111111,1101000 +11001000001010,1010100 +11101011001110,0111100 +00110001111001,1100001 +00000001000010,1000010 +01101100111111,0001001 +00101101010001,1000100 +01001010000101,0100000 +11010000110101,1101001 +10001001011011,0011101 +10100010110001,1000000 +01000111101101,1110000 +10010011111111,0110110 +01010101100110,1001000 +01101111010101,1000010 +11000110010000,1111101 +00000001110011,1110011 +00011000110100,0100000 +10011111000011,0001100 +01111011100011,1010000 +10001100010000,1011010 +01111110111111,0000000 +11011001001101,0111110 +01010101000100,1001100 +11110111001110,0110110 +11111000101010,1101000 +00100011111010,1010100 +00011111000011,1110100 +11011001011100,0100000 +10110001000010,0010100 +11001000101101,1110001 +00111101111101,1100010 +00010000000111,0001011 +10110011010011,0001100 +01001111110000,1101011 +00110010110110,0110100 +01001001001110,1110010 +01111100000111,0100110 +11111110010000,1101111 +00110110111010,0111110 +00111100100110,0101000 +11001010000011,1011000 +11000000110010,1100010 +10111111101100,0100011 +10100100100100,1100100 +10010001101011,0100011 +10110011100101,0110000 +10010011000100,0001110 +01110111000111,1100000 +01100001010011,1111101 +00000100101010,0101000 +01001111100010,1111001 +11000101111100,0010000 +11100011011100,0101010 +11000100101110,1110100 +10110011111000,0111110 +00001000101001,0101110 +00101111110110,1011110 +00100100011100,0001000 +10111011101110,0100010 +00101010001010,0011010 +10110001110111,0101011 +01010001010011,1101101 +10001000011001,1101110 +11101100010001,1011000 +11111110101101,1010010 +00011010001000,0000101 +10000000011110,1101110 +00011011100010,1010110 +10000100010101,1100110 +00110010000011,0010100 +00001101010010,1101000 +11100001000001,0101110 +01100111010000,1000011 +10111000111000,1111000 +11111000011011,1011001 +11101111010000,0100111 +11001100101001,1110100 diff --git a/tracks/qcs/solutions/Genshin_Impact-71/predictions/mystery-C/test_outputs.csv b/tracks/qcs/solutions/Genshin_Impact-71/predictions/mystery-C/test_outputs.csv new file mode 100644 index 000000000..ebd6837e5 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/predictions/mystery-C/test_outputs.csv @@ -0,0 +1,1501 @@ +input,output +111000111101,100100101000 +110111100010,110101111100 +100001100101,100100101010 +011011011011,001001101101 +011011000101,000011100001 +001111010111,000110011011 +101001011100,011000000100 +011000001101,000100001000 +101001101101,100000010110 +010011010010,001000011100 +010111000100,000010111000 +101001101100,100001111000 +110111001011,001111111101 +010110001001,000101011100 +001010011101,000110011100 +101101101100,100100100100 +110101100101,110001110110 +110000000001,000001100000 +101111111000,110101011000 +101011101110,100000000110 +011010000001,000000110100 +010000110011,011001100000 +000011101000,000011110000 +000010101101,000010110100 +000010111000,000011100000 +100011101110,101100011010 +001100110101,001000000100 +010101000001,000000101010 +111111110111,101000010111 +111110011011,010100010110 +110011101011,111100010101 +111001011010,010110101100 +001010000100,000001010000 +001010110011,001111111100 +110001111101,101101100110 +010101111001,011001100110 +100100110100,110001100000 +111100100101,111001100100 +111101100100,111001011000 +010101000101,000010010110 +011100011000,001010100000 +100001011000,011000110000 +101100101110,100111101000 +111011101111,110110001011 +011000101001,011110110000 +111010111010,100010000100 +110110011010,010010100100 +010110100110,010100010100 +000100010101,000010101000 +111111000110,000101111010 +000010011111,000001111100 +000101110100,000111011000 +110011011111,010110100011 +010100010000,001010000000 +111101110011,101110101001 +101010001001,001011110100 +101111000010,000010111100 +011111101101,011001110101 +100001010101,010101101010 +010110101001,010000111100 +011001111011,010101000001 +000111010101,000011001001 +110110110100,100101001000 +110010111111,101101010010 +000000101101,000000000000 +111100000100,000111100000 +011000100011,011001001000 +100110111001,111100111100 +001011110011,001110100101 +000111111000,000100011000 +111110000110,000101110100 +001010011010,000111011000 +110101100000,110101000000 +111100111001,100100100100 +101111000011,000011101101 +011100010010,001111110000 +111111101111,110000001111 +110000011101,010100010000 +101011010110,010001101010 +001111001110,000010010110 +001100101111,001110110100 +010110001010,000100000100 +011001000011,000001001110 +011011111001,010111000001 +011100111110,010011011000 +000101000001,000000001010 +000001001100,000000011000 +000001000111,000000001110 +000010100101,000010010100 +101001101010,100100001100 +000100110010,000110010000 +011111001011,000110010011 +100001101111,101110111110 +010111001000,000101110000 +111111000001,000001111110 +010010110101,011000001100 +101001010100,010011101000 +000010111101,000011110100 +100101000000,000000000000 +011111000100,000011111000 +010101100010,010100110100 +111100000011,000010110100 +100111001100,001101010100 +111001111110,100111010010 +110011100011,110000111001 +010111001110,000110100110 +001101110111,001001000101 +001101100001,001101011010 +011001110000,010011100000 +011100001111,000100101100 +111001101011,110010000001 +001000011101,000111010000 +001100101011,001111100100 +010011001100,000110100100 +001111111000,001001011000 +010110111101,011000110010 +000001010001,000000100010 +001110000111,000001000110 +110101000100,000110101000 +110001100111,110100111110 +001111000111,000001001011 +111000010110,011011010000 +110101000111,000101101001 +101010101111,100000001010 +010110100000,010110000000 +001110110010,001010000100 +001000110101,001101010000 +111011110011,101011110101 +111000001100,001010100000 +001110100010,001110111000 +101100000111,000110110100 +111000101111,110101011000 +001001111001,001111101010 +010110101101,010010010010 +000000100011,000000000000 +000100101000,000101000000 +100100001000,001001000000 +010111000000,000000000000 +100110000010,000010011000 +010110100100,010101110000 +010110111110,011001001100 +001000011000,000110000000 +111111011111,010000101111 +110001000011,000010010110 +111011111110,100101010110 +111111111010,100101011010 +001111110000,001011010000 +001101110000,001000010000 +011110100111,011101010110 +010101110100,011100111000 +110010011001,010010110100 +101010101000,100101100000 +010010100110,010000111000 +110001111111,101110010001 +000001000010,000000000100 +101001001001,001011001010 +110000010111,011101010000 +101100111001,110111111000 +110001100001,110000010010 +010011110010,011011011100 +100110110100,110010001000 +000011001101,000000100001 +000101000000,000000000000 +000100111010,000111010000 +001110111111,001001110110 +110010101101,111010101100 +101110101000,100010010000 +110001000100,000110001000 +011001010001,001100001010 +001110011000,000101010000 +001101110101,001001101110 +011001110111,010000110001 +101100111100,110000110000 +010100101010,010010110000 +000001010101,000000101010 +011000100101,011011110000 +010111010100,001000100100 +110010001010,001111101000 +011110000100,000011110000 +101011110001,111111001110 +100010100011,100000101100 +100011001001,001001110110 +000001001110,000000011100 +111000011000,010101000000 +011011110101,010010001001 +111000000001,000001110000 +101010110101,111000011100 +011001110001,010011001010 +110100000000,000000000000 +111100000111,000100101100 +001000110010,001100100000 +100001101001,101000110010 +111101001011,001100011001 +111011000001,000001110110 +111011000111,000100000011 +000001100011,000001000110 +000100100111,000100111000 +001111001101,000010100101 +000100001100,000001100000 +101101011110,011000101010 +101111001111,001100100111 +110001010000,011000100000 +011111111010,010010011010 +110111111110,101001001110 +100101110010,110100001100 +000110000001,000000001100 +101000001010,001001100000 +110101011011,010010001001 +011000010000,001100000000 +110010100001,110011100100 +100001111100,111101111000 +110010011010,010001011000 +100000001111,001111000000 +010010001111,000111000010 +111000010011,011110101000 +010000001011,000101100000 +011100000101,000011000100 +010110010001,001011101100 +010111101111,010010111011 +111000011100,010001100000 +001010110100,001110110000 +001111011100,000100101100 +001000010101,000101010000 +011101110010,010101101100 +111110010001,011110000010 +010111111000,011010011000 +011110011101,001001101010 +111100100000,111100000000 +101001001100,001111011000 +101010101110,100001100100 +010110010111,001001111010 +100010110011,110001101100 +010101111110,011010001010 +011101010010,001111001100 +101010010001,010100110100 +111111000100,000111111000 +100111010011,010001001101 +001010101000,001001100000 +110001010101,011111011010 +011000110011,010011001000 +101000011010,011101100000 +011001110011,010010011110 +111111011110,010001101110 +111011110001,101000011110 +111001010011,011110011110 +000001100110,000001001100 +011010010010,001100011000 +010100001001,000101101000 +101111011100,011010101100 +000011101111,000011101101 +001000110011,001100110000 +000010111011,000011101100 +011010010001,001101110100 +101011001101,001110001001 +111101001111,001000001101 +101010100010,101001101000 +011001010101,001111000110 +101000101111,100011001000 +000011001100,000000100100 +001000001110,000011100000 +000010101100,000010110000 +110111100011,110100101101 +000010110011,000011001100 +110010001101,001000101100 +011010001011,000111100010 +110000011011,010001010000 +000001110010,000001100100 +111111100000,111111000000 +001111101011,001101100011 +110111111001,101111110001 +000100101101,000101101000 +000001101010,000001010100 +001100000011,000000100100 +011101110111,010110010101 +110110011110,010101001100 +001010101100,001000001000 +011000001000,000110000000 +010100000011,000001111000 +000001011010,000000110100 +111100001001,001110000100 +101000010110,010000010000 +001101001101,000010011110 +000101101000,000100110000 +000010000010,000000001000 +011010000111,000010110010 +100110100111,100010011010 +001000110110,001101100000 +110001101011,111111001110 +100111110101,110010011001 +100011111011,111000010101 +111101010001,011111000110 +111001100011,111011101110 +000110001101,000001000010 +001000100111,001001110000 +011100101001,011000000100 +111101101000,110101110000 +000101010101,000010010110 +101000000000,000000000000 +011011101100,011111010100 +011101111000,010000101000 +010011000101,000010111110 +110010100110,110110111000 +100011100100,100111011000 +111010111101,100111000010 +010100001010,000100110000 +101011101000,100100001000 +101011010000,010101100000 +000111000101,000000110001 +110111011101,010110010101 +011111111011,010010101011 +111101100011,111111110001 +100100001001,001000101000 +101011111110,110101100110 +101101010111,010011000101 +010001100101,010011101010 +001101100101,001100001110 +101110001000,001011100000 +101001011111,011011110001 +000000101110,000000000000 +011001000001,000000110010 +001110010010,000111111000 +100001111101,111100000110 +010000000101,000010100000 +011101010011,001111110001 +000000000110,000000000000 +111111011101,010010101101 +111011010101,011000001001 +101010010101,010011101100 +101001110001,111100001010 +101011111011,110001101101 +101000100101,101100110000 +100000000101,000101000000 +001001101000,001011010000 +000101001001,000001011010 +011001011001,001001011010 +100010010101,010100110100 +111011010111,011011100011 +001110101001,001100000010 +101110011101,011011001010 +110001110010,100110010100 +100100011010,011000110000 +000110011110,000010110100 +000100011101,000011101000 +111101000000,000000000000 +110011000100,000110011000 +101101100000,101101000000 +010110100001,010110101100 +101101110100,111101111000 +011010011101,001011111100 +000111101101,000110111001 +111111101010,110101001010 +000011000011,000000001001 +100111010110,010100111010 +110100101110,111111001000 +100001001010,001010010100 +010101110011,011110100001 +010010110100,011000110000 +111001010101,011001100110 +101110011111,011000001110 +010011001111,000111011101 +000001111110,000001111100 +000000011001,000000000000 +110011101010,111101000010 +100000001100,001100000000 +110111010100,011100100100 +000111110001,000101011110 +111111110100,101011010100 +100110110011,110111110010 +010101100110,010110000010 +110111011001,010000110001 +011111011001,001011001001 +100101011000,011011110000 +101000101110,100010010000 +111100001110,001001011000 +001110001101,000010110010 +110001101000,111101010000 +101111101101,100111010101 +001001110111,001100100001 +101100100010,101110110000 +101101110011,111011110001 +010010110000,011011000000 +101110101110,100100101100 +100001110001,110000010010 +011011101110,011110000110 +100111111100,111010101100 +001010010001,000101010100 +010001011110,001111111100 +110101110000,100000010000 +010000000000,000000000000 +101111010010,010100100010 +110101100001,110100011010 +001010001100,000011110000 +110011100101,110101000001 +001101111110,001010101010 +000111000111,000000100011 +111111010111,011000100111 +111010000100,000111010000 +001101010000,000110100000 +010111110001,011101111110 +101011111100,110110001100 +111010001000,001110100000 +000101110000,000111100000 +101100110011,111010010100 +101011011100,011001110100 +100000000010,000010000000 +101001100111,101111000001 +010000011010,001101000000 +000100110101,000110101000 +010101101000,010010110000 +011011011001,001000000001 +110000100101,110111100000 +101101101000,100001110000 +101100110100,111100010000 +101011100101,101111100001 +000010111111,000011111100 +110010011011,010000000010 +010111001111,000110011011 +100010100000,100010000000 +011001011101,001010110110 +100101000101,000101100110 +011010011000,001000010000 +100011110110,110101001010 +011111010000,001111100000 +100001111010,111011110100 +111101011110,010000011010 +101000110111,111001001000 +000010100110,000010011000 +110010001111,001011100010 +111011010000,011101100000 +000000110010,000000000000 +010111100001,010111101110 +111010100100,111100110000 +101101011000,011100001000 +101001110110,111001111100 +111011111101,100110000101 +101011110100,111000100100 +010011111011,011111010101 +010111011010,001111110010 +100000101101,101101000000 +111110100010,111100000100 +000110011010,000010000100 +010110001011,000100101010 +101000100011,101011110000 +110001001001,001101110010 +000010000001,000000000100 +111010110111,101100101010 +100001011110,011110111100 +110000111010,101000100000 +010000100000,010000000000 +101001110100,111010011000 +010000000100,000010000000 +110101111101,101001111110 +101011101001,100101011110 +110101001011,001111010001 +100010111001,111010010100 +100001010111,010111101110 +111111100110,111001000110 +101101001110,001101110010 +011000101010,011111100000 +000110110101,000100000010 +111110111110,100000111100 +000111000110,000000101010 +011001010100,001111101000 +010110100010,010111011000 +011111100101,011101111001 +001001011000,000110110000 +001010011000,000111100000 +101010000100,000101010000 +101000010011,010111110000 +110011011101,010101001001 +110110001000,001101100000 +001001101011,001011101110 +010110011100,001101101000 +011100101100,011011010000 +100101000010,000010010100 +110001011100,010101111000 +001011100011,001011111001 +011111110101,010101100101 +110000101111,111011010000 +001101000111,000001011001 +010101001101,000111001110 +100110111110,111000001100 +011101111010,010001000010 +010011001101,000110010001 +011101000100,000011101000 +011111111101,010001101101 +000110010110,000011100100 +011001010010,001101010100 +000001011100,000000111000 +100110011110,011101110100 +101111100100,101001000100 +011111110011,010110100011 +011110001101,000101001010 +100111100010,100100111100 +011001001010,000111110100 +000111010011,000011110101 +110110110111,100111000110 +000101101110,000100010010 +011011001110,000101111010 +001011111010,001101010010 +111010101011,110000110010 +001010101001,001001110100 +100000001000,001000000000 +110110111011,101100111010 +110110001100,001000101000 +100000111101,111101000000 +110001111110,101111000010 +011100001011,000110110100 +011001110110,010000000010 +110100010100,011101100000 +100101000001,000001001010 +001011010001,000101110110 +101110010100,010001001000 +010010010100,001011010000 +111110110010,101100100100 +111100111101,100000110100 +011101101000,011001110000 +100001110000,110001100000 +100101001110,001111100010 +011100000000,000000000000 +010101110000,011111100000 +000100001001,000001001000 +100000000000,000000000000 +011111000111,000010011011 +100100001111,001110000100 +010001101111,010110000001 +100110001000,001001100000 +000101010010,000010110100 +111001101100,110111111000 +110110101010,111011000100 +111111011010,010101101010 +001110110110,001011110100 +111110100011,111101111010 +110100001001,001100011000 +100011000001,000001000110 +001101001000,000011010000 +100010100100,100110010000 +110001001100,001001011000 +110111000111,000101110011 +000110011011,000010001010 +000110000010,000000011000 +000100011000,000011000000 +001101011011,000100101001 +001000000100,000001000000 +010011110111,011000011101 +100110000100,000100110000 +101001000011,000011110110 +000111001111,000001001011 +110111100001,110110011110 +110101111111,101010010101 +110000010010,011011000000 +100001111111,111110000001 +101101101110,100110001010 +100110101111,101011111010 +000100100100,000100100000 +101010010100,010010110000 +001011100100,001010111000 +011111010011,001110000011 +010101010001,001010011010 +101010100001,101011010100 +010010000101,000010110100 +111111010101,011010100101 +011000111100,010110100000 +011010100100,011000110000 +000010110001,000011000100 +010110101100,010010101000 +110001111100,101100000100 +110100001111,001010010100 +111100001000,001111000000 +011100100110,011110101000 +010101100111,010110101001 +110011111010,101010010010 +000010100000,000010000000 +000011111100,000010110100 +100000101110,101110000000 +001011101110,001001111010 +110000000100,000110000000 +111001100001,111000001010 +011011110000,010001010000 +001001001111,000011100001 +100101100101,100010010110 +011000110100,010000100000 +001100011110,000101101000 +001110001100,000010101000 +011100001001,000111111000 +010000011101,001110100000 +101011011111,011010110011 +110101101010,111000011100 +010110101000,010000010000 +001100000000,000000000000 +010001110110,011010011100 +101001010111,010001100001 +111011001100,001010010100 +101100010001,010111011000 +101000010010,010110100000 +101001101011,100101011110 +000111100011,000111010101 +110101011100,010110100100 +000011000111,000000010101 +010010101101,010101001100 +001010110111,001110010010 +111000001111,001001011000 +001001010001,000100110010 +011111111100,010001011100 +011001011010,001000101100 +111011100110,111110101010 +101100100000,101100000000 +111101011111,010001101101 +000100101110,000101110000 +011110111001,010010010010 +111111000011,000010111101 +100100100101,100011101000 +001011110001,001110001110 +100111110110,110000000110 +110000011001,010011100000 +001110010101,000110010010 +010011010011,001000111001 +000101010000,000010100000 +000000010110,000000000000 +111010101100,110101001000 +100010010000,010001000000 +111001110101,101100010110 +000101101011,000100100001 +000111100100,000111111000 +011100100001,011100111000 +101111110101,111111000101 +110111111100,101011101100 +111110011100,010011011000 +110100001011,001111000100 +100011101001,101010001110 +111000001110,001000110000 +111111001110,001001110110 +001001010000,000100100000 +011010001100,000100001000 +100011101101,101110010001 +011111001101,000101010101 +001110110011,001010011010 +111100100011,111110110100 +011011001010,000111000010 +100101011011,011001010001 +000001110110,000001101100 +011100101010,011001001000 +110001001000,001100010000 +000111000011,000000010101 +111010101111,110111101010 +001101011001,000100010110 +101011101111,100001010011 +001111001000,000011110000 +110010001000,001100100000 +011001000100,000011001000 +001000000011,000000110000 +010111000111,000011010011 +100000011000,011000000000 +010110100011,010111110010 +110110010110,011111010100 +010100000101,000010011000 +111011010110,011010011010 +110100001110,001011001000 +010110110010,011101111000 +010011111010,011111100010 +001101010011,000110010001 +100000111100,111100000000 +011001011111,001011001001 +101011100100,101110111000 +000110101101,000111000010 +010000011100,001110000000 +011111110000,010111010000 +000000001010,000000000000 +001100011100,000101010000 +101000000100,000101000000 +110011000001,000001100110 +100100101110,101000001000 +001111101101,001100010101 +111001100100,111110101000 +011010111101,010100000010 +100110101010,101100000100 +010111011100,001101001100 +110110011101,010110110010 +110100100000,110100000000 +001110010001,000111011100 +100110000101,000101111100 +100111110000,110101010000 +100001000100,000100001000 +111011001001,001111011110 +110000101001,111101100000 +010100011011,001110000100 +000110100001,000110001100 +111100011000,010110100000 +110010000010,000011001000 +001001001100,000011011000 +110100100111,110011100100 +100101100001,100100101010 +010101101011,010011010001 +110000010101,011111100000 +111110011101,010010011010 +111101111110,100011011010 +101000100100,101101000000 +000101110111,000111001001 +011101011000,001010001000 +001000111011,001110110000 +101111110001,111010100001 +010110111010,011010100100 +011101101011,011000011001 +101111001000,001011110000 +101111011110,011001001110 +011111010101,001101000101 +011000111001,010101110000 +011000001111,000101101000 +110100010110,011110001000 +111111111000,100111011000 +000000000010,000000000000 +111110111001,100111010010 +010101001111,000110111001 +010100011000,001111000000 +101010001100,001111110000 +111101010101,011011011110 +001011100000,001011000000 +100011100110,100100110010 +110100000110,000100001000 +111000000011,000010101000 +010110100111,010100111010 +010000100010,010001000000 +100011010110,010111110010 +010010001000,000100100000 +001110011100,000100011000 +111110001100,001011101000 +010110111000,011011010000 +000011010110,000001110010 +000000111001,000000000000 +001010000011,000000111100 +100110110010,110110111000 +001101001100,000010000100 +001101110110,001001010010 +000000111101,000000000000 +110111101000,111001001000 +000011101100,000011100100 +110011000111,000101001101 +111010111001,100000011100 +111110111111,100001011110 +110110100100,110011110000 +111001000110,000101011100 +100111100011,100101110101 +001000110100,001101000000 +001001111011,001111011110 +101001010101,010010000110 +010001111010,011100001100 +010010111000,011111100000 +011110000000,000000000000 +110000110011,100110010000 +000001011110,000000111100 +010000010011,001001100000 +011011010101,001110110001 +100111111010,111110001010 +110010111010,101011011000 +101010010111,010000110010 +111111011000,010111101000 +000100010010,000010010000 +011001100011,011000101110 +100010100010,100001001000 +111101111100,100000110100 +001011010010,000101011100 +111011010010,011110111100 +111110101011,110101100110 +110111000110,000100011010 +101000001110,001100010000 +011000110101,010000001000 +111111100101,111010000101 +010101110110,011101100010 +000100001110,000001110000 +110110000111,000101111010 +001011111000,001101101000 +010001110011,011000110110 +000010010110,000001011000 +100010110110,110100111000 +100010111101,111110001100 +111011000011,000010100101 +110111010001,011010111110 +001011001010,000010000010 +110010110011,100100111100 +110111010111,011110101011 +000101001101,000001110110 +011011011010,001001010010 +110110000100,000110110000 +101100000010,000010110000 +100000111001,111001000000 +100011010100,010101111000 +010000110001,011000100000 +000101001011,000001000001 +010011011000,001101001000 +110001010110,011100011100 +110100110101,100110111000 +100100001011,001010111000 +011101001000,000111010000 +010000000110,000011000000 +000101011010,000011101100 +011111000011,000001011101 +000001001101,000000011010 +111000110101,101101001000 +111100111000,100101100000 +110101101110,111110110010 +101110001010,001000100100 +110011000011,000010011001 +001111000101,000001101001 +100000010101,010101000000 +101101100011,101110010001 +111000101001,110000001000 +010010100001,010010100100 +101100101101,100100100100 +011101000101,000011001110 +101011110010,111101111100 +001010010000,000101000000 +101100000101,000100000100 +101000111011,110010001000 +100110001001,001000011100 +011100101111,011010101100 +000010001011,000000101100 +111101111101,100001010001 +111111000010,000011111100 +100000011010,011010000000 +011010110100,010011110000 +110101100111,110010011001 +000111100001,000111001110 +100101000111,000111110001 +001111011011,000101010011 +100001101011,101010110110 +010111101011,010000000011 +011000011101,001010001000 +101111111110,110001101110 +010001101011,010100001110 +010100110011,011111111000 +011101000001,000000111010 +101100101111,100110001100 +111111011100,010011101100 +010111110101,011111011001 +110010110101,100011001100 +101101010101,010001101110 +101101110000,111000010000 +001001000111,000001111110 +110101010011,011001100001 +011001111001,010100111010 +011100110101,010110100100 +000100101010,000101010000 +001110010000,000111000000 +101001100011,101010001110 +101011010001,010100001110 +000111111110,000100110110 +100011001111,001111101101 +000100010001,000010001000 +110000010011,011010010000 +100011111000,111010101000 +001000010100,000101000000 +100001011011,011011110110 +110101111000,101101001000 +011010100011,011011000010 +110101001000,001101010000 +111101101111,110011001101 +110010111110,101100100100 +100001011101,011101111010 +010110000001,000000101100 +010100010110,001000001000 +111011100101,111100110001 +010011000010,000001001100 +100010011111,011110000010 +000011111101,000010110001 +010010001010,000101101000 +111000101110,110100110000 +001111111011,001001110011 +010100110100,011101100000 +010110011111,001100100110 +011010100110,011001000100 +110000011000,010010000000 +000111110110,000101111010 +001010010010,000101101000 +101101100110,101001100010 +010001100011,010000010110 +010011011010,001100100010 +000111011010,000010110010 +111000110001,101011110000 +111011000010,000011101100 +110011100010,110001101100 +010001000110,000011001100 +101101010011,010100110001 +011100110111,010111001100 +100011010011,010010011001 +111011011000,010100101000 +000101011001,000011111010 +100111010100,010111000100 +001010000110,000001111000 +011110000011,000001011010 +001100001001,000011011000 +000011000010,000000001100 +110001110011,100111110110 +010011010110,001010001010 +100100100000,100100000000 +011100110010,010100001000 +101010100101,101110101100 +010010111101,011100101100 +010111010010,001010000010 +100011001110,001110101010 +001010000111,000001100010 +110101001110,001011010010 +111100011001,010111000100 +001011101010,001000100010 +011001101011,011110111110 +001001100000,001001000000 +100010011010,011011101000 +110101000010,000011010100 +101110101111,100101110110 +011001100001,011001110010 +101010011010,011100111000 +111011001101,001011101001 +001000111000,001110000000 +000101100000,000101000000 +011000010110,001110010000 +101110110101,111110110010 +101111111111,110000001111 +001100011010,000100001000 +011111100100,011101000100 +010000010001,001000100000 +011110100000,011110000000 +001000100000,001000000000 +100000110011,110011000000 +011001101000,011111010000 +100010010010,010011001000 +111011110000,101001010000 +000000101111,000000000000 +100111010101,010110101001 +110111010010,011001000010 +010001100100,010011001000 +010010100010,010011001000 +001011010101,000100010001 +000110111010,000101000100 +001000010011,000100110000 +010111001011,000100111101 +110111101001,111000010001 +101001000001,000001010010 +100101101011,101111100001 +000100100101,000100101000 +111110110001,101111000010 +101011000011,000011111001 +101100100101,101010000100 +010100000100,000010100000 +110110011111,010100010110 +000101111010,000110011100 +100000001001,001001000000 +010100011010,001110110000 +100001100111,100110101110 +000110010010,000011011000 +001010010110,000100000100 +100001001001,001001010010 +111010110101,101110111100 +100111110011,110110101101 +100001011001,011001110010 +100101111001,111111000110 +110011110110,100001101010 +000011111001,000010101110 +010111101010,010000110010 +100000000011,000011000000 +000010100001,000010000100 +011001010011,001101101110 +010000010111,001011100000 +010001010110,001011101100 +010011111111,011100100011 +101110100110,101010110100 +000001010000,000000100000 +001110101011,001100111010 +111010010111,011011001010 +010000001000,000100000000 +111110010111,011000001110 +111111111101,100010011101 +101101010010,010101001100 +110101011110,010100001010 +011010001010,000111011000 +000010000011,000000001100 +101110100010,101101111000 +100010110111,110101111100 +011000011011,001000101000 +110010010100,011111010000 +110000011110,010110100000 +001010111111,001101110010 +001100111111,001011110100 +110011001101,001000110001 +010001001000,000100010000 +111110000001,000001111100 +101101001111,001100010101 +100000001110,001110000000 +010010010111,001010000010 +101110101001,100011000010 +111110100100,111010001000 +111011000110,000101001010 +011101100001,011101111010 +001100100100,001101100000 +010111110110,011110000110 +011100010110,001101101000 +010101010100,001001011000 +011000110111,010001101000 +101101111100,110001010100 +110110011000,010001010000 +000001100010,000001000100 +001001111000,001111110000 +111111110010,101101010010 +111010011111,010010011010 +101010001111,001101110010 +001111110010,001011100010 +101100100110,101000101000 +111000000100,000111000000 +101101110111,111110100101 +000000000001,000000000000 +101110000010,000010111000 +110101001111,001010000101 +111010000001,000001110100 +001100010100,000111100000 +111000111100,100101100000 +000001101101,000001011010 +100010011101,011100001100 +100000101100,101100000000 +010111001001,000101000001 +100000110100,110100000000 +100101110011,110101000001 +101101000001,000001011010 +010000111110,011111000000 +000001010010,000000100100 +010100100000,010100000000 +110111011111,010100100111 +010000010101,001010100000 +110100100100,110001100000 +010011101101,010100110001 +111001100111,111101010001 +000100110111,000110111000 +100001010011,010011100110 +000000100110,000000000000 +111000100101,111110001000 +011000010111,001110101000 +111000111111,100111011000 +101010100011,101000000010 +101000111110,110110010000 +011100101110,011010011000 +011011000001,000000110110 +110100001101,001001111000 +101011000000,000000000000 +101110001001,001010000010 +001110101110,001101001100 +110011010010,011010011100 +100111011000,011010101000 +010000110010,011001000000 +100101100110,100000000010 +001100101000,001111000000 +100011010001,010000010110 +010011001011,000101000101 +010000001111,000111100000 +101110100111,101011100110 +000001000110,000000001100 +101100100111,101001110100 +001010111110,001101100100 +111100101110,110011011000 +011100001110,000100011000 +101001001011,001000011110 +101101110101,111100011110 +000111111101,000100100101 +000111101100,000110110100 +011011100000,011011000000 +101111110011,111001000011 +000100001000,000001000000 +101101111000,110111001000 +011111100000,011111000000 +010001001101,000110111010 +101110101100,100111101000 +000111101000,000110001000 +011100010101,001100100100 +010010011000,001101100000 +000011111011,000010100101 +100111100100,100000000100 +001110000000,000000000000 +011000000001,000000110000 +000011000110,000000010010 +100111001110,001111000110 +110110000110,000100010100 +100000110101,110101000000 +011011000011,000001000101 +010010010001,001001100100 +101001111010,110010101100 +000110111111,000101111010 +110101101101,111100011110 +100110011000,011010010000 +000111110111,000101110011 +100011110100,110110000100 +001111000110,000001011010 +111110101101,110011101010 +101001011101,011001010110 +001110001011,000011011010 +001001000001,000000010010 +001111111001,001001001001 +011000000011,000001001000 +001100010110,000111001000 +110110101011,111010011010 +111100101100,110000110000 +100010101110,101101111000 +110010101111,111000010010 +101000001101,001110110000 +001110110100,001011001000 +000001101100,000001011000 +101000100110,101111100000 +000100011001,000011001000 +101111011010,011111001010 +101111110010,111000010010 +111011011110,010011100110 +100001110100,110101101000 +011111000110,000010111010 +000100011100,000011100000 +011001000111,000010100001 +000011111010,000010100010 +111001110010,101001110100 +011101101001,011001010110 +010000101100,010110000000 +101101100101,101011001110 +000011011000,000001001000 +101111100001,101110111110 +010110101110,010011110100 +001011001110,000011011010 +000010001110,000000111000 +010001110000,011001100000 +011100010001,001110111000 +001111010101,000110111001 +010001010111,001011011110 +000100000111,000000111000 +010001101010,010100110100 +010011101111,010101111101 +100011000110,000110010010 +110101001101,001001101110 +010100010011,001011111000 +111011110010,101010000010 +001011010100,000100000100 +011010111100,010100101000 +111111000000,000000000000 +011010110110,010010100100 +010001011010,001101110100 +011000001011,000111001000 +101000111001,110000110000 +010000000011,000001100000 +011010101001,011101001100 +000110001110,000001010100 +111011001000,001110110000 +111010010100,011001110000 +100110000110,000110100100 +111100010100,011010010000 +011100111111,010011101100 +011101001010,000110011100 +001100111010,001010001000 +000000111000,000000000000 +001100101110,001110101000 +101101001001,001010100110 +101001000010,000010100100 +110000111011,101001010000 +110110010100,011100001000 +010100001000,000101000000 +010101011111,001101000101 +001100000110,000001001000 +011010011010,001001111000 +001110110000,001010100000 +100111111111,111000000111 +001101110001,001000000110 +111110111011,100101010110 +101110111010,110110010100 +101110000101,000100010010 +110110000011,000010001010 +000011100101,000011011110 +100001111001,111000001010 +001011011001,000111011110 +011010101110,011111100100 +001000010001,000100010000 +000000111011,000000000000 +101100110110,111110101000 +000011010000,000001100000 +111001100101,111111000110 +001011011011,000111110101 +000000100100,000000000000 +000111100110,000111101010 +010011010100,001011111000 +001001100100,001000101000 +100010010111,010110111100 +110000110111,100011010000 +100100001101,001100011000 +100100010011,010000111000 +100100001100,001101100000 +010110010000,001011000000 +111001100010,111010010100 +100000110001,110001000000 +011100100000,011100000000 +101111101100,100110001100 +111001011100,010001000100 +100110010011,010001110010 +001100001111,000010110100 +001100010011,000110100100 +000101100111,000101110001 +011111010100,001101100100 +101100011011,011111010100 +111101100000,111101000000 +011101110110,010110110010 +101010111111,110101001010 +100000111010,111010000000 +111111100100,111011000100 +001110111011,001000000110 +010101100011,010100000001 +010010000011,000001101100 +101111001001,001010010001 +101110000000,000000000000 +101111010111,010010111011 +011111101110,011000001110 +110000110010,100111000000 +100010110010,110000101000 +100100011101,011110011000 +000100000100,000000100000 +110101001001,001100000110 +010001101110,010110111100 +011110111101,010000011010 +000100101100,000101100000 +000100011011,000011011000 +101011010100,010010000100 +010000101101,010110100000 +111111010010,011101100010 +100000010000,010000000000 +011100001010,000110001000 +111100000010,000011110000 +110100010000,011010000000 +011010011011,001001010010 +000010101001,000010100100 +000001100001,000001000010 +101101001000,001011010000 +100011100001,100010100110 +101101111011,110101011001 +100001100001,100000100010 +111110010000,011111000000 +011011100001,011011110110 +101010111101,110110111100 +011001001011,000111011110 +111001010001,011101001010 +000100101011,000101011000 +011101000110,000010100010 +110000000111,000101010000 +010001001001,000100110010 +100111101010,101101010010 +010001010010,001001100100 +110110101001,111001111100 +100010100110,100101011000 +001000111001,001110010000 +010101100001,010101101010 +100010101011,101000011100 +011110100010,011111111000 +011011101010,011101100010 +011010010101,001110011100 +001010100000,001010000000 +010010011100,001111110000 +111101000001,000001111010 +011010111110,010101010100 +101100110111,111111110100 +110101100011,110111000001 +100110010111,010101011010 +000001100101,000001001010 +010101110001,011111011010 +100101011101,011110101110 +010000010000,001000000000 +111010011101,010001000010 +011100110011,010100110100 +001011101100,001001010100 +110001100101,110110011010 +101000011001,011111010000 +110000111001,101011100000 +010010111011,011110111100 +000010011001,000001100100 +110010000000,000000000000 +000000110000,000000000000 +111101100101,111000011110 +110100011010,010011110000 +110110011011,010011011010 +010000111100,011110000000 +001001111110,001110100010 +011000000101,000011110000 +101011011011,011101001101 +011111000001,000000111110 +111011000000,000000000000 +101111011011,011110110011 +010000110000,011000000000 +000011110100,000010000100 +001011110101,001111010001 +100101011100,011111000100 +101010011110,011011100100 +001111000000,000000000000 +110110101100,111110101000 +011011010110,001111101010 +101111110110,111101100110 +011101101101,011010000001 +010011111110,011100000110 +101110001101,001111110010 +111110001000,001111100000 +101100001101,001111000100 +111110010100,011011001000 +010001011011,001101001110 +110111101100,111111110100 +111000110011,101001101000 +000101111100,000110100100 +000111101001,000110000001 +000101011000,000011110000 +011110010001,001111111100 +100011101100,101111100100 +111101100001,111100000110 +001001011011,000110011110 +001010101101,001000011100 +100010100101,100111010100 +111100101111,110010011100 +011110011111,001000101110 +000010111110,000011111000 +011001000110,000010011100 +001010100110,001011111000 +101010000111,000110010010 +001111100100,001110000100 +001010100100,001011010000 +001001000010,000000100100 +100110100110,100011100100 +111101110000,101100010000 +001101011100,000101100100 +101011000100,000101011000 +000110010101,000011111100 +000101010001,000010101010 +111101101010,110110111100 +100010110100,110111010000 +011110101000,011010010000 +001101100100,001100011000 +000100110100,000110100000 +000001011011,000000110110 +001001010010,000100010100 +000001010100,000000101000 +010100000110,000011110000 +001101001110,000010110010 +101111100011,101101011101 +101100011000,011100100000 +100011100101,100110111110 +000011000101,000000011110 +011101000011,000001010001 +010000100011,010001100000 +011011111000,010111101000 +011000110000,010010000000 +010111100000,010111000000 +011100010000,001110000000 +010110101010,010001000100 +001101001010,000011101100 +100100101011,101110111000 +000001101110,000001011100 +101111101000,100011001000 +100110010000,010011000000 +110010010010,011010101000 +111110100101,111011110010 +000000001000,000000000000 +000010011100,000001110000 +001101100011,001101100001 +000101010110,000010000010 +000100110011,000110011000 +101101101001,100000010110 +111001000001,000001110010 +110101111010,101110111100 +100100110010,110101010000 +100000001011,001011000000 +111010111100,100110101000 +001000000010,000000100000 +110010100000,110010000000 +100001000010,000010000100 +000001101000,000001010000 +101100101000,100000100000 +101010101010,100111011000 +101111000100,000101111000 +111100100010,111111110000 +000010011000,000001100000 +000010111100,000011110000 +001010010100,000100110000 +011001101001,011111101010 +001100001110,000010101000 +101000100010,101010100000 +100110000111,000111101010 +100011100111,100101110101 +000110011100,000010101000 +001011100001,001011010110 +011100100010,011101110000 +101001011010,011101001100 +100011000100,000100011000 +110100101001,111010011000 +001011111111,001100110011 +111010011100,010000101000 +011100001101,000101100100 +010101111100,011011100100 +010101011011,001110110001 +100100110011,110100111000 +110101111100,101000010100 +100111001101,001100111001 +100111001011,001010011101 +100001000000,000000000000 +100111100101,100001001001 +110000110000,100100000000 +011100101101,011011100100 +000110010100,000011110000 +011111101000,011011001000 +101000111111,110111001000 +001110100001,001110011100 +011101100011,011100110001 +110000010000,011000000000 +111000000111,000100011000 +110001001111,001011000001 +000011100001,000011000110 +000101010111,000010001001 +001101001001,000011000110 +110111001111,001010111011 +100111010111,010101110011 +101000100001,101001010000 +111110010110,011001001100 +001100100000,001100000000 +111100101010,110111001000 +001111110101,001010000101 +010100101110,010001001000 +100110110111,110000111010 +111111110101,101010010101 +100101101000,101100110000 +110111101010,111010110010 +101000101101,100001110000 +010100011111,001101100100 +101100011111,011001001100 +010111111101,011001010101 +111110001011,001100100110 +100111111011,111111000011 +001011000010,000000101100 +010111110010,011100100010 +011001010000,001100100000 +000001011000,000000110000 +110011010101,011110100001 +001100001000,000011000000 +111110111010,100100110100 +011011001100,000100010100 +100100010001,010011001000 +111101000111,000100100101 +010101010111,001000011001 +101001011001,011111101010 +011110111010,010011010100 +110100100011,110110000100 +111111100111,111000000111 +010010100011,010011101100 +110010100011,110001011100 +010011110001,011010110110 +001011011100,000110110100 +100111011001,011011100001 +101000010100,010011000000 +111111001111,001000110111 +110000110001,100101100000 +001111011000,000101101000 +111110001010,001101100100 +100111110001,110100111110 +101110110011,111000111010 +011110110101,010100001010 +011001100010,011000010100 +000101100101,000101100110 +000000001111,000000000000 +010001001110,000111011100 +100000011110,011110000000 +011110111111,010001101110 +011011110001,010001101110 +101111010000,010111100000 +110010101010,111100011000 +000011010111,000001110101 +110110100101,110010100010 +101110100100,101000001000 +110100100001,110101101000 +111111100010,111101000010 +001001110101,001100000110 +010001111111,011110100001 +001010011100,000110001000 +100100111001,111110101000 +001010001000,000010100000 +000000010011,000000000000 +111111110011,101100010011 +110110100110,110001010100 +011101100110,011111100010 +111111011011,010100101011 +111101100010,111110001100 +110000100100,110110000000 +111011011001,010101000001 +100100000010,000010010000 +001110011010,000101100100 +110000111000,101010000000 +100010010100,010101010000 +101001001000,001010010000 +101000010101,010010110000 +101011001100,001111100100 +111011100011,111000010101 +000110001000,000001100000 +011001001101,000100010110 +010101011001,001111000110 +110110010011,011000101010 +010110010011,001010001010 +010111011110,001100110110 +101001011110,011010100010 +001011000111,000001101101 +010010100101,010001110100 +001100000101,000001111000 +100110111010,111111000100 diff --git a/tracks/qcs/solutions/Genshin_Impact-71/predictions/mystery-D/test_outputs.csv b/tracks/qcs/solutions/Genshin_Impact-71/predictions/mystery-D/test_outputs.csv new file mode 100644 index 000000000..25bf69897 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/predictions/mystery-D/test_outputs.csv @@ -0,0 +1,625 @@ +input,output +1100101001,10110101010 +0001001110,00100000100 +1010000100,10010100000 +1001111011,01010010101 +1011111010,01000011110 +0100001001,00010010100 +0110100000,00100111100 +0111001011,00010110110 +1001000011,10001001010 +0001011110,10000100100 +1111001111,10100110001 +1010101100,10111011100 +1010001111,10111001110 +1101110100,01001111010 +1011111011,01000100011 +1100001100,10110100000 +1101001000,10111110000 +1101101100,10111111010 +0101010100,10111110000 +1000101111,10100101001 +1001010000,01001010000 +0101011000,10110110000 +0000110111,10010010001 +0010111011,10010110001 +0011000000,00001001000 +1000110011,01001001110 +1001010011,01000011010 +1110001001,10101110100 +1111001110,10100101100 +0000111101,10001000110 +0110010000,10100100000 +0000110001,10000100010 +0100010010,10101010000 +0001000001,00000010100 +1010110101,01001110110 +1010000000,10011000000 +0101100100,00101101010 +1100011001,01001110100 +1011100101,10011011001 +0011000011,00001011010 +1101010010,01010011000 +0001000000,00000010000 +0110011101,10101100010 +0000001010,00100110000 +1011100111,10011010011 +1011110110,01001111110 +1001100110,10000000110 +1011111101,01011010101 +1111011100,01001000100 +1010010110,01000011000 +0100000000,00100000000 +1011100110,10011011110 +0000011100,10001100000 +0011010110,10011100100 +0111101001,00010011001 +1011010011,01011000110 +0101000011,00100101010 +1111011000,01010111000 +0011111000,10011000110 +0001111111,10000000011 +1101001011,10111000110 +0001100101,00001011110 +0110101000,00010111100 +0101010010,10101101000 +1110000011,10001110010 +1111110000,01000011110 +0101001001,00010101100 +0001011101,10001010010 +1001010110,01011111000 +1101011101,01010001010 +0111100100,00101001110 +0100100011,00100001110 +1011100000,10010010110 +1100000011,10010010010 +1100101111,10110111001 +0010110011,10000000001 +0111000110,00101010100 +0100111010,10111101100 +1011100011,10010001101 +0100110010,10101001100 +1100101000,10110110100 +1000101000,10100100100 +0011001110,00101010100 +0010010010,10000110000 +1010110001,01011011010 +0110010111,10110110110 +0001000111,00001010110 +1011111000,01001010110 +1111001101,10100011010 +0000100110,00001001100 +0110010110,10110011000 +1110010101,01010111100 +1011000011,10010111010 +1010001001,10111010100 +1110011000,01011100000 +1010011100,01010010000 +0011000111,00000101110 +1000110100,01011100100 +0101101111,00010100011 +1111111000,01010011110 +1000111001,01010001010 +1011011111,01010110001 +1111110011,01001100011 +0110100001,00100111010 +1010111011,01001001001 +1011001101,10110001010 +1010100001,10011101010 +1001010100,01010110000 +0100100110,00101011100 +0110110101,10111001110 +0110110011,10101010001 +0001111010,10011101010 +1011001010,10110000100 +1000100110,10001101100 +0000101000,00100000100 +1000100000,10000100100 +0001100010,00000001010 +0100000101,00101001100 +1010100000,10011101100 +1100000111,10011000110 +0010111101,10000101110 +1000010001,01000100100 +0100001101,00010111100 +0110101101,00010011110 +0010111000,10011001100 +0010000001,00001000100 +1010000010,10011010000 +1110000110,10000011000 +1100011010,01000001000 +1001111100,01000101010 +0111001001,00010000010 +1011111111,01010000111 +1100101110,10110100010 +0000001111,00100001110 +1100100010,10010101100 +1111010110,01010001100 +1111001011,10100001110 +1011110010,01011001110 +1101100100,10010111010 +1011000001,10010101100 +1000000111,10001000110 +0110010101,10111011100 +1001101101,10101010001 +0010010101,10010011100 +0010100110,00000100010 +0100011101,10101000010 +1010000111,10010100110 +1011001111,10110100001 +1111001100,10100000100 +1010010101,01001011100 +1110000101,10000011100 +0100011100,10101100000 +0001101000,00100010010 +1110000001,10001100100 +1100010000,01010000000 +1011100001,10010010001 +0101001110,00010100100 +1100001001,10110010100 +1000111011,01011111110 +0111001101,00010101010 +1100000101,10011001100 +0011010111,10011011110 +0110000000,00100100000 +1100101101,10110010110 +1100101011,10110000001 +0110000101,00101101100 +0000011000,10010000000 +0001110001,10000110110 +1101011000,01000001000 +1110001111,10101101110 +0010000000,00001000000 +0001011001,10010101100 +1001001101,10101100010 +0001011010,10011101000 +0000101100,00100100100 +0111110011,10101111101 +1110110101,01010011110 +0000000000,00000000000 +1100010101,01000011100 +1110100011,10001010001 +1111000111,10001111110 +0111101011,00010100011 +1010100101,10010010110 +1100011011,01000111010 +1111011001,01010010010 +0101110111,10110111101 +1101110000,01011011010 +1011101101,10110100101 +1001001110,10101000100 +1010101011,10111010001 +0100111000,10110010100 +0001101100,00100110010 +0111000010,00100000100 +1110010111,01011110110 +0010101100,00101101100 +1010011000,01000100000 +0000111100,10001100100 +1000001111,10100001110 +1111011111,01000101001 +1100110101,01000100110 +1000001100,10100100000 +1000101011,10100011110 +1101011010,01001111000 +0101101110,00010110110 +1111111010,01011100001 +0000011011,10011011010 +0111010010,10101000100 +1010010011,01010001010 +1111101101,10100101101 +1001100101,10000000001 +1001011000,01011010000 +0111110110,10110100001 +0000000100,00001000000 +0110101010,00010010010 +0011101101,00101111001 +1000110010,01001110100 +0100000010,00100010000 +1011010110,01001010100 +0000010010,10001010000 +1000000001,10000000100 +1111101100,10100111110 +1001001100,10101110000 +1101000100,10010001000 +1110111100,01000010010 +1110011001,01011001100 +1111110001,01000111001 +1011010010,01011111000 +0001000100,00001010000 +1110100100,10000100010 +1011100100,10011010110 +0110101001,00010100110 +1101000010,10011101000 +0010011110,10001111000 +1111110110,01010110001 +0001001000,00100010000 +1111100011,10000000011 +0110101100,00010000010 +0110110010,10101100010 +1010100011,10011111110 +1000111110,01000000010 +1111110010,01001000001 +0101011010,10111011000 +0010101010,00101111100 +0110111011,10111101001 +0110011111,10100111110 +0110100100,00101111100 +0100010110,10110101000 +0111110100,10111001110 +0101100001,00100101110 +1011101100,10110110110 +1001100011,10001101001 +1001101000,10101110010 +0111100000,00100001110 +1000001101,10100111100 +0001111000,10010010010 +0111101101,00010110101 +1110001100,10101010000 +0110001011,00010011010 +1010110110,01000110010 +0111001100,00010111000 +0110110110,10110001010 +0101001010,00010011000 +1011110100,01000110110 +0100110001,10100110010 +0101001000,00010110000 +1010011001,01000001100 +0000000101,00001001100 +1100110010,01011101100 +0010110010,10000111100 +1110101011,10101101001 +1101011100,01010101000 +1100000000,10010000000 +0100100001,00100010010 +1110110001,01001100110 +1000100101,10001101010 +0100110110,10110111100 +0110101110,00010101010 +1110111110,01001111010 +1101101011,10111110101 +0000011010,10011110000 +0101100010,00100111010 +0000000111,00001000110 +0010001111,00101001110 +1100001011,10110101010 +0000000110,00001001000 +1101100111,10010111101 +1101000101,10010000010 +0101010011,10101011010 +0101001011,00010000110 +1000100001,10000100010 +0100110111,10110001001 +0101011101,10101110010 +1101111010,01001010110 +0100010111,10110010110 +0010000101,00000101100 +1001111001,01011011110 +1000101110,10100111100 +1000001010,10100110000 +1000101100,10100010100 +0011101010,00101110110 +1010110111,01000000101 +1101101001,10111000001 +0100111011,10111000001 +0101001100,00010001000 +1101101110,10111001110 +0011110100,10010100110 +0000000011,00000010010 +1011101001,10110001001 +0001100001,00000010110 +0010001001,00101010100 +1001100010,10001101010 +0001001111,00100011110 +0001110010,10001001010 +0100101001,00010001010 +1000101001,10100110010 +0111111010,10111111110 +0010110111,10011011001 +0001001001,00100001100 +1010010111,01000110110 +1111110101,01011110101 +0000101001,00100010010 +1001111101,01000001001 +0111000000,00100011000 +1001010111,01011001110 +0100010100,10111000000 +1010101111,10111100101 +1000110001,01000010010 +0111010001,10100111100 +1101011110,01011010100 +1110000010,10001110000 +0010001110,00101011000 +0011110001,10001100001 +0100101000,00010010100 +1000011110,01000111000 +1001100100,10000001010 +0011000001,00001001100 +0001100000,00000010010 +1010111100,01010111100 +1101010101,01001100010 +1100110011,01011011110 +1001001010,10101101000 +1001000010,10001001000 +1000010101,01011101100 +1010011111,01011011110 +1111000110,10001110100 +0100011111,10100011110 +1110010010,01000001000 +1000001011,10100101010 +1011000100,10011101000 +0001000101,00001011100 +1101110011,01010010101 +0100110000,10100010100 +1000000100,10001000000 +0001001011,00100111010 +0101111010,10111000110 +0011111100,10000010110 +0001101101,00100100001 +0100011010,10111110000 +0111100010,00100011110 +1001100001,10001110110 +0010000010,00001010000 +1111111110,01000101001 +1101111000,01000111010 +1111100100,10001011110 +1000010110,01010101000 +0110001100,00010010000 +1011011001,01001000010 +0100000011,00100010010 +0111011101,10101011010 +1110111101,01000100001 +1010010001,01011100100 +1000011000,01010000000 +0000101111,00100001001 +1101111011,01001101101 +0000111011,10011011110 +1011010111,01001111110 +1110101001,10101010110 +1000011100,01001100000 +0110110001,10100000110 +0000100001,00000000010 +0011011011,10010110110 +1010011110,01011111000 +1100111001,01001011010 +0100010101,10111101100 +1001011101,01000110010 +0010111111,10001010101 +1001111111,01001100011 +0010000111,00000100110 +0011001010,00101111000 +1001001011,10101111010 +0010101000,00101001100 +0101110101,10111010001 +0010010000,10001000000 +1101101111,10111010011 +0011101111,00101001011 +1100101010,10110011100 +1110010000,01001100000 +0000001110,00100011000 +1101011011,01001010110 +1000010010,01001010000 +1101111001,01000010001 +1110011101,01000010010 +1011111001,01001101001 +0000000010,00000010000 +0010101101,00101110110 +0100000110,00101001000 +0111110101,10111100101 +0011001001,00101011100 +1000100010,10000110100 +1011011011,01000001110 +1010001011,10111101010 +1100000110,10011001000 +0111001010,00010100100 +1011011100,01011011000 +1011001110,10110110100 +0001011111,10000000001 +1000100100,10001100100 +0010100100,00000101100 +1010011011,01001111010 +1101010100,01001001000 +0100101100,00010110100 +0111110111,10110011011 +1100100001,10010110010 +0011100000,00001000110 +0101010000,10100110000 +0000100011,00000010110 +1111011110,01000011100 +0010011000,10011000000 +0001101010,00100101010 +1110110111,01011010101 +0111100011,00100011101 +0110001110,00010111000 +0101000100,00101110000 +1100011110,01010111000 +0001010110,10010111000 +0111101100,00010101110 +0111101111,00010000111 +0000011111,10000011110 +0100001011,00010101010 +0110001001,00010110100 +0000101101,00100111010 +0110011010,10111001000 +1100111111,01010100101 +0110001010,00010001000 +1111010111,01010100001 +0111100101,00101000101 +0011110000,10001000110 +0101101100,00010011010 +0101000101,00101111100 +1101110101,01001001001 +0010101110,00101010010 +0100111101,10101010110 +0010001011,00101101010 +0010100011,00001011110 +0111001111,00010010001 +0011100001,00001000001 +1000010011,01001110010 +1001000111,10000110110 +1110110100,01010100010 +1100001111,10110001110 +1001110001,01001001110 +1100000010,10010010000 +0101111110,10100001110 +0101101011,00010010101 +1111010001,01000000010 +0000110010,10001010100 +0010111010,10010000010 +0001111100,10001110010 +1100100011,10010101110 +1111100101,10001010101 +1100010100,01000100000 +0011110011,10000001101 +0101011011,10111100110 +1010010100,01001100000 +1011011101,01011101010 +1001110110,01011000110 +0011010100,10010101000 +1101001001,10111101100 +0110011001,10110001100 +1000110111,01010110001 +0010000100,00000100000 +0010101001,00101011010 +1011101000,10110010110 +1101000110,10010000100 +0100100100,00101010100 +0110000111,00101100110 +1100110110,01001000010 +1101110010,01010100110 +0010100001,00001001010 +0100100010,00100001100 +0100000100,00101000000 +0001000011,00000001010 +1001001001,10101001100 +0101111011,10111110101 +1001011011,01010100110 +0010011011,10010111010 +0111010011,10101100110 +0011110110,10011101110 +0110000100,00101100000 +0101000001,00100110100 +1011001100,10110011000 +1010110010,01010000010 +0110111110,10100011010 +1101010011,01010111010 +1110001101,10101000010 +1111000101,10001110010 +1101111100,01010000110 +0110100101,00101110110 +0110100110,00101110010 +0010001000,00101000000 +1111100001,10000011001 +0011000100,00000101000 +1110101000,10101000010 +1110001000,10101100000 +1000011101,01001000010 +1000000011,10000010010 +0001110110,10010111010 +0011100101,00000101001 +1111101110,10100001001 +1010000011,10011010010 +0000011101,10001000010 +1011101110,10110000001 +1111010100,01011111000 +1111000010,10000100100 +0111111100,10101101110 +1001111010,01010111010 +1111010101,01011001010 +1111101111,10100010111 +1001100111,10000001101 +1000111010,01011001100 +1101001101,10111010010 +1011110000,01010010110 +1100100110,10011111100 +0111111101,10101001101 +1110101100,10101100010 +0001101011,00100111001 +0100001010,00010110000 +0111101010,00010111110 +1011110011,01011101101 +1011110111,01001001011 +0001010011,10001101010 +1111101001,10100000101 +0010011111,10001011110 +0011101011,00101101101 +1001111110,01001010110 +0100011110,10100111000 +1110110011,01000001001 +1110011110,01001000100 +0100011000,10110000000 +0011010011,10000000110 +1011010101,01000110010 +0101011111,10100100001 +0100100111,00101010001 +0001010000,10000010000 +0010010110,10011101000 +0010110101,10010010110 +0011000110,00000100100 +1101110110,01000001110 +0011101100,00101100110 +0101001111,00010111110 +0010000011,00001010010 +1010111010,01001100010 +0001101001,00100001110 +0100101010,00010101100 +1010011010,01001001000 +1101101000,10111011010 +0110110111,10110100101 +1011001001,10110111100 +1000111000,01010100100 +1010110000,01011101100 +1010111001,01000100110 +1100101100,10110001100 +0111100001,00100001001 +0110011110,10100000100 +1010101101,10111001110 +1101101010,10111100110 +1001000100,10000110000 +0011011010,10010000100 +0110010100,10111100000 +1010000001,10011000100 +1010001000,10111000000 +0000111000,10010000100 +0111011100,10101111000 +1000100011,10000110110 +0100111001,10110101010 +1100010001,01010100100 +0010000110,00000101000 +0100111111,10100000101 +0100111110,10100100010 +1010100100,10010011100 +0100101101,00010100110 +0111010111,10110000001 +0111000100,00101011000 +1011011110,01010001100 +0101111001,10110000001 +0100011011,10111011010 +0001111101,10001010001 +0101111111,10100110011 +0111001000,00010011000 +1000110110,01010011100 +1001101011,10101000101 +1000111111,01000111001 +1010110011,01010100001 +1001001111,10101011110 +0010111110,10001110010 +0111011111,10100001001 +0100101110,00010000010 +1000010000,01000000000 +0111011110,10100101100 +1110010110,01011011000 +0111010101,10111110010 +0101100101,00101100001 +1001010101,01010000010 +0111100111,00101001011 +1100100100,10011110100 +0101100111,00101101101 +1000000000,10000000000 +1100111110,01010010010 +1101001110,10111100100 +1110000100,10000010000 +0100010000,10100000000 +0011101110,00101011110 +1010100010,10011111100 +1000010100,01011000000 diff --git a/tracks/qcs/solutions/Genshin_Impact-71/predictions/prediction_manifest.json b/tracks/qcs/solutions/Genshin_Impact-71/predictions/prediction_manifest.json new file mode 100644 index 000000000..96096c4b9 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/predictions/prediction_manifest.json @@ -0,0 +1,42 @@ +{ + "all_official_commitments_match": true, + "challenge": "QuantumBFS/quantum.harness issue 71", + "instances": { + "A": { + "commitment_matches": true, + "gates": 37, + "netlist": "tracks/qcs/solutions/Genshin_Impact-71/frontier-347/netlists/mystery-A.txt", + "netlist_sha256": "56c95acceeedb40f54fdae2e5fc2840d1bf58e0989f46a950e6a56742fd20e24", + "official_commitment_sha256": "51e3f026def41778ecd0d7dcaee9f970b9937488e6716891932b73824c16d4c7", + "prediction": "tracks/qcs/solutions/Genshin_Impact-71/predictions/mystery-A/test_outputs.csv", + "prediction_sha256": "51e3f026def41778ecd0d7dcaee9f970b9937488e6716891932b73824c16d4c7" + }, + "B": { + "commitment_matches": true, + "gates": 49, + "netlist": "tracks/qcs/solutions/Genshin_Impact-71/frontier-347/netlists/mystery-B.txt", + "netlist_sha256": "289b60494a703cf7f74a4aef2f540d225aeff22d86c4d709aaa5b661cc69a0a7", + "official_commitment_sha256": "e2c9d0e23ee36bfc0f12d7f39fdfe2ca5a8abe8eb194fec56500733694b75c28", + "prediction": "tracks/qcs/solutions/Genshin_Impact-71/predictions/mystery-B/test_outputs.csv", + "prediction_sha256": "e2c9d0e23ee36bfc0f12d7f39fdfe2ca5a8abe8eb194fec56500733694b75c28" + }, + "C": { + "commitment_matches": true, + "gates": 152, + "netlist": "tracks/qcs/solutions/Genshin_Impact-71/frontier-347/netlists/mystery-C.txt", + "netlist_sha256": "67540307369fedfffdb2b1a6473eff5e0bbfeb0e4873d03fddbeceb653cd071c", + "official_commitment_sha256": "c7b37413844bf0b10ebad0010046469f500354a22cc2ba95cbe42709f8e8337d", + "prediction": "tracks/qcs/solutions/Genshin_Impact-71/predictions/mystery-C/test_outputs.csv", + "prediction_sha256": "c7b37413844bf0b10ebad0010046469f500354a22cc2ba95cbe42709f8e8337d" + }, + "D": { + "commitment_matches": true, + "gates": 109, + "netlist": "tracks/qcs/solutions/Genshin_Impact-71/frontier-347/netlists/mystery-D.txt", + "netlist_sha256": "cd3f317f4a0b88818e54869e40b4550fd67549f8eb4a5eb02c74db4ec6864dbd", + "official_commitment_sha256": "b445a717483303fa3c5d8a1f7abe81888b267b7c472121c9c464fa9766808580", + "prediction": "tracks/qcs/solutions/Genshin_Impact-71/predictions/mystery-D/test_outputs.csv", + "prediction_sha256": "b445a717483303fa3c5d8a1f7abe81888b267b7c472121c9c464fa9766808580" + } + } +} diff --git a/tracks/qcs/solutions/Genshin_Impact-71/routes/exact_tn/README.md b/tracks/qcs/solutions/Genshin_Impact-71/routes/exact_tn/README.md new file mode 100644 index 000000000..d3e08c059 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/routes/exact_tn/README.md @@ -0,0 +1,10 @@ +# Exact GF(2) tensor-network route + +This is a train-only, exact finite-field tensor-train route for OCCAM issue 71. +It consumes the frozen discovery manifests produced by the sibling +`symbolic_hybrid` route, computes exact GF(2) cut ranks, serializes explicit TT +cores, compiles them to challenge gates, and performs exhaustive full-domain +audits. + +The Slurm entry point is `slurm_exact_tn.sh`. It uses canonical seed 42 and +writes durable results under `results/occam71/routes/exact-tn-seed42`. diff --git a/tracks/qcs/solutions/Genshin_Impact-71/routes/exact_tn/exact_tn.py b/tracks/qcs/solutions/Genshin_Impact-71/routes/exact_tn/exact_tn.py new file mode 100644 index 000000000..25cdad97e --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/routes/exact_tn/exact_tn.py @@ -0,0 +1,910 @@ +#!/usr/bin/env python3 +"""Exact finite-field tensor-network route for OCCAM issue 71. + +The discovery boundary is deliberately narrow: this program consumes the +train-only ``discovery.json`` manifests produced by the symbolic route and the +``train.csv`` file named in each manifest. It never reads a generator, public +test input, commitment, hidden output, or competitor contribution. + +For every frozen Boolean function it: + +* computes minimal exact GF(2) tensor-train ranks for every output bit under + several input-variable orders; +* computes exact GF(2) ranks of the sparse graph tensor + T(input, output) = [output == f(input)] under arithmetic and grouped orders; +* writes an explicit exact TT decomposition for the best fixed input order; +* compiles that TT contraction to the challenge AND/XOR gate language; and +* exhaustively audits the compiled circuit on both training rows and the full + frozen semantic domain. + +All rank factorizations use bit-exact Gaussian elimination over GF(2). +""" + +from __future__ import annotations + +import argparse +import gzip +import hashlib +import json +import os +import platform +import sys +import time +from dataclasses import dataclass +from pathlib import Path +from typing import Iterable, Sequence + + +ROOT_SEED = 42 +SCHEMA = "occam71-exact-gf2-tensor-train-v1" +INSTANCES = ("mystery-A", "mystery-B", "mystery-C", "mystery-D") + + +def sha256_file(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as handle: + for block in iter(lambda: handle.read(1 << 20), b""): + digest.update(block) + return digest.hexdigest() + + +def atomic_json(path: Path, value: object) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + temporary = path.with_name(path.name + ".tmp") + temporary.write_text( + json.dumps(value, indent=2, sort_keys=True) + "\n", + encoding="utf-8", + newline="\n", + ) + os.replace(temporary, path) + + +def import_symbolic_routes(route_dir: Path): + route_dir = route_dir.resolve() + sys.path.insert(0, str(route_dir)) + try: + import routes # type: ignore + finally: + sys.path.pop(0) + return routes + + +def validate_discovery(discovery: dict[str, object], instance: str) -> None: + if int(discovery.get("root_seed", -1)) != ROOT_SEED: + raise ValueError(f"{instance}: discovery root seed is not {ROOT_SEED}") + if "train-only" not in str(discovery.get("schema", "")): + raise ValueError(f"{instance}: expected a train-only discovery manifest") + boundary = discovery.get("read_boundary") + if not isinstance(boundary, dict): + raise ValueError(f"{instance}: missing discovery read boundary") + allowed = boundary.get("allowed") + if not isinstance(allowed, list) or len(allowed) != 1: + raise ValueError(f"{instance}: discovery must name exactly one allowed input") + train_path = str(discovery.get("train_path", "")) + if str(allowed[0]) != train_path: + raise ValueError(f"{instance}: manifest train path/read boundary disagree") + selection = discovery.get("selected") + if not isinstance(selection, dict) or "expr_id" not in selection: + raise ValueError(f"{instance}: no frozen selected expression") + + +def validate_train_rows( + rows: Sequence[tuple[str, str]], input_width: int, output_width: int +) -> None: + if not rows: + raise ValueError("training set is empty") + for input_bits, output_bits in rows: + if ( + len(input_bits) != input_width + or len(output_bits) != output_width + or set(input_bits) - {"0", "1"} + or set(output_bits) - {"0", "1"} + ): + raise ValueError("training CSV must contain fixed-width binary strings only") + + +def gf2_rank_factor(rows: Sequence[int]) -> tuple[list[int], list[int]]: + """Return A rows and B rows such that M = A B over GF(2). + + ``rows`` are bit-packed rows of M. Each returned A row is a bit mask of + coefficients in the echelon-row basis B. B contains independent + bit-packed rows. The construction is exact and deterministic. + """ + + pivot_basis: dict[int, tuple[int, int]] = {} + basis_rows: list[int] = [] + for original in rows: + value = original + while value: + pivot = value.bit_length() - 1 + existing = pivot_basis.get(pivot) + if existing is None: + basis_index = len(basis_rows) + pivot_basis[pivot] = (value, basis_index) + basis_rows.append(value) + break + value ^= existing[0] + + coefficients: list[int] = [] + for original in rows: + value = original + coefficient = 0 + while value: + pivot = value.bit_length() - 1 + basis_row, basis_index = pivot_basis[pivot] + value ^= basis_row + coefficient ^= 1 << basis_index + coefficients.append(coefficient) + return coefficients, basis_rows + + +@dataclass(frozen=True) +class TTCore: + left_rank: int + right_rank: int + # rows[2*a + physical_bit] is a bit mask over the right bond. + rows: tuple[int, ...] + + @property + def ones(self) -> int: + return sum(row.bit_count() for row in self.rows) + + +@dataclass(frozen=True) +class TTDecomposition: + variable_order: tuple[int, ...] + ranks: tuple[int, ...] + cores: tuple[TTCore, ...] + + @property + def dense_entries(self) -> int: + return sum( + 2 * self.ranks[index] * self.ranks[index + 1] + for index in range(len(self.variable_order)) + ) + + @property + def nonzero_entries(self) -> int: + return sum(core.ones for core in self.cores) + + @property + def peak_rank(self) -> int: + return max(self.ranks) + + +def tt_decompose(truth: int, variable_order: Sequence[int]) -> TTDecomposition: + """Exact tensor-train factorization of a binary truth tensor over GF(2).""" + + nvariables = len(variable_order) + if nvariables == 0: + raise ValueError("at least one variable is required") + if truth == 0: + raise ValueError("the all-zero tensor is not used by this benchmark") + if truth.bit_length() > (1 << nvariables): + raise ValueError("truth tensor exceeds declared variable count") + + bond_rows = [truth] + left_rank = 1 + ranks = [1] + cores: list[TTCore] = [] + for position in range(nvariables): + tail_width = 1 << (nvariables - position - 1) + mask = (1 << tail_width) - 1 + unfolded_rows: list[int] = [] + for row in bond_rows: + unfolded_rows.append(row & mask) + unfolded_rows.append((row >> tail_width) & mask) + coefficients, basis_rows = gf2_rank_factor(unfolded_rows) + right_rank = len(basis_rows) + if right_rank == 0: + raise AssertionError("nonzero TT unexpectedly acquired zero bond rank") + cores.append( + TTCore( + left_rank=left_rank, + right_rank=right_rank, + rows=tuple(coefficients), + ) + ) + ranks.append(right_rank) + bond_rows = basis_rows + left_rank = right_rank + + if ranks[-1] != 1 or bond_rows != [1]: + raise AssertionError( + f"invalid terminal factor: rank={ranks[-1]}, rows={bond_rows[:4]}" + ) + return TTDecomposition( + variable_order=tuple(variable_order), + ranks=tuple(ranks), + cores=tuple(cores), + ) + + +def contract_tt(decomposition: TTDecomposition, ordered_bits: Sequence[int]) -> int: + if len(ordered_bits) != len(decomposition.cores): + raise ValueError("assignment width mismatch") + state = 1 + for bit, core in zip(ordered_bits, decomposition.cores): + next_state = 0 + active = state + while active: + least = active & -active + left_index = least.bit_length() - 1 + next_state ^= core.rows[2 * left_index + int(bit)] + active ^= least + state = next_state + if state not in (0, 1): + raise AssertionError(f"invalid scalar TT contraction {state}") + return state + + +def permute_truth( + canonical_truth: int, input_width: int, variable_order: Sequence[int] +) -> int: + """Permute a challenge truth table into lexicographic TT physical order.""" + + if sorted(variable_order) != list(range(input_width)): + raise ValueError("input variable order is not a permutation") + packed = bytearray((1 << input_width) // 8) + for canonical_assignment in range(1 << input_width): + if not ((canonical_truth >> canonical_assignment) & 1): + continue + ordered_assignment = 0 + for original_index in variable_order: + bit = (canonical_assignment >> (input_width - 1 - original_index)) & 1 + ordered_assignment = (ordered_assignment << 1) | bit + packed[ordered_assignment >> 3] |= 1 << (ordered_assignment & 7) + return int.from_bytes(packed, "little") + + +def input_orders(operand_width: int) -> dict[str, tuple[int, ...]]: + n = operand_width + return { + "grouped_lsb": tuple(range(2 * n)), + "grouped_msb": tuple(range(n - 1, -1, -1)) + + tuple(range(2 * n - 1, n - 1, -1)), + "interleaved_lsb": tuple( + index for bit in range(n) for index in (bit, n + bit) + ), + "interleaved_msb": tuple( + index for bit in range(n - 1, -1, -1) for index in (bit, n + bit) + ), + } + + +def relation_orders( + operand_width: int, output_width: int +) -> dict[str, tuple[tuple[str, int], ...]]: + n = operand_width + grouped_inputs = tuple(("i", index) for index in range(2 * n)) + grouped_outputs = tuple(("o", index) for index in range(output_width)) + interleaved_inputs = tuple( + ("i", index) for bit in range(n) for index in (bit, n + bit) + ) + digit_lsb: list[tuple[str, int]] = [] + for bit in range(max(n, output_width)): + if bit < n: + digit_lsb.extend((("i", bit), ("i", n + bit))) + if bit < output_width: + digit_lsb.append(("o", bit)) + digit_msb: list[tuple[str, int]] = [] + for bit in range(max(n, output_width) - 1, -1, -1): + if bit < n: + digit_msb.extend((("i", bit), ("i", n + bit))) + if bit < output_width: + digit_msb.append(("o", bit)) + return { + "inputs_then_outputs_lsb": grouped_inputs + grouped_outputs, + "interleaved_inputs_then_outputs_lsb": interleaved_inputs + grouped_outputs, + "digit_lsb": tuple(digit_lsb), + "digit_msb": tuple(digit_msb), + } + + +def graph_truth( + routes, + selection: dict[str, object], + input_width: int, + output_width: int, + order: Sequence[tuple[str, int]], +) -> int: + total_variables = input_width + output_width + if len(order) != total_variables or len(set(order)) != total_variables: + raise ValueError("relation order does not cover every graph variable") + packed = bytearray((1 << total_variables) // 8) + for canonical_assignment in range(1 << input_width): + input_bits = format(canonical_assignment, f"0{input_width}b") + output_bits = routes.encode_hypothesis( + selection, input_bits, output_width + ) + ordered_assignment = 0 + for kind, index in order: + bit = input_bits[index] if kind == "i" else output_bits[index] + ordered_assignment = (ordered_assignment << 1) | int(bit) + packed[ordered_assignment >> 3] |= 1 << (ordered_assignment & 7) + return int.from_bytes(packed, "little") + + +def stats(decomposition: TTDecomposition) -> dict[str, object]: + return { + "ranks": list(decomposition.ranks), + "peak_rank": decomposition.peak_rank, + "dense_core_entries": decomposition.dense_entries, + "nonzero_core_entries": decomposition.nonzero_entries, + } + + +Signal = int | str + + +class ChallengeCompiler: + def __init__(self, input_width: int): + self.input_width = input_width + self.gates: list[tuple[str, str, str, str]] = [] + self.cache: dict[tuple[str, str, str], str] = {} + + @staticmethod + def toggle(signal: str) -> str: + return signal[1:] if signal.startswith("~") else "~" + signal + + @staticmethod + def complements(left: str, right: str) -> bool: + return ChallengeCompiler.toggle(left) == right + + def emit(self, operation: str, left: str, right: str) -> str: + if operation in {"AND", "XOR"} and right < left: + left, right = right, left + key = (operation, left, right) + cached = self.cache.get(key) + if cached is not None: + return cached + output = f"w{len(self.gates) + 1}" + self.gates.append((output, operation, left, right)) + self.cache[key] = output + return output + + def conjunction(self, left: Signal, right: Signal) -> Signal: + if left == 0 or right == 0: + return 0 + if left == 1: + return right + if right == 1: + return left + assert isinstance(left, str) and isinstance(right, str) + if left == right: + return left + if self.complements(left, right): + return 0 + return self.emit("AND", left, right) + + def xor_many(self, terms: Iterable[Signal]) -> Signal: + parity = 0 + bases: dict[str, int] = {} + for term in terms: + if term == 0: + continue + if term == 1: + parity ^= 1 + continue + assert isinstance(term, str) + if term.startswith("~"): + parity ^= 1 + term = term[1:] + bases[term] = bases.get(term, 0) ^ 1 + active = sorted(base for base, coefficient in bases.items() if coefficient) + if not active: + return parity + result = active[0] + for term in active[1:]: + result = self.emit("XOR", result, term) + return self.toggle(result) if parity else result + + @staticmethod + def selector(input_token: str, at_zero: int, at_one: int) -> Signal: + pair = (at_zero, at_one) + if pair == (0, 0): + return 0 + if pair == (1, 1): + return 1 + if pair == (0, 1): + return input_token + if pair == (1, 0): + return ChallengeCompiler.toggle(input_token) + raise AssertionError(pair) + + def compile_tt(self, decomposition: TTDecomposition) -> Signal: + state: list[Signal] = [1] + for original_input, core in zip( + decomposition.variable_order, decomposition.cores + ): + if len(state) != core.left_rank: + raise AssertionError("TT/compiler bond-rank disagreement") + input_token = f"x{original_input + 1}" + next_state: list[Signal] = [] + for right_index in range(core.right_rank): + terms: list[Signal] = [] + for left_index, left_signal in enumerate(state): + at_zero = ( + core.rows[2 * left_index] >> right_index + ) & 1 + at_one = ( + core.rows[2 * left_index + 1] >> right_index + ) & 1 + selector = self.selector(input_token, at_zero, at_one) + terms.append(self.conjunction(left_signal, selector)) + next_state.append(self.xor_many(terms)) + state = next_state + if len(state) != 1: + raise AssertionError("TT did not contract to a scalar") + return state[0] + + def materialize_constant(self, signal: Signal) -> str: + if isinstance(signal, str): + return signal + zero = self.emit("XOR", "x1", "x1") + return zero if signal == 0 else self.toggle(zero) + + def write(self, path: Path, outputs: Sequence[Signal]) -> None: + output_tokens = [self.materialize_constant(signal) for signal in outputs] + path.parent.mkdir(parents=True, exist_ok=True) + with path.open("w", encoding="utf-8", newline="\n") as handle: + handle.write(f"INPUTS {self.input_width}\n") + for output, operation, left, right in self.gates: + handle.write(f"{output} = {operation} {left} {right}\n") + handle.write("OUTPUTS " + " ".join(output_tokens) + "\n") + + +def serialize_tt( + path: Path, + instance: str, + output_index: int, + decomposition: TTDecomposition, +) -> None: + payload = { + "schema": SCHEMA, + "field": "GF(2)", + "instance": instance, + "output_index": output_index, + "variable_order": list(decomposition.variable_order), + "ranks": list(decomposition.ranks), + "dense_core_entries": decomposition.dense_entries, + "nonzero_core_entries": decomposition.nonzero_entries, + "cores": [ + { + "shape": [core.left_rank, 2, core.right_rank], + "rows_hex": [hex(row) for row in core.rows], + } + for core in decomposition.cores + ], + } + path.parent.mkdir(parents=True, exist_ok=True) + encoded = (json.dumps(payload, sort_keys=True) + "\n").encode("utf-8") + temporary = path.with_name(path.name + ".tmp") + with temporary.open("wb") as raw: + with gzip.GzipFile(filename="", mode="wb", fileobj=raw, mtime=0) as handle: + handle.write(encoded) + os.replace(temporary, path) + + +def audit_circuit( + routes, + discovery: dict[str, object], + circuit_path: Path, + train_rows: Sequence[tuple[str, str]], +) -> dict[str, object]: + actual, universe, gate_count = routes.simulate_challenge_bitparallel(circuit_path) + expected = routes.expected_tables( + dict(discovery["selected"]), + int(discovery["input_width"]), + int(discovery["output_width"]), + ) + if len(actual) != len(expected): + raise AssertionError("compiled circuit output width mismatch") + bit_mismatches = [(a ^ b).bit_count() for a, b in zip(actual, expected)] + mismatch_union = 0 + for actual_bit, expected_bit in zip(actual, expected): + mismatch_union |= actual_bit ^ expected_bit + train_mismatches = 0 + for input_bits, output_bits in train_rows: + assignment = int(input_bits, 2) + predicted = "".join( + "1" if ((table >> assignment) & 1) else "0" for table in actual + ) + train_mismatches += predicted != output_bits + return { + "schema": "occam71-exact-tn-challenge-audit-v1", + "circuit": str(circuit_path), + "circuit_sha256": sha256_file(circuit_path), + "gates": gate_count, + "domain": universe.bit_count(), + "train_rows": len(train_rows), + "train_vector_mismatches": train_mismatches, + "train_accuracy": 1 - train_mismatches / len(train_rows), + "full_domain_vector_mismatches": mismatch_union.bit_count(), + "full_domain_accuracy": 1 - mismatch_union.bit_count() / universe.bit_count(), + "output_bit_mismatches": bit_mismatches, + "exact_full_domain": mismatch_union == 0, + } + + +def run_instance( + instance: str, + symbolic_root: Path, + route_dir: Path, + output_root: Path, +) -> dict[str, object]: + started = time.monotonic() + routes = import_symbolic_routes(route_dir) + symbolic_instance = symbolic_root / instance + discovery_path = symbolic_instance / "discovery.json" + discovery = json.loads(discovery_path.read_text(encoding="utf-8")) + validate_discovery(discovery, instance) + input_width = int(discovery["input_width"]) + operand_width = int(discovery["operand_width"]) + output_width = int(discovery["output_width"]) + selection = dict(discovery["selected"]) + train_path = Path(str(discovery["train_path"])).resolve() + train_rows = routes.read_csv_exact(train_path, ("input", "output")) + validate_train_rows(train_rows, input_width, output_width) + expected_tables = routes.expected_tables(selection, input_width, output_width) + + instance_dir = output_root / instance + instance_dir.mkdir(parents=True, exist_ok=True) + print( + f"[{instance}] expr={selection['expr_id']} input={input_width} " + f"output={output_width} train={len(train_rows)}", + flush=True, + ) + + order_map = input_orders(operand_width) + output_profiles: dict[str, object] = {} + aggregate_scores: dict[str, dict[str, int]] = { + name: {"dense_core_entries": 0, "peak_rank": 0, "nonzero_core_entries": 0} + for name in order_map + } + for output_index, canonical_truth in enumerate(expected_tables): + per_order: dict[str, object] = {} + for order_name, order in order_map.items(): + ordered_truth = permute_truth(canonical_truth, input_width, order) + decomposition = tt_decompose(ordered_truth, order) + profile = stats(decomposition) + per_order[order_name] = profile + aggregate_scores[order_name]["dense_core_entries"] += int( + profile["dense_core_entries"] + ) + aggregate_scores[order_name]["nonzero_core_entries"] += int( + profile["nonzero_core_entries"] + ) + aggregate_scores[order_name]["peak_rank"] = max( + aggregate_scores[order_name]["peak_rank"], + int(profile["peak_rank"]), + ) + output_profiles[str(output_index)] = per_order + print(f"[{instance}] output bit {output_index + 1}/{output_width}", flush=True) + + best_order_name = min( + order_map, + key=lambda name: ( + aggregate_scores[name]["dense_core_entries"], + aggregate_scores[name]["nonzero_core_entries"], + aggregate_scores[name]["peak_rank"], + name, + ), + ) + best_order = order_map[best_order_name] + print( + f"[{instance}] best output-TT order={best_order_name} " + f"dense={aggregate_scores[best_order_name]['dense_core_entries']}", + flush=True, + ) + + compiler = ChallengeCompiler(input_width) + compiled_outputs: list[Signal] = [] + decompositions: list[TTDecomposition] = [] + for output_index, canonical_truth in enumerate(expected_tables): + ordered_truth = permute_truth(canonical_truth, input_width, best_order) + decomposition = tt_decompose(ordered_truth, best_order) + # Deterministic exact contraction spot/full check. Full contraction is + # cheap for these domains and independent of the later gate simulator. + for canonical_assignment in range(1 << input_width): + ordered_bits = [ + (canonical_assignment >> (input_width - 1 - original)) & 1 + for original in best_order + ] + actual = contract_tt(decomposition, ordered_bits) + expected = (canonical_truth >> canonical_assignment) & 1 + if actual != expected: + raise AssertionError( + f"{instance} bit {output_index}: TT contraction mismatch" + ) + serialize_tt( + instance_dir / f"output-{output_index:02d}-best-tt.json.gz", + instance, + output_index, + decomposition, + ) + compiled_outputs.append(compiler.compile_tt(decomposition)) + decompositions.append(decomposition) + + circuit_path = instance_dir / "exact-gf2-tt.txt" + compiler.write(circuit_path, compiled_outputs) + audit = audit_circuit(routes, discovery, circuit_path, train_rows) + if not audit["exact_full_domain"] or audit["train_vector_mismatches"]: + raise RuntimeError(f"{instance}: exact TT circuit audit failed") + atomic_json(instance_dir / "circuit-audit.json", audit) + + relation_profiles: dict[str, object] = {} + for relation_name, relation_order in relation_orders( + operand_width, output_width + ).items(): + relation = graph_truth( + routes, selection, input_width, output_width, relation_order + ) + # TT variable identifiers are positional here; the human-readable + # relation_order beside the profile carries the semantic labels. + decomposition = tt_decompose(relation, range(len(relation_order))) + relation_profiles[relation_name] = { + "variable_order": [ + f"{kind}{index}" for kind, index in relation_order + ], + **stats(decomposition), + } + print( + f"[{instance}] graph order={relation_name} " + f"peak={decomposition.peak_rank}", + flush=True, + ) + + manifest = { + "schema": SCHEMA, + "root_seed": ROOT_SEED, + "instance": instance, + "frozen_expression": selection, + "input_width": input_width, + "operand_width": operand_width, + "output_width": output_width, + "train_rows": len(train_rows), + "discovery_manifest": str(discovery_path.resolve()), + "discovery_sha256": sha256_file(discovery_path), + "train_path": str(train_path), + "train_sha256": sha256_file(train_path), + "read_boundary": { + "recognition_inputs": [str(discovery_path.resolve()), str(train_path)], + "post_discovery_semantic_audit": "frozen selected expression only", + "never_read": [ + "generator source", + "test_inputs.csv", + "commitment.sha256", + "test_outputs.csv", + "competitor PR content", + ], + }, + "field": "GF(2)", + "output_bit_tt": { + "orders": {name: list(order) for name, order in order_map.items()}, + "profiles": output_profiles, + "aggregate": aggregate_scores, + "selected_order": best_order_name, + "selected_order_indices": list(best_order), + }, + "graph_relation_tt": relation_profiles, + "explicit_decompositions": [ + { + "output_index": index, + "path": str( + (instance_dir / f"output-{index:02d}-best-tt.json.gz").resolve() + ), + "sha256": sha256_file( + instance_dir / f"output-{index:02d}-best-tt.json.gz" + ), + **stats(decomposition), + } + for index, decomposition in enumerate(decompositions) + ], + "compiled_challenge": audit, + "runtime_seconds": time.monotonic() - started, + } + atomic_json(instance_dir / "tn-summary.json", manifest) + print( + f"[{instance}] exact gates={audit['gates']} " + f"runtime={manifest['runtime_seconds']:.1f}s", + flush=True, + ) + return manifest + + +def write_report( + output_root: Path, + results: Sequence[dict[str, object]], + baseline_root: Path | None, +) -> None: + rows: list[dict[str, object]] = [] + for result in results: + instance = str(result["instance"]) + output_tt = dict(result["output_bit_tt"]) + selected = str(output_tt["selected_order"]) + aggregate = dict(dict(output_tt["aggregate"])[selected]) + relation = dict(result["graph_relation_tt"]) + best_relation_name = min( + relation, + key=lambda name: ( + int(dict(relation[name])["peak_rank"]), + int(dict(relation[name])["dense_core_entries"]), + name, + ), + ) + baseline_gates: int | None = None + if baseline_root is not None: + baseline_path = baseline_root / instance / "abc-flow-summary.json" + if baseline_path.is_file(): + baseline = json.loads(baseline_path.read_text(encoding="utf-8")) + baseline_gates = int(dict(baseline["best"])["gates"]) + tn_gates = int(dict(result["compiled_challenge"])["gates"]) + rows.append( + { + "instance": instance, + "expression": dict(result["frozen_expression"])["expression"], + "selected_output_order": selected, + "output_peak_rank": int(aggregate["peak_rank"]), + "output_dense_entries": int(aggregate["dense_core_entries"]), + "output_nonzero_entries": int(aggregate["nonzero_core_entries"]), + "best_graph_order": best_relation_name, + "best_graph_peak_rank": int( + dict(relation[best_relation_name])["peak_rank"] + ), + "tn_challenge_gates": tn_gates, + "symbolic_bdd_gates": baseline_gates, + "tn_to_bdd_gate_ratio": ( + tn_gates / baseline_gates if baseline_gates else None + ), + "exact_full_domain": bool( + dict(result["compiled_challenge"])["exact_full_domain"] + ), + } + ) + + summary = { + "schema": "occam71-exact-tn-summary-v1", + "root_seed": ROOT_SEED, + "instances": rows, + "environment": { + "python": sys.version, + "platform": platform.platform(), + }, + } + atomic_json(output_root / "summary.json", summary) + + lines = [ + "# Issue 71 exact finite-field tensor-network route", + "", + "This route uses only each train-only discovery manifest and its `train.csv` " + "during recognition. Every expression is frozen before full-domain " + "enumeration. No generator, test input, commitment, hidden output, or " + "competitor contribution is read.", + "", + "All decompositions and ranks are exact over GF(2). The per-output TT " + "cores are serialized, independently contracted over the full input " + "domain, compiled to the challenge AND/XOR language, and exhaustively " + "audited again with a bit-parallel gate simulator.", + "", + "| Instance | Frozen function | Best input order | Peak output rank | " + "Dense TT entries | Exact TN gates | Exact BDD gates | Ratio | " + "Best graph order / peak |", + "|---|---|---:|---:|---:|---:|---:|---:|---:|", + ] + for row in rows: + baseline = ( + str(row["symbolic_bdd_gates"]) + if row["symbolic_bdd_gates"] is not None + else "n/a" + ) + ratio = ( + f"{float(row['tn_to_bdd_gate_ratio']):.2f}×" + if row["tn_to_bdd_gate_ratio"] is not None + else "n/a" + ) + lines.append( + f"| {row['instance']} | `{row['expression']}` | " + f"{row['selected_output_order']} | {row['output_peak_rank']} | " + f"{row['output_dense_entries']} | {row['tn_challenge_gates']} | " + f"{baseline} | {ratio} | {row['best_graph_order']} / " + f"{row['best_graph_peak_rank']} |" + ) + lines.extend( + [ + "", + "## Interpretation", + "", + "For a fixed physical-variable order, every listed cut rank is the " + "minimal possible TT bond dimension over GF(2). Thus the rank " + "profiles quantify both the useful arithmetic locality and the " + "obstruction: where multiplication or sum-of-squares produces a " + "large middle bond, no exact GF(2) TT with that order can use a " + "smaller bond.", + "", + "The explicit TT-to-gate compiler is a direct contraction compiler, " + "not a logic optimizer. A gate count above the exact BDD baseline " + "therefore means the TN remains a valuable structural diagnostic but " + "does not itself beat the current circuit route. A smaller count is " + "an immediately usable exact challenge circuit.", + "", + "See each `tn-summary.json` for every cut rank, every variable order, " + "the exact circuit audit, discovery/train hashes, and serialized-core " + "hashes.", + ] + ) + (output_root / "REPORT.md").write_text( + "\n".join(lines) + "\n", encoding="utf-8", newline="\n" + ) + + +def write_hash_manifest(output_root: Path, code_paths: Sequence[Path]) -> None: + excluded = {"SHA256SUMS", "RUN_COMPLETE"} + paths = [ + path + for path in output_root.rglob("*") + if ( + path.is_file() + and path.name not in excluded + and "logs" not in path.relative_to(output_root).parts + and not path.name.startswith("run-metadata-") + ) + ] + paths.extend(path.resolve() for path in code_paths) + unique = sorted(set(paths), key=lambda path: str(path)) + lines = [f"{sha256_file(path)} {path}" for path in unique] + (output_root / "SHA256SUMS").write_text( + "\n".join(lines) + "\n", encoding="utf-8", newline="\n" + ) + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser() + parser.add_argument("--symbolic-root", required=True, type=Path) + parser.add_argument("--route-dir", required=True, type=Path) + parser.add_argument("--output", required=True, type=Path) + parser.add_argument("--baseline-root", type=Path) + parser.add_argument( + "--instances", + nargs="+", + default=list(INSTANCES), + choices=list(INSTANCES), + ) + return parser + + +def main() -> None: + args = build_parser().parse_args() + if ROOT_SEED != 42: + raise AssertionError("canonical root seed changed") + output_root = args.output.resolve() + output_root.mkdir(parents=True, exist_ok=True) + results = [ + run_instance( + instance, + args.symbolic_root.resolve(), + args.route_dir.resolve(), + output_root, + ) + for instance in args.instances + ] + write_report( + output_root, + results, + args.baseline_root.resolve() if args.baseline_root else None, + ) + code_paths = [Path(__file__).resolve()] + slurm_path = Path(__file__).with_name("slurm_exact_tn.sh").resolve() + if slurm_path.is_file(): + code_paths.append(slurm_path) + write_hash_manifest(output_root, code_paths) + (output_root / "RUN_COMPLETE").write_text( + "exact GF(2) tensor-network route complete\n", + encoding="utf-8", + newline="\n", + ) + print(f"RUN_COMPLETE {output_root}", flush=True) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/routes/exact_tn/finalize_hashes.py b/tracks/qcs/solutions/Genshin_Impact-71/routes/exact_tn/finalize_hashes.py new file mode 100644 index 000000000..05cd2356b --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/routes/exact_tn/finalize_hashes.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +from pathlib import Path + +import exact_tn + + +REPO = Path( + "/home/user_milksang/private/homefile/quantum_harness/issue71_occam" +) +OUTPUT = REPO / "results/occam71/routes/exact-tn-seed42" +CODE = ( + REPO + / "tracks/qcs/solutions/Genshin_Impact-71/routes/exact_tn" +) + +exact_tn.write_hash_manifest( + OUTPUT, + [CODE / "exact_tn.py", CODE / "slurm_exact_tn.sh"], +) diff --git a/tracks/qcs/solutions/Genshin_Impact-71/routes/exact_tn/slurm_exact_tn.sh b/tracks/qcs/solutions/Genshin_Impact-71/routes/exact_tn/slurm_exact_tn.sh new file mode 100644 index 000000000..865d413af --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/routes/exact_tn/slurm_exact_tn.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +#SBATCH --job-name=occam71-exact-tn +#SBATCH --nodes=1 +#SBATCH --ntasks=1 +#SBATCH --cpus-per-task=8 +#SBATCH --mem=96G +#SBATCH --time=04:00:00 +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/routes/exact-tn-seed42/logs/exact-tn-%j.out +#SBATCH --error=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/routes/exact-tn-seed42/logs/exact-tn-%j.err + +set -euo pipefail + +repo=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +code="$repo/tracks/qcs/solutions/Genshin_Impact-71/routes/exact_tn" +symbolic="$repo/results/occam71/routes/symbolic-hybrid-seed42" +output="$repo/results/occam71/routes/exact-tn-seed42" + +mkdir -p "$output/logs" +{ + echo "job_id=${SLURM_JOB_ID}" + echo "host=$(hostname)" + echo "start=$(date --iso-8601=seconds)" + echo "root_seed=42" + echo "python=$(python3 --version 2>&1)" + echo "code_sha256=$(sha256sum "$code/exact_tn.py" | awk '{print $1}')" + echo "slurm_sha256=$(sha256sum "$code/slurm_exact_tn.sh" | awk '{print $1}')" +} > "$output/run-metadata-${SLURM_JOB_ID}.txt" + +python3 -u "$code/test_exact_tn.py" -v +python3 -u "$code/exact_tn.py" \ + --symbolic-root "$symbolic" \ + --route-dir "$repo/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid" \ + --baseline-root "$symbolic" \ + --output "$output" \ + --instances mystery-A mystery-B mystery-C mystery-D + +{ + echo "end=$(date --iso-8601=seconds)" + echo "status=success" +} >> "$output/run-metadata-${SLURM_JOB_ID}.txt" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/routes/exact_tn/test_exact_tn.py b/tracks/qcs/solutions/Genshin_Impact-71/routes/exact_tn/test_exact_tn.py new file mode 100644 index 000000000..1199dc1d7 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/routes/exact_tn/test_exact_tn.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 + +import itertools +import tempfile +import unittest +from pathlib import Path + +from exact_tn import ChallengeCompiler, contract_tt, permute_truth, tt_decompose + + +class ExactTensorTrainTests(unittest.TestCase): + def test_all_nonzero_three_variable_tensors(self) -> None: + for truth in range(1, 1 << (1 << 3)): + decomposition = tt_decompose(truth, (0, 1, 2)) + for assignment in range(8): + bits = [(assignment >> shift) & 1 for shift in (2, 1, 0)] + self.assertEqual( + contract_tt(decomposition, bits), + (truth >> assignment) & 1, + ) + + def test_permutation(self) -> None: + # Canonical function x0 XOR (x1 AND x2). + truth = 0 + for assignment in range(8): + bits = [(assignment >> shift) & 1 for shift in (2, 1, 0)] + truth |= (bits[0] ^ (bits[1] & bits[2])) << assignment + order = (2, 0, 1) + permuted = permute_truth(truth, 3, order) + decomposition = tt_decompose(permuted, order) + for assignment in range(8): + canonical = [(assignment >> shift) & 1 for shift in (2, 1, 0)] + ordered = [canonical[index] for index in order] + self.assertEqual( + contract_tt(decomposition, ordered), + (truth >> assignment) & 1, + ) + + def test_challenge_compiler_semantics(self) -> None: + # Use the same small function, compile, then evaluate the text circuit + # with a deliberately independent scalar evaluator. + truth = 0 + for assignment in range(8): + bits = [(assignment >> shift) & 1 for shift in (2, 1, 0)] + truth |= (bits[0] ^ (bits[1] & bits[2])) << assignment + decomposition = tt_decompose(truth, (0, 1, 2)) + compiler = ChallengeCompiler(3) + output = compiler.compile_tt(decomposition) + with tempfile.TemporaryDirectory() as directory: + path = Path(directory) / "circuit.txt" + compiler.write(path, [output]) + lines = path.read_text(encoding="utf-8").splitlines() + for assignment in range(8): + bits = [(assignment >> shift) & 1 for shift in (2, 1, 0)] + values = {f"x{index + 1}": bit for index, bit in enumerate(bits)} + + def get(token: str) -> int: + if token.startswith("~"): + return values[token[1:]] ^ 1 + return values[token] + + outputs = [] + for line in lines[1:]: + fields = line.split() + if fields[0] == "OUTPUTS": + outputs = [get(token) for token in fields[1:]] + continue + destination, _, operation, left, right = fields + a, b = get(left), get(right) + values[destination] = ( + a & b if operation == "AND" else a ^ b + ) + self.assertEqual(outputs, [(truth >> assignment) & 1]) + + +if __name__ == "__main__": + unittest.main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/README.md b/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/README.md new file mode 100644 index 000000000..9620a3277 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/README.md @@ -0,0 +1,25 @@ +# Issue 71: train-only symbolic, incomplete Espresso, and BDD/ABC hybrid + +This directory is an independent, reproducible experiment for +`QuantumBFS/quantum.harness#71`. + +Safety and information boundaries: + +- `routes.py discover` receives only explicit `train.csv` paths. It does not + scan the repository, import a generator, read test inputs/commitments, or + consume competitor PR content. +- Public commitment auditing is a separate post-freeze process. +- The incomplete PLA uses `.type fr`: observed `1` and `0` bits are the ON/OFF + sets and omitted minterms are don't-cares under Espresso semantics. +- The companion BLIF does not rely on omission: `.exdc` explicitly lists every + unseen minterm for every output. +- Official Berkeley ABC source is pinned by commit and statically inspected + before execution. The standalone Espresso entry point uses the embedded + Berkeley Espresso implementation, passing `F`, `D`, and `R` to `espresso`. +- Heavy tool builds and synthesis run under Slurm. Root random seed is 42. + +The semantic hybrid builds four shared reduced ordered BDDs from each frozen +hypothesis, selects the smallest, hands its cofactors to ABC, maps to K=2 LUTs, +then translates each two-input truth table to one challenge gate (free +complemented signal tokens). Every resulting challenge circuit is evaluated +bit-parallel over its entire input domain. diff --git a/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/generate_arithmetic_templates.py b/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/generate_arithmetic_templates.py new file mode 100644 index 000000000..c057d5015 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/generate_arithmetic_templates.py @@ -0,0 +1,182 @@ +#!/usr/bin/env python3 +"""Generate arithmetic circuits selected only by frozen train-only discovery.""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path +import sys + + +def build_absdiff(circuit_module, nbits: int): + """Unsigned |x-y| via ripple subtraction and conditional two's complement.""" + builder = circuit_module.CircuitBuilder(2 * nbits) + x = [f"x{i + 1}" for i in range(nbits)] + y = [f"x{nbits + i + 1}" for i in range(nbits)] + + difference = [builder.gate("XOR", x[0], y[0])] + borrow = builder.gate("AND", circuit_module.invert(x[0]), y[0]) + for idx in range(1, nbits): + parity = builder.gate("XOR", x[idx], y[idx]) + difference.append(builder.gate("XOR", parity, borrow)) + generate = builder.gate("AND", circuit_module.invert(x[idx]), y[idx]) + propagate = builder.gate("AND", circuit_module.invert(parity), borrow) + # These terms are disjoint, so XOR equals OR. + borrow = builder.gate("XOR", generate, propagate) + + # If borrow=1, the modular difference is negative. Conditionally invert + # every bit and add one. Bit zero simplifies back to difference[0]. + outputs = [difference[0]] + carry = builder.gate("AND", circuit_module.invert(difference[0]), borrow) + for idx in range(1, nbits): + conditional = builder.gate("XOR", difference[idx], borrow) + outputs.append(builder.gate("XOR", conditional, carry)) + if idx + 1 < nbits: + carry = builder.gate("AND", conditional, carry) + return builder.finish(outputs) + + +def build_sum_of_squares(circuit_module, nbits: int): + """Build x²+y² from diagonal and shared-weight cross products.""" + builder = circuit_module.CircuitBuilder(2 * nbits) + columns: list[list[str]] = [[] for _ in range(2 * nbits + 2)] + for offset in (0, nbits): + bits = [f"x{offset + i + 1}" for i in range(nbits)] + for idx, bit in enumerate(bits): + columns[2 * idx].append(bit) + for left in range(nbits): + for right in range(left + 1, nbits): + partial = builder.gate("AND", bits[left], bits[right]) + columns[left + right + 1].append(partial) + + outputs: list[str] = [] + for weight in range(2 * nbits + 1): + column = columns[weight] + while len(column) >= 3: + total, carry = builder.full_adder( + column.pop(), column.pop(), column.pop() + ) + column.append(total) + columns[weight + 1].append(carry) + if len(column) == 2: + total, carry = builder.half_adder(column[0], column[1]) + outputs.append(total) + columns[weight + 1].append(carry) + elif len(column) == 1: + outputs.append(column[0]) + else: + raise AssertionError(f"empty sum-of-squares column {weight}") + return builder.finish(outputs) + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--discovery-root", required=True) + parser.add_argument("--search-dir", required=True) + parser.add_argument("--out", required=True) + args = parser.parse_args() + + search_dir = Path(args.search_dir).resolve() + sys.path.insert(0, str(search_dir)) + import circuit # type: ignore[import-not-found] + + discovery_root = Path(args.discovery_root).resolve() + index = json.loads( + (discovery_root / "discovery-index.json").read_text(encoding="utf-8") + ) + if index["schema"] != "occam71-train-only-index-v1": + raise ValueError("unexpected discovery index schema") + + out = Path(args.out).resolve() + out.mkdir(parents=True, exist_ok=True) + results: dict[str, object] = {} + formula_by_expr = { + "x_plus_y": "add", + "x_times_y": "mul", + "abs_x_minus_y": "absdiff", + "sum_of_squares": "sos", + } + for name, index_entry in sorted(index["instances"].items()): + manifest = json.loads( + (discovery_root / name / "discovery.json").read_text(encoding="utf-8") + ) + selected = manifest["selected"] + if selected != index_entry["selected"]: + raise ValueError(f"{name}: index/manifest selection mismatch") + for key, expected in ( + ("layout", "grouped"), + ("input_endian", "lsb"), + ("output_endian", "lsb"), + ): + if selected[key] != expected: + raise ValueError(f"{name}: unsupported {key}={selected[key]}") + + nbits = int(manifest["operand_width"]) + expr_id = selected["expr_id"] + if expr_id == "x_plus_y": + generated = circuit.build_adder(nbits) + elif expr_id == "x_times_y": + generated = circuit.build_multiplier(nbits) + elif expr_id == "abs_x_minus_y": + generated = build_absdiff(circuit, nbits) + elif expr_id == "sum_of_squares": + generated = build_sum_of_squares(circuit, nbits) + else: + raise ValueError(f"{name}: no arithmetic template for {expr_id}") + if len(generated.outputs) != int(manifest["output_width"]): + raise ValueError(f"{name}: generated output width mismatch") + + instance_dir = out / name + instance_dir.mkdir(parents=True, exist_ok=True) + template_path = instance_dir / "template.txt" + generated.write(template_path) + formula_audit = circuit.verify_formula( + generated, formula_by_expr[expr_id] + ) + train_audit = circuit.verify_dataset(generated, manifest["train_path"]) + if formula_audit["failures"] or ( + train_audit["exact"] != train_audit["rows"] + ): + raise RuntimeError(f"{name}: generated template failed audit") + result = { + "instance": name, + "discovery_manifest": str(discovery_root / name / "discovery.json"), + "discovery_train_sha256": manifest["train_sha256"], + "selected": selected, + "template": str(template_path), + "template_sha256": circuit.sha256_file(template_path), + "gates": len(generated.gates), + "formula_audit": formula_audit, + "train_audit": train_audit, + "structure": generated.structural_audit(), + } + (instance_dir / "template-audit.json").write_text( + json.dumps(result, indent=2, sort_keys=True) + "\n", + encoding="utf-8", + newline="\n", + ) + results[name] = result + print( + f"{name}: {expr_id}, gates={len(generated.gates)}, " + f"full failures={formula_audit['failures']}", + flush=True, + ) + + summary = { + "schema": "occam71-train-symbolic-to-arithmetic-template-v1", + "root_seed": 42, + "instances": results, + } + (out / "template-summary.json").write_text( + json.dumps(summary, indent=2, sort_keys=True) + "\n", + encoding="utf-8", + newline="\n", + ) + (out / "TEMPLATES_COMPLETE").write_text( + "success\n", encoding="utf-8", newline="\n" + ) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/routes.py b/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/routes.py new file mode 100644 index 000000000..ee8e4f9cc --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/routes.py @@ -0,0 +1,1196 @@ +#!/usr/bin/env python3 +"""Reproducible non-leaking baselines for quantum.harness issue 71. + +The discovery subcommand reads *only* explicitly supplied train.csv files. +Auditing against public commitments and exhaustive semantic/circuit checks is a +separate subcommand and consumes a frozen discovery manifest. + +Only Python's standard library is used. +""" + +from __future__ import annotations + +import argparse +import csv +import hashlib +import itertools +import json +import math +import os +from dataclasses import dataclass +from pathlib import Path +from typing import Callable, Iterable, Iterator, Sequence + + +INSTANCE_NAMES = ( + "practice-add-n4", + "practice-mul-n4", + "mystery-A", + "mystery-B", + "mystery-C", + "mystery-D", +) +ROOT_SEED = 42 + + +def sha256_file(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as handle: + for block in iter(lambda: handle.read(1 << 20), b""): + digest.update(block) + return digest.hexdigest() + + +def atomic_json(path: Path, payload: object) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + tmp = path.with_suffix(path.suffix + ".tmp") + tmp.write_text( + json.dumps(payload, indent=2, sort_keys=True) + "\n", + encoding="utf-8", + newline="\n", + ) + os.replace(tmp, path) + + +def read_csv_exact(path: Path, header: Sequence[str]) -> list[tuple[str, ...]]: + with path.open("r", encoding="utf-8", newline="") as handle: + reader = csv.reader(handle) + actual = next(reader, None) + if actual != list(header): + raise ValueError(f"{path}: expected header {list(header)!r}, got {actual!r}") + rows: list[tuple[str, ...]] = [] + for lineno, row in enumerate(reader, start=2): + if len(row) != len(header): + raise ValueError(f"{path}:{lineno}: malformed row") + for field in row: + if not field or set(field) - {"0", "1"}: + raise ValueError(f"{path}:{lineno}: non-binary field {field!r}") + rows.append(tuple(row)) + if not rows: + raise ValueError(f"{path}: empty dataset") + return rows + + +def bits_to_int(bits: str, endian: str) -> int: + if endian == "lsb": + return sum((ch == "1") << idx for idx, ch in enumerate(bits)) + if endian == "msb": + return int(bits, 2) + raise ValueError(endian) + + +def int_to_bits(value: int, width: int, endian: str) -> str: + if value < 0 or value >= (1 << width): + raise ValueError(f"value {value} does not fit {width} bits") + msb = f"{value:0{width}b}" + if endian == "msb": + return msb + if endian == "lsb": + return msb[::-1] + raise ValueError(endian) + + +def decode_operands(bits: str, layout: str, endian: str) -> tuple[int, int]: + if len(bits) % 2: + raise ValueError("input width must be even") + if layout == "grouped": + half = len(bits) // 2 + left, right = bits[:half], bits[half:] + elif layout == "interleaved": + left, right = bits[0::2], bits[1::2] + else: + raise ValueError(layout) + return bits_to_int(left, endian), bits_to_int(right, endian) + + +@dataclass(frozen=True) +class Expression: + expr_id: str + display: str + complexity: int + function: Callable[[int, int], int] + + +def _safe_lcm(x: int, y: int) -> int: + return math.lcm(x, y) + + +def expression_library() -> tuple[Expression, ...]: + """A generic, preregistered arithmetic/bitwise hypothesis library. + + This library contains no instance-name dispatch and is applied identically + to every dataset and every candidate bit encoding. + """ + + entries = ( + ("zero", "0", 0, lambda x, y: 0), + ("one", "1", 0, lambda x, y: 1), + ("x", "x", 1, lambda x, y: x), + ("y", "y", 1, lambda x, y: y), + ("x_plus_y", "x + y", 3, lambda x, y: x + y), + ("abs_x_minus_y", "|x - y|", 4, lambda x, y: abs(x - y)), + ("x_times_y", "x * y", 3, lambda x, y: x * y), + ("x_xor_y", "x XOR y", 3, lambda x, y: x ^ y), + ("x_and_y", "x AND y", 3, lambda x, y: x & y), + ("x_or_y", "x OR y", 3, lambda x, y: x | y), + ("min_x_y", "min(x, y)", 3, min), + ("max_x_y", "max(x, y)", 3, max), + ("gcd_x_y", "gcd(x, y)", 4, math.gcd), + ("lcm_x_y", "lcm(x, y)", 4, _safe_lcm), + ("x_squared", "x^2", 3, lambda x, y: x * x), + ("y_squared", "y^2", 3, lambda x, y: y * y), + ("sum_of_squares", "x^2 + y^2", 7, lambda x, y: x * x + y * y), + ("square_of_sum", "(x + y)^2", 7, lambda x, y: (x + y) ** 2), + ( + "abs_difference_of_squares", + "|x^2 - y^2|", + 8, + lambda x, y: abs(x * x - y * y), + ), + ("x_squared_plus_y", "x^2 + y", 5, lambda x, y: x * x + y), + ("x_plus_y_squared", "x + y^2", 5, lambda x, y: x + y * y), + ("triangular_x_plus_y", "x(x+1)/2 + y", 7, lambda x, y: x * (x + 1) // 2 + y), + ("x_plus_triangular_y", "x + y(y+1)/2", 7, lambda x, y: x + y * (y + 1) // 2), + ("x_times_x_plus_y", "x(x+y)", 6, lambda x, y: x * (x + y)), + ("y_times_x_plus_y", "y(x+y)", 6, lambda x, y: y * (x + y)), + ) + return tuple(Expression(*entry) for entry in entries) + + +EXPRESSION_BY_ID = {expr.expr_id: expr for expr in expression_library()} + + +def evaluate_hypothesis(selection: dict[str, object], input_bits: str) -> int: + x, y = decode_operands( + input_bits, + str(selection["layout"]), + str(selection["input_endian"]), + ) + return EXPRESSION_BY_ID[str(selection["expr_id"])].function(x, y) + + +def encode_hypothesis( + selection: dict[str, object], input_bits: str, output_width: int +) -> str: + value = evaluate_hypothesis(selection, input_bits) + if selection["range_mode"] == "modulo": + value %= 1 << output_width + elif selection["range_mode"] != "exact": + raise ValueError(selection["range_mode"]) + return int_to_bits(value, output_width, str(selection["output_endian"])) + + +def discover_one(train_path: Path) -> dict[str, object]: + rows = read_csv_exact(train_path, ("input", "output")) + input_width = len(rows[0][0]) + output_width = len(rows[0][1]) + if input_width % 2: + raise ValueError(f"{train_path}: odd input width") + if any(len(inp) != input_width or len(out) != output_width for inp, out in rows): + raise ValueError(f"{train_path}: inconsistent widths") + if len({inp for inp, _ in rows}) != len(rows): + raise ValueError(f"{train_path}: duplicate training input") + + exact: list[dict[str, object]] = [] + ranked: list[dict[str, object]] = [] + semantic_vectors: set[tuple[int, ...]] = set() + tested = 0 + for layout, input_endian, output_endian, range_mode in itertools.product( + ("grouped", "interleaved"), + ("lsb", "msb"), + ("lsb", "msb"), + ("exact", "modulo"), + ): + decoded = [ + decode_operands(inp, layout, input_endian) for inp, _ in rows + ] + expected = [bits_to_int(out, output_endian) for _, out in rows] + for expr in expression_library(): + tested += 1 + raw = [expr.function(x, y) for x, y in decoded] + if range_mode == "modulo": + predicted = [value % (1 << output_width) for value in raw] + else: + predicted = raw + vector = tuple(predicted) + semantic_vectors.add(vector) + mismatches = sum(a != b for a, b in zip(predicted, expected)) + mean_abs = ( + sum(abs(a - b) for a, b in zip(predicted, expected)) / len(rows) + ) + candidate = { + "expr_id": expr.expr_id, + "expression": expr.display, + "complexity": expr.complexity, + "layout": layout, + "input_endian": input_endian, + "output_endian": output_endian, + "range_mode": range_mode, + "mismatches": mismatches, + "mean_absolute_error": mean_abs, + } + ranked.append(candidate) + if mismatches == 0: + exact.append(candidate) + + if not exact: + top = sorted( + ranked, + key=lambda item: ( + item["mismatches"], + item["mean_absolute_error"], + item["complexity"], + item["expr_id"], + ), + )[:10] + raise RuntimeError(f"{train_path}: no exact hypothesis; top={top!r}") + + exact.sort( + key=lambda item: ( + item["complexity"], + item["expr_id"], + item["layout"], + item["input_endian"], + item["output_endian"], + item["range_mode"], + ) + ) + # Exact and modulo modes coincide when the expression never overflows. + # Prefer the stronger non-wrapping claim in a deterministic tie. + exact.sort(key=lambda item: item["range_mode"] != "exact") + selected = exact[0] + return { + "schema": "occam71-train-only-discovery-v1", + "root_seed": ROOT_SEED, + "read_boundary": { + "allowed": [str(train_path.resolve())], + "forbidden_during_discovery": [ + "generator source", + "test_inputs.csv", + "commitment.sha256", + "test_outputs.csv", + "competitor PR content", + ], + }, + "train_path": str(train_path.resolve()), + "train_sha256": sha256_file(train_path), + "train_rows": len(rows), + "input_width": input_width, + "operand_width": input_width // 2, + "output_width": output_width, + "candidates_tested": tested, + "distinct_prediction_vectors": len(semantic_vectors), + "exact_hypothesis_count": len(exact), + "exact_hypotheses": exact, + "selected": selected, + "training": { + "matched": len(rows), + "total": len(rows), + "accuracy": 1.0, + }, + } + + +def command_discover(args: argparse.Namespace) -> None: + root = Path(args.datasets_root).resolve() + out = Path(args.out).resolve() + out.mkdir(parents=True, exist_ok=True) + index: dict[str, object] = { + "schema": "occam71-train-only-index-v1", + "root_seed": ROOT_SEED, + "instances": {}, + } + # Paths are constructed explicitly; there is no recursive directory scan. + for name in INSTANCE_NAMES: + result = discover_one(root / name / "train.csv") + target = out / name / "discovery.json" + atomic_json(target, result) + index["instances"][name] = { + "manifest": str(target), + "train_sha256": result["train_sha256"], + "selected": result["selected"], + } + print( + f"{name}: {result['selected']['expression']} " + f"({result['selected']['layout']}, " + f"{result['selected']['input_endian']} -> " + f"{result['selected']['output_endian']}), " + f"train={result['train_rows']}/{result['train_rows']}" + ) + atomic_json(out / "discovery-index.json", index) + + +def load_discovery(path: Path) -> dict[str, object]: + data = json.loads(path.read_text(encoding="utf-8")) + if data.get("schema") != "occam71-train-only-discovery-v1": + raise ValueError(f"{path}: unsupported manifest") + train = Path(str(data["train_path"])) + actual = sha256_file(train) + if actual != data["train_sha256"]: + raise ValueError(f"{path}: train file changed after discovery") + return data + + +def write_prediction_csv( + path: Path, + inputs: Iterable[str], + selection: dict[str, object], + output_width: int, +) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + tmp = path.with_suffix(path.suffix + ".tmp") + with tmp.open("w", encoding="utf-8", newline="\n") as handle: + # The withheld file mirrors train.csv and retains the supplied input. + handle.write("input,output\n") + for input_bits in inputs: + output_bits = encode_hypothesis(selection, input_bits, output_width) + handle.write(f"{input_bits},{output_bits}\n") + os.replace(tmp, path) + + +def command_audit_commitments(args: argparse.Namespace) -> None: + datasets = Path(args.datasets_root).resolve() + root = Path(args.work).resolve() + summary: dict[str, object] = { + "schema": "occam71-post-freeze-commitment-audit-v1", + "instances": {}, + } + for name in INSTANCE_NAMES: + discovery_path = root / name / "discovery.json" + discovery = load_discovery(discovery_path) + test_path = datasets / name / "test_inputs.csv" + inputs = [row[0] for row in read_csv_exact(test_path, ("input",))] + if any(len(bits) != discovery["input_width"] for bits in inputs): + raise ValueError(f"{test_path}: width mismatch") + prediction_path = root / name / "test_outputs.predicted.csv" + write_prediction_csv( + prediction_path, + inputs, + dict(discovery["selected"]), + int(discovery["output_width"]), + ) + actual_hash = sha256_file(prediction_path) + commitment_path = datasets / name / "commitment.sha256" + commitment = commitment_path.read_text(encoding="utf-8").split()[0] + matched = actual_hash == commitment + result = { + "discovery_manifest": str(discovery_path), + "prediction_path": str(prediction_path), + "test_rows": len(inputs), + "prediction_sha256": actual_hash, + "published_commitment_sha256": commitment, + "commitment_match": matched, + } + atomic_json(root / name / "commitment-audit.json", result) + summary["instances"][name] = result + print(f"{name}: test commitment {'MATCH' if matched else 'MISMATCH'} ({actual_hash})") + if not matched: + raise RuntimeError(f"{name}: frozen train-only hypothesis failed commitment") + atomic_json(root / "commitment-audit-summary.json", summary) + + +def all_bitstrings(width: int) -> Iterator[str]: + for value in range(1 << width): + yield f"{value:0{width}b}" + + +def write_train_pla( + path: Path, rows: Sequence[tuple[str, str]], input_width: int, output_width: int +) -> None: + with path.open("w", encoding="utf-8", newline="\n") as handle: + handle.write(f".i {input_width}\n") + handle.write(f".o {output_width}\n") + handle.write(".ilb " + " ".join(f"i{i}" for i in range(input_width)) + "\n") + handle.write(".ob " + " ".join(f"o{i}" for i in range(output_width)) + "\n") + # In Espresso semantics, FR gives explicit ON and OFF sets; minterms + # absent from both sets are the don't-care set. + handle.write(".type fr\n") + handle.write(f".p {len(rows)}\n") + for input_bits, output_bits in rows: + handle.write(f"{input_bits} {output_bits}\n") + handle.write(".e\n") + + +def write_full_pla( + path: Path, + selection: dict[str, object], + input_width: int, + output_width: int, +) -> None: + domain = 1 << input_width + with path.open("w", encoding="utf-8", newline="\n") as handle: + handle.write(f".i {input_width}\n") + handle.write(f".o {output_width}\n") + handle.write(".ilb " + " ".join(f"i{i}" for i in range(input_width)) + "\n") + handle.write(".ob " + " ".join(f"o{i}" for i in range(output_width)) + "\n") + handle.write(".type fr\n") + handle.write(f".p {domain}\n") + for input_bits in all_bitstrings(input_width): + output_bits = encode_hypothesis(selection, input_bits, output_width) + handle.write(f"{input_bits} {output_bits}\n") + handle.write(".e\n") + + +def write_train_exdc_blif( + path: Path, rows: Sequence[tuple[str, str]], input_width: int, output_width: int +) -> tuple[list[int], int]: + observed = {input_bits: output_bits for input_bits, output_bits in rows} + if len(observed) != len(rows): + raise ValueError("duplicate training inputs") + unseen = [bits for bits in all_bitstrings(input_width) if bits not in observed] + on_counts: list[int] = [] + with path.open("w", encoding="utf-8", newline="\n") as handle: + handle.write(".model train_incomplete\n") + inputs = " ".join(f"i{i}" for i in range(input_width)) + outputs = " ".join(f"o{i}" for i in range(output_width)) + handle.write(f".inputs {inputs}\n") + handle.write(f".outputs {outputs}\n") + for output_idx in range(output_width): + handle.write(f".names {inputs} o{output_idx}\n") + count = 0 + for input_bits, output_bits in rows: + if output_bits[output_idx] == "1": + handle.write(f"{input_bits} 1\n") + count += 1 + on_counts.append(count) + # A .names cover alone makes absent minterms OFF. The .exdc network + # below explicitly changes exactly the unseen minterms to don't-care, + # leaving every observed 0 and 1 fixed. + handle.write(".exdc\n") + # ABC external DC is a separate one-output global-care network. + handle.write(f".inputs {inputs}\n") + handle.write(".outputs exdc\n") + handle.write(f".names {inputs} exdc\n") + for input_bits in unseen: + handle.write(f"{input_bits} 1\n") + handle.write(".end\n") + return on_counts, len(unseen) + + +def audit_generated_specs( + train_pla: Path, + train_blif: Path, + rows: Sequence[tuple[str, str]], + input_width: int, + output_width: int, + on_counts: Sequence[int], + unseen_count: int, +) -> dict[str, object]: + pla_lines = train_pla.read_text(encoding="utf-8").splitlines() + if ".type fr" not in pla_lines: + raise AssertionError("PLA is not .type fr") + cube_lines = [line for line in pla_lines if line and line[0] in "01-"] + parsed = {} + for line in cube_lines: + left, right = line.split() + if len(left) != input_width or len(right) != output_width: + raise AssertionError("malformed PLA cube") + if "-" in left or "-" in right: + raise AssertionError("training PLA should explicitly fix every observed bit") + parsed[left] = right + expected = dict(rows) + if parsed != expected: + raise AssertionError("PLA observed relation differs from train.csv") + if (1 << input_width) - len(parsed) != unseen_count: + raise AssertionError("PLA don't-care count mismatch") + + # Audit the generated BLIF structurally instead of trusting the writer. + main_seen = [set() for _ in range(output_width)] + exdc_seen: set[str] = set() + in_exdc = False + current: tuple[str, int] | None = None + for raw in train_blif.read_text(encoding="utf-8").splitlines(): + line = raw.strip() + if not line: + continue + if line == ".exdc": + in_exdc = True + current = None + elif line.startswith(".names "): + out_name = line.split()[-1] + if in_exdc: + if out_name != "exdc": + raise AssertionError("EXDC must have one global output") + current = ("dc", 0) + else: + if not out_name.startswith("o"): + raise AssertionError("unexpected BLIF output name") + current = ("main", int(out_name[1:])) + elif line.startswith("."): + current = None + elif current is not None: + pattern, value = line.split() + if value != "1" or len(pattern) != input_width: + raise AssertionError("malformed BLIF cube") + if current[0] == "dc": + exdc_seen.add(pattern) + else: + main_seen[current[1]].add(pattern) + + observed = dict(rows) + unseen = set(all_bitstrings(input_width)) - set(observed) + for output_idx in range(output_width): + expected_on = { + inp for inp, out in rows if out[output_idx] == "1" + } + if main_seen[output_idx] != expected_on: + raise AssertionError(f"BLIF ON-set mismatch for output {output_idx}") + if main_seen[output_idx] & exdc_seen: + raise AssertionError("observed ON set overlaps EXDC") + if exdc_seen != unseen: + raise AssertionError("BLIF global EXDC differs from unseen input set") + return { + "pla_type": "fr", + "observed_minterms": len(rows), + "unseen_dont_care_minterms_per_output": unseen_count, + "domain_minterms": 1 << input_width, + "on_minterms_per_output": list(on_counts), + "pla_sha256": sha256_file(train_pla), + "blif_sha256": sha256_file(train_blif), + "checks": { + "all_training_outputs_fixed": True, + "all_and_only_unseen_inputs_are_dont_care": True, + "pla_blif_relation_agree": True, + }, + } + + +def command_specs(args: argparse.Namespace) -> None: + root = Path(args.work).resolve() + for name in INSTANCE_NAMES: + directory = root / name + discovery = load_discovery(directory / "discovery.json") + rows = [ + (row[0], row[1]) + for row in read_csv_exact(Path(str(discovery["train_path"])), ("input", "output")) + ] + input_width = int(discovery["input_width"]) + output_width = int(discovery["output_width"]) + train_pla = directory / "train-incomplete.pla" + train_blif = directory / "train-incomplete-exdc.blif" + full_pla = directory / "semantic-full.pla" + write_train_pla(train_pla, rows, input_width, output_width) + on_counts, unseen_count = write_train_exdc_blif( + train_blif, rows, input_width, output_width + ) + write_full_pla( + full_pla, + dict(discovery["selected"]), + input_width, + output_width, + ) + audit = audit_generated_specs( + train_pla, + train_blif, + rows, + input_width, + output_width, + on_counts, + unseen_count, + ) + audit["semantic_full_pla_sha256"] = sha256_file(full_pla) + atomic_json(directory / "spec-audit.json", audit) + print( + f"{name}: PLA/BLIF audited; observed={len(rows)}, " + f"DC={unseen_count}, domain={1 << input_width}" + ) + + +@dataclass +class BddBuild: + nodes: dict[int, tuple[int, int, int, int]] + unique: dict[tuple[int, int, int], int] + + def __init__(self) -> None: + self.nodes = {} + self.unique = {} + + def build( + self, + level: int, + order: Sequence[int], + assignments: Sequence[tuple[str, str]], + output_idx: int, + ) -> int: + first = assignments[0][1][output_idx] + if all(output[output_idx] == first for _, output in assignments): + return 1 if first == "1" else 0 + if level >= len(order): + raise AssertionError("nonconstant BDD leaf") + var = order[level] + low = [pair for pair in assignments if pair[0][var] == "0"] + high = [pair for pair in assignments if pair[0][var] == "1"] + if not low or not high: + raise AssertionError("incomplete full-domain partition") + low_id = self.build(level + 1, order, low, output_idx) + high_id = self.build(level + 1, order, high, output_idx) + if low_id == high_id: + return low_id + key = (level, low_id, high_id) + known = self.unique.get(key) + if known is not None: + return known + node_id = len(self.nodes) + 2 + self.nodes[node_id] = (level, var, low_id, high_id) + self.unique[key] = node_id + return node_id + + +def variable_orders(operand_width: int) -> dict[str, list[int]]: + n = operand_width + return { + "grouped_lsb": list(range(2 * n)), + "grouped_msb": list(range(n - 1, -1, -1)) + + list(range(2 * n - 1, n - 1, -1)), + "interleaved_lsb": [item for idx in range(n) for item in (idx, n + idx)], + "interleaved_msb": [ + item for idx in range(n - 1, -1, -1) for item in (idx, n + idx) + ], + } + + +def write_bdd_blif( + path: Path, + input_width: int, + output_width: int, + build: BddBuild, + roots: Sequence[int], +) -> None: + def signal(node_id: int) -> str: + if node_id == 0: + return "__zero" + if node_id == 1: + return "__one" + return f"n{node_id}" + + with path.open("w", encoding="utf-8", newline="\n") as handle: + handle.write(".model semantic_bdd\n") + handle.write(".inputs " + " ".join(f"i{i}" for i in range(input_width)) + "\n") + handle.write(".outputs " + " ".join(f"o{i}" for i in range(output_width)) + "\n") + handle.write(".names __zero\n") + handle.write(".names __one\n1\n") + for node_id in sorted(build.nodes): + _level, var, low, high = build.nodes[node_id] + # n = (not select and low) or (select and high) + handle.write( + f".names i{var} {signal(low)} {signal(high)} {signal(node_id)}\n" + ) + handle.write("01- 1\n") + handle.write("1-1 1\n") + for output_idx, root in enumerate(roots): + handle.write(f".names {signal(root)} o{output_idx}\n1 1\n") + handle.write(".end\n") + + +def command_bdd(args: argparse.Namespace) -> None: + root = Path(args.work).resolve() + for name in INSTANCE_NAMES: + directory = root / name + discovery = load_discovery(directory / "discovery.json") + input_width = int(discovery["input_width"]) + output_width = int(discovery["output_width"]) + selection = dict(discovery["selected"]) + assignments = [ + (bits, encode_hypothesis(selection, bits, output_width)) + for bits in all_bitstrings(input_width) + ] + metrics: dict[str, object] = { + "schema": "occam71-shared-robdd-cofactor-v1", + "input_width": input_width, + "output_width": output_width, + "orders": {}, + } + candidates: list[tuple[int, str, Path]] = [] + for order_name, order in variable_orders(int(discovery["operand_width"])).items(): + build = BddBuild() + roots = [ + build.build(0, order, assignments, output_idx) + for output_idx in range(output_width) + ] + blif_path = directory / f"semantic-bdd-{order_name}.blif" + write_bdd_blif( + blif_path, input_width, output_width, build, roots + ) + data = { + "variable_order": order, + "nonterminal_nodes": len(build.nodes), + "roots": roots, + "blif_path": str(blif_path), + "blif_sha256": sha256_file(blif_path), + } + metrics["orders"][order_name] = data + candidates.append((len(build.nodes), order_name, blif_path)) + node_count, selected_order, selected_path = min(candidates) + metrics["selected"] = { + "order_name": selected_order, + "nonterminal_nodes": node_count, + "blif_path": str(selected_path), + } + atomic_json(directory / "bdd-metrics.json", metrics) + print(f"{name}: selected {selected_order}, shared ROBDD nodes={node_count}") + + +@dataclass(frozen=True) +class BlifNode: + fanins: tuple[str, ...] + output: str + cubes: tuple[tuple[str, str], ...] + + +def logical_blif_lines(path: Path) -> list[str]: + result: list[str] = [] + pending = "" + for raw in path.read_text(encoding="utf-8").splitlines(): + line = raw.split("#", 1)[0].strip() + if not line: + continue + if line.endswith("\\"): + pending += line[:-1].strip() + " " + continue + result.append((pending + line).strip()) + pending = "" + if pending: + raise ValueError(f"{path}: dangling continuation") + return result + + +def parse_blif(path: Path) -> tuple[list[str], list[str], list[BlifNode]]: + inputs: list[str] = [] + outputs: list[str] = [] + nodes: list[BlifNode] = [] + current_header: list[str] | None = None + current_cubes: list[tuple[str, str]] = [] + in_exdc = False + + def finish() -> None: + nonlocal current_header, current_cubes + if current_header is not None: + if len(current_header) < 1: + raise AssertionError + fanins = tuple(current_header[:-1]) + output = current_header[-1] + nodes.append(BlifNode(fanins, output, tuple(current_cubes))) + current_header = None + current_cubes = [] + + for line in logical_blif_lines(path): + if line.startswith("."): + finish() + fields = line.split() + directive = fields[0] + if directive == ".exdc": + in_exdc = True + elif in_exdc: + # ABC may preserve the now-unused external DC network. + pass + elif directive == ".inputs": + inputs.extend(fields[1:]) + elif directive == ".outputs": + outputs.extend(fields[1:]) + elif directive == ".names": + current_header = fields[1:] + elif directive in (".model", ".end"): + pass + elif directive in (".latch", ".subckt", ".gate"): + raise ValueError(f"{path}: unsupported sequential/hierarchical directive {directive}") + elif current_header is not None and not in_exdc: + fields = line.split() + fanin_count = len(current_header) - 1 + if fanin_count == 0: + if fields != ["1"]: + raise ValueError(f"{path}: malformed constant cube {line!r}") + current_cubes.append(("", "1")) + elif len(fields) == 1: + current_cubes.append((fields[0], "1")) + elif len(fields) == 2: + current_cubes.append((fields[0], fields[1])) + else: + raise ValueError(f"{path}: malformed cube {line!r}") + finish() + return inputs, outputs, nodes + + +def simulate_blif_bitparallel( + path: Path, +) -> tuple[list[str], list[str], dict[str, int], int]: + inputs, outputs, nodes = parse_blif(path) + width = len(inputs) + domain = 1 << width + universe = (1 << domain) - 1 + values: dict[str, int] = {} + for position, name in enumerate(inputs): + table = 0 + shift = width - 1 - position + for assignment in range(domain): + if (assignment >> shift) & 1: + table |= 1 << assignment + values[name] = table + + pending = list(nodes) + while pending: + progress = False + remaining: list[BlifNode] = [] + for node in pending: + if any(name not in values for name in node.fanins): + remaining.append(node) + continue + one_cubes = [cube for cube in node.cubes if cube[1] == "1"] + zero_cubes = [cube for cube in node.cubes if cube[1] == "0"] + if one_cubes and zero_cubes: + raise ValueError(f"{path}: mixed output phases at {node.output}") + default = universe if zero_cubes else 0 + result = default + cubes = zero_cubes or one_cubes + for pattern, _phase in cubes: + if len(pattern) != len(node.fanins): + raise ValueError(f"{path}: cube width mismatch at {node.output}") + term = universe + for char, fanin in zip(pattern, node.fanins): + if char == "1": + term &= values[fanin] + elif char == "0": + term &= values[fanin] ^ universe + elif char != "-": + raise ValueError(f"{path}: invalid cube character {char!r}") + if zero_cubes: + result &= term ^ universe + else: + result |= term + values[node.output] = result + progress = True + if not progress: + missing = sorted( + { + fanin + for node in remaining + for fanin in node.fanins + if fanin not in values + } + ) + raise ValueError(f"{path}: unresolved/cyclic signals {missing[:20]}") + pending = remaining + if any(name not in values for name in outputs): + raise ValueError(f"{path}: undriven output") + return inputs, outputs, values, universe + + +def truth_signature( + function: Callable[[bool, bool], bool], +) -> tuple[int, int, int, int]: + return tuple( + int(function(bool(a), bool(b))) + for a, b in ((0, 0), (0, 1), (1, 0), (1, 1)) + ) + + +BASE_OPS: dict[str, Callable[[bool, bool], bool]] = { + "AND": lambda a, b: a and b, + "OR": lambda a, b: a or b, + "XOR": lambda a, b: a != b, + "NAND": lambda a, b: not (a and b), + "NOR": lambda a, b: not (a or b), + "XNOR": lambda a, b: a == b, +} + + +def canonical_gate_for_signature( + signature: tuple[int, int, int, int] +) -> tuple[str, bool, bool, bool]: + candidates: list[tuple[str, bool, bool, bool]] = [] + for op_name, op in BASE_OPS.items(): + for neg_a, neg_b, neg_out in itertools.product((False, True), repeat=3): + def represented(a: bool, b: bool) -> bool: + value = op(a ^ neg_a, b ^ neg_b) + return value ^ neg_out + + if truth_signature(represented) == signature: + candidates.append((op_name, neg_a, neg_b, neg_out)) + if not candidates: + raise ValueError(f"no challenge-gate representation for {signature}") + preference = {"XOR": 0, "XNOR": 1, "AND": 2, "OR": 3, "NAND": 4, "NOR": 5} + return min( + candidates, + key=lambda item: ( + sum(item[1:]), + preference[item[0]], + item, + ), + ) + + +def toggle(token: str) -> str: + return token[1:] if token.startswith("~") else "~" + token + + +def blif_node_signature(node: BlifNode) -> tuple[int, ...]: + width = len(node.fanins) + result: list[int] = [] + for assignment in itertools.product((0, 1), repeat=width): + one_cubes = [cube for cube in node.cubes if cube[1] == "1"] + zero_cubes = [cube for cube in node.cubes if cube[1] == "0"] + if one_cubes and zero_cubes: + raise ValueError(f"mixed phases at {node.output}") + value = 1 if zero_cubes else 0 + for pattern, _phase in zero_cubes or one_cubes: + matched = all( + char == "-" or int(char) == bit + for char, bit in zip(pattern, assignment) + ) + if matched: + value = 0 if zero_cubes else 1 + result.append(value) + return tuple(result) + + +def command_convert_k2(args: argparse.Namespace) -> None: + source = Path(args.blif).resolve() + destination = Path(args.out).resolve() + inputs, outputs, nodes = parse_blif(source) + aliases: dict[str, str] = { + name: f"x{idx + 1}" for idx, name in enumerate(inputs) + } + gates: list[tuple[str, str, str, str]] = [] + const_zero: str | None = None + + def constant(value: int) -> str: + nonlocal const_zero + if const_zero is None: + wire = f"w{len(gates) + 1}" + gates.append((wire, "XOR", "x1", "x1")) + const_zero = wire + return const_zero if value == 0 else toggle(const_zero) + + pending = list(nodes) + while pending: + progress = False + remaining: list[BlifNode] = [] + for node in pending: + if any(name not in aliases for name in node.fanins): + remaining.append(node) + continue + signature = blif_node_signature(node) + if len(node.fanins) == 0: + aliases[node.output] = constant(signature[0]) + elif len(node.fanins) == 1: + source_token = aliases[node.fanins[0]] + if signature == (0, 0): + aliases[node.output] = constant(0) + elif signature == (1, 1): + aliases[node.output] = constant(1) + elif signature == (0, 1): + aliases[node.output] = source_token + elif signature == (1, 0): + aliases[node.output] = toggle(source_token) + else: + raise AssertionError(signature) + elif len(node.fanins) == 2: + op, neg_a, neg_b, neg_out = canonical_gate_for_signature(signature) + left = aliases[node.fanins[0]] + right = aliases[node.fanins[1]] + if neg_a: + left = toggle(left) + if neg_b: + right = toggle(right) + wire = f"w{len(gates) + 1}" + gates.append((wire, op, left, right)) + aliases[node.output] = toggle(wire) if neg_out else wire + else: + raise ValueError( + f"{source}: node {node.output} has {len(node.fanins)} fanins; expected K<=2" + ) + progress = True + if not progress: + raise ValueError(f"{source}: unresolved nodes during conversion") + pending = remaining + + output_tokens = [aliases[name] for name in outputs] + destination.parent.mkdir(parents=True, exist_ok=True) + with destination.open("w", encoding="utf-8", newline="\n") as handle: + handle.write(f"INPUTS {len(inputs)}\n") + for wire, op, left, right in gates: + handle.write(f"{wire} = {op} {left} {right}\n") + handle.write("OUTPUTS " + " ".join(output_tokens) + "\n") + metadata = { + "source_blif": str(source), + "source_blif_sha256": sha256_file(source), + "challenge_path": str(destination), + "challenge_sha256": sha256_file(destination), + "inputs": len(inputs), + "outputs": len(outputs), + "gates": len(gates), + "all_logic_nodes_at_most_two_fanins": True, + } + atomic_json(destination.with_suffix(".conversion.json"), metadata) + print(f"{destination}: {len(gates)} challenge gates") + + +def parse_challenge(path: Path) -> tuple[int, list[tuple[str, str, str, str]], list[str]]: + ninputs: int | None = None + gates: list[tuple[str, str, str, str]] = [] + outputs: list[str] | None = None + for raw in path.read_text(encoding="utf-8").splitlines(): + line = raw.split("#", 1)[0].strip() + if not line: + continue + fields = line.split() + if fields[0] == "INPUTS": + ninputs = int(fields[1]) + elif fields[0] == "OUTPUTS": + outputs = fields[1:] + else: + if len(fields) != 5 or fields[1] != "=": + raise ValueError(f"{path}: malformed challenge line {line!r}") + gates.append((fields[0], fields[2], fields[3], fields[4])) + if ninputs is None or outputs is None: + raise ValueError(f"{path}: missing header/output") + return ninputs, gates, outputs + + +def simulate_challenge_bitparallel(path: Path) -> tuple[tuple[int, ...], int, int]: + ninputs, gates, outputs = parse_challenge(path) + domain = 1 << ninputs + universe = (1 << domain) - 1 + values: dict[str, int] = {} + for input_idx in range(ninputs): + table = 0 + # Challenge x1 is the first character and also the LSB of the + # benchmark operand; enumeration here follows string order. + shift = ninputs - 1 - input_idx + for assignment in range(domain): + if (assignment >> shift) & 1: + table |= 1 << assignment + values[f"x{input_idx + 1}"] = table + + def value(token: str) -> int: + inverted = token.startswith("~") + base = token[1:] if inverted else token + result = values[base] + return result ^ universe if inverted else result + + for output, op, left_token, right_token in gates: + left, right = value(left_token), value(right_token) + if op == "AND": + result = left & right + elif op == "OR": + result = left | right + elif op == "XOR": + result = left ^ right + elif op == "NAND": + result = (left & right) ^ universe + elif op == "NOR": + result = (left | right) ^ universe + elif op == "XNOR": + result = (left ^ right) ^ universe + else: + raise ValueError(f"{path}: unsupported gate {op}") + values[output] = result + return tuple(value(token) for token in outputs), universe, len(gates) + + +def expected_tables( + selection: dict[str, object], input_width: int, output_width: int +) -> tuple[int, ...]: + tables = [0] * output_width + for assignment, input_bits in enumerate(all_bitstrings(input_width)): + output_bits = encode_hypothesis(selection, input_bits, output_width) + for idx, char in enumerate(output_bits): + if char == "1": + tables[idx] |= 1 << assignment + return tuple(tables) + + +def command_verify(args: argparse.Namespace) -> None: + discovery = load_discovery(Path(args.discovery).resolve()) + circuit_path = Path(args.circuit).resolve() + actual, universe, gates = simulate_challenge_bitparallel(circuit_path) + expected = expected_tables( + dict(discovery["selected"]), + int(discovery["input_width"]), + int(discovery["output_width"]), + ) + if len(actual) != len(expected): + raise ValueError("output width mismatch") + output_bit_mismatches = [(a ^ e).bit_count() for a, e in zip(actual, expected)] + any_mismatch = 0 + for a, e in zip(actual, expected): + any_mismatch |= a ^ e + vector_mismatches = any_mismatch.bit_count() + domain = universe.bit_count() + + train_rows = read_csv_exact(Path(str(discovery["train_path"])), ("input", "output")) + train_mismatches = 0 + for input_bits, output_bits in train_rows: + assignment = int(input_bits, 2) + predicted = "".join( + "1" if (table >> assignment) & 1 else "0" for table in actual + ) + if predicted != output_bits: + train_mismatches += 1 + result = { + "schema": "occam71-challenge-circuit-exhaustive-audit-v1", + "circuit": str(circuit_path), + "circuit_sha256": sha256_file(circuit_path), + "gates": gates, + "domain": domain, + "train_rows": len(train_rows), + "train_vector_mismatches": train_mismatches, + "train_accuracy": 1 - train_mismatches / len(train_rows), + "full_domain_vector_mismatches": vector_mismatches, + "full_domain_accuracy": 1 - vector_mismatches / domain, + "output_bit_mismatches": output_bit_mismatches, + "exact_full_domain": vector_mismatches == 0, + } + output = Path(args.out).resolve() + atomic_json(output, result) + print( + f"{circuit_path.name}: gates={gates}, train mismatches={train_mismatches}, " + f"full mismatches={vector_mismatches}/{domain}" + ) + if args.require_exact and vector_mismatches: + raise RuntimeError("circuit is not exact over the full domain") + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser() + sub = parser.add_subparsers(dest="command", required=True) + + discover = sub.add_parser("discover") + discover.add_argument("--datasets-root", required=True) + discover.add_argument("--out", required=True) + discover.set_defaults(func=command_discover) + + audit = sub.add_parser("audit-commitments") + audit.add_argument("--datasets-root", required=True) + audit.add_argument("--work", required=True) + audit.set_defaults(func=command_audit_commitments) + + specs = sub.add_parser("specs") + specs.add_argument("--work", required=True) + specs.set_defaults(func=command_specs) + + bdd = sub.add_parser("bdd") + bdd.add_argument("--work", required=True) + bdd.set_defaults(func=command_bdd) + + convert = sub.add_parser("convert-k2") + convert.add_argument("--blif", required=True) + convert.add_argument("--out", required=True) + convert.set_defaults(func=command_convert_k2) + + verify = sub.add_parser("verify") + verify.add_argument("--discovery", required=True) + verify.add_argument("--circuit", required=True) + verify.add_argument("--out", required=True) + verify.add_argument("--require-exact", action="store_true") + verify.set_defaults(func=command_verify) + return parser + + +def main() -> None: + parser = build_parser() + args = parser.parse_args() + args.func(args) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/run_abc_only.py b/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/run_abc_only.py new file mode 100644 index 000000000..5db940a0a --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/run_abc_only.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +"""Run native ABC EXDC and exact semantic/ROBDD hybrid flows.""" + +from __future__ import annotations + +import argparse +import json +import os +from pathlib import Path +import shutil +import subprocess + +import routes +from run_experiments import abc_map, abc_map_incomplete, convert_and_audit + + +def run_one(name: str, work: Path, abc: Path) -> dict[str, object]: + directory = work / name + discovery = directory / "discovery.json" + + incomplete_blif = directory / "abc-incomplete-k2.blif" + abc_map_incomplete( + abc, + directory / "train-incomplete-exdc.blif", + incomplete_blif, + directory / "abc-incomplete.log", + ) + incomplete_audit = convert_and_audit( + discovery, + incomplete_blif, + directory / "abc-incomplete.txt", + directory / "abc-incomplete-audit.json", + require_exact=False, + ) + + bdd_metrics = json.loads( + (directory / "bdd-metrics.json").read_text(encoding="utf-8") + ) + candidates: list[dict[str, object]] = [] + for order_name, order_data in sorted(bdd_metrics["orders"].items()): + mapped = directory / f"hybrid-bdd-{order_name}-k2.blif" + challenge = directory / f"hybrid-bdd-{order_name}.txt" + audit_path = directory / f"hybrid-bdd-{order_name}-audit.json" + abc_map( + abc, + Path(order_data["blif_path"]), + mapped, + directory / f"abc-bdd-{order_name}.log", + ) + audit = convert_and_audit( + discovery, mapped, challenge, audit_path, require_exact=True + ) + candidates.append( + { + "route": f"bdd-{order_name}", + "source_nodes": order_data["nonterminal_nodes"], + "mapped_blif": str(mapped), + "challenge": str(challenge), + "audit": str(audit_path), + "gates": audit["gates"], + } + ) + + semantic_mapped = directory / "hybrid-semantic-full-k2.blif" + semantic_challenge = directory / "hybrid-semantic-full.txt" + semantic_audit_path = directory / "hybrid-semantic-full-audit.json" + abc_map( + abc, + directory / "semantic-full.pla", + semantic_mapped, + directory / "abc-semantic-full.log", + ) + semantic_audit = convert_and_audit( + discovery, + semantic_mapped, + semantic_challenge, + semantic_audit_path, + require_exact=True, + ) + candidates.append( + { + "route": "semantic-full", + "source_nodes": None, + "mapped_blif": str(semantic_mapped), + "challenge": str(semantic_challenge), + "audit": str(semantic_audit_path), + "gates": semantic_audit["gates"], + } + ) + + best = min(candidates, key=lambda item: (item["gates"], item["route"])) + best_path = directory / "hybrid-best.txt" + shutil.copyfile(str(best["challenge"]), best_path) + result = { + "instance": name, + "abc_incomplete": incomplete_audit, + "exact_candidates": candidates, + "best": {**best, "copied_challenge": str(best_path)}, + } + routes.atomic_json(directory / "abc-flow-summary.json", result) + return result + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--work", required=True) + parser.add_argument("--abc", required=True) + args = parser.parse_args() + work = Path(args.work).resolve() + abc = Path(args.abc).resolve() + if not os.access(abc, os.X_OK): + raise ValueError(f"ABC executable missing: {abc}") + version = subprocess.run( + (str(abc), "-c", "version; quit"), + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + check=True, + ).stdout + summary: dict[str, object] = { + "schema": "occam71-native-abc-and-hybrid-v1", + "root_seed": routes.ROOT_SEED, + "abc": str(abc), + "abc_sha256": routes.sha256_file(abc), + "abc_version_output": version, + "instances": {}, + } + for name in routes.INSTANCE_NAMES: + summary["instances"][name] = run_one(name, work, abc) + routes.atomic_json(work / "abc-flow-summary.json", summary) + (work / "ABC_FLOW_COMPLETE").write_text( + "success\n", encoding="utf-8", newline="\n" + ) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/run_espresso_only.py b/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/run_espresso_only.py new file mode 100644 index 000000000..6fa0b0237 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/run_espresso_only.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +"""Run standalone Berkeley Espresso on incomplete PLAs and audit its circuits.""" + +from __future__ import annotations + +import argparse +import json +import os +from pathlib import Path +import subprocess + +import routes +from run_experiments import abc_map, convert_and_audit, run_logged + + +def run_one(name: str, work: Path, abc: Path, espresso: Path) -> dict[str, object]: + directory = work / name + source = directory / "train-incomplete.pla" + minimized = directory / "espresso-minimized.pla" + run_logged( + (str(espresso), "-o", "f", str(source)), + directory / "espresso.log", + stdout_path=minimized, + ) + + mapped = directory / "espresso-incomplete-k2.blif" + abc_map(abc, minimized, mapped, directory / "abc-espresso-incomplete.log") + challenge = directory / "espresso-incomplete.txt" + audit_path = directory / "espresso-incomplete-audit.json" + audit = convert_and_audit( + directory / "discovery.json", + mapped, + challenge, + audit_path, + require_exact=False, + ) + result = { + "instance": name, + "input_pla": str(source), + "minimized_pla": str(minimized), + "mapped_blif": str(mapped), + "challenge": str(challenge), + "audit": str(audit_path), + "result": audit, + } + routes.atomic_json(directory / "espresso-flow-summary.json", result) + return result + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--work", required=True) + parser.add_argument("--abc", required=True) + parser.add_argument("--espresso", required=True) + args = parser.parse_args() + work = Path(args.work).resolve() + abc = Path(args.abc).resolve() + espresso = Path(args.espresso).resolve() + for executable in (abc, espresso): + if not os.access(executable, os.X_OK): + raise ValueError(f"executable missing: {executable}") + + espresso_help = subprocess.run( + (str(espresso), "-h"), + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + check=False, + ).stdout + abc_version = subprocess.run( + (str(abc), "-c", "version; quit"), + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + check=True, + ).stdout + summary: dict[str, object] = { + "schema": "occam71-standalone-berkeley-espresso-incomplete-v1", + "root_seed": routes.ROOT_SEED, + "abc": str(abc), + "abc_sha256": routes.sha256_file(abc), + "abc_version_output": abc_version, + "espresso": str(espresso), + "espresso_sha256": routes.sha256_file(espresso), + "espresso_help_output": espresso_help, + "instances": {}, + } + for name in routes.INSTANCE_NAMES: + summary["instances"][name] = run_one(name, work, abc, espresso) + routes.atomic_json(work / "espresso-flow-summary.json", summary) + (work / "ESPRESSO_FLOW_COMPLETE").write_text( + "success\n", encoding="utf-8", newline="\n" + ) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/run_experiments.py b/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/run_experiments.py new file mode 100644 index 000000000..211255113 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/run_experiments.py @@ -0,0 +1,280 @@ +#!/usr/bin/env python3 +"""Run pinned Espresso and ABC flows, convert K=2 LUTs, and audit exhaustively.""" + +from __future__ import annotations + +import argparse +import json +import os +from pathlib import Path +import shutil +import subprocess +import sys +from typing import Sequence + +import routes + + +def run_logged( + argv: Sequence[str], + log_path: Path, + *, + stdout_path: Path | None = None, +) -> None: + log_path.parent.mkdir(parents=True, exist_ok=True) + if stdout_path is None: + completed = subprocess.run( + list(argv), + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + check=False, + ) + log_path.write_text(completed.stdout, encoding="utf-8", newline="\n") + else: + with stdout_path.open("w", encoding="utf-8", newline="\n") as output: + completed = subprocess.run( + list(argv), + text=True, + stdout=output, + stderr=subprocess.PIPE, + check=False, + ) + log_path.write_text(completed.stderr, encoding="utf-8", newline="\n") + if completed.returncode: + raise RuntimeError( + f"command failed ({completed.returncode}): {list(argv)!r}; log={log_path}" + ) + + +def abc_map(abc: Path, source: Path, destination: Path, log_path: Path) -> None: + command = ( + f"read {source}; " + "strash; balance; rewrite; refactor; rewrite -z; balance; " + "if -K 2; print_stats; " + f"write_blif {destination}" + ) + run_logged((str(abc), "-c", command), log_path) + if not destination.is_file(): + raise RuntimeError(f"ABC did not create {destination}") + + +def abc_map_incomplete( + abc: Path, source: Path, destination: Path, log_path: Path +) -> None: + # collapse selects an ISOP using the global pExdc care network. + command = ( + f"read_blif {source}; strash; collapse; strash; " + "balance; rewrite; refactor; rewrite -z; balance; " + "if -K 2; print_stats; " + f"write_blif {destination}" + ) + run_logged((str(abc), "-c", command), log_path) + if not destination.is_file(): + raise RuntimeError(f"ABC did not create {destination}") + + +def convert_and_audit( + discovery_path: Path, + mapped_blif: Path, + challenge_path: Path, + audit_path: Path, + *, + require_exact: bool, +) -> dict[str, object]: + routes.command_convert_k2( + type("Args", (), {"blif": str(mapped_blif), "out": str(challenge_path)}) + ) + routes.command_verify( + type( + "Args", + (), + { + "discovery": str(discovery_path), + "circuit": str(challenge_path), + "out": str(audit_path), + "require_exact": require_exact, + }, + ) + ) + return json.loads(audit_path.read_text(encoding="utf-8")) + + +def run_one(name: str, work: Path, abc: Path, espresso: Path) -> dict[str, object]: + directory = work / name + discovery_path = directory / "discovery.json" + + # Confirm ABC's BLIF reader recognizes the explicit external don't-care + # network. This roundtrip is a format check, not the minimization route. + exdc_roundtrip = directory / "train-incomplete-exdc.roundtrip.blif" + run_logged( + ( + str(abc), + "-c", + f"read_blif {directory / 'train-incomplete-exdc.blif'}; " + f"print_stats; write_blif {exdc_roundtrip}", + ), + directory / "abc-exdc-roundtrip.log", + ) + + abc_incomplete_blif = directory / "abc-incomplete-k2.blif" + abc_map_incomplete( + abc, + directory / "train-incomplete-exdc.blif", + abc_incomplete_blif, + directory / "abc-incomplete.log", + ) + abc_incomplete_audit = convert_and_audit( + discovery_path, + abc_incomplete_blif, + directory / "abc-incomplete.txt", + directory / "abc-incomplete-audit.json", + require_exact=False, + ) + + # Standalone Berkeley Espresso consumes F/D/R from `.type fr`. ABC's + # `read_pla` intentionally ignores `.type`, so it is used only after + # Espresso has selected a fully specified ON cover. + minimized = directory / "espresso-minimized.pla" + run_logged( + (str(espresso), "-o", "f", str(directory / "train-incomplete.pla")), + directory / "espresso.log", + stdout_path=minimized, + ) + incomplete_blif = directory / "espresso-incomplete-k2.blif" + abc_map( + abc, + minimized, + incomplete_blif, + directory / "abc-espresso-incomplete.log", + ) + incomplete_audit = convert_and_audit( + discovery_path, + incomplete_blif, + directory / "espresso-incomplete.txt", + directory / "espresso-incomplete-audit.json", + require_exact=False, + ) + + exact_candidates: list[dict[str, object]] = [] + bdd_metrics = json.loads( + (directory / "bdd-metrics.json").read_text(encoding="utf-8") + ) + for order_name, order_data in sorted(bdd_metrics["orders"].items()): + source = Path(order_data["blif_path"]) + mapped = directory / f"hybrid-bdd-{order_name}-k2.blif" + challenge = directory / f"hybrid-bdd-{order_name}.txt" + audit_path = directory / f"hybrid-bdd-{order_name}-audit.json" + abc_map(abc, source, mapped, directory / f"abc-bdd-{order_name}.log") + audit = convert_and_audit( + discovery_path, + mapped, + challenge, + audit_path, + require_exact=True, + ) + exact_candidates.append( + { + "route": f"bdd-{order_name}", + "source_nodes": order_data["nonterminal_nodes"], + "challenge": str(challenge), + "audit": str(audit_path), + "gates": audit["gates"], + } + ) + + # Also hand the frozen semantic truth table directly to ABC. This controls + # for any restriction introduced by the selected BDD decomposition. + semantic_mapped = directory / "hybrid-semantic-full-k2.blif" + semantic_challenge = directory / "hybrid-semantic-full.txt" + semantic_audit_path = directory / "hybrid-semantic-full-audit.json" + abc_map( + abc, + directory / "semantic-full.pla", + semantic_mapped, + directory / "abc-semantic-full.log", + ) + semantic_audit = convert_and_audit( + discovery_path, + semantic_mapped, + semantic_challenge, + semantic_audit_path, + require_exact=True, + ) + exact_candidates.append( + { + "route": "semantic-full", + "source_nodes": None, + "challenge": str(semantic_challenge), + "audit": str(semantic_audit_path), + "gates": semantic_audit["gates"], + } + ) + + best = min(exact_candidates, key=lambda item: (item["gates"], item["route"])) + best_path = directory / "hybrid-best.txt" + shutil.copyfile(best["challenge"], best_path) + best_audit = convert_and_audit( + discovery_path, + Path(best["challenge"]).with_name( + Path(best["challenge"]).stem + "-k2.blif" + ) + if False + else ( + semantic_mapped + if best["route"] == "semantic-full" + else directory / f"hybrid-{best['route']}-k2.blif" + ), + directory / "hybrid-best-reconverted.txt", + directory / "hybrid-best-reconverted-audit.json", + require_exact=True, + ) + # Preserve the selected original byte-for-byte; the reconversion above is + # a deterministic audit of the selected mapped BLIF. + if routes.sha256_file(best_path) != routes.sha256_file( + directory / "hybrid-best-reconverted.txt" + ): + raise RuntimeError("selected hybrid conversion is not deterministic") + + result = { + "instance": name, + "espresso_incomplete": incomplete_audit, + "abc_incomplete": abc_incomplete_audit, + "exact_candidates": exact_candidates, + "best": { + **best, + "copied_challenge": str(best_path), + "reconversion_audit": best_audit, + }, + } + routes.atomic_json(directory / "flow-summary.json", result) + return result + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--work", required=True) + parser.add_argument("--abc", required=True) + parser.add_argument("--espresso", required=True) + args = parser.parse_args() + work = Path(args.work).resolve() + abc = Path(args.abc).resolve() + espresso = Path(args.espresso).resolve() + if not os.access(abc, os.X_OK) or not os.access(espresso, os.X_OK): + raise ValueError("ABC/Espresso executable missing") + + summary: dict[str, object] = { + "schema": "occam71-official-espresso-abc-hybrid-v1", + "root_seed": routes.ROOT_SEED, + "abc": str(abc), + "espresso": str(espresso), + "instances": {}, + } + for name in routes.INSTANCE_NAMES: + summary["instances"][name] = run_one(name, work, abc, espresso) + routes.atomic_json(work / "flow-summary.json", summary) + (work / "FLOW_COMPLETE").write_text("success\n", encoding="utf-8", newline="\n") + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/run_frontier_abc.py b/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/run_frontier_abc.py new file mode 100644 index 000000000..ebb224767 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/run_frontier_abc.py @@ -0,0 +1,182 @@ +#!/usr/bin/env python3 +"""Audit native ABC transformations of the exact C154 and D109 frontiers.""" + +from __future__ import annotations + +import argparse +import json +import os +from pathlib import Path +import subprocess +import sys +from typing import Sequence + +import routes +from run_experiments import convert_and_audit + + +INSTANCES = { + "C": { + "expected_sha256": ( + "f9396ee77462152c03fe061a4676944dc04e83787ef2a3915263bd2be8010bd9" + ), + "expected_gates": 154, + "discovery_dir": "mystery-C", + }, + "D": { + "expected_sha256": ( + "cd3f317f4a0b88818e54869e40b4550fd67549f8eb4a5eb02c74db4ec6864dbd" + ), + "expected_gates": 109, + "discovery_dir": "mystery-D", + }, +} + +FLOWS = { + "roundtrip": "", + "strash_if": "strash; if -K 2;", + "dc2_if": "strash; dc2; if -K 2;", + "resyn2_explicit_if": ( + "strash; balance; rewrite; refactor; balance; rewrite; " + "rewrite -z; balance; refactor -z; rewrite -z; balance; if -K 2;" + ), + "xag_put_if": "&get; &xag; &put; if -K 2;", + "xag_put_strash_if": "&get; &xag; &put; strash; if -K 2;", + "gia_dc2_put_if": "&get; &dc2; &put; if -K 2;", +} + + +def run( + argv: Sequence[str], + log: Path, +) -> subprocess.CompletedProcess[str]: + completed = subprocess.run( + list(argv), + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + check=False, + ) + log.parent.mkdir(parents=True, exist_ok=True) + log.write_text(completed.stdout, encoding="utf-8", newline="\n") + return completed + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--work", required=True) + parser.add_argument("--abc", required=True) + parser.add_argument("--bridge", required=True) + parser.add_argument("--python", default=sys.executable) + parser.add_argument("--circuit-c", required=True) + parser.add_argument("--circuit-d", required=True) + args = parser.parse_args() + + work = Path(args.work).resolve() + abc = Path(args.abc).resolve() + bridge = Path(args.bridge).resolve() + circuits = {"C": Path(args.circuit_c), "D": Path(args.circuit_d)} + if not os.access(abc, os.X_OK): + raise ValueError(f"ABC executable missing: {abc}") + + report: dict[str, object] = { + "schema": "occam71-frontier-native-abc-v1", + "root_seed": routes.ROOT_SEED, + "abc": str(abc), + "abc_sha256": routes.sha256_file(abc), + "flows": FLOWS, + "instances": {}, + } + for instance, config in INSTANCES.items(): + source = circuits[instance].resolve() + actual_sha = routes.sha256_file(source) + if actual_sha != config["expected_sha256"]: + raise ValueError(f"{instance} source SHA mismatch: {actual_sha}") + + directory = work / instance + directory.mkdir(parents=True, exist_ok=True) + source_blif = directory / "source.blif" + bridge_run = run( + ( + args.python, + "-u", + str(bridge), + "to-blif", + str(source), + str(source_blif), + "--model", + f"frontier_{instance}", + ), + directory / "bridge.log", + ) + if bridge_run.returncode or not source_blif.is_file(): + raise RuntimeError(f"bridge failed for {instance}") + + discovery = ( + work.parent + / "symbolic-hybrid-seed42" + / str(config["discovery_dir"]) + / "discovery.json" + ) + instance_report: dict[str, object] = { + "source": str(source), + "source_sha256": actual_sha, + "source_gates": config["expected_gates"], + "source_blif": str(source_blif), + "routes": {}, + } + for flow_name, flow in FLOWS.items(): + mapped = directory / f"{flow_name}.blif" + command = ( + f"read_blif {source_blif}; {flow} " + f"print_stats; write_blif {mapped}" + ) + completed = run( + (str(abc), "-c", command), + directory / f"{flow_name}.abc.log", + ) + route_report: dict[str, object] = { + "command": command, + "abc_returncode": completed.returncode, + "abc_log": str(directory / f"{flow_name}.abc.log"), + } + if completed.returncode or not mapped.is_file(): + route_report["status"] = "ABC_FAILED_NOT_COMPARABLE" + else: + try: + audit_path = directory / f"{flow_name}.audit.json" + audit = convert_and_audit( + discovery, + mapped, + directory / f"{flow_name}.txt", + audit_path, + require_exact=True, + ) + except Exception as exc: # preserve an honest incomparable result + route_report["status"] = "CONVERSION_OR_AUDIT_FAILED" + route_report["error"] = f"{type(exc).__name__}: {exc}" + else: + route_report.update( + { + "status": "EXACT_COMPARABLE", + "mapped_blif": str(mapped), + "challenge": str(directory / f"{flow_name}.txt"), + "audit": str(audit_path), + "gates": audit["gates"], + "full_mismatches": audit[ + "full_domain_vector_mismatches" + ], + } + ) + instance_report["routes"][flow_name] = route_report + routes.atomic_json(directory / "summary.partial.json", instance_report) + report["instances"][instance] = instance_report + + routes.atomic_json(work / "frontier-abc-summary.json", report) + (work / "FRONTIER_ABC_COMPLETE").write_text( + "success\n", encoding="utf-8", newline="\n" + ) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/run_frontier_abc_c152.py b/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/run_frontier_abc_c152.py new file mode 100644 index 000000000..247138f30 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/run_frontier_abc_c152.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +"""Run the native ABC audit matrix on the current exact C152/D109 frontier.""" + +from __future__ import annotations + +import run_frontier_abc as base + + +base.INSTANCES["C"] = { + "expected_sha256": ( + "67540307369fedfffdb2b1a6473eff5e0bbfeb0e4873d03fddbeceb653cd071c" + ), + "expected_gates": 152, + "discovery_dir": "mystery-C", +} + + +if __name__ == "__main__": + base.main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/slurm_build_abc.sh b/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/slurm_build_abc.sh new file mode 100644 index 000000000..3b32d1f37 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/slurm_build_abc.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +#SBATCH --job-name=occam71-abc-build +#SBATCH --cpus-per-task=8 +#SBATCH --mem=16G +#SBATCH --time=01:00:00 +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/routes/symbolic-hybrid-seed42/logs/abc-build-%j.out +#SBATCH --error=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/routes/symbolic-hybrid-seed42/logs/abc-build-%j.err + +set -euo pipefail + +repo=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +archive="$repo/results/occam71/tools/abc-source-e76768b9d34f.tar" +tool_root="$repo/results/occam71/tools/abc-e76768b9d34f" +build="$tool_root/job-$SLURM_JOB_ID" +expected_archive_sha=6a2cb045808579a9c7fe1758b94de968dcf4cbb114c7b726091e776d40b9cdf9 + +mkdir -p "$repo/results/occam71/routes/symbolic-hybrid-seed42/logs" +mkdir -p "$build/source" "$build/bin" + +actual_archive_sha=$(sha256sum "$archive" | awk '{print $1}') +if [[ "$actual_archive_sha" != "$expected_archive_sha" ]]; then + echo "archive SHA mismatch: $actual_archive_sha" >&2 + exit 2 +fi + +tar -xf "$archive" -C "$build/source" +cd "$build/source" +make -j "$SLURM_CPUS_PER_TASK" ABC_MAKE_NO_DEPS=1 ABC_USE_NO_READLINE=1 +make -j "$SLURM_CPUS_PER_TASK" ABC_MAKE_NO_DEPS=1 ABC_USE_NO_READLINE=1 libabc.a + +cp -p abc "$build/bin/abc" + +# ABC embeds the original Berkeley Espresso sources but intentionally excludes +# its CLI main.c from libabc.a. Compile that official entry point and link it +# against the just-built pinned library, so `.type fr` reaches espresso(F,D,R). +gcc -std=gnu89 -Wno-implicit-int -Wno-return-type \ + -Isrc -Isrc/misc/util -include src/misc/util/abc_global.h $(./arch_flags) \ + -c src/misc/espresso/main.c -o "$build/espresso-main.o" +g++ "$build/espresso-main.o" libabc.a \ + -lm -ldl -lpthread -o "$build/bin/espresso" + +"$build/bin/abc" -c "version" > "$build/abc-version.txt" +printf '.i 1\n.o 1\n.type fr\n.p 1\n0 1\n.e\n' > "$build/smoke.pla" +"$build/bin/espresso" -o f "$build/smoke.pla" > "$build/smoke-minimized.pla" +grep -q '^0 1$' "$build/smoke-minimized.pla" + +sha256sum "$build/bin/abc" "$build/bin/espresso" > "$build/tool-sha256.txt" +{ + printf 'source_commit=%s\n' e76768b9d34f9dc67cb6608efecd55db271ff849 + printf 'source_archive_sha256=%s\n' "$actual_archive_sha" + printf 'slurm_job_id=%s\n' "$SLURM_JOB_ID" + printf 'slurm_node=%s\n' "$SLURMD_NODENAME" + printf 'root_seed=%s\n' 42 +} > "$build/build-metadata.txt" +printf 'success\n' > "$build/BUILD_COMPLETE" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/slurm_espresso_berkeley.sh b/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/slurm_espresso_berkeley.sh new file mode 100644 index 000000000..57812c1d2 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/slurm_espresso_berkeley.sh @@ -0,0 +1,112 @@ +#!/usr/bin/env bash +#SBATCH --job-name=occ71-espresso-ucb +#SBATCH --cpus-per-task=4 +#SBATCH --mem=16G +#SBATCH --time=01:00:00 +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/routes/symbolic-hybrid-seed42/logs/espresso-ucb-%j.out +#SBATCH --error=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/routes/symbolic-hybrid-seed42/logs/espresso-ucb-%j.err + +set -euo pipefail +export PYTHONHASHSEED=42 +export LC_ALL=C + +repo=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +code="$repo/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid" +work="$repo/results/occam71/routes/symbolic-hybrid-seed42" +archive="$repo/results/occam71/tools/espresso-berkeley-1994.tar.gz" +abc="$repo/results/occam71/tools/abc-e76768b9d34f/job-42912/source/abc" +tool_root="$repo/results/occam71/tools/espresso-berkeley-1994" +build="$tool_root/job-$SLURM_JOB_ID" +expected_archive_sha=757256c24d33f343068a67f51a57f8dfbe8af01d122a8434ef99572544cb4802 + +mkdir -p "$work/logs" "$build/source" "$build/bin" +actual_archive_sha=$(sha256sum "$archive" | awk '{print $1}') +if [[ "$actual_archive_sha" != "$expected_archive_sha" ]]; then + echo "official Berkeley source archive SHA mismatch" >&2 + exit 2 +fi +tar -xzf "$archive" -C "$build/source" +compat_patch="$code/espresso-berkeley-errtrap-stdarg.patch" +compat_patch_sha=$(sha256sum "$compat_patch" | awk '{print $1}') +( + cd "$build/source" + chmod u+w errtrap/errtrap.c errtrap/errtrap.h + patch --batch -p1 < "$compat_patch" +) + +# Build the 1994 U.C. Berkeley distribution with its own original support +# libraries. gnu89 and -fcommon restore the C dialect/global-symbol semantics +# assumed by this pre-ANSI-era release. The only source patch mechanically +# translates the obsolete errtrap support library from varargs.h to stdarg.h; +# no Espresso algorithm source is changed. +cd "$build/source" +mkdir -p lib include doc bin man/man1 man/man5 +compiler="gcc -std=gnu89 -fcommon" +( + cd utility + make ../include/utility.h CAD=.. CC="$compiler" COMPFLAG=-O2 +) +# The archived `port` package reimplements ANSI libc routines such as strlen +# and strstr. Those shims conflict with Rocky Linux's modern libc prototypes, +# so install its public headers but deliberately link an empty compatibility +# archive; all required routines are supplied by the system libc. +cp -p port/port.h port/ansi.h port/copyright.h include/ +cp -p uprintf/uprintf.h errtrap/errtrap.h st/st.h mm/mm.h include/ +ar rcs lib/libport.a +# The archived mm package replaces malloc/free/realloc and conflicts with +# modern libc prototypes. Its public header is empty and Espresso needs no +# package-specific symbols, so retain the expected archive name while using +# the system allocator. +ar rcs lib/libmm.a +for package in uprintf errtrap st; do + ( + cd "$package" + make "lib${package}.a" CAD=.. CC="$compiler" COMPFLAG=-O2 + ) + cp -p "$package/lib${package}.a" lib/ +done +( + cd utility + make libutility.a \ + OBJ='cpu_time.o prtime.o strsav.o' \ + CAD=.. CC="$compiler" COMPFLAG=-O2 +) +cp -p utility/libutility.a lib/ +( + cd espresso + make espresso CAD=.. CC="$compiler" COMPFLAG=-O2 +) +cp -p espresso/espresso "$build/bin/espresso" +cp -p "$abc" "$build/bin/abc" + +printf '.i 1\n.o 1\n.type fr\n.p 1\n0 1\n.e\n' > "$build/smoke.pla" +"$build/bin/espresso" -o f "$build/smoke.pla" \ + > "$build/smoke-minimized.pla" 2> "$build/smoke.log" +grep -q '^0 1$' "$build/smoke-minimized.pla" + +sha256sum "$build/bin/espresso" "$build/bin/abc" \ + "$build/smoke.pla" "$build/smoke-minimized.pla" \ + > "$build/tool-and-smoke-sha256.txt" +{ + printf 'source_url=%s\n' \ + 'https://ptolemy.berkeley.edu/projects/embedded/pubs/downloads/espresso/espresso.tar.gz' + printf 'source_archive_sha256=%s\n' "$actual_archive_sha" + printf 'source_last_updated=%s\n' '1994-11-01' + printf 'compiler_mode=%s\n' 'gcc -std=gnu89 -fcommon -O2' + printf 'algorithm_source_patches=%s\n' 'none' + printf 'support_compatibility_patch=%s\n' \ + 'espresso-berkeley-errtrap-stdarg.patch' + printf 'support_compatibility_patch_sha256=%s\n' "$compat_patch_sha" + printf 'compatibility_action=%s\n' \ + 'system libc replaces archived port string/memory shims and mm allocator; errtrap varargs.h mechanically translated to stdarg.h; utility archive contains only Espresso-referenced cpu_time/prtime/strsav objects' + printf 'slurm_job_id=%s\n' "$SLURM_JOB_ID" + printf 'slurm_node=%s\n' "$SLURMD_NODENAME" + printf 'root_seed=%s\n' 42 +} > "$build/build-metadata.txt" +printf 'success\n' > "$build/SMOKE_COMPLETE" + +python -u "$code/run_espresso_only.py" \ + --work "$work" \ + --abc "$build/bin/abc" \ + --espresso "$build/bin/espresso" +printf 'success\n' > "$build/BUILD_AND_RUN_COMPLETE" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/slurm_finish_espresso.sh b/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/slurm_finish_espresso.sh new file mode 100644 index 000000000..29bb445dc --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/slurm_finish_espresso.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +#SBATCH --job-name=occam71-espresso +#SBATCH --cpus-per-task=4 +#SBATCH --mem=16G +#SBATCH --time=01:00:00 +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/routes/symbolic-hybrid-seed42/logs/espresso-%j.out +#SBATCH --error=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/routes/symbolic-hybrid-seed42/logs/espresso-%j.err + +set -euo pipefail + +repo=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +code="$repo/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid" +work="$repo/results/occam71/routes/symbolic-hybrid-seed42" +source="$repo/results/occam71/tools/abc-e76768b9d34f/job-42912/source" +archive="$repo/results/occam71/tools/abc-source-e76768b9d34f.tar" +tool_root="$repo/results/occam71/tools/espresso-e76768b9d34f" +build="$tool_root/job-$SLURM_JOB_ID" +expected_commit=e76768b9d34f9dc67cb6608efecd55db271ff849 +expected_archive_sha=6a2cb045808579a9c7fe1758b94de968dcf4cbb114c7b726091e776d40b9cdf9 +expected_main_sha=2361118aac6635e52f21f71b08b6df8e5a1444f9d6e42021c1c5b1c2be736b0b +expected_abc_sha=a971b5a85892e3bf2d09b6d62eaf6f608d7638936e39d6e4b3095e9c72c9c771 + +mkdir -p "$work/logs" "$build/bin" +test -x "$source/abc" +test -f "$source/libabc.a" +actual_archive_sha=$(sha256sum "$archive" | awk '{print $1}') +actual_main_sha=$(sha256sum "$source/src/misc/espresso/main.c" | awk '{print $1}') +actual_abc_sha=$(sha256sum "$source/abc" | awk '{print $1}') +if [[ "$actual_archive_sha" != "$expected_archive_sha" ]] || + [[ "$actual_main_sha" != "$expected_main_sha" ]] || + [[ "$actual_abc_sha" != "$expected_abc_sha" ]]; then + echo "pinned source/tool hash mismatch" >&2 + exit 2 +fi + +# The embedded standalone CLI is official Berkeley Espresso source. In ABC's +# tree its historical include points at ABC's application header rather than +# the adjacent Espresso CLI header; restore that one include in a recorded copy. +sed 's#"base/main/main.h"#"main.h"#' \ + "$source/src/misc/espresso/main.c" > "$build/espresso-cli-main.c" +diff -u "$source/src/misc/espresso/main.c" \ + "$build/espresso-cli-main.c" > "$build/espresso-cli-include.patch" || true + +cd "$source" +gcc -std=gnu89 -Wno-implicit-int -Wno-return-type \ + -Isrc -Isrc/misc/util -Isrc/misc/espresso \ + -include src/misc/util/abc_global.h $(./arch_flags) \ + -c "$build/espresso-cli-main.c" -o "$build/espresso-main.o" +mkdir -p "$build/espresso-objects" +for c_source in src/misc/espresso/*.c; do + if [[ "$c_source" == "src/misc/espresso/main.c" ]]; then + continue + fi + object="$build/espresso-objects/$(basename "${c_source%.c}").o" + gcc -std=gnu89 -Wno-implicit-int -Wno-return-type \ + -Isrc -Isrc/misc/util -Isrc/misc/espresso \ + -include src/misc/util/abc_global.h $(./arch_flags) \ + -c "$c_source" -o "$object" +done +g++ "$build/espresso-main.o" "$build"/espresso-objects/*.o libabc.a \ + -lm -ldl -lpthread -o "$build/bin/espresso" +cp -p abc "$build/bin/abc" + +printf '.i 1\n.o 1\n.type fr\n.p 1\n0 1\n.e\n' > "$build/smoke.pla" +"$build/bin/espresso" -o f "$build/smoke.pla" \ + > "$build/smoke-minimized.pla" 2> "$build/smoke.log" +grep -q '^0 1$' "$build/smoke-minimized.pla" + +sha256sum "$build/bin/abc" "$build/bin/espresso" \ + "$build/espresso-cli-main.c" "$build/espresso-cli-include.patch" \ + > "$build/tool-sha256.txt" +{ + printf 'source_commit=%s\n' "$expected_commit" + printf 'source_archive_sha256=%s\n' "$actual_archive_sha" + printf 'source_main_sha256=%s\n' "$actual_main_sha" + printf 'abc_sha256=%s\n' "$actual_abc_sha" + printf 'source_tree=%s\n' "$source" + printf 'slurm_job_id=%s\n' "$SLURM_JOB_ID" + printf 'slurm_node=%s\n' "$SLURMD_NODENAME" + printf 'root_seed=%s\n' 42 +} > "$build/build-metadata.txt" +printf 'success\n' > "$build/SMOKE_COMPLETE" + +python -u "$code/run_espresso_only.py" \ + --work "$work" --abc "$build/bin/abc" \ + --espresso "$build/bin/espresso" +printf 'success\n' > "$build/BUILD_AND_RUN_COMPLETE" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/slurm_frontier_abc.sh b/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/slurm_frontier_abc.sh new file mode 100644 index 000000000..b1b994bb2 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/slurm_frontier_abc.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +#SBATCH --job-name=occ71-frontier-abc +#SBATCH --cpus-per-task=2 +#SBATCH --mem=8G +#SBATCH --time=00:30:00 +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/routes/frontier-abc-seed42/logs/job-%j.out +#SBATCH --error=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/routes/frontier-abc-seed42/logs/job-%j.err + +set -euo pipefail +export PYTHONHASHSEED=42 + +repo=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +code="$repo/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid" +search="$repo/tracks/qcs/solutions/Genshin_Impact-71/search" +work="$repo/results/occam71/routes/frontier-abc-seed42" +abc="$repo/results/occam71/tools/abc-e76768b9d34f/job-42912/source/abc" +circuit_c="$repo/results/occam71/frontier-349/netlists/mystery-C.txt" +circuit_d="$repo/results/occam71/frontier-349/netlists/mystery-D.txt" + +mkdir -p "$work/logs" +test "$(sha256sum "$abc" | awk '{print $1}')" = \ + a971b5a85892e3bf2d09b6d62eaf6f608d7638936e39d6e4b3095e9c72c9c771 +test "$(sha256sum "$circuit_c" | awk '{print $1}')" = \ + f9396ee77462152c03fe061a4676944dc04e83787ef2a3915263bd2be8010bd9 +test "$(sha256sum "$circuit_d" | awk '{print $1}')" = \ + cd3f317f4a0b88818e54869e40b4550fd67549f8eb4a5eb02c74db4ec6864dbd + +python -u "$code/run_frontier_abc.py" \ + --work "$work" \ + --abc "$abc" \ + --bridge "$search/bridge.py" \ + --circuit-c "$circuit_c" \ + --circuit-d "$circuit_d" + +{ + printf 'slurm_job_id=%s\n' "$SLURM_JOB_ID" + printf 'slurm_node=%s\n' "$SLURMD_NODENAME" + printf 'root_seed=%s\n' 42 + printf 'abc_sha256=%s\n' "$(sha256sum "$abc" | awk '{print $1}')" +} > "$work/run-metadata.txt" +sha256sum "$work/frontier-abc-summary.json" \ + "$work/C/summary.partial.json" "$work/D/summary.partial.json" \ + > "$work/SHA256SUMS" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/slurm_frontier_abc_c152.sh b/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/slurm_frontier_abc_c152.sh new file mode 100644 index 000000000..4a077dd91 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/slurm_frontier_abc_c152.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +#SBATCH --job-name=occ71-c152-abc +#SBATCH --cpus-per-task=2 +#SBATCH --mem=8G +#SBATCH --time=00:30:00 +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/routes/frontier-abc-c152-d109-seed42/logs/job-%j.out +#SBATCH --error=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/routes/frontier-abc-c152-d109-seed42/logs/job-%j.err + +set -euo pipefail +export PYTHONHASHSEED=42 + +repo=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +code="$repo/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid" +search="$repo/tracks/qcs/solutions/Genshin_Impact-71/search" +work="$repo/results/occam71/routes/frontier-abc-c152-d109-seed42" +abc="$repo/results/occam71/tools/abc-e76768b9d34f/job-42912/source/abc" +circuit_c="$repo/results/occam71/eslim/C-stage2-from154-grid-seed42/03-size7-job43188/candidate.txt" +circuit_d="$repo/results/occam71/frontier-349/netlists/mystery-D.txt" + +mkdir -p "$work/logs" +test "$(sha256sum "$abc" | awk '{print $1}')" = \ + a971b5a85892e3bf2d09b6d62eaf6f608d7638936e39d6e4b3095e9c72c9c771 +test "$(sha256sum "$circuit_c" | awk '{print $1}')" = \ + 67540307369fedfffdb2b1a6473eff5e0bbfeb0e4873d03fddbeceb653cd071c +test "$(sha256sum "$circuit_d" | awk '{print $1}')" = \ + cd3f317f4a0b88818e54869e40b4550fd67549f8eb4a5eb02c74db4ec6864dbd + +python -u "$code/run_frontier_abc_c152.py" \ + --work "$work" \ + --abc "$abc" \ + --bridge "$search/bridge.py" \ + --circuit-c "$circuit_c" \ + --circuit-d "$circuit_d" + +{ + printf 'slurm_job_id=%s\n' "$SLURM_JOB_ID" + printf 'slurm_node=%s\n' "$SLURMD_NODENAME" + printf 'root_seed=%s\n' 42 + printf 'abc_sha256=%s\n' "$(sha256sum "$abc" | awk '{print $1}')" +} > "$work/run-metadata.txt" +sha256sum "$work/frontier-abc-summary.json" \ + "$work/C/summary.partial.json" "$work/D/summary.partial.json" \ + > "$work/SHA256SUMS" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/slurm_generate_templates.sh b/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/slurm_generate_templates.sh new file mode 100644 index 000000000..17d69bade --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/slurm_generate_templates.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +#SBATCH --job-name=occ71-templates +#SBATCH --cpus-per-task=1 +#SBATCH --mem=8G +#SBATCH --time=00:20:00 +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/routes/arithmetic-templates-seed42/logs/job-%j.out +#SBATCH --error=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/routes/arithmetic-templates-seed42/logs/job-%j.err + +set -euo pipefail +export PYTHONHASHSEED=42 + +repo=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +code="$repo/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid" +search="$repo/tracks/qcs/solutions/Genshin_Impact-71/search" +discovery="$repo/results/occam71/routes/symbolic-hybrid-seed42" +out="$repo/results/occam71/routes/arithmetic-templates-seed42" + +mkdir -p "$out/logs" +python -u "$code/generate_arithmetic_templates.py" \ + --discovery-root "$discovery" \ + --search-dir "$search" \ + --out "$out" +{ + printf 'slurm_job_id=%s\n' "$SLURM_JOB_ID" + printf 'slurm_node=%s\n' "$SLURMD_NODENAME" + printf 'root_seed=%s\n' 42 +} > "$out/run-metadata.txt" +sha256sum "$out/template-summary.json" \ + "$out"/*/template.txt "$out"/*/template-audit.json \ + > "$out/SHA256SUMS" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/slurm_run_abc_only.sh b/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/slurm_run_abc_only.sh new file mode 100644 index 000000000..d6fc3d4c7 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/slurm_run_abc_only.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +#SBATCH --job-name=occ71-abc-routes +#SBATCH --cpus-per-task=8 +#SBATCH --mem=32G +#SBATCH --time=02:00:00 +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/routes/symbolic-hybrid-seed42/logs/abc-routes-%j.out +#SBATCH --error=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/routes/symbolic-hybrid-seed42/logs/abc-routes-%j.err + +set -euo pipefail + +repo=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +code="$repo/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid" +work="$repo/results/occam71/routes/symbolic-hybrid-seed42" +abc="$repo/results/occam71/tools/abc-e76768b9d34f/job-42912/source/abc" + +test -x "$abc" +mkdir -p "$work/logs" +cd "$code" +python3 -m py_compile routes.py run_experiments.py run_abc_only.py test_routes.py +python3 test_routes.py -v +python3 routes.py bdd --work "$work" +python3 run_abc_only.py --work "$work" --abc "$abc" +{ + printf 'slurm_job_id=%s\n' "$SLURM_JOB_ID" + printf 'slurm_node=%s\n' "$SLURMD_NODENAME" + printf 'root_seed=%s\n' 42 + sha256sum "$abc" routes.py run_experiments.py run_abc_only.py test_routes.py +} > "$work/abc-run-metadata-$SLURM_JOB_ID.txt" +test -f "$work/ABC_FLOW_COMPLETE" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/slurm_run_routes.sh b/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/slurm_run_routes.sh new file mode 100644 index 000000000..ae5b14d03 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/slurm_run_routes.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +#SBATCH --job-name=occam71-routes +#SBATCH --cpus-per-task=8 +#SBATCH --mem=32G +#SBATCH --time=02:00:00 +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/routes/symbolic-hybrid-seed42/logs/routes-%j.out +#SBATCH --error=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/routes/symbolic-hybrid-seed42/logs/routes-%j.err + +set -euo pipefail + +: "${ABC_BUILD_JOB:?submit with --export=ALL,ABC_BUILD_JOB=}" +repo=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +code="$repo/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid" +work="$repo/results/occam71/routes/symbolic-hybrid-seed42" +tools="$repo/results/occam71/tools/abc-e76768b9d34f/job-$ABC_BUILD_JOB" + +test -f "$tools/BUILD_COMPLETE" +test -x "$tools/bin/abc" +test -x "$tools/bin/espresso" +mkdir -p "$work/logs" + +cd "$code" +python3 -m py_compile routes.py run_experiments.py test_routes.py +python3 test_routes.py -v +python3 routes.py bdd --work "$work" +python3 run_experiments.py \ + --work "$work" \ + --abc "$tools/bin/abc" \ + --espresso "$tools/bin/espresso" + +{ + printf 'slurm_job_id=%s\n' "$SLURM_JOB_ID" + printf 'slurm_node=%s\n' "$SLURMD_NODENAME" + printf 'abc_build_job=%s\n' "$ABC_BUILD_JOB" + printf 'root_seed=%s\n' 42 + sha256sum routes.py run_experiments.py test_routes.py +} > "$work/run-metadata-$SLURM_JOB_ID.txt" +test -f "$work/FLOW_COMPLETE" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/test_routes.py b/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/test_routes.py new file mode 100644 index 000000000..4e32a41f3 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/routes/symbolic_hybrid/test_routes.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import tempfile +import unittest +from pathlib import Path + +import routes + + +class RoutesTest(unittest.TestCase): + def test_bit_encodings(self) -> None: + self.assertEqual(routes.bits_to_int("1001", "lsb"), 9) + self.assertEqual(routes.bits_to_int("1001", "msb"), 9) + self.assertEqual(routes.int_to_bits(6, 4, "lsb"), "0110") + self.assertEqual(routes.decode_operands("100101", "grouped", "lsb"), (1, 5)) + + def test_gate_representation_covers_all_nontrivial_two_input_functions(self) -> None: + projections_and_constants = { + (0, 0, 0, 0), + (1, 1, 1, 1), + (0, 0, 1, 1), + (1, 1, 0, 0), + (0, 1, 0, 1), + (1, 0, 1, 0), + } + for value in range(16): + signature = tuple((value >> bit) & 1 for bit in range(4)) + if signature in projections_and_constants: + continue + routes.canonical_gate_for_signature(signature) + + def test_blif_to_challenge_and_exhaustive_simulation(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + source = root / "xor.blif" + source.write_text( + ".model xor\n" + ".inputs a b\n" + ".outputs z\n" + ".names a b z\n" + "01 1\n" + "10 1\n" + ".end\n", + encoding="utf-8", + newline="\n", + ) + out = root / "xor.txt" + args = type("Args", (), {"blif": str(source), "out": str(out)}) + routes.command_convert_k2(args) + tables, _mask, gates = routes.simulate_challenge_bitparallel(out) + self.assertEqual(gates, 1) + self.assertEqual(tables, (0b0110,)) + + def test_incomplete_spec_audit(self) -> None: + rows = [("00", "0"), ("11", "1")] + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + pla = root / "train.pla" + blif = root / "train.blif" + routes.write_train_pla(pla, rows, 2, 1) + on, unseen = routes.write_train_exdc_blif(blif, rows, 2, 1) + audit = routes.audit_generated_specs( + pla, blif, rows, 2, 1, on, unseen + ) + self.assertEqual(audit["unseen_dont_care_minterms_per_output"], 2) + self.assertTrue(audit["checks"]["pla_blif_relation_agree"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/analyze_mffc.py b/tracks/qcs/solutions/Genshin_Impact-71/search/analyze_mffc.py new file mode 100644 index 000000000..aa8b3fc81 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/analyze_mffc.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +"""Read-only MFFC inventory using the strict parser in divisor_resynth.""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +from divisor_resynth import fanout_counts, mffc, parse_circuit, prune_and_compact + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("input", type=Path) + args = parser.parse_args() + circuit = prune_and_compact(parse_circuit(args.input)) + counts = fanout_counts(circuit) + roots = [] + for root in range(1, len(circuit.gates) + 1): + removed = mffc(circuit, root, counts) + if len(removed) > 1: + roots.append( + { + "root": root, + "mffc_size": len(removed), + "max_useful_cost": min(3, len(removed) - 1), + "excluded_prior_wires": sorted( + wire for wire in removed if wire < root + ), + } + ) + print( + json.dumps( + { + "gates": len(circuit.gates), + "roots_with_mffc_gt_1": roots, + }, + indent=2, + sort_keys=True, + ) + ) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/audit_arithmetic_formula.py b/tracks/qcs/solutions/Genshin_Impact-71/search/audit_arithmetic_formula.py new file mode 100644 index 000000000..3378f56f2 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/audit_arithmetic_formula.py @@ -0,0 +1,280 @@ +#!/usr/bin/env python3 +"""Independent exhaustive formula audit for all four Occam mysteries. + +No search, bridge, reference-netlist, or synthesis module is imported. The +candidate is strict-ASCII parsed and evaluated directly on the complete input +domain against the selected arithmetic formula. +""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import re +from dataclasses import dataclass +from pathlib import Path +from typing import Callable + + +GATE_RE = re.compile( + r"^(w[1-9][0-9]*) = (AND|OR|XOR|NAND|NOR|XNOR) " + r"(~?(?:x|w)[1-9][0-9]*) (~?(?:x|w)[1-9][0-9]*)$" +) +TOKEN_RE = re.compile(r"^(~)?([xw])([1-9][0-9]*)$") + + +@dataclass(frozen=True) +class Instance: + name: str + n: int + outputs: int + formula_name: str + formula: Callable[[int, int], int] + + +INSTANCES = { + "A": Instance("A", 8, 9, "x+y", lambda x, y: x + y), + "B": Instance("B", 7, 7, "abs(x-y)", lambda x, y: abs(x - y)), + "C": Instance("C", 6, 12, "x*y", lambda x, y: x * y), + "D": Instance("D", 5, 11, "x*x+y*y", lambda x, y: x * x + y * y), +} + + +@dataclass(frozen=True) +class Gate: + out: str + op: str + left: str + right: str + + +def parse_token(token: str) -> tuple[bool, str, int]: + match = TOKEN_RE.fullmatch(token) + if match is None: + raise ValueError(f"invalid token {token!r}") + return bool(match.group(1)), match.group(2), int(match.group(3)) + + +def parse_candidate( + path: Path, instance: Instance +) -> tuple[list[Gate], list[str], bytes]: + raw = path.read_bytes() + if not raw: + raise ValueError("empty candidate") + try: + text = raw.decode("ascii") + except UnicodeDecodeError as exc: + raise ValueError("candidate must be strict ASCII") from exc + lines = [line.strip() for line in text.splitlines() if line.strip()] + if len(lines) < 2: + raise ValueError("candidate is too short") + fields = lines[0].split() + if len(fields) != 2 or fields[0] != "INPUTS" or not fields[1].isdigit(): + raise ValueError("invalid INPUTS header") + n_inputs = int(fields[1]) + if n_inputs != 2 * instance.n: + raise ValueError( + f"instance {instance.name} requires {2 * instance.n} inputs, " + f"got {n_inputs}" + ) + + gates: list[Gate] = [] + defined = {f"x{i}" for i in range(1, n_inputs + 1)} + outputs: list[str] | None = None + for line_number, line in enumerate(lines[1:], start=2): + if line.startswith("OUTPUTS "): + if outputs is not None or line_number != len(lines): + raise ValueError("OUTPUTS must occur exactly once at EOF") + outputs = line.split()[1:] + continue + if outputs is not None: + raise ValueError("gate after OUTPUTS") + match = GATE_RE.fullmatch(line) + if match is None: + raise ValueError(f"invalid gate syntax on line {line_number}") + gate = Gate(*match.groups()) + expected_out = f"w{len(gates) + 1}" + if gate.out != expected_out: + raise ValueError( + f"non-canonical wire order: expected {expected_out}, got {gate.out}" + ) + if gate.out in defined: + raise ValueError(f"duplicate signal {gate.out}") + for token in (gate.left, gate.right): + _, prefix, index = parse_token(token) + if f"{prefix}{index}" not in defined: + raise ValueError(f"forward or unknown reference {token}") + gates.append(gate) + defined.add(gate.out) + + if outputs is None: + raise ValueError("missing OUTPUTS") + if len(outputs) != instance.outputs: + raise ValueError( + f"instance {instance.name} requires {instance.outputs} outputs, " + f"got {len(outputs)}" + ) + for token in outputs: + _, prefix, index = parse_token(token) + if f"{prefix}{index}" not in defined: + raise ValueError(f"unknown output {token}") + return gates, outputs, raw + + +def read_token(token: str, values: dict[str, int]) -> int: + inverted, prefix, index = parse_token(token) + return values[f"{prefix}{index}"] ^ int(inverted) + + +def apply_gate(op: str, left: int, right: int) -> int: + if op == "AND": + return left & right + if op == "OR": + return left | right + if op == "XOR": + return left ^ right + if op == "NAND": + return 1 ^ (left & right) + if op == "NOR": + return 1 ^ (left | right) + if op == "XNOR": + return 1 ^ (left ^ right) + raise AssertionError(op) + + +def exhaustive_audit( + instance: Instance, + gates: list[Gate], + outputs: list[str], +) -> tuple[list[dict[str, int]], dict[str, int]]: + assignments = 1 << (2 * instance.n) + truth_tables = { + **{f"x{i}": 0 for i in range(1, 2 * instance.n + 1)}, + **{gate.out: 0 for gate in gates}, + } + mismatches: list[dict[str, int]] = [] + limit = 1 << instance.n + for x in range(limit): + for y in range(limit): + row = x | (y << instance.n) + values = { + **{ + f"x{i + 1}": (x >> i) & 1 + for i in range(instance.n) + }, + **{ + f"x{i + instance.n + 1}": (y >> i) & 1 + for i in range(instance.n) + }, + } + for name, bit in values.items(): + truth_tables[name] |= bit << row + for gate in gates: + values[gate.out] = apply_gate( + gate.op, + read_token(gate.left, values), + read_token(gate.right, values), + ) + truth_tables[gate.out] |= values[gate.out] << row + actual = sum( + read_token(token, values) << bit + for bit, token in enumerate(outputs) + ) + expected = instance.formula(x, y) + if actual != expected and len(mismatches) < 16: + mismatches.append( + { + "x": x, + "y": y, + "expected": expected, + "actual": actual, + } + ) + assert assignments == limit * limit + return mismatches, truth_tables + + +def structural_audit( + assignments: int, + gates: list[Gate], + outputs: list[str], + truth_tables: dict[str, int], +) -> dict[str, object]: + gate_by_out = {gate.out: gate for gate in gates} + live: set[str] = set() + stack = [parse_token(token)[1:] for token in outputs] + while stack: + prefix, index = stack.pop() + name = f"{prefix}{index}" + if name in live or prefix == "x": + continue + live.add(name) + gate = gate_by_out[name] + for token in (gate.left, gate.right): + _, child_prefix, child_index = parse_token(token) + stack.append((child_prefix, child_index)) + dead = sorted(set(gate_by_out) - live, key=lambda name: int(name[1:])) + + mask = (1 << assignments) - 1 + seen: dict[int, str] = {} + duplicates: list[tuple[str, str]] = [] + complements: list[tuple[str, str]] = [] + constants: list[str] = [] + for gate in gates: + table = truth_tables[gate.out] + if table in (0, mask): + constants.append(gate.out) + if table in seen: + duplicates.append((gate.out, seen[table])) + complement = table ^ mask + if complement in seen: + complements.append((gate.out, seen[complement])) + seen.setdefault(table, gate.out) + return { + "dead_gates": dead, + "constant_gates": constants, + "duplicate_gate_functions": duplicates, + "complement_gate_functions": complements, + } + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("instance", choices=sorted(INSTANCES)) + parser.add_argument("candidate", type=Path) + parser.add_argument("--report", type=Path) + args = parser.parse_args() + + instance = INSTANCES[args.instance] + gates, outputs, raw = parse_candidate(args.candidate, instance) + assignments = 1 << (2 * instance.n) + mismatches, truth_tables = exhaustive_audit(instance, gates, outputs) + structure = structural_audit(assignments, gates, outputs, truth_tables) + report = { + "instance": instance.name, + "candidate": str(args.candidate), + "candidate_sha256": hashlib.sha256(raw).hexdigest(), + "inputs": 2 * instance.n, + "outputs": instance.outputs, + "gates": len(gates), + "assignments": assignments, + "output_bits_checked": assignments * instance.outputs, + "formula": instance.formula_name, + "equivalent": not mismatches, + "mismatches": mismatches, + "structure": structure, + } + rendered = json.dumps(report, indent=2) + print(rendered) + if args.report is not None: + args.report.parent.mkdir(parents=True, exist_ok=True) + temporary = args.report.with_suffix(args.report.suffix + ".tmp") + temporary.write_text(rendered + "\n", encoding="utf-8") + temporary.replace(args.report) + if mismatches or structure["dead_gates"]: + raise SystemExit(1) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/audit_c_formula.py b/tracks/qcs/solutions/Genshin_Impact-71/search/audit_c_formula.py new file mode 100644 index 000000000..c9049061a --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/audit_c_formula.py @@ -0,0 +1,234 @@ +#!/usr/bin/env python3 +"""Independent exhaustive audit for an Occam mystery-C candidate. + +This strict parser/evaluator imports no search, bridge, reference-netlist, or +eSLIM code. It checks the candidate directly against z = x*y for all 64*64 +assignments and reports dead, constant, duplicate, and complementary gates. +""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import re +from dataclasses import dataclass +from pathlib import Path + + +GATE_RE = re.compile( + r"^(w[1-9][0-9]*) = (AND|OR|XOR|NAND|NOR|XNOR) " + r"(~?(?:x|w)[1-9][0-9]*) (~?(?:x|w)[1-9][0-9]*)$" +) +TOKEN_RE = re.compile(r"^(~)?([xw])([1-9][0-9]*)$") + + +@dataclass(frozen=True) +class Gate: + out: str + op: str + left: str + right: str + + +def parse_token(token: str) -> tuple[bool, str, int]: + match = TOKEN_RE.fullmatch(token) + if match is None: + raise ValueError(f"invalid token {token!r}") + return bool(match.group(1)), match.group(2), int(match.group(3)) + + +def parse_candidate(path: Path) -> tuple[int, list[Gate], list[str], bytes]: + raw = path.read_bytes() + if not raw: + raise ValueError("empty candidate") + try: + text = raw.decode("ascii") + except UnicodeDecodeError as exc: + raise ValueError("candidate must be strict ASCII") from exc + lines = [line.strip() for line in text.splitlines() if line.strip()] + if len(lines) < 2 or not lines[0].startswith("INPUTS "): + raise ValueError("missing INPUTS header") + fields = lines[0].split() + if len(fields) != 2 or not fields[1].isdigit(): + raise ValueError("invalid INPUTS header") + n_inputs = int(fields[1]) + if n_inputs != 12: + raise ValueError(f"mystery C requires 12 inputs, got {n_inputs}") + + gates: list[Gate] = [] + defined = {f"x{i}" for i in range(1, n_inputs + 1)} + outputs: list[str] | None = None + for line_number, line in enumerate(lines[1:], start=2): + if line.startswith("OUTPUTS "): + if outputs is not None or line_number != len(lines): + raise ValueError("OUTPUTS must occur exactly once at EOF") + outputs = line.split()[1:] + continue + if outputs is not None: + raise ValueError("gate after OUTPUTS") + match = GATE_RE.fullmatch(line) + if match is None: + raise ValueError(f"invalid gate syntax on line {line_number}") + gate = Gate(*match.groups()) + if gate.out in defined: + raise ValueError(f"duplicate signal {gate.out}") + expected_out = f"w{len(gates) + 1}" + if gate.out != expected_out: + raise ValueError( + f"non-canonical wire order: expected {expected_out}, got {gate.out}" + ) + for token in (gate.left, gate.right): + _, prefix, index = parse_token(token) + base = f"{prefix}{index}" + if base not in defined: + raise ValueError(f"forward or unknown reference {token}") + gates.append(gate) + defined.add(gate.out) + + if outputs is None: + raise ValueError("missing OUTPUTS") + if len(outputs) != 12: + raise ValueError(f"mystery C requires 12 outputs, got {len(outputs)}") + for token in outputs: + _, prefix, index = parse_token(token) + if f"{prefix}{index}" not in defined: + raise ValueError(f"unknown output {token}") + return n_inputs, gates, outputs, raw + + +def read_token(token: str, values: dict[str, int]) -> int: + inverted, prefix, index = parse_token(token) + return values[f"{prefix}{index}"] ^ int(inverted) + + +def apply_gate(op: str, left: int, right: int) -> int: + if op == "AND": + return left & right + if op == "OR": + return left | right + if op == "XOR": + return left ^ right + if op == "NAND": + return 1 ^ (left & right) + if op == "NOR": + return 1 ^ (left | right) + if op == "XNOR": + return 1 ^ (left ^ right) + raise AssertionError(op) + + +def exhaustive_audit( + gates: list[Gate], + outputs: list[str], +) -> tuple[list[dict[str, object]], dict[str, int]]: + mismatches: list[dict[str, object]] = [] + truth_tables = {f"x{i}": 0 for i in range(1, 13)} + truth_tables.update({gate.out: 0 for gate in gates}) + for x in range(64): + for y in range(64): + row = x | (y << 6) + values = { + **{f"x{i + 1}": (x >> i) & 1 for i in range(6)}, + **{f"x{i + 7}": (y >> i) & 1 for i in range(6)}, + } + for name, bit in values.items(): + truth_tables[name] |= bit << row + for gate in gates: + left = read_token(gate.left, values) + right = read_token(gate.right, values) + values[gate.out] = apply_gate(gate.op, left, right) + truth_tables[gate.out] |= values[gate.out] << row + actual = sum( + read_token(token, values) << bit + for bit, token in enumerate(outputs) + ) + expected = x * y + if actual != expected and len(mismatches) < 16: + mismatches.append( + { + "x": x, + "y": y, + "expected": expected, + "actual": actual, + } + ) + return mismatches, truth_tables + + +def structural_audit( + gates: list[Gate], + outputs: list[str], + truth_tables: dict[str, int], +) -> dict[str, object]: + gate_by_out = {gate.out: gate for gate in gates} + live: set[str] = set() + stack = [parse_token(token)[1:] for token in outputs] + while stack: + prefix, index = stack.pop() + name = f"{prefix}{index}" + if name in live or name.startswith("x"): + continue + live.add(name) + gate = gate_by_out[name] + for token in (gate.left, gate.right): + _, child_prefix, child_index = parse_token(token) + stack.append((child_prefix, child_index)) + dead = sorted(set(gate_by_out) - live, key=lambda name: int(name[1:])) + + mask = (1 << 4096) - 1 + seen: dict[int, str] = {} + duplicates: list[tuple[str, str]] = [] + complements: list[tuple[str, str]] = [] + constants: list[str] = [] + for gate in gates: + table = truth_tables[gate.out] + if table in (0, mask): + constants.append(gate.out) + if table in seen: + duplicates.append((gate.out, seen[table])) + complement = table ^ mask + if complement in seen: + complements.append((gate.out, seen[complement])) + seen.setdefault(table, gate.out) + return { + "dead_gates": dead, + "constant_gates": constants, + "duplicate_gate_functions": duplicates, + "complement_gate_functions": complements, + } + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("candidate", type=Path) + parser.add_argument("--report", type=Path) + args = parser.parse_args() + + n_inputs, gates, outputs, raw = parse_candidate(args.candidate) + mismatches, truth_tables = exhaustive_audit(gates, outputs) + structure = structural_audit(gates, outputs, truth_tables) + report = { + "candidate": str(args.candidate), + "candidate_sha256": hashlib.sha256(raw).hexdigest(), + "inputs": n_inputs, + "outputs": len(outputs), + "gates": len(gates), + "assignments": 4096, + "output_bits_checked": 4096 * 12, + "formula": "x*y", + "equivalent": not mismatches, + "mismatches": mismatches, + "structure": structure, + } + rendered = json.dumps(report, indent=2) + print(rendered) + if args.report is not None: + args.report.parent.mkdir(parents=True, exist_ok=True) + args.report.write_text(rendered + "\n", encoding="utf-8") + if mismatches or structure["dead_gates"]: + raise SystemExit(1) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/audit_d_formula.py b/tracks/qcs/solutions/Genshin_Impact-71/search/audit_d_formula.py new file mode 100644 index 000000000..0a666c5f5 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/audit_d_formula.py @@ -0,0 +1,237 @@ +#!/usr/bin/env python3 +"""Independent exhaustive audit for an Occam mystery-D candidate. + +This parser/evaluator intentionally does not import the search, bridge, or +reference-circuit implementation. It checks the candidate directly against +z = x*x + y*y for all 32*32 assignments. +""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import re +from dataclasses import dataclass +from pathlib import Path + + +GATE_RE = re.compile( + r"^(w[1-9][0-9]*) = (AND|OR|XOR|NAND|NOR|XNOR) " + r"(~?(?:x|w)[1-9][0-9]*) (~?(?:x|w)[1-9][0-9]*)$" +) +TOKEN_RE = re.compile(r"^(~)?([xw])([1-9][0-9]*)$") + + +@dataclass(frozen=True) +class Gate: + out: str + op: str + left: str + right: str + + +def parse_token(token: str) -> tuple[bool, str, int]: + match = TOKEN_RE.fullmatch(token) + if match is None: + raise ValueError(f"invalid token {token!r}") + return bool(match.group(1)), match.group(2), int(match.group(3)) + + +def parse_candidate(path: Path) -> tuple[int, list[Gate], list[str], bytes]: + raw = path.read_bytes() + if not raw: + raise ValueError("empty candidate") + try: + text = raw.decode("ascii") + except UnicodeDecodeError as exc: + raise ValueError("candidate must be strict ASCII") from exc + lines = [line.strip() for line in text.splitlines() if line.strip()] + if len(lines) < 2 or not lines[0].startswith("INPUTS "): + raise ValueError("missing INPUTS header") + fields = lines[0].split() + if len(fields) != 2 or not fields[1].isdigit(): + raise ValueError("invalid INPUTS header") + n_inputs = int(fields[1]) + if n_inputs != 10: + raise ValueError(f"mystery D requires 10 inputs, got {n_inputs}") + + gates: list[Gate] = [] + defined = {f"x{i}" for i in range(1, n_inputs + 1)} + outputs: list[str] | None = None + for line_number, line in enumerate(lines[1:], start=2): + if line.startswith("OUTPUTS "): + if outputs is not None or line_number != len(lines): + raise ValueError("OUTPUTS must occur exactly once at EOF") + outputs = line.split()[1:] + continue + if outputs is not None: + raise ValueError("gate after OUTPUTS") + match = GATE_RE.fullmatch(line) + if match is None: + raise ValueError(f"invalid gate syntax on line {line_number}") + gate = Gate(*match.groups()) + if gate.out in defined: + raise ValueError(f"duplicate signal {gate.out}") + expected_out = f"w{len(gates) + 1}" + if gate.out != expected_out: + raise ValueError( + f"non-canonical wire order: expected {expected_out}, got {gate.out}" + ) + for token in (gate.left, gate.right): + _, prefix, index = parse_token(token) + base = f"{prefix}{index}" + if base not in defined: + raise ValueError(f"forward or unknown reference {token}") + gates.append(gate) + defined.add(gate.out) + + if outputs is None: + raise ValueError("missing OUTPUTS") + if len(outputs) != 11: + raise ValueError(f"mystery D requires 11 outputs, got {len(outputs)}") + for token in outputs: + _, prefix, index = parse_token(token) + if f"{prefix}{index}" not in defined: + raise ValueError(f"unknown output {token}") + return n_inputs, gates, outputs, raw + + +def read_token(token: str, values: dict[str, int]) -> int: + inverted, prefix, index = parse_token(token) + value = values[f"{prefix}{index}"] + return value ^ int(inverted) + + +def apply_gate(op: str, left: int, right: int) -> int: + if op == "AND": + value = left & right + elif op == "OR": + value = left | right + elif op == "XOR": + value = left ^ right + elif op == "NAND": + value = 1 ^ (left & right) + elif op == "NOR": + value = 1 ^ (left | right) + elif op == "XNOR": + value = 1 ^ (left ^ right) + else: + raise AssertionError(op) + return value + + +def exhaustive_audit( + gates: list[Gate], + outputs: list[str], +) -> tuple[list[dict[str, object]], dict[str, int]]: + mismatches: list[dict[str, object]] = [] + truth_tables = {f"x{i}": 0 for i in range(1, 11)} + truth_tables.update({gate.out: 0 for gate in gates}) + for x in range(32): + for y in range(32): + row = x | (y << 5) + values = { + **{f"x{i + 1}": (x >> i) & 1 for i in range(5)}, + **{f"x{i + 6}": (y >> i) & 1 for i in range(5)}, + } + for name, bit in values.items(): + truth_tables[name] |= bit << row + for gate in gates: + left = read_token(gate.left, values) + right = read_token(gate.right, values) + values[gate.out] = apply_gate(gate.op, left, right) + truth_tables[gate.out] |= values[gate.out] << row + actual = sum( + read_token(token, values) << bit + for bit, token in enumerate(outputs) + ) + expected = x * x + y * y + if actual != expected and len(mismatches) < 16: + mismatches.append( + { + "x": x, + "y": y, + "expected": expected, + "actual": actual, + } + ) + return mismatches, truth_tables + + +def structural_audit( + gates: list[Gate], + outputs: list[str], + truth_tables: dict[str, int], +) -> dict[str, object]: + gate_by_out = {gate.out: gate for gate in gates} + live: set[str] = set() + stack = [parse_token(token)[1:] for token in outputs] + while stack: + prefix, index = stack.pop() + name = f"{prefix}{index}" + if name in live or name.startswith("x"): + continue + live.add(name) + gate = gate_by_out[name] + for token in (gate.left, gate.right): + _, child_prefix, child_index = parse_token(token) + stack.append((child_prefix, child_index)) + dead = sorted(set(gate_by_out) - live, key=lambda name: int(name[1:])) + + mask = (1 << 1024) - 1 + seen: dict[int, str] = {} + duplicates: list[tuple[str, str]] = [] + complements: list[tuple[str, str]] = [] + constants: list[str] = [] + for gate in gates: + table = truth_tables[gate.out] + if table in (0, mask): + constants.append(gate.out) + if table in seen: + duplicates.append((gate.out, seen[table])) + complement = table ^ mask + if complement in seen: + complements.append((gate.out, seen[complement])) + seen.setdefault(table, gate.out) + return { + "dead_gates": dead, + "constant_gates": constants, + "duplicate_gate_functions": duplicates, + "complement_gate_functions": complements, + } + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("candidate", type=Path) + parser.add_argument("--report", type=Path) + args = parser.parse_args() + + n_inputs, gates, outputs, raw = parse_candidate(args.candidate) + mismatches, truth_tables = exhaustive_audit(gates, outputs) + structure = structural_audit(gates, outputs, truth_tables) + report = { + "candidate": str(args.candidate), + "candidate_sha256": hashlib.sha256(raw).hexdigest(), + "inputs": n_inputs, + "outputs": len(outputs), + "gates": len(gates), + "assignments": 1024, + "output_bits_checked": 1024 * 11, + "formula": "x*x + y*y", + "equivalent": not mismatches, + "mismatches": mismatches, + "structure": structure, + } + rendered = json.dumps(report, indent=2) + print(rendered) + if args.report is not None: + args.report.parent.mkdir(parents=True, exist_ok=True) + args.report.write_text(rendered + "\n", encoding="utf-8") + if mismatches or structure["dead_gates"]: + raise SystemExit(1) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/audit_reference.py b/tracks/qcs/solutions/Genshin_Impact-71/search/audit_reference.py new file mode 100644 index 000000000..b8b0a4816 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/audit_reference.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +"""Audit untrusted reference netlists without importing or executing them.""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +from circuit import Circuit, verify_formula + + +FAMILIES = { + "mystery-A": ("add", 37), + "mystery-B": ("absdiff", 49), + "mystery-C": ("mul", 156), + "mystery-D": ("sos", 113), +} + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("reference_dir", type=Path) + args = parser.parse_args() + results = [] + for instance, (family, claimed_gates) in FAMILIES.items(): + path = args.reference_dir / f"{instance}.txt" + circuit = Circuit.parse(path) + formula = verify_formula(circuit, family) + record = { + "instance": instance, + "family": family, + "path": str(path), + "claimed_gates": claimed_gates, + "observed_gates": len(circuit.gates), + "gate_count_match": len(circuit.gates) == claimed_gates, + "formula": formula, + "structure": circuit.structural_audit(), + } + if not record["gate_count_match"] or formula["failures"]: + raise RuntimeError(f"reference audit failed for {instance}") + results.append(record) + print(json.dumps({"results": results}, indent=2, sort_keys=True), flush=True) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/README.md b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/README.md new file mode 100644 index 000000000..dafe2f4df --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/README.md @@ -0,0 +1,22 @@ +# Issue #71: train-only shared BDD route + +This directory is an independent experiment arm. It consumes only an official +`train.csv`; revealed arithmetic semantics are used only after fitting for an +exhaustive audit. + +For each output bit, the learner builds a prefix acceptor in a candidate +variable order and merges compatible wildcard transition pairs bottom-up. +All output roots share one canonical reduced BDD manager. The compiler maps +BDD MUX nodes to the challenge gate basis with free phases and global gate CSE. + +Order selection uses a seed-42 training/validation split: + +1. blocked input order, +2. LSB-interleaved order, +3. MSB-interleaved order, +4. bounded seed-42 sifting, +5. adjacent-swap hill climbing. + +The independent auditor reparses every emitted netlist, checks gate ordering and +arity, and exhaustively compares all inputs against the organizer-revealed +function. It shares no netlist parser or simulator with the learner. diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/bdd_prefix_occam_v3.py b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/bdd_prefix_occam_v3.py new file mode 100644 index 000000000..d19458d6b --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/bdd_prefix_occam_v3.py @@ -0,0 +1,431 @@ +#!/usr/bin/env python3 +"""Prefix-compressed minimum-width/minimum-state OBDD learner. + +This is logically equivalent to the sample-wise SAT encoding, but assigns one +state variable to each distinct observed prefix rather than duplicating it for +every sample sharing that prefix. It is substantially smaller on the large +formal instances while preserving exactly the same train-only hypothesis class +and Occam objective. +""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import math +import time +from dataclasses import dataclass +from pathlib import Path +from typing import Sequence + +from decision_diagram_learn_v2 import ( + BDDManager, + INSTANCE_SPECS, + Sample, + compile_bdd, + evaluate_samples, + full_domain_audit, + read_training_csv, + write_netlist, +) + + +class _ClauseSink: + def __init__(self, target: object, weighted: bool = False) -> None: + self.target = target + self.weighted = weighted + self.count = 0 + + def hard(self, clause: list[int]) -> None: + if self.weighted: + self.target.append(clause) + else: + self.target.add_clause(clause) + self.count += 1 + + +class PrefixEncoding: + def __init__(self, samples: Sequence[Sample], order: Sequence[int], output_bit: int, k: int): + self.samples = samples + self.order = list(order) + self.output_bit = output_bit + self.k = k + self.width = 1 << k + self.depths = len(order) + 1 + codes = [] + labels: dict[int, int] = {} + for sample in samples: + code = 0 + for depth, variable in enumerate(order): + code |= sample.bits[variable] << depth + label = sample.output[output_bit] + old = labels.get(code) + if old is not None and old != label: + raise ValueError("conflicting duplicate input") + labels[code] = label + codes.append(code) + self.codes = sorted(set(codes)) + self.labels = labels + self.prefixes: list[list[int]] = [] + self.prefix_index: list[dict[int, int]] = [] + self.p_offsets: list[int] = [] + next_variable = 1 + for depth in range(self.depths): + mask = (1 << depth) - 1 + values = sorted({code & mask for code in self.codes}) + self.prefixes.append(values) + self.prefix_index.append({value: idx for idx, value in enumerate(values)}) + self.p_offsets.append(next_variable) + next_variable += len(values) * k + self.t_base = next_variable + self.t_count = len(order) * self.width * 2 * k + self.o_base = self.t_base + self.t_count + self.r_base = self.o_base + self.width + self.nvars = self.r_base + self.depths * self.width - 1 + distinguished = self.codes[0] + self.distinguished_prefix = [ + distinguished & ((1 << depth) - 1) for depth in range(self.depths) + ] + + def pvar(self, depth: int, prefix_index: int, bit: int) -> int: + return self.p_offsets[depth] + prefix_index * self.k + bit + + def tvar(self, depth: int, state: int, branch: int, bit: int) -> int: + return self.t_base + ((((depth * self.width + state) * 2 + branch) * self.k) + bit) + + def ovar(self, state: int) -> int: + return self.o_base + state + + def rvar(self, depth: int, state: int) -> int: + return self.r_base + depth * self.width + state + + def mismatch(self, depth: int, prefix_index: int, state: int) -> list[int]: + result = [] + for bit in range(self.k): + variable = self.pvar(depth, prefix_index, bit) + result.append(-variable if ((state >> bit) & 1) else variable) + return result + + def add_base(self, sink: _ClauseSink) -> None: + # The only depth-0 prefix starts in state zero. + for bit in range(self.k): + sink.hard([-self.pvar(0, 0, bit)]) + + # Sound layer-wise state-name symmetry breaking. + for depth in range(1, self.depths): + prefix = self.distinguished_prefix[depth] + index = self.prefix_index[depth][prefix] + for bit in range(self.k): + sink.hard([-self.pvar(depth, index, bit)]) + + # One deterministic transition constraint per distinct observed edge. + for depth in range(len(self.order)): + next_mask = (1 << (depth + 1)) - 1 + edges = set() + for code in self.codes: + parent = code & ((1 << depth) - 1) + branch = (code >> depth) & 1 + child = code & next_mask + edges.add( + ( + self.prefix_index[depth][parent], + branch, + self.prefix_index[depth + 1][child], + ) + ) + for parent, branch, child in sorted(edges): + for state in range(self.width): + prefix = self.mismatch(depth, parent, state) + for bit in range(self.k): + nxt = self.pvar(depth + 1, child, bit) + transition = self.tvar(depth, state, branch, bit) + sink.hard(prefix + [-nxt, transition]) + sink.hard(prefix + [nxt, -transition]) + + last = self.depths - 1 + for code, label in sorted(self.labels.items()): + leaf = self.prefix_index[last][code] + for state in range(self.width): + clause = self.mismatch(last, leaf, state) + clause.append(self.ovar(state) if label else -self.ovar(state)) + sink.hard(clause) + + def make_wcnf(self) -> tuple[object, int, int]: + from pysat.formula import WCNF + + formula = WCNF() + sink = _ClauseSink(formula, weighted=True) + self.add_base(sink) + sink.hard([self.rvar(0, 0)]) + + # Observed prefix states are reachable. + for depth, prefixes in enumerate(self.prefixes): + for prefix_index in range(len(prefixes)): + for state in range(self.width): + sink.hard( + self.mismatch(depth, prefix_index, state) + + [self.rvar(depth, state)] + ) + + # Complete both observed and missing outgoing branches of every + # reachable state, then minimize the resulting reachable-state count. + for depth in range(len(self.order)): + for state in range(self.width): + for branch in (0, 1): + for nxt in range(self.width): + transition_mismatch = [] + for bit in range(self.k): + variable = self.tvar(depth, state, branch, bit) + transition_mismatch.append( + -variable if ((nxt >> bit) & 1) else variable + ) + sink.hard( + [-self.rvar(depth, state)] + + transition_mismatch + + [self.rvar(depth + 1, nxt)] + ) + soft_count = self.depths * self.width + for depth in range(self.depths): + for state in range(self.width): + formula.append([-self.rvar(depth, state)], weight=1) + return formula, sink.count, soft_count + + def decode(self, model: Sequence[int], manager: BDDManager) -> int: + positive = {literal for literal in model if literal > 0} + transitions = {} + for depth in range(len(self.order)): + for state in range(self.width): + for branch in (0, 1): + nxt = 0 + for bit in range(self.k): + if self.tvar(depth, state, branch, bit) in positive: + nxt |= 1 << bit + transitions[depth, state, branch] = nxt + labels = [int(self.ovar(state) in positive) for state in range(self.width)] + memo = {} + + def build(depth: int, state: int) -> int: + key = (depth, state) + if key in memo: + return memo[key] + if depth == len(self.order): + result = labels[state] + else: + low = build(depth + 1, transitions[depth, state, 0]) + high = build(depth + 1, transitions[depth, state, 1]) + result = manager.intern(self.order[depth], low, high) + memo[key] = result + return result + + return build(0, 0) + + +@dataclass +class OutputResult: + root: int | None + width: int | None + reachable_cost: int | None + feasibility: list[dict[str, object]] + maxsat_seconds: float | None + + +def learn_output( + samples: Sequence[Sample], + order: Sequence[int], + output_bit: int, + manager: BDDManager, + *, + max_width: int, + conflict_budget: int, + solver_name: str, +) -> OutputResult: + labels = {sample.output[output_bit] for sample in samples} + if len(labels) == 1: + return OutputResult(next(iter(labels)), 1, len(order) + 1, [], 0.0) + from pysat.examples.rc2 import RC2 + from pysat.solvers import Solver + + attempts = [] + max_k = int(math.log2(max_width)) + chosen_k = None + for k in range(1, max_k + 1): + encoding = PrefixEncoding(samples, order, output_bit, k) + started = time.monotonic() + with Solver(name=solver_name) as solver: + sink = _ClauseSink(solver) + encoding.add_base(sink) + if conflict_budget > 0: + solver.conf_budget(conflict_budget) + status = solver.solve_limited(expect_interrupt=True) + else: + status = solver.solve() + stats = solver.accum_stats() + elapsed = time.monotonic() - started + record = { + "width": encoding.width, + "status": "SAT" if status is True else "UNSAT" if status is False else "UNKNOWN", + "variables": encoding.nvars, + "clauses": sink.count, + "prefix_counts": [len(items) for items in encoding.prefixes], + "conflicts": stats.get("conflicts"), + "decisions": stats.get("decisions"), + "elapsed_seconds": elapsed, + } + attempts.append(record) + print( + f"[prefix-feasible] bit={output_bit} width={encoding.width} " + f"status={record['status']} vars={encoding.nvars} clauses={sink.count} " + f"conflicts={record['conflicts']} elapsed={elapsed:.3f}s", + flush=True, + ) + if status is True: + chosen_k = k + break + if chosen_k is None: + return OutputResult(None, None, None, attempts, None) + + encoding = PrefixEncoding(samples, order, output_bit, chosen_k) + formula, hard, soft = encoding.make_wcnf() + started = time.monotonic() + with RC2( + formula, + solver=solver_name, + adapt=True, + exhaust=True, + incr=False, + minz=True, + trim=0, + verbose=0, + ) as optimizer: + model = optimizer.compute() + cost = optimizer.cost + elapsed = time.monotonic() - started + print( + f"[prefix-maxsat] bit={output_bit} width={encoding.width} cost={cost} " + f"hard={hard} soft={soft} elapsed={elapsed:.3f}s", + flush=True, + ) + if model is None: + return OutputResult(None, encoding.width, None, attempts, elapsed) + root = encoding.decode(model, manager) + one_bit = [Sample(row.bits, (row.output[output_bit],)) for row in samples] + if evaluate_samples(manager, [root], one_bit)[0] != len(samples): + raise AssertionError("decoded prefix MaxSAT model violates samples") + return OutputResult(root, encoding.width, int(cost), attempts, elapsed) + + +def parse_order(text: str, size: int) -> list[int]: + values = [int(piece) for piece in text.split(",") if piece] + if sorted(values) != list(range(size)): + raise ValueError("fixed order is not a permutation") + return values + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--dataset-root", type=Path, required=True) + parser.add_argument("--instance", choices=sorted(INSTANCE_SPECS), required=True) + parser.add_argument("--output-dir", type=Path, required=True) + parser.add_argument("--fixed-order", required=True) + parser.add_argument("--fixed-order-name", required=True) + parser.add_argument("--seed", type=int, default=42) + parser.add_argument("--max-width", type=int, default=16) + parser.add_argument("--conflict-budget", type=int, default=1_000_000) + parser.add_argument("--solver", default="cadical195") + args = parser.parse_args() + + kind, n, m = INSTANCE_SPECS[args.instance] + order = parse_order(args.fixed_order, 2 * n) + path = args.dataset_root / args.instance / "train.csv" + samples = read_training_csv(path, 2 * n, m) + args.output_dir.mkdir(parents=True, exist_ok=True) + started = time.monotonic() + manager = BDDManager() + roots = [] + results = [] + for bit in range(m): + print(f"[output] bit={bit}", flush=True) + result = learn_output( + samples, + order, + bit, + manager, + max_width=args.max_width, + conflict_budget=args.conflict_budget, + solver_name=args.solver, + ) + results.append(result) + if result.root is None: + break + roots.append(result.root) + + report: dict[str, object] = { + "schema": "occam71-prefix-compressed-obdd-v3", + "instance": args.instance, + "seed": args.seed, + "order_name": args.fixed_order_name, + "order": order, + "max_width": args.max_width, + "conflict_budget": args.conflict_budget, + "solver": args.solver, + "training_sha256": hashlib.sha256(path.read_bytes()).hexdigest(), + "solved_outputs": len(roots), + "outputs": [ + { + "root": result.root, + "width": result.width, + "reachable_cost": result.reachable_cost, + "feasibility": result.feasibility, + "maxsat_seconds": result.maxsat_seconds, + } + for result in results + ], + } + if len(roots) == m: + builder, signals = compile_bdd(manager, roots, 2 * n) + netlist = args.output_dir / f"{args.instance}.prefix-occam-bdd.txt" + write_netlist( + netlist, + builder, + signals, + metadata=[ + "prefix-compressed minimum-width/minimum-state OBDD from train.csv only", + f"instance={args.instance} seed={args.seed} order_name={args.fixed_order_name}", + "order_zero_based=" + ",".join(map(str, order)), + ], + ) + train_rows, train_bits = evaluate_samples(manager, roots, samples) + audit = full_domain_audit(manager, roots, kind=kind, n=n, m=m) + report.update( + { + "status": "COMPLETE_CANDIDATE", + "training_rows": len(samples), + "training_exact_rows": train_rows, + "training_correct_bits": train_bits, + "nodes": len(manager.reachable(roots)), + "gates": len(builder.lines), + "netlist": str(netlist), + "full_domain_audit": audit, + } + ) + status = 0 + print( + f"[done] train={train_rows}/{len(samples)} " + f"full={audit['exact_rows']}/{audit['total_rows']} " + f"nodes={report['nodes']} gates={report['gates']}", + flush=True, + ) + else: + report["status"] = "NO_COMPLETE_CANDIDATE_WITHIN_WIDTH_OR_BUDGET" + status = 3 + print(f"[done] {report['status']} solved={len(roots)}/{m}", flush=True) + report["elapsed_seconds"] = time.monotonic() - started + report_path = args.output_dir / f"{args.instance}.prefix-occam-summary.json" + report_path.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", "ascii") + print(f"[artifact] {report_path}", flush=True) + return status + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/bdd_sat_occam_fixed.py b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/bdd_sat_occam_fixed.py new file mode 100644 index 000000000..70db8715a --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/bdd_sat_occam_fixed.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +"""Run the Occam MaxSAT learner on one preregistered variable order.""" + +from __future__ import annotations + +import argparse +from typing import Sequence + +import bdd_sat_occam_v2 as implementation + + +def main(argv: Sequence[str] | None = None) -> int: + parser = argparse.ArgumentParser(add_help=False) + parser.add_argument("--fixed-order", required=True) + parser.add_argument("--fixed-order-name", required=True) + known, remaining = parser.parse_known_args(argv) + order = [int(piece) for piece in known.fixed_order.split(",") if piece] + if sorted(order) != list(range(len(order))): + raise ValueError("fixed order must be a zero-based permutation") + + def only_order(n: int) -> dict[str, list[int]]: + if 2 * n != len(order): + raise ValueError("fixed order length does not match instance") + return {known.fixed_order_name: list(order)} + + def no_search( + samples: object, + n: int, + **kwargs: object, + ) -> tuple[list[int], dict[str, object]]: + if 2 * n != len(order): + raise ValueError("fixed order length does not match instance") + return list(order), { + "mode": "preregistered_fixed_order", + "name": known.fixed_order_name, + "order": list(order), + "seed": kwargs.get("seed", 42), + "note": "all base/sifting orders were evaluated in the separate greedy BDD arm", + } + + implementation.base_orders = only_order + implementation.choose_order = no_search + return implementation.main(remaining) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/bdd_sat_occam_v2.py b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/bdd_sat_occam_v2.py new file mode 100644 index 000000000..e7bf85567 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/bdd_sat_occam_v2.py @@ -0,0 +1,417 @@ +#!/usr/bin/env python3 +"""Lexicographic Occam refinement for SAT-learned layered OBDDs. + +Stage 1 finds the smallest power-of-two width consistent with the samples. +Stage 2 uses weighted MaxSAT to minimize the number of reachable states over +all layers, including transitions on unobserved branches. This explicitly +implements the challenge's simplicity bias instead of accepting an arbitrary +SAT completion. +""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import math +import time +from dataclasses import dataclass +from pathlib import Path +from typing import Sequence + +from bdd_sat_refine import LayeredEncoding, SATAttempt, attempt_to_json +from decision_diagram_learn_v2 import ( + BDDManager, + INSTANCE_SPECS, + Sample, + base_orders, + choose_order, + compile_bdd, + evaluate_samples, + full_domain_audit, + read_training_csv, + write_netlist, +) + + +class _WCNFAdapter: + def __init__(self, formula: object) -> None: + self.formula = formula + + def add_clause(self, clause: list[int]) -> None: + self.formula.append(clause) + + +class ReachableEncoding(LayeredEncoding): + def __init__(self, samples: Sequence[Sample], order: Sequence[int], bit: int, k: int): + super().__init__(samples, order, bit, k) + self.r_base = self.nvars + 1 + self.r_count = self.depths * self.width + self.nvars = self.r_base + self.r_count - 1 + + def rvar(self, depth: int, state: int) -> int: + return self.r_base + depth * self.width + state + + def make_wcnf(self) -> tuple[object, int, int]: + from pysat.formula import WCNF + + formula = WCNF() + base_clauses = self.add_to_solver(_WCNFAdapter(formula)) + hard_clauses = base_clauses + + def hard(clause: list[int]) -> None: + nonlocal hard_clauses + formula.append(clause) + hard_clauses += 1 + + hard([self.rvar(0, 0)]) + + # Every state actually used by an example is reachable. + for sample_id in range(self.n_samples): + for depth in range(self.depths): + for state in range(self.width): + hard(self.mismatch(sample_id, depth, state) + [self.rvar(depth, state)]) + + # Both branches of every reachable state count toward the completed + # total function, even when the training sample omitted that branch. + for depth in range(len(self.order)): + for state in range(self.width): + for branch in (0, 1): + for nxt in range(self.width): + transition_mismatch = [] + for bit in range(self.k): + variable = self.tvar(depth, state, branch, bit) + transition_mismatch.append( + -variable if ((nxt >> bit) & 1) else variable + ) + hard( + [-self.rvar(depth, state)] + + transition_mismatch + + [self.rvar(depth + 1, nxt)] + ) + + # Unit-weight soft clauses exactly minimize sum_d |reachable states_d|. + for depth in range(self.depths): + for state in range(self.width): + formula.append([-self.rvar(depth, state)], weight=1) + return formula, hard_clauses, self.depths * self.width + + +@dataclass +class OccamOutput: + root: int | None + width: int | None + reachable_state_cost: int | None + attempts: list[SATAttempt] + maxsat_elapsed_seconds: float | None + + +def _stats(solver: object) -> tuple[int | None, int | None]: + try: + values = solver.accum_stats() + except Exception: + return None, None + return values.get("conflicts"), values.get("decisions") + + +def learn_occam_output( + samples: Sequence[Sample], + order: Sequence[int], + output_bit: int, + manager: BDDManager, + *, + max_width: int, + conflict_budget: int, + solver_name: str, +) -> OccamOutput: + labels = {sample.output[output_bit] for sample in samples} + if len(labels) == 1: + # One terminal state is reachable at every conceptual layer, but the + # reduced BDD contains no internal node. + return OccamOutput(next(iter(labels)), 1, len(order) + 1, [], 0.0) + + from pysat.examples.rc2 import RC2 + from pysat.solvers import Solver + + attempts: list[SATAttempt] = [] + max_k = int(math.log2(max_width)) + if 1 << max_k != max_width: + raise ValueError("max_width must be a power of two") + chosen_k: int | None = None + for k in range(1, max_k + 1): + encoding = LayeredEncoding(samples, order, output_bit, k) + started = time.monotonic() + with Solver(name=solver_name) as solver: + clauses = encoding.add_to_solver(solver) + if conflict_budget > 0: + solver.conf_budget(conflict_budget) + status = solver.solve_limited(expect_interrupt=True) + else: + status = solver.solve() + conflicts, decisions = _stats(solver) + elapsed = time.monotonic() - started + attempt = SATAttempt( + encoding.width, + "SAT" if status is True else "UNSAT" if status is False else "UNKNOWN", + encoding.nvars, + clauses, + conflicts, + decisions, + elapsed, + ) + attempts.append(attempt) + print( + f"[feasible] bit={output_bit} width={encoding.width} " + f"status={attempt.status} conflicts={conflicts} elapsed={elapsed:.3f}s", + flush=True, + ) + if status is True: + chosen_k = k + break + if chosen_k is None: + return OccamOutput(None, None, None, attempts, None) + + encoding = ReachableEncoding(samples, order, output_bit, chosen_k) + formula, hard_clauses, soft_clauses = encoding.make_wcnf() + started = time.monotonic() + with RC2( + formula, + solver=solver_name, + adapt=True, + exhaust=True, + incr=False, + minz=True, + trim=0, + verbose=0, + ) as optimizer: + model = optimizer.compute() + cost = optimizer.cost + elapsed = time.monotonic() - started + print( + f"[maxsat] bit={output_bit} width={encoding.width} cost={cost} " + f"hard={hard_clauses} soft={soft_clauses} elapsed={elapsed:.3f}s", + flush=True, + ) + if model is None: + return OccamOutput(None, encoding.width, None, attempts, elapsed) + root = encoding.decode(model, manager) + one_bit_samples = [ + Sample(sample.bits, (sample.output[output_bit],)) for sample in samples + ] + if evaluate_samples(manager, [root], one_bit_samples)[0] != len(samples): + raise AssertionError("MaxSAT decoded OBDD violates training data") + return OccamOutput(root, encoding.width, int(cost), attempts, elapsed) + + +def learn_occam_order( + samples: Sequence[Sample], + order: Sequence[int], + *, + max_width: int, + conflict_budget: int, + solver_name: str, +) -> tuple[BDDManager, list[int], list[OccamOutput]]: + manager = BDDManager() + roots: list[int] = [] + results: list[OccamOutput] = [] + for bit in range(len(samples[0].output)): + print(f"[output] bit={bit}", flush=True) + result = learn_occam_output( + samples, + order, + bit, + manager, + max_width=max_width, + conflict_budget=conflict_budget, + solver_name=solver_name, + ) + results.append(result) + if result.root is None: + break + roots.append(result.root) + return manager, roots, results + + +def objective( + solved: int, + state_costs: Sequence[int], + widths: Sequence[int], + nodes: int, + gates: int, +) -> tuple[int, int, int, int, int]: + return (solved, -sum(state_costs), -sum(widths), -nodes, -gates) + + +def main(argv: Sequence[str] | None = None) -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--dataset-root", type=Path, required=True) + parser.add_argument("--instance", choices=sorted(INSTANCE_SPECS), required=True) + parser.add_argument("--output-dir", type=Path, required=True) + parser.add_argument("--seed", type=int, default=42) + parser.add_argument("--max-width", type=int, default=16) + parser.add_argument("--conflict-budget", type=int, default=2_000_000) + parser.add_argument("--solver", default="cadical195") + parser.add_argument("--sift-variables", type=int, default=6) + parser.add_argument("--adjacent-passes", type=int, default=2) + args = parser.parse_args(argv) + + kind, n, m = INSTANCE_SPECS[args.instance] + training_path = args.dataset_root / args.instance / "train.csv" + samples = read_training_csv(training_path, 2 * n, m) + args.output_dir.mkdir(parents=True, exist_ok=True) + started = time.monotonic() + + sifted, heuristic_selection = choose_order( + samples, + n, + seed=args.seed, + validation_fraction=0.2, + sift_variables=args.sift_variables, + adjacent_passes=args.adjacent_passes, + progress=False, + ) + orders = base_orders(n) + orders["seed42_sift_adjacent"] = sifted + candidates = [] + seen: set[tuple[int, ...]] = set() + for name, order in orders.items(): + if tuple(order) not in seen: + seen.add(tuple(order)) + candidates.append((name, order)) + + reports: list[dict[str, object]] = [] + successful = [] + for name, order in candidates: + print(f"[candidate] name={name} order={order}", flush=True) + candidate_started = time.monotonic() + manager, roots, output_results = learn_occam_order( + samples, + order, + max_width=args.max_width, + conflict_budget=args.conflict_budget, + solver_name=args.solver, + ) + solved = len(roots) + widths = [result.width for result in output_results if result.width is not None] + costs = [ + result.reachable_state_cost + for result in output_results + if result.reachable_state_cost is not None + ] + nodes = len(manager.reachable(roots)) + gates = -1 + netlist = None + if solved == m: + builder, output_signals = compile_bdd(manager, roots, 2 * n) + gates = len(builder.lines) + path = args.output_dir / f"{args.instance}.{name}.occam-bdd.txt" + write_netlist( + path, + builder, + output_signals, + metadata=[ + "minimum-width then minimum-reachable-state OBDD from train.csv only", + f"instance={args.instance} seed={args.seed} order_name={name}", + "order_zero_based=" + ",".join(map(str, order)), + ], + ) + netlist = str(path) + successful.append( + ( + objective(solved, costs, widths, nodes, gates), + name, + order, + manager, + roots, + ) + ) + report = { + "name": name, + "order": order, + "solved_outputs": solved, + "widths": widths, + "reachable_state_costs": costs, + "nodes": nodes, + "gates": gates, + "netlist": netlist, + "outputs": [ + { + "root": result.root, + "width": result.width, + "reachable_state_cost": result.reachable_state_cost, + "maxsat_elapsed_seconds": result.maxsat_elapsed_seconds, + "attempts": [attempt_to_json(item) for item in result.attempts], + } + for result in output_results + ], + "elapsed_seconds": time.monotonic() - candidate_started, + } + reports.append(report) + (args.output_dir / f"{args.instance}.{name}.occam-report.json").write_text( + json.dumps(report, indent=2, sort_keys=True) + "\n", "ascii" + ) + + summary: dict[str, object] = { + "schema": "occam71-min-reachable-obdd-v2", + "instance": args.instance, + "seed": args.seed, + "max_width": args.max_width, + "conflict_budget": args.conflict_budget, + "solver": args.solver, + "training_sha256": hashlib.sha256(training_path.read_bytes()).hexdigest(), + "heuristic_order_selection": heuristic_selection, + "candidates": reports, + "elapsed_seconds": time.monotonic() - started, + } + if successful: + selected_objective, name, order, manager, roots = max( + successful, key=lambda item: item[0] + ) + builder, output_signals = compile_bdd(manager, roots, 2 * n) + selected = args.output_dir / f"{args.instance}.selected.occam-bdd.txt" + write_netlist( + selected, + builder, + output_signals, + metadata=[ + "train-only selected minimum-reachable-state OBDD", + f"instance={args.instance} seed={args.seed} order_name={name}", + "order_zero_based=" + ",".join(map(str, order)), + ], + ) + train_rows, train_bits = evaluate_samples(manager, roots, samples) + audit = full_domain_audit(manager, roots, kind=kind, n=n, m=m) + summary.update( + { + "status": "COMPLETE_CANDIDATE", + "selected_order_name": name, + "selected_order": order, + "selection_objective": list(selected_objective), + "selected_nodes": len(manager.reachable(roots)), + "selected_gates": len(builder.lines), + "selected_netlist": str(selected), + "training_rows": len(samples), + "training_exact_rows": train_rows, + "training_correct_bits": train_bits, + "full_domain_audit_after_selection": audit, + } + ) + exit_status = 0 + print( + f"[done] selected={name} train={train_rows}/{len(samples)} " + f"full={audit['exact_rows']}/{audit['total_rows']} " + f"nodes={summary['selected_nodes']} gates={summary['selected_gates']}", + flush=True, + ) + else: + summary["status"] = "NO_COMPLETE_CANDIDATE_WITHIN_WIDTH_OR_BUDGET" + exit_status = 3 + print(f"[done] {summary['status']}", flush=True) + path = args.output_dir / f"{args.instance}.occam-bdd-summary.json" + path.write_text(json.dumps(summary, indent=2, sort_keys=True) + "\n", "ascii") + print(f"[artifact] {path}", flush=True) + return exit_status + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/bdd_sat_refine.py b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/bdd_sat_refine.py new file mode 100644 index 000000000..4eed299ec --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/bdd_sat_refine.py @@ -0,0 +1,467 @@ +#!/usr/bin/env python3 +"""Minimum-width layered OBDD learning by SAT for issue #71. + +For a fixed variable order and one output bit, examples traverse a deterministic +layered branching program. Binary state variables encode the state reached by +each example, and shared transition variables enforce determinism. Widths +1,2,4,... are tried in ascending order. Candidate order selection uses only +training constraints, BDD size, and extracted gate count; revealed semantics +are consulted only after selection for an exhaustive audit. +""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import math +import time +from dataclasses import dataclass +from pathlib import Path +from typing import Sequence + +from decision_diagram_learn_v2 import ( + BDDManager, + INSTANCE_SPECS, + Sample, + base_orders, + choose_order, + compile_bdd, + evaluate_samples, + full_domain_audit, + read_training_csv, + write_netlist, +) + + +@dataclass +class SATAttempt: + width: int + status: str + variables: int + clauses: int + conflicts: int | None + decisions: int | None + elapsed_seconds: float + + +@dataclass +class SATOutput: + root: int | None + width: int | None + attempts: list[SATAttempt] + + +class LayeredEncoding: + def __init__(self, samples: Sequence[Sample], order: Sequence[int], bit: int, k: int): + self.samples = samples + self.order = order + self.output_bit = bit + self.k = k + self.width = 1 << k + self.n_samples = len(samples) + self.depths = len(order) + 1 + self.b_base = 1 + self.b_count = self.n_samples * self.depths * k + self.t_base = self.b_base + self.b_count + self.t_count = len(order) * self.width * 2 * k + self.o_base = self.t_base + self.t_count + self.nvars = self.o_base + self.width - 1 + + def bvar(self, sample: int, depth: int, bit: int) -> int: + return self.b_base + ((sample * self.depths + depth) * self.k + bit) + + def tvar(self, depth: int, state: int, branch: int, bit: int) -> int: + return self.t_base + ((((depth * self.width + state) * 2 + branch) * self.k) + bit) + + def ovar(self, state: int) -> int: + return self.o_base + state + + def mismatch(self, sample: int, depth: int, state: int) -> list[int]: + result = [] + for bit in range(self.k): + variable = self.bvar(sample, depth, bit) + result.append(-variable if ((state >> bit) & 1) else variable) + return result + + def add_to_solver(self, solver: object) -> int: + clauses = 0 + + def add(clause: list[int]) -> None: + nonlocal clauses + solver.add_clause(clause) + clauses += 1 + + # Every example begins at state zero. + for sample in range(self.n_samples): + for bit in range(self.k): + add([-self.bvar(sample, 0, bit)]) + + # State names are independently permutable at every layer. Pinning one + # example to zero at each layer is a sound symmetry break. + for depth in range(1, self.depths): + for bit in range(self.k): + add([-self.bvar(0, depth, bit)]) + + # If an example is in q, its next-state bits equal the shared transition + # delta[depth,q,input]. Two clauses encode each conditional equivalence. + for sample_id, sample in enumerate(self.samples): + for depth, variable_index in enumerate(self.order): + branch = sample.bits[variable_index] + for state in range(self.width): + prefix = self.mismatch(sample_id, depth, state) + for bit in range(self.k): + nxt = self.bvar(sample_id, depth + 1, bit) + transition = self.tvar(depth, state, branch, bit) + add(prefix + [-nxt, transition]) + add(prefix + [nxt, -transition]) + + last = self.depths - 1 + for sample_id, sample in enumerate(self.samples): + label = sample.output[self.output_bit] + for state in range(self.width): + clause = self.mismatch(sample_id, last, state) + clause.append(self.ovar(state) if label else -self.ovar(state)) + add(clause) + return clauses + + def decode( + self, + model: Sequence[int], + manager: BDDManager, + ) -> int: + positive = {literal for literal in model if literal > 0} + transitions: dict[tuple[int, int, int], int] = {} + for depth in range(len(self.order)): + for state in range(self.width): + for branch in (0, 1): + nxt = 0 + for bit in range(self.k): + if self.tvar(depth, state, branch, bit) in positive: + nxt |= 1 << bit + transitions[depth, state, branch] = nxt + labels = [int(self.ovar(state) in positive) for state in range(self.width)] + memo: dict[tuple[int, int], int] = {} + + def build(depth: int, state: int) -> int: + key = (depth, state) + old = memo.get(key) + if old is not None: + return old + if depth == len(self.order): + result = labels[state] + else: + low = build(depth + 1, transitions[depth, state, 0]) + high = build(depth + 1, transitions[depth, state, 1]) + result = manager.intern(self.order[depth], low, high) + memo[key] = result + return result + + return build(0, 0) + + +def _solver_stats(solver: object) -> tuple[int | None, int | None]: + try: + stats = solver.accum_stats() + except Exception: + return None, None + return stats.get("conflicts"), stats.get("decisions") + + +def learn_output( + samples: Sequence[Sample], + order: Sequence[int], + output_bit: int, + manager: BDDManager, + *, + max_width: int, + conflict_budget: int, + solver_name: str, +) -> SATOutput: + labels = {sample.output[output_bit] for sample in samples} + if len(labels) == 1: + return SATOutput(next(iter(labels)), 1, []) + attempts: list[SATAttempt] = [] + max_k = int(math.log2(max_width)) + if (1 << max_k) != max_width: + raise ValueError("max_width must be a power of two") + + from pysat.solvers import Solver + + for k in range(1, max_k + 1): + encoding = LayeredEncoding(samples, order, output_bit, k) + started = time.monotonic() + with Solver(name=solver_name) as solver: + clauses = encoding.add_to_solver(solver) + if conflict_budget > 0: + solver.conf_budget(conflict_budget) + status = solver.solve_limited(expect_interrupt=True) + else: + status = solver.solve() + conflicts, decisions = _solver_stats(solver) + elapsed = time.monotonic() - started + attempts.append( + SATAttempt( + encoding.width, + "SAT" if status is True else "UNSAT" if status is False else "UNKNOWN", + encoding.nvars, + clauses, + conflicts, + decisions, + elapsed, + ) + ) + print( + f"[sat] bit={output_bit} width={encoding.width} " + f"status={attempts[-1].status} vars={encoding.nvars} " + f"clauses={clauses} conflicts={conflicts} elapsed={elapsed:.3f}s", + flush=True, + ) + if status is True: + model = solver.get_model() + if model is None: + raise AssertionError("SAT solver returned no model") + root = encoding.decode(model, manager) + one_bit_samples = [ + Sample(sample.bits, (sample.output[output_bit],)) for sample in samples + ] + if evaluate_samples(manager, [root], one_bit_samples)[0] != len(samples): + raise AssertionError("decoded SAT OBDD violates training data") + return SATOutput(root, encoding.width, attempts) + return SATOutput(None, None, attempts) + + +def learn_order( + samples: Sequence[Sample], + order: Sequence[int], + *, + max_width: int, + conflict_budget: int, + solver_name: str, +) -> tuple[BDDManager, list[int], list[SATOutput]]: + manager = BDDManager() + roots: list[int] = [] + outputs: list[SATOutput] = [] + for output_bit in range(len(samples[0].output)): + print(f"[output] bit={output_bit}", flush=True) + result = learn_output( + samples, + order, + output_bit, + manager, + max_width=max_width, + conflict_budget=conflict_budget, + solver_name=solver_name, + ) + outputs.append(result) + if result.root is None: + break + roots.append(result.root) + return manager, roots, outputs + + +def attempt_to_json(attempt: SATAttempt) -> dict[str, object]: + return { + "width": attempt.width, + "status": attempt.status, + "variables": attempt.variables, + "clauses": attempt.clauses, + "conflicts": attempt.conflicts, + "decisions": attempt.decisions, + "elapsed_seconds": attempt.elapsed_seconds, + } + + +def order_objective( + solved: int, + widths: Sequence[int], + nodes: int, + gates: int, +) -> tuple[int, int, int, int]: + return (solved, -sum(widths), -nodes, -gates) + + +def main(argv: Sequence[str] | None = None) -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--dataset-root", type=Path, required=True) + parser.add_argument("--instance", choices=sorted(INSTANCE_SPECS), required=True) + parser.add_argument("--output-dir", type=Path, required=True) + parser.add_argument("--seed", type=int, default=42) + parser.add_argument("--max-width", type=int, default=16) + parser.add_argument("--conflict-budget", type=int, default=2_000_000) + parser.add_argument("--solver", default="cadical195") + parser.add_argument("--sift-variables", type=int, default=6) + parser.add_argument("--adjacent-passes", type=int, default=2) + args = parser.parse_args(argv) + + kind, n, m = INSTANCE_SPECS[args.instance] + training_path = args.dataset_root / args.instance / "train.csv" + samples = read_training_csv(training_path, 2 * n, m) + args.output_dir.mkdir(parents=True, exist_ok=True) + started = time.monotonic() + + # Sifting and adjacent swaps operate on a seed-42 train-only holdout score. + # Its final order joins the three named base orders as a fourth SAT candidate. + sifted, heuristic_selection = choose_order( + samples, + n, + seed=args.seed, + validation_fraction=0.2, + sift_variables=args.sift_variables, + adjacent_passes=args.adjacent_passes, + progress=False, + ) + candidates = base_orders(n) + candidates["seed42_sift_adjacent"] = sifted + unique_candidates: list[tuple[str, list[int]]] = [] + seen: set[tuple[int, ...]] = set() + for name, order in candidates.items(): + key = tuple(order) + if key not in seen: + seen.add(key) + unique_candidates.append((name, order)) + + candidate_reports: list[dict[str, object]] = [] + successful: list[ + tuple[tuple[int, int, int, int], str, list[int], BDDManager, list[int]] + ] = [] + for name, order in unique_candidates: + print(f"[candidate] name={name} order={order}", flush=True) + candidate_start = time.monotonic() + manager, roots, sat_outputs = learn_order( + samples, + order, + max_width=args.max_width, + conflict_budget=args.conflict_budget, + solver_name=args.solver, + ) + solved = len(roots) + widths = [output.width for output in sat_outputs if output.width is not None] + nodes = len(manager.reachable(roots)) + gates = -1 + netlist_path: str | None = None + if solved == m: + train_exact, _ = evaluate_samples(manager, roots, samples) + if train_exact != len(samples): + raise AssertionError("combined SAT BDD violates training data") + builder, output_signals = compile_bdd(manager, roots, 2 * n) + gates = len(builder.lines) + candidate_netlist = args.output_dir / f"{args.instance}.{name}.sat-bdd.txt" + write_netlist( + candidate_netlist, + builder, + output_signals, + metadata=[ + "minimum-width layered OBDD learned by SAT from train.csv only", + f"instance={args.instance} seed={args.seed} order_name={name}", + "order_zero_based=" + ",".join(map(str, order)), + ], + ) + netlist_path = str(candidate_netlist) + successful.append( + ( + order_objective(solved, widths, nodes, gates), + name, + order, + manager, + roots, + ) + ) + report = { + "name": name, + "order": order, + "solved_outputs": solved, + "widths": widths, + "nodes": nodes, + "gates": gates, + "netlist": netlist_path, + "output_attempts": [ + { + "root": output.root, + "width": output.width, + "attempts": [attempt_to_json(attempt) for attempt in output.attempts], + } + for output in sat_outputs + ], + "elapsed_seconds": time.monotonic() - candidate_start, + } + candidate_reports.append(report) + (args.output_dir / f"{args.instance}.{name}.sat-report.json").write_text( + json.dumps(report, indent=2, sort_keys=True) + "\n", "ascii" + ) + + if not successful: + summary = { + "schema": "occam71-sat-bdd-v1", + "instance": args.instance, + "status": "NO_COMPLETE_CANDIDATE_WITHIN_WIDTH_OR_BUDGET", + "seed": args.seed, + "max_width": args.max_width, + "conflict_budget": args.conflict_budget, + "solver": args.solver, + "heuristic_order_selection": heuristic_selection, + "candidates": candidate_reports, + "elapsed_seconds": time.monotonic() - started, + "training_sha256": hashlib.sha256(training_path.read_bytes()).hexdigest(), + } + (args.output_dir / f"{args.instance}.sat-bdd-summary.json").write_text( + json.dumps(summary, indent=2, sort_keys=True) + "\n", "ascii" + ) + print(f"[done] {summary['status']}", flush=True) + return 3 + + objective, selected_name, selected_order, manager, roots = max( + successful, key=lambda item: item[0] + ) + builder, output_signals = compile_bdd(manager, roots, 2 * n) + selected_netlist = args.output_dir / f"{args.instance}.selected.sat-bdd.txt" + write_netlist( + selected_netlist, + builder, + output_signals, + metadata=[ + "training-only selected minimum-width layered OBDD", + f"instance={args.instance} seed={args.seed} order_name={selected_name}", + "order_zero_based=" + ",".join(map(str, selected_order)), + ], + ) + train_rows, train_bits = evaluate_samples(manager, roots, samples) + audit = full_domain_audit(manager, roots, kind=kind, n=n, m=m) + summary = { + "schema": "occam71-sat-bdd-v1", + "instance": args.instance, + "status": "COMPLETE_CANDIDATE", + "seed": args.seed, + "max_width": args.max_width, + "conflict_budget": args.conflict_budget, + "solver": args.solver, + "training_rows": len(samples), + "training_exact_rows": train_rows, + "training_correct_bits": train_bits, + "training_sha256": hashlib.sha256(training_path.read_bytes()).hexdigest(), + "heuristic_order_selection": heuristic_selection, + "candidates": candidate_reports, + "selected_order_name": selected_name, + "selected_order": selected_order, + "selection_objective": list(objective), + "selected_nodes": len(manager.reachable(roots)), + "selected_gates": len(builder.lines), + "selected_netlist": str(selected_netlist), + "full_domain_audit_after_selection": audit, + "elapsed_seconds": time.monotonic() - started, + } + summary_path = args.output_dir / f"{args.instance}.sat-bdd-summary.json" + summary_path.write_text(json.dumps(summary, indent=2, sort_keys=True) + "\n", "ascii") + print( + f"[done] selected={selected_name} train={train_rows}/{len(samples)} " + f"full={audit['exact_rows']}/{audit['total_rows']} " + f"nodes={summary['selected_nodes']} gates={summary['selected_gates']}", + flush=True, + ) + print(f"[artifact] {selected_netlist}", flush=True) + print(f"[artifact] {summary_path}", flush=True) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/decision_diagram_learn.py b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/decision_diagram_learn.py new file mode 100644 index 000000000..15da2e539 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/decision_diagram_learn.py @@ -0,0 +1,767 @@ +#!/usr/bin/env python3 +"""Train-only shared multi-root BDD learner for Occam's Circuit issue #71. + +The learner never reads a reference circuit. For each output bit it builds a +layered prefix acceptor from the labelled examples, then merges bottom-up +states whose observed outgoing transitions are compatible. Missing +transitions are don't-cares and are completed toward an already observed +transition, which is the smallest deterministic completion at that state. + +Variable ordering is selected only from a deterministic seed-42 split of the +official training rows. Revealed arithmetic semantics are used solely by the +post-training full-domain audit. +""" + +from __future__ import annotations + +import argparse +import csv +import hashlib +import json +import random +import sys +import time +from dataclasses import dataclass +from pathlib import Path +from typing import Iterable, Sequence + + +INSTANCE_SPECS = { + "practice-add-n4": ("add", 4, 5), + "practice-mul-n4": ("mul", 4, 8), + "mystery-A": ("add", 8, 9), + "mystery-B": ("absdiff", 7, 7), + "mystery-C": ("mul", 6, 12), + "mystery-D": ("sos", 5, 11), +} + + +@dataclass(frozen=True) +class Sample: + bits: tuple[int, ...] + output: tuple[int, ...] + + +def read_training_csv(path: Path, n_inputs: int, n_outputs: int) -> list[Sample]: + """Strict parser for the two-column official training format.""" + rows: list[Sample] = [] + seen: dict[tuple[int, ...], tuple[int, ...]] = {} + with path.open("r", encoding="ascii", newline="") as handle: + reader = csv.reader(handle, strict=True) + try: + header = next(reader) + except StopIteration as exc: + raise ValueError(f"{path}: empty CSV") from exc + if header != ["input", "output"]: + raise ValueError(f"{path}: expected header input,output; got {header!r}") + for line_no, row in enumerate(reader, 2): + if len(row) != 2: + raise ValueError(f"{path}:{line_no}: expected exactly two fields") + ibits, obits = row + if len(ibits) != n_inputs or set(ibits) - {"0", "1"}: + raise ValueError(f"{path}:{line_no}: invalid input bit string") + if len(obits) != n_outputs or set(obits) - {"0", "1"}: + raise ValueError(f"{path}:{line_no}: invalid output bit string") + key = tuple(ord(ch) - 48 for ch in ibits) + value = tuple(ord(ch) - 48 for ch in obits) + old = seen.get(key) + if old is not None and old != value: + raise ValueError(f"{path}:{line_no}: conflicting duplicate input") + if old is None: + seen[key] = value + rows.append(Sample(key, value)) + if not rows: + raise ValueError(f"{path}: no samples") + return rows + + +@dataclass(frozen=True) +class BDDNode: + var: int + low: int + high: int + + +class BDDManager: + """Canonical reduced ordered BDD manager shared by all output roots.""" + + def __init__(self) -> None: + self.nodes: dict[int, BDDNode] = {} + self.unique: dict[BDDNode, int] = {} + self.next_id = 2 # 0 and 1 are terminals + + def intern(self, var: int, low: int, high: int) -> int: + if low == high: + return low + node = BDDNode(var, low, high) + old = self.unique.get(node) + if old is not None: + return old + node_id = self.next_id + self.next_id += 1 + self.nodes[node_id] = node + self.unique[node] = node_id + return node_id + + def reachable(self, roots: Sequence[int]) -> set[int]: + todo = [root for root in roots if root >= 2] + found: set[int] = set() + while todo: + node_id = todo.pop() + if node_id in found: + continue + found.add(node_id) + node = self.nodes[node_id] + if node.low >= 2: + todo.append(node.low) + if node.high >= 2: + todo.append(node.high) + return found + + def evaluate_root(self, root: int, bits: Sequence[int]) -> int: + node_id = root + while node_id >= 2: + node = self.nodes[node_id] + node_id = node.high if bits[node.var] else node.low + return node_id + + def evaluate(self, roots: Sequence[int], bits: Sequence[int]) -> tuple[int, ...]: + return tuple(self.evaluate_root(root, bits) for root in roots) + + +@dataclass +class _Cluster: + low: int | None + high: int | None + prefixes: list[int] + support: int + + def compatible(self, pattern: tuple[int | None, int | None]) -> bool: + low, high = pattern + return ( + (self.low is None or low is None or self.low == low) + and (self.high is None or high is None or self.high == high) + ) + + def absorb( + self, + pattern: tuple[int | None, int | None], + prefixes: Sequence[int], + ) -> None: + low, high = pattern + if self.low is None: + self.low = low + if self.high is None: + self.high = high + self.prefixes.extend(prefixes) + self.support += len(prefixes) + + +def _stable_tie(seed: int, output_bit: int, depth: int, values: object) -> int: + payload = f"{seed}|{output_bit}|{depth}|{values!r}".encode("ascii") + return int.from_bytes(hashlib.sha256(payload).digest()[:8], "big") + + +def _cluster_patterns( + patterns: dict[int, tuple[int | None, int | None]], + *, + var: int, + manager: BDDManager, + seed: int, + output_bit: int, + depth: int, +) -> dict[int, int]: + """Greedily cover wildcard transition pairs by compatible total pairs. + + Exact pairs are anchors. One-sided pairs are placed into an anchor that + preserves all observed transitions while preferring a reduced or already + shared BDD node. The procedure is deterministic for the recorded seed. + """ + by_pattern: dict[tuple[int | None, int | None], list[int]] = {} + for prefix, pattern in patterns.items(): + if pattern == (None, None): + raise AssertionError("observed prefix cannot have two missing children") + by_pattern.setdefault(pattern, []).append(prefix) + + ordered = sorted( + by_pattern, + key=lambda p: ( + -int(p[0] is not None) - int(p[1] is not None), + -len(by_pattern[p]), + _stable_tie(seed, output_bit, depth, p), + ), + ) + clusters: list[_Cluster] = [] + for pattern in ordered: + prefixes = by_pattern[pattern] + compatible: list[tuple[tuple[int, int, int, int], int]] = [] + for idx, cluster in enumerate(clusters): + if not cluster.compatible(pattern): + continue + low = cluster.low if cluster.low is not None else pattern[0] + high = cluster.high if cluster.high is not None else pattern[1] + if low is None and high is not None: + low = high + if high is None and low is not None: + high = low + assert low is not None and high is not None + reduced = int(low == high) + shared = int(BDDNode(var, low, high) in manager.unique) + tie = -(_stable_tie(seed, output_bit, depth, (pattern, idx)) & 0x7FFFFFFF) + compatible.append(((reduced, shared, cluster.support, tie), idx)) + if compatible: + _, idx = max(compatible) + clusters[idx].absorb(pattern, prefixes) + else: + clusters.append(_Cluster(pattern[0], pattern[1], list(prefixes), len(prefixes))) + + assignment: dict[int, int] = {} + for cluster in clusters: + low, high = cluster.low, cluster.high + if low is None: + if high is None: + raise AssertionError("empty cluster") + low = high + if high is None: + high = low + node_id = manager.intern(var, low, high) + for prefix in cluster.prefixes: + assignment[prefix] = node_id + if assignment.keys() != patterns.keys(): + raise AssertionError("incomplete cluster assignment") + return assignment + + +def _ordered_code(bits: Sequence[int], order: Sequence[int]) -> int: + code = 0 + for depth, var in enumerate(order): + code |= bits[var] << depth + return code + + +def learn_shared_bdd( + samples: Sequence[Sample], + order: Sequence[int], + *, + seed: int = 42, +) -> tuple[BDDManager, list[int]]: + n_inputs = len(samples[0].bits) + n_outputs = len(samples[0].output) + if sorted(order) != list(range(n_inputs)): + raise ValueError("order must be a permutation of all input variables") + manager = BDDManager() + codes = [_ordered_code(sample.bits, order) for sample in samples] + roots: list[int] = [] + for output_bit in range(n_outputs): + state_of_prefix: dict[int, int] = {} + for sample, code in zip(samples, codes): + label = sample.output[output_bit] + old = state_of_prefix.get(code) + if old is not None and old != label: + raise ValueError("conflicting duplicate training input") + state_of_prefix[code] = label + for depth in range(n_inputs - 1, -1, -1): + mask = (1 << depth) - 1 + patterns: dict[int, list[int | None]] = {} + for child_prefix, child_state in state_of_prefix.items(): + parent = child_prefix & mask + branch = (child_prefix >> depth) & 1 + pair = patterns.setdefault(parent, [None, None]) + old = pair[branch] + if old is not None and old != child_state: + raise AssertionError("nondeterministic prefix transition") + pair[branch] = child_state + state_of_prefix = _cluster_patterns( + {prefix: (pair[0], pair[1]) for prefix, pair in patterns.items()}, + var=order[depth], + manager=manager, + seed=seed, + output_bit=output_bit, + depth=depth, + ) + if set(state_of_prefix) != {0}: + raise AssertionError("root prefix missing") + roots.append(state_of_prefix[0]) + for sample in samples: + if manager.evaluate(roots, sample.bits) != sample.output: + raise AssertionError("learned BDD lost a training constraint") + return manager, roots + + +@dataclass(frozen=True) +class Signal: + ref: str | None + inverted: bool = False + const: int | None = None + + @staticmethod + def constant(value: int) -> "Signal": + return Signal(None, False, value) + + def negate(self) -> "Signal": + if self.const is not None: + return Signal.constant(1 - self.const) + return Signal(self.ref, not self.inverted) + + def sort_key(self) -> tuple[str, int, int]: + return (self.ref or "", int(self.inverted), -1 if self.const is None else self.const) + + def render(self) -> str: + if self.const is not None: + raise ValueError("constant must be materialized before rendering") + assert self.ref is not None + return f"~{self.ref}" if self.inverted else self.ref + + +class GateBuilder: + """Phase-aware AND/XOR gate builder with global structural CSE.""" + + def __init__(self, n_inputs: int) -> None: + self.n_inputs = n_inputs + self.lines: list[tuple[str, str, str, str]] = [] + self.and_cache: dict[tuple[tuple[str, bool], tuple[str, bool]], Signal] = {} + self.xor_cache: dict[tuple[str, str], Signal] = {} + + def clone(self) -> "GateBuilder": + other = GateBuilder(self.n_inputs) + other.lines = list(self.lines) + other.and_cache = dict(self.and_cache) + other.xor_cache = dict(self.xor_cache) + return other + + def _new_wire(self) -> str: + return f"w{len(self.lines) + 1}" + + @staticmethod + def _same_base(a: Signal, b: Signal) -> bool: + return a.const is None and b.const is None and a.ref == b.ref + + def and_(self, a: Signal, b: Signal) -> Signal: + if a.const is not None: + return Signal.constant(0) if a.const == 0 else b + if b.const is not None: + return Signal.constant(0) if b.const == 0 else a + if self._same_base(a, b): + return Signal.constant(0) if a.inverted != b.inverted else a + if b.sort_key() < a.sort_key(): + a, b = b, a + assert a.ref is not None and b.ref is not None + key = ((a.ref, a.inverted), (b.ref, b.inverted)) + old = self.and_cache.get(key) + if old is not None: + return old + wire = self._new_wire() + self.lines.append((wire, "AND", a.render(), b.render())) + result = Signal(wire) + self.and_cache[key] = result + # The complement is the same scored gate spelled NAND, so no extra key + # is required; callers carry the free output phase on Signal. + return result + + def or_(self, a: Signal, b: Signal) -> Signal: + return self.and_(a.negate(), b.negate()).negate() + + def xor(self, a: Signal, b: Signal) -> Signal: + if a.const is not None: + return b if a.const == 0 else b.negate() + if b.const is not None: + return a if b.const == 0 else a.negate() + if self._same_base(a, b): + return Signal.constant(int(a.inverted != b.inverted)) + phase = a.inverted ^ b.inverted + assert a.ref is not None and b.ref is not None + left, right = sorted((a.ref, b.ref)) + key = (left, right) + old = self.xor_cache.get(key) + if old is None: + wire = self._new_wire() + self.lines.append((wire, "XOR", left, right)) + old = Signal(wire) + self.xor_cache[key] = old + return old.negate() if phase else old + + def mux(self, select: Signal, low: Signal, high: Signal) -> Signal: + if low == high: + return low + if low.const == 0 and high.const == 1: + return select + if low.const == 1 and high.const == 0: + return select.negate() + if low.const == 0: + return self.and_(select, high) + if high.const == 0: + return self.and_(select.negate(), low) + if low.const == 1: + return self.or_(select.negate(), high) + if high.const == 1: + return self.or_(select, low) + if self._same_base(low, high) and low.inverted != high.inverted: + # low XOR select gives low for select=0 and ~low for select=1. + return self.xor(low, select) + + # Compare two exact three-gate Shannon decompositions under current CSE. + xor_trial = self.clone() + delta = xor_trial.xor(low, high) + changed = xor_trial.and_(select, delta) + xor_result = xor_trial.xor(low, changed) + + sop_trial = self.clone() + low_term = sop_trial.and_(select.negate(), low) + high_term = sop_trial.and_(select, high) + sop_result = sop_trial.or_(low_term, high_term) + + if len(sop_trial.lines) < len(xor_trial.lines): + self.lines = sop_trial.lines + self.and_cache = sop_trial.and_cache + self.xor_cache = sop_trial.xor_cache + return sop_result + self.lines = xor_trial.lines + self.and_cache = xor_trial.and_cache + self.xor_cache = xor_trial.xor_cache + return xor_result + + def materialize_constant(self, value: int) -> Signal: + base = Signal("x1") + zero = self.xor(base, base) + return zero if value == 0 else zero.negate() + + +def compile_bdd( + manager: BDDManager, + roots: Sequence[int], + n_inputs: int, +) -> tuple[GateBuilder, list[Signal]]: + builder = GateBuilder(n_inputs) + memo: dict[int, Signal] = { + 0: Signal.constant(0), + 1: Signal.constant(1), + } + + def compile_node(node_id: int) -> Signal: + old = memo.get(node_id) + if old is not None: + return old + node = manager.nodes[node_id] + low = compile_node(node.low) + high = compile_node(node.high) + select = Signal(f"x{node.var + 1}") + result = builder.mux(select, low, high) + memo[node_id] = result + return result + + outputs = [compile_node(root) for root in roots] + for idx, output in enumerate(outputs): + if output.const is not None: + outputs[idx] = builder.materialize_constant(output.const) + return builder, outputs + + +def write_netlist( + path: Path, + builder: GateBuilder, + outputs: Sequence[Signal], + *, + metadata: Sequence[str], +) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + with path.open("w", encoding="ascii", newline="\n") as handle: + for line in metadata: + safe = line.encode("ascii", "strict").decode("ascii") + handle.write(f"# {safe}\n") + handle.write(f"INPUTS {builder.n_inputs}\n") + for wire, op, left, right in builder.lines: + handle.write(f"{wire} = {op} {left} {right}\n") + handle.write("OUTPUTS " + " ".join(output.render() for output in outputs) + "\n") + + +def base_orders(n: int) -> dict[str, list[int]]: + return { + "blocked": list(range(2 * n)), + "lsb_interleaved": [item for bit in range(n) for item in (bit, n + bit)], + "msb_interleaved": [ + item for bit in range(n - 1, -1, -1) for item in (bit, n + bit) + ], + } + + +def evaluate_samples( + manager: BDDManager, + roots: Sequence[int], + samples: Sequence[Sample], +) -> tuple[int, int]: + rows = 0 + bits = 0 + for sample in samples: + predicted = manager.evaluate(roots, sample.bits) + rows += int(predicted == sample.output) + bits += sum(a == b for a, b in zip(predicted, sample.output)) + return rows, bits + + +@dataclass(frozen=True) +class OrderScore: + exact_rows: int + correct_bits: int + gates: int + nodes: int + + def objective(self) -> tuple[int, int, int, int]: + return (self.exact_rows, self.correct_bits, -self.gates, -self.nodes) + + +def score_order( + fit: Sequence[Sample], + validation: Sequence[Sample], + order: Sequence[int], + seed: int, +) -> OrderScore: + manager, roots = learn_shared_bdd(fit, order, seed=seed) + exact_rows, correct_bits = evaluate_samples(manager, roots, validation) + builder, _ = compile_bdd(manager, roots, len(order)) + return OrderScore( + exact_rows, + correct_bits, + len(builder.lines), + len(manager.reachable(roots)), + ) + + +def choose_order( + samples: Sequence[Sample], + n: int, + *, + seed: int, + validation_fraction: float, + sift_variables: int, + adjacent_passes: int, + progress: bool = True, +) -> tuple[list[int], dict[str, object]]: + rng = random.Random(seed) + indices = list(range(len(samples))) + rng.shuffle(indices) + n_valid = max(1, round(len(samples) * validation_fraction)) + valid_ids = set(indices[:n_valid]) + fit = [sample for idx, sample in enumerate(samples) if idx not in valid_ids] + validation = [sample for idx, sample in enumerate(samples) if idx in valid_ids] + cache: dict[tuple[int, ...], OrderScore] = {} + trace: list[dict[str, object]] = [] + + def assess(order: Sequence[int], tag: str) -> OrderScore: + key = tuple(order) + score = cache.get(key) + if score is None: + score = score_order(fit, validation, order, seed) + cache[key] = score + trace.append({"tag": tag, "order": list(order), **score.__dict__}) + if progress: + print( + f"[order] {tag} rows={score.exact_rows}/{len(validation)} " + f"bits={score.correct_bits}/{len(validation) * len(samples[0].output)} " + f"gates={score.gates} nodes={score.nodes}", + flush=True, + ) + return score + + candidates = base_orders(n) + scored = [(assess(order, name), name, order) for name, order in candidates.items()] + best_score, best_name, best_order = max(scored, key=lambda item: item[0].objective()) + best_order = list(best_order) + + variables = list(best_order) + rng.shuffle(variables) + for var in variables[: min(sift_variables, len(variables))]: + old_position = best_order.index(var) + without = [item for item in best_order if item != var] + local_best = (best_score, old_position, list(best_order)) + positions = list(range(len(best_order))) + rng.shuffle(positions) + for position in positions: + trial = list(without) + trial.insert(position, var) + score = assess(trial, f"sift-v{var}-p{position}") + candidate = (score.objective(), -position) + incumbent = (local_best[0].objective(), -local_best[1]) + if candidate > incumbent: + local_best = (score, position, trial) + best_score, _, best_order = local_best + + for pass_id in range(adjacent_passes): + positions = list(range(len(best_order) - 1)) + rng.shuffle(positions) + changed = False + for position in positions: + trial = list(best_order) + trial[position], trial[position + 1] = trial[position + 1], trial[position] + score = assess(trial, f"adj-{pass_id}-p{position}") + if score.objective() > best_score.objective(): + best_score = score + best_order = trial + changed = True + if not changed: + break + + summary = { + "seed": seed, + "fit_rows": len(fit), + "validation_rows": len(validation), + "selected_from": best_name, + "selected_order": best_order, + "selected_score": best_score.__dict__, + "evaluated_orders": len(cache), + "trace": trace, + } + return best_order, summary + + +def bits_for_xy(x: int, y: int, n: int) -> tuple[int, ...]: + return tuple((x >> bit) & 1 for bit in range(n)) + tuple( + (y >> bit) & 1 for bit in range(n) + ) + + +def truth_value(kind: str, x: int, y: int) -> int: + if kind == "add": + return x + y + if kind == "mul": + return x * y + if kind == "absdiff": + return abs(x - y) + if kind == "sos": + return x * x + y * y + raise ValueError(f"unknown audit function {kind!r}") + + +def full_domain_audit( + manager: BDDManager, + roots: Sequence[int], + *, + kind: str, + n: int, + m: int, +) -> dict[str, object]: + exact_rows = 0 + correct_bits = 0 + first_failures: list[dict[str, object]] = [] + total = 1 << (2 * n) + mask = (1 << n) - 1 + for packed in range(total): + x, y = packed & mask, packed >> n + bits = bits_for_xy(x, y, n) + predicted_bits = manager.evaluate(roots, bits) + expected_value = truth_value(kind, x, y) + expected_bits = tuple((expected_value >> bit) & 1 for bit in range(m)) + exact_rows += int(predicted_bits == expected_bits) + correct_bits += sum(a == b for a, b in zip(predicted_bits, expected_bits)) + if predicted_bits != expected_bits and len(first_failures) < 8: + first_failures.append( + { + "x": x, + "y": y, + "predicted": "".join(map(str, predicted_bits)), + "expected": "".join(map(str, expected_bits)), + } + ) + return { + "total_rows": total, + "exact_rows": exact_rows, + "row_accuracy": exact_rows / total, + "correct_bits": correct_bits, + "total_bits": total * m, + "bit_accuracy": correct_bits / (total * m), + "first_failures": first_failures, + } + + +def parse_order(text: str, n_inputs: int) -> list[int]: + values = [int(piece) for piece in text.split(",") if piece] + if sorted(values) != list(range(n_inputs)): + raise argparse.ArgumentTypeError("order must list every zero-based input index once") + return values + + +def main(argv: Sequence[str] | None = None) -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--dataset-root", type=Path, required=True) + parser.add_argument("--instance", choices=sorted(INSTANCE_SPECS), required=True) + parser.add_argument("--output-dir", type=Path, required=True) + parser.add_argument("--seed", type=int, default=42) + parser.add_argument("--validation-fraction", type=float, default=0.2) + parser.add_argument("--sift-variables", type=int, default=6) + parser.add_argument("--adjacent-passes", type=int, default=2) + parser.add_argument("--fixed-order") + args = parser.parse_args(argv) + + kind, n, m = INSTANCE_SPECS[args.instance] + training_path = args.dataset_root / args.instance / "train.csv" + samples = read_training_csv(training_path, 2 * n, m) + started = time.monotonic() + if args.fixed_order: + order = parse_order(args.fixed_order, 2 * n) + selection: dict[str, object] = { + "seed": args.seed, + "selected_order": order, + "mode": "fixed", + } + else: + order, selection = choose_order( + samples, + n, + seed=args.seed, + validation_fraction=args.validation_fraction, + sift_variables=args.sift_variables, + adjacent_passes=args.adjacent_passes, + ) + print(f"[fit] instance={args.instance} rows={len(samples)} order={order}", flush=True) + manager, roots = learn_shared_bdd(samples, order, seed=args.seed) + train_rows, train_bits = evaluate_samples(manager, roots, samples) + builder, outputs = compile_bdd(manager, roots, 2 * n) + audit = full_domain_audit(manager, roots, kind=kind, n=n, m=m) + elapsed = time.monotonic() - started + + args.output_dir.mkdir(parents=True, exist_ok=True) + netlist_path = args.output_dir / f"{args.instance}.bdd.txt" + write_netlist( + netlist_path, + builder, + outputs, + metadata=[ + "self-written train-only compatible-state BDD learner", + f"instance={args.instance} seed={args.seed}", + "order_zero_based=" + ",".join(map(str, order)), + ], + ) + report = { + "schema": "occam71-bdd-route-v1", + "instance": args.instance, + "function_used_only_for_audit": kind, + "n": n, + "m": m, + "training_rows": len(samples), + "training_exact_rows": train_rows, + "training_correct_bits": train_bits, + "order_selection": selection, + "bdd_reachable_nodes": len(manager.reachable(roots)), + "bdd_allocated_nodes": len(manager.nodes), + "gate_count": len(builder.lines), + "root_ids": roots, + "full_domain_audit": audit, + "elapsed_seconds": elapsed, + "netlist": str(netlist_path), + "training_sha256": hashlib.sha256(training_path.read_bytes()).hexdigest(), + } + report_path = args.output_dir / f"{args.instance}.report.json" + report_path.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="ascii") + print( + f"[done] train={train_rows}/{len(samples)} " + f"full={audit['exact_rows']}/{audit['total_rows']} " + f"nodes={report['bdd_reachable_nodes']} gates={report['gate_count']} " + f"elapsed={elapsed:.3f}s", + flush=True, + ) + print(f"[artifact] {netlist_path}", flush=True) + print(f"[artifact] {report_path}", flush=True) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/decision_diagram_learn_v2.py b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/decision_diagram_learn_v2.py new file mode 100644 index 000000000..a23dd74f4 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/decision_diagram_learn_v2.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +"""Reviewed entry point for decision_diagram_learn with constant materialization. + +Kept separate so the original experiment snapshot remains immutable. +""" + +from decision_diagram_learn import * # noqa: F401,F403 +from decision_diagram_learn import GateBuilder, Signal, main + + +def _materialize_constant(self: GateBuilder, value: int) -> Signal: + zero = getattr(self, "_constant_zero_signal", None) + if zero is None: + wire = self._new_wire() + self.lines.append((wire, "XOR", "x1", "x1")) + zero = Signal(wire) + self._constant_zero_signal = zero + return zero if value == 0 else zero.negate() + + +GateBuilder.materialize_constant = _materialize_constant + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/independent_bdd_audit.py b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/independent_bdd_audit.py new file mode 100644 index 000000000..4cf207e28 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/independent_bdd_audit.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python3 +"""Independent strict parser and exhaustive auditor for generated BDD netlists.""" + +from __future__ import annotations + +import argparse +import json +import re +from pathlib import Path +from typing import Sequence + +from decision_diagram_learn import INSTANCE_SPECS, bits_for_xy, truth_value + + +NAME_RE = re.compile(r"(~)?(x[1-9][0-9]*|w[1-9][0-9]*)\Z") +GATE_RE = re.compile( + r"(w[1-9][0-9]*) = (AND|OR|XOR|NAND|NOR|XNOR) " + r"(~?(?:x[1-9][0-9]*|w[1-9][0-9]*)) " + r"(~?(?:x[1-9][0-9]*|w[1-9][0-9]*))\Z" +) + + +def parse_operand(token: str, values: dict[str, int]) -> int: + match = NAME_RE.fullmatch(token) + if match is None: + raise ValueError(f"invalid operand {token!r}") + inverted, name = match.groups() + if name not in values: + raise ValueError(f"use before definition: {name}") + value = values[name] + return 1 - value if inverted else value + + +def parse_netlist(path: Path) -> tuple[int, list[tuple[str, str, str, str]], list[str]]: + n_inputs: int | None = None + gates: list[tuple[str, str, str, str]] = [] + outputs: list[str] | None = None + defined: set[str] = set() + with path.open("r", encoding="ascii", newline="") as handle: + for line_no, raw in enumerate(handle, 1): + line = raw.rstrip("\r\n") + if not line or line.startswith("#"): + continue + if n_inputs is None: + pieces = line.split(" ") + if len(pieces) != 2 or pieces[0] != "INPUTS" or not pieces[1].isdigit(): + raise ValueError(f"{path}:{line_no}: expected INPUTS") + n_inputs = int(pieces[1]) + if n_inputs <= 0: + raise ValueError(f"{path}:{line_no}: invalid input count") + defined.update(f"x{i}" for i in range(1, n_inputs + 1)) + continue + if line.startswith("OUTPUTS "): + if outputs is not None: + raise ValueError(f"{path}:{line_no}: duplicate OUTPUTS") + outputs = line.split(" ")[1:] + if not outputs: + raise ValueError(f"{path}:{line_no}: no outputs") + for operand in outputs: + match = NAME_RE.fullmatch(operand) + if match is None or match.group(2) not in defined: + raise ValueError(f"{path}:{line_no}: invalid output {operand!r}") + continue + if outputs is not None: + raise ValueError(f"{path}:{line_no}: content after OUTPUTS") + match = GATE_RE.fullmatch(line) + if match is None: + raise ValueError(f"{path}:{line_no}: malformed gate") + wire, op, left, right = match.groups() + if wire in defined: + raise ValueError(f"{path}:{line_no}: duplicate wire {wire}") + for operand in (left, right): + operand_name = operand[1:] if operand.startswith("~") else operand + if operand_name not in defined: + raise ValueError(f"{path}:{line_no}: use before definition {operand_name}") + gates.append((wire, op, left, right)) + defined.add(wire) + if n_inputs is None or outputs is None: + raise ValueError(f"{path}: incomplete netlist") + return n_inputs, gates, outputs + + +def evaluate( + n_inputs: int, + gates: Sequence[tuple[str, str, str, str]], + outputs: Sequence[str], + bits: Sequence[int], +) -> tuple[int, ...]: + if len(bits) != n_inputs: + raise ValueError("wrong input length") + values = {f"x{i + 1}": bit for i, bit in enumerate(bits)} + for wire, op, left_text, right_text in gates: + left = parse_operand(left_text, values) + right = parse_operand(right_text, values) + if op == "AND": + value = left & right + elif op == "OR": + value = left | right + elif op == "XOR": + value = left ^ right + elif op == "NAND": + value = 1 - (left & right) + elif op == "NOR": + value = 1 - (left | right) + elif op == "XNOR": + value = 1 - (left ^ right) + else: + raise AssertionError(op) + values[wire] = value + return tuple(parse_operand(output, values) for output in outputs) + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--instance", choices=sorted(INSTANCE_SPECS), required=True) + parser.add_argument("--netlist", type=Path, required=True) + parser.add_argument("--json", type=Path) + args = parser.parse_args() + + kind, n, m = INSTANCE_SPECS[args.instance] + n_inputs, gates, outputs = parse_netlist(args.netlist) + if n_inputs != 2 * n or len(outputs) != m: + raise ValueError("netlist arity does not match instance") + total = 1 << (2 * n) + mask = (1 << n) - 1 + exact = 0 + correct_bits = 0 + failures = [] + for packed in range(total): + x, y = packed & mask, packed >> n + bits = bits_for_xy(x, y, n) + predicted = evaluate(n_inputs, gates, outputs, bits) + value = truth_value(kind, x, y) + expected = tuple((value >> bit) & 1 for bit in range(m)) + exact += int(predicted == expected) + correct_bits += sum(a == b for a, b in zip(predicted, expected)) + if predicted != expected and len(failures) < 8: + failures.append( + { + "x": x, + "y": y, + "predicted": "".join(map(str, predicted)), + "expected": "".join(map(str, expected)), + } + ) + report = { + "schema": "occam71-independent-netlist-audit-v1", + "instance": args.instance, + "gates": len(gates), + "total_rows": total, + "exact_rows": exact, + "row_accuracy": exact / total, + "correct_bits": correct_bits, + "total_bits": total * m, + "bit_accuracy": correct_bits / (total * m), + "first_failures": failures, + } + if args.json: + args.json.parent.mkdir(parents=True, exist_ok=True) + args.json.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", "ascii") + print(json.dumps(report, sort_keys=True), flush=True) + return 0 if exact == total else 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/independent_bitset_audit.py b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/independent_bitset_audit.py new file mode 100644 index 000000000..4ebf952e3 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/independent_bitset_audit.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python3 +"""Independent strict netlist parser with exhaustive bit-parallel simulation.""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import re +from pathlib import Path + + +SPECS = { + "practice-add-n4": ("add", 4, 5), + "practice-mul-n4": ("mul", 4, 8), + "mystery-A": ("add", 8, 9), + "mystery-B": ("absdiff", 7, 7), + "mystery-C": ("mul", 6, 12), + "mystery-D": ("sos", 5, 11), +} +OPERAND = re.compile(r"(~)?(x[1-9][0-9]*|w[1-9][0-9]*)\Z") +GATE = re.compile( + r"(w[1-9][0-9]*) = (AND|OR|XOR|NAND|NOR|XNOR) " + r"(~?(?:x[1-9][0-9]*|w[1-9][0-9]*)) " + r"(~?(?:x[1-9][0-9]*|w[1-9][0-9]*))\Z" +) + + +def parse(path: Path) -> tuple[int, list[tuple[str, str, str, str]], list[str]]: + n_inputs = None + gates = [] + outputs = None + defined = set() + with path.open("r", encoding="ascii", newline="") as handle: + for line_no, raw in enumerate(handle, 1): + line = raw.rstrip("\r\n") + if not line or line.startswith("#"): + continue + if n_inputs is None: + fields = line.split(" ") + if len(fields) != 2 or fields[0] != "INPUTS" or not fields[1].isdigit(): + raise ValueError(f"{path}:{line_no}: malformed INPUTS") + n_inputs = int(fields[1]) + if n_inputs <= 0: + raise ValueError(f"{path}:{line_no}: nonpositive INPUTS") + defined = {f"x{i}" for i in range(1, n_inputs + 1)} + continue + if line.startswith("OUTPUTS "): + if outputs is not None: + raise ValueError(f"{path}:{line_no}: duplicate OUTPUTS") + outputs = line.split(" ")[1:] + for token in outputs: + match = OPERAND.fullmatch(token) + if match is None or match.group(2) not in defined: + raise ValueError(f"{path}:{line_no}: invalid output") + continue + if outputs is not None: + raise ValueError(f"{path}:{line_no}: content after OUTPUTS") + match = GATE.fullmatch(line) + if match is None: + raise ValueError(f"{path}:{line_no}: malformed gate") + wire, op, left, right = match.groups() + if wire in defined: + raise ValueError(f"{path}:{line_no}: duplicate wire") + for token in (left, right): + name = token[1:] if token.startswith("~") else token + if name not in defined: + raise ValueError(f"{path}:{line_no}: use before definition") + gates.append((wire, op, left, right)) + defined.add(wire) + if n_inputs is None or outputs is None: + raise ValueError(f"{path}: incomplete netlist") + return n_inputs, gates, outputs + + +def truth_function(kind: str, x: int, y: int) -> int: + if kind == "add": + return x + y + if kind == "mul": + return x * y + if kind == "absdiff": + return abs(x - y) + if kind == "sos": + return x * x + y * y + raise AssertionError(kind) + + +def operand_value(token: str, values: dict[str, int], domain_mask: int) -> int: + match = OPERAND.fullmatch(token) + if match is None: + raise ValueError("invalid operand") + inverted, name = match.groups() + value = values[name] + return (value ^ domain_mask) if inverted else value + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--instance", choices=sorted(SPECS), required=True) + parser.add_argument("--netlist", type=Path, required=True) + parser.add_argument("--json", type=Path) + args = parser.parse_args() + kind, n, m = SPECS[args.instance] + n_inputs, gates, outputs = parse(args.netlist) + if n_inputs != 2 * n or len(outputs) != m: + raise ValueError("netlist arity mismatch") + total = 1 << n_inputs + domain_mask = (1 << total) - 1 + values = {} + for variable in range(n_inputs): + truth = 0 + for row in range(total): + if (row >> variable) & 1: + truth |= 1 << row + values[f"x{variable + 1}"] = truth + for wire, op, left_text, right_text in gates: + left = operand_value(left_text, values, domain_mask) + right = operand_value(right_text, values, domain_mask) + if op == "AND": + value = left & right + elif op == "OR": + value = left | right + elif op == "XOR": + value = left ^ right + elif op == "NAND": + value = (left & right) ^ domain_mask + elif op == "NOR": + value = (left | right) ^ domain_mask + elif op == "XNOR": + value = (left ^ right) ^ domain_mask + else: + raise AssertionError(op) + values[wire] = value + predicted = [operand_value(token, values, domain_mask) for token in outputs] + expected = [0] * m + input_mask = (1 << n) - 1 + for row in range(total): + x, y = row & input_mask, row >> n + result = truth_function(kind, x, y) + for bit in range(m): + if (result >> bit) & 1: + expected[bit] |= 1 << row + bad = 0 + bit_errors = 0 + for actual, wanted in zip(predicted, expected): + difference = actual ^ wanted + bad |= difference + bit_errors += difference.bit_count() + exact = total - bad.bit_count() + failures = [] + remaining = bad + while remaining and len(failures) < 8: + low_bit = remaining & -remaining + row = low_bit.bit_length() - 1 + x, y = row & input_mask, row >> n + actual_text = "".join(str((value >> row) & 1) for value in predicted) + expected_text = "".join(str((value >> row) & 1) for value in expected) + failures.append( + {"x": x, "y": y, "predicted": actual_text, "expected": expected_text} + ) + remaining ^= low_bit + report = { + "schema": "occam71-independent-bitset-audit-v1", + "instance": args.instance, + "netlist_sha256": hashlib.sha256(args.netlist.read_bytes()).hexdigest(), + "gates": len(gates), + "total_rows": total, + "exact_rows": exact, + "row_accuracy": exact / total, + "correct_bits": total * m - bit_errors, + "total_bits": total * m, + "bit_accuracy": (total * m - bit_errors) / (total * m), + "first_failures": failures, + } + if args.json: + args.json.parent.mkdir(parents=True, exist_ok=True) + args.json.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", "ascii") + print(json.dumps(report, sort_keys=True), flush=True) + return 0 if exact == total else 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/slurm_bdd_occam_mysteries.sbatch b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/slurm_bdd_occam_mysteries.sbatch new file mode 100644 index 000000000..321d0281b --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/slurm_bdd_occam_mysteries.sbatch @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +#SBATCH --job-name=occ71-bddocc-main +#SBATCH --partition=home +#SBATCH --nodelist=n006 +#SBATCH --array=0-3 +#SBATCH --cpus-per-task=1 +#SBATCH --mem=48G +#SBATCH --time=18:00:00 +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/bdd-route/logs/%A_occmain_%a.out +#SBATCH --error=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/bdd-route/logs/%A_occmain_%a.err + +set -euo pipefail + +ROOT=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +CODE="$ROOT/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route" +DATA="$ROOT/results/occam71/occam-circuit/datasets" +PYSAT_VENV="${PYSAT_VENV:?PYSAT_VENV must name the audited offline environment}" +RUN="$ROOT/results/occam71/bdd-route/job-${SLURM_ARRAY_JOB_ID}/occam-main-${SLURM_ARRAY_TASK_ID}" +mkdir -p "$RUN" + +instances=(mystery-A mystery-B mystery-C mystery-D) +order_names=(lsb_interleaved msb_interleaved lsb_interleaved lsb_interleaved) +orders=( + 0,8,1,9,2,10,3,11,4,12,5,13,6,14,7,15 + 6,13,5,12,4,11,3,10,2,9,1,8,0,7 + 0,6,1,7,2,8,3,9,4,10,5,11 + 0,5,1,6,2,7,3,8,4,9 +) +max_widths=(8 8 16 16) +instance="${instances[$SLURM_ARRAY_TASK_ID]}" +order_name="${order_names[$SLURM_ARRAY_TASK_ID]}" +order="${orders[$SLURM_ARRAY_TASK_ID]}" +max_width="${max_widths[$SLURM_ARRAY_TASK_ID]}" + +set +e +"$PYSAT_VENV/bin/python" -u "$CODE/bdd_sat_occam_fixed.py" \ + --fixed-order "$order" \ + --fixed-order-name "$order_name" \ + --dataset-root "$DATA" \ + --instance "$instance" \ + --output-dir "$RUN" \ + --seed 42 \ + --max-width "$max_width" \ + --conflict-budget 500000 \ + --solver cadical195 \ + --sift-variables 0 \ + --adjacent-passes 0 +learner_status=$? +set -e + +netlist="$RUN/${instance}.selected.occam-bdd.txt" +audit_status=NA +if test -f "$netlist"; then + set +e + "$PYSAT_VENV/bin/python" -u "$CODE/independent_bdd_audit.py" \ + --instance "$instance" \ + --netlist "$netlist" \ + --json "$RUN/${instance}.selected.occam-audit.json" + audit_status=$? + set -e +fi +printf '%s\n' "$learner_status" > "$RUN/learner_exit_status.txt" +printf '%s\n' "$audit_status" > "$RUN/audit_exit_status.txt" +sha256sum \ + "$CODE/bdd_sat_occam_fixed.py" \ + "$CODE/bdd_sat_occam_v2.py" \ + "$CODE/bdd_sat_refine.py" \ + "$CODE/decision_diagram_learn.py" \ + "$CODE/decision_diagram_learn_v2.py" \ + "$CODE/independent_bdd_audit.py" \ + "$DATA/$instance/train.csv" > "$RUN/INPUT_SHA256SUMS" +if test -f "$netlist"; then + sha256sum "$netlist" > "$RUN/NETLIST_SHA256SUM" +fi +printf 'BDD_OCCAM_MAIN_COMPLETE instance=%s learner_status=%s audit_status=%s\n' \ + "$instance" "$learner_status" "$audit_status" > "$RUN/COMPLETE" +cat "$RUN/COMPLETE" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/slurm_bdd_occam_warmups_v2.sbatch b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/slurm_bdd_occam_warmups_v2.sbatch new file mode 100644 index 000000000..5f2174129 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/slurm_bdd_occam_warmups_v2.sbatch @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +#SBATCH --job-name=occ71-bddocc-warm +#SBATCH --partition=home +#SBATCH --nodelist=n006 +#SBATCH --array=0-1 +#SBATCH --cpus-per-task=1 +#SBATCH --mem=32G +#SBATCH --time=12:00:00 +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/bdd-route/logs/%A_occwarm_%a.out +#SBATCH --error=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/bdd-route/logs/%A_occwarm_%a.err + +set -euo pipefail + +ROOT=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +CODE="$ROOT/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route" +DATA="$ROOT/results/occam71/occam-circuit/datasets" +PYSAT_VENV="${PYSAT_VENV:?PYSAT_VENV must name the audited offline environment}" +RUN="$ROOT/results/occam71/bdd-route/job-${SLURM_ARRAY_JOB_ID}/occam-warmup-${SLURM_ARRAY_TASK_ID}" +mkdir -p "$RUN" + +instances=(practice-add-n4 practice-mul-n4) +instance="${instances[$SLURM_ARRAY_TASK_ID]}" + +"$PYSAT_VENV/bin/python" -u "$CODE/bdd_sat_occam_v2.py" \ + --dataset-root "$DATA" \ + --instance "$instance" \ + --output-dir "$RUN" \ + --seed 42 \ + --max-width 16 \ + --conflict-budget 2000000 \ + --solver cadical195 \ + --sift-variables 6 \ + --adjacent-passes 2 + +set +e +"$PYSAT_VENV/bin/python" -u "$CODE/independent_bdd_audit.py" \ + --instance "$instance" \ + --netlist "$RUN/${instance}.selected.occam-bdd.txt" \ + --json "$RUN/${instance}.selected.occam-audit.json" +audit_status=$? +set -e +printf '%s\n' "$audit_status" > "$RUN/audit_exit_status.txt" +sha256sum \ + "$CODE/bdd_sat_occam_v2.py" \ + "$CODE/bdd_sat_refine.py" \ + "$CODE/decision_diagram_learn.py" \ + "$CODE/decision_diagram_learn_v2.py" \ + "$CODE/independent_bdd_audit.py" \ + "$DATA/$instance/train.csv" \ + "$RUN/${instance}.selected.occam-bdd.txt" > "$RUN/SHA256SUMS" +printf 'BDD_OCCAM_WARMUP_COMPLETE instance=%s audit_status=%s\n' \ + "$instance" "$audit_status" > "$RUN/COMPLETE" +cat "$RUN/COMPLETE" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/slurm_bdd_prefix_mysteries_v3.sbatch b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/slurm_bdd_prefix_mysteries_v3.sbatch new file mode 100644 index 000000000..c8c9d8622 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/slurm_bdd_prefix_mysteries_v3.sbatch @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +#SBATCH --job-name=occ71-bddprefix +#SBATCH --partition=home +#SBATCH --nodelist=n006 +#SBATCH --array=0-3 +#SBATCH --cpus-per-task=1 +#SBATCH --mem=48G +#SBATCH --time=18:00:00 +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/bdd-route/logs/%A_prefix_%a.out +#SBATCH --error=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/bdd-route/logs/%A_prefix_%a.err + +set -euo pipefail +ROOT=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +CODE="$ROOT/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route" +DATA="$ROOT/results/occam71/occam-circuit/datasets" +PYSAT_VENV="${PYSAT_VENV:?PYSAT_VENV must name the audited offline environment}" +RUN="$ROOT/results/occam71/bdd-route/job-${SLURM_ARRAY_JOB_ID}/prefix-${SLURM_ARRAY_TASK_ID}" +mkdir -p "$RUN" + +instances=(mystery-A mystery-B mystery-C mystery-D) +order_names=(lsb_interleaved msb_interleaved lsb_interleaved lsb_interleaved) +orders=( + 0,8,1,9,2,10,3,11,4,12,5,13,6,14,7,15 + 6,13,5,12,4,11,3,10,2,9,1,8,0,7 + 0,6,1,7,2,8,3,9,4,10,5,11 + 0,5,1,6,2,7,3,8,4,9 +) +max_widths=(8 16 16 16) +instance="${instances[$SLURM_ARRAY_TASK_ID]}" +order_name="${order_names[$SLURM_ARRAY_TASK_ID]}" +order="${orders[$SLURM_ARRAY_TASK_ID]}" +max_width="${max_widths[$SLURM_ARRAY_TASK_ID]}" + +set +e +"$PYSAT_VENV/bin/python" -u "$CODE/bdd_prefix_occam_v3.py" \ + --dataset-root "$DATA" \ + --instance "$instance" \ + --output-dir "$RUN" \ + --fixed-order "$order" \ + --fixed-order-name "$order_name" \ + --seed 42 \ + --max-width "$max_width" \ + --conflict-budget 2000000 \ + --solver cadical195 +learner_status=$? +set -e + +netlist="$RUN/${instance}.prefix-occam-bdd.txt" +audit_status=NA +if test -f "$netlist"; then + set +e + "$PYSAT_VENV/bin/python" -u "$CODE/independent_bdd_audit.py" \ + --instance "$instance" \ + --netlist "$netlist" \ + --json "$RUN/${instance}.prefix-occam-audit.json" + audit_status=$? + set -e +fi +printf '%s\n' "$learner_status" > "$RUN/learner_exit_status.txt" +printf '%s\n' "$audit_status" > "$RUN/audit_exit_status.txt" +sha256sum \ + "$CODE/bdd_prefix_occam_v3.py" \ + "$CODE/decision_diagram_learn.py" \ + "$CODE/decision_diagram_learn_v2.py" \ + "$CODE/independent_bdd_audit.py" \ + "$DATA/$instance/train.csv" > "$RUN/INPUT_SHA256SUMS" +if test -f "$netlist"; then + sha256sum "$netlist" > "$RUN/NETLIST_SHA256SUM" +fi +printf 'BDD_PREFIX_COMPLETE instance=%s learner_status=%s audit_status=%s\n' \ + "$instance" "$learner_status" "$audit_status" > "$RUN/COMPLETE" +cat "$RUN/COMPLETE" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/slurm_bdd_route_v2.sbatch b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/slurm_bdd_route_v2.sbatch new file mode 100644 index 000000000..82df57569 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/slurm_bdd_route_v2.sbatch @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +#SBATCH --job-name=occam71-bdd +#SBATCH --partition=home +#SBATCH --nodelist=n006 +#SBATCH --array=0-5 +#SBATCH --cpus-per-task=1 +#SBATCH --mem=12G +#SBATCH --time=06:00:00 +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/bdd-route/logs/%A_%a.out +#SBATCH --error=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/bdd-route/logs/%A_%a.err + +set -euo pipefail + +ROOT=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +CODE="$ROOT/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route" +DATA="$ROOT/results/occam71/occam-circuit/datasets" +RUN="$ROOT/results/occam71/bdd-route/job-${SLURM_ARRAY_JOB_ID}/task-${SLURM_ARRAY_TASK_ID}" +mkdir -p "$RUN" + +instances=( + practice-add-n4 + practice-mul-n4 + mystery-A + mystery-B + mystery-C + mystery-D +) +instance="${instances[$SLURM_ARRAY_TASK_ID]}" + +python3 -u "$CODE/decision_diagram_learn_v2.py" \ + --dataset-root "$DATA" \ + --instance "$instance" \ + --output-dir "$RUN" \ + --seed 42 \ + --validation-fraction 0.2 \ + --sift-variables 6 \ + --adjacent-passes 2 + +set +e +python3 -u "$CODE/independent_bdd_audit.py" \ + --instance "$instance" \ + --netlist "$RUN/${instance}.bdd.txt" \ + --json "$RUN/${instance}.independent-audit.json" +audit_status=$? +set -e + +printf '%s\n' "$audit_status" > "$RUN/audit_exit_status.txt" +sha256sum \ + "$CODE/decision_diagram_learn.py" \ + "$CODE/decision_diagram_learn_v2.py" \ + "$CODE/independent_bdd_audit.py" \ + "$DATA/$instance/train.csv" \ + "$RUN/${instance}.bdd.txt" > "$RUN/SHA256SUMS" +printf 'BDD_ROUTE_COMPLETE instance=%s audit_status=%s\n' \ + "$instance" "$audit_status" > "$RUN/COMPLETE" +cat "$RUN/COMPLETE" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/slurm_bdd_sat_warmups.sbatch b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/slurm_bdd_sat_warmups.sbatch new file mode 100644 index 000000000..c251a8de2 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/slurm_bdd_sat_warmups.sbatch @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +#SBATCH --job-name=occ71-bddsat-warm +#SBATCH --partition=home +#SBATCH --nodelist=n006 +#SBATCH --array=0-1 +#SBATCH --cpus-per-task=1 +#SBATCH --mem=20G +#SBATCH --time=06:00:00 +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/bdd-route/logs/%A_satwarm_%a.out +#SBATCH --error=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/bdd-route/logs/%A_satwarm_%a.err + +set -euo pipefail + +ROOT=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +CODE="$ROOT/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route" +DATA="$ROOT/results/occam71/occam-circuit/datasets" +PYSAT_VENV="${PYSAT_VENV:?PYSAT_VENV must name the audited offline environment}" +RUN="$ROOT/results/occam71/bdd-route/job-${SLURM_ARRAY_JOB_ID}/sat-warmup-${SLURM_ARRAY_TASK_ID}" +mkdir -p "$RUN" + +instances=(practice-add-n4 practice-mul-n4) +instance="${instances[$SLURM_ARRAY_TASK_ID]}" + +"$PYSAT_VENV/bin/python" -u "$CODE/bdd_sat_refine.py" \ + --dataset-root "$DATA" \ + --instance "$instance" \ + --output-dir "$RUN" \ + --seed 42 \ + --max-width 16 \ + --conflict-budget 2000000 \ + --solver cadical195 \ + --sift-variables 6 \ + --adjacent-passes 2 + +set +e +"$PYSAT_VENV/bin/python" -u "$CODE/independent_bdd_audit.py" \ + --instance "$instance" \ + --netlist "$RUN/${instance}.selected.sat-bdd.txt" \ + --json "$RUN/${instance}.selected.independent-audit.json" +audit_status=$? +set -e +printf '%s\n' "$audit_status" > "$RUN/audit_exit_status.txt" +sha256sum \ + "$CODE/bdd_sat_refine.py" \ + "$CODE/decision_diagram_learn.py" \ + "$CODE/decision_diagram_learn_v2.py" \ + "$CODE/independent_bdd_audit.py" \ + "$DATA/$instance/train.csv" \ + "$RUN/${instance}.selected.sat-bdd.txt" > "$RUN/SHA256SUMS" +printf 'BDD_SAT_WARMUP_COMPLETE instance=%s audit_status=%s\n' \ + "$instance" "$audit_status" > "$RUN/COMPLETE" +cat "$RUN/COMPLETE" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/slurm_zdd_route.sbatch b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/slurm_zdd_route.sbatch new file mode 100644 index 000000000..1f01e80e4 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/slurm_zdd_route.sbatch @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +#SBATCH --job-name=occ71-zdd +#SBATCH --partition=home +#SBATCH --nodelist=n006 +#SBATCH --array=0-5 +#SBATCH --cpus-per-task=1 +#SBATCH --mem=32G +#SBATCH --time=08:00:00 +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/zdd-route/logs/%A_%a.out +#SBATCH --error=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/zdd-route/logs/%A_%a.err + +set -euo pipefail +ROOT=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +CODE="$ROOT/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route" +DATA="$ROOT/results/occam71/occam-circuit/datasets" +RUN="$ROOT/results/occam71/zdd-route/job-${SLURM_ARRAY_JOB_ID}/task-${SLURM_ARRAY_TASK_ID}" +mkdir -p "$RUN" +instances=( + practice-add-n4 + practice-mul-n4 + mystery-A + mystery-B + mystery-C + mystery-D +) +instance="${instances[$SLURM_ARRAY_TASK_ID]}" + +python3 -u "$CODE/zdd_shared_route.py" \ + --dataset-root "$DATA" \ + --instance "$instance" \ + --output-dir "$RUN" \ + --seed 42 \ + --sift-variables 6 \ + --adjacent-passes 2 + +set +e +python3 -u "$CODE/independent_bitset_audit.py" \ + --instance "$instance" \ + --netlist "$RUN/${instance}.shared-zdd.txt" \ + --json "$RUN/${instance}.independent-bitset-audit.json" +audit_status=$? +set -e +printf '%s\n' "$audit_status" > "$RUN/audit_exit_status.txt" +sha256sum \ + "$CODE/zdd_shared_route.py" \ + "$CODE/independent_bitset_audit.py" \ + "$CODE/decision_diagram_learn.py" \ + "$CODE/decision_diagram_learn_v2.py" \ + "$DATA/$instance/train.csv" \ + "$RUN/${instance}.shared-zdd.txt" > "$RUN/SHA256SUMS" +printf 'ZDD_ROUTE_COMPLETE instance=%s audit_status=%s\n' \ + "$instance" "$audit_status" > "$RUN/COMPLETE" +cat "$RUN/COMPLETE" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/test_bdd_prefix_occam_v3.py b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/test_bdd_prefix_occam_v3.py new file mode 100644 index 000000000..2a321cc8f --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/test_bdd_prefix_occam_v3.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 + +import unittest + +from bdd_prefix_occam_v3 import PrefixEncoding, learn_output +from decision_diagram_learn_v2 import BDDManager, Sample + + +class PrefixOccamTests(unittest.TestCase): + def test_compresses_shared_prefixes_and_learns_xor(self) -> None: + samples = [Sample((a, b), (a ^ b,)) for a in (0, 1) for b in (0, 1)] + encoding = PrefixEncoding(samples, [0, 1], 0, 1) + self.assertEqual([len(items) for items in encoding.prefixes], [1, 2, 4]) + manager = BDDManager() + result = learn_output( + samples, + [0, 1], + 0, + manager, + max_width=2, + conflict_budget=100000, + solver_name="cadical195", + ) + self.assertEqual(result.width, 2) + self.assertEqual(result.reachable_cost, 5) + for sample in samples: + self.assertEqual(manager.evaluate_root(result.root, sample.bits), sample.output[0]) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/test_bdd_sat_occam_fixed.py b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/test_bdd_sat_occam_fixed.py new file mode 100644 index 000000000..d6c654529 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/test_bdd_sat_occam_fixed.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 + +import unittest +from unittest.mock import patch + +import bdd_sat_occam_fixed +import bdd_sat_occam_v2 + + +class FixedOrderWrapperTests(unittest.TestCase): + def test_forwards_one_valid_order(self) -> None: + captured = {} + + def fake_main(arguments): + captured["arguments"] = arguments + captured["orders"] = bdd_sat_occam_v2.base_orders(2) + captured["chosen"] = bdd_sat_occam_v2.choose_order([], 2, seed=42) + return 7 + + with patch.object(bdd_sat_occam_v2, "main", fake_main): + status = bdd_sat_occam_fixed.main( + [ + "--fixed-order", + "0,2,1,3", + "--fixed-order-name", + "lsb_interleaved", + "--instance", + "practice-add-n4", + ] + ) + self.assertEqual(status, 7) + self.assertEqual(captured["arguments"], ["--instance", "practice-add-n4"]) + self.assertEqual(captured["orders"], {"lsb_interleaved": [0, 2, 1, 3]}) + self.assertEqual(captured["chosen"][0], [0, 2, 1, 3]) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/test_bdd_sat_occam_v2.py b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/test_bdd_sat_occam_v2.py new file mode 100644 index 000000000..f2a11d9eb --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/test_bdd_sat_occam_v2.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 + +import unittest + +from bdd_sat_occam_v2 import learn_occam_output +from decision_diagram_learn_v2 import BDDManager, Sample + + +class OccamSATTests(unittest.TestCase): + def test_xor_minimizes_reachable_states(self) -> None: + samples = [Sample((a, b), (a ^ b,)) for a in (0, 1) for b in (0, 1)] + manager = BDDManager() + result = learn_occam_output( + samples, + [0, 1], + 0, + manager, + max_width=2, + conflict_budget=100000, + solver_name="cadical195", + ) + self.assertEqual(result.width, 2) + self.assertEqual(result.reachable_state_cost, 5) + self.assertIsNotNone(result.root) + for sample in samples: + self.assertEqual(manager.evaluate_root(result.root, sample.bits), sample.output[0]) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/test_bdd_sat_refine.py b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/test_bdd_sat_refine.py new file mode 100644 index 000000000..534b09158 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/test_bdd_sat_refine.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 + +import unittest + +from bdd_sat_refine import learn_output +from decision_diagram_learn_v2 import BDDManager, Sample + + +class LayeredSATTests(unittest.TestCase): + def test_xor_needs_width_two(self) -> None: + samples = [Sample((a, b), (a ^ b,)) for a in (0, 1) for b in (0, 1)] + manager = BDDManager() + result = learn_output( + samples, + [0, 1], + 0, + manager, + max_width=2, + conflict_budget=100000, + solver_name="cadical195", + ) + self.assertEqual(result.width, 2) + self.assertIsNotNone(result.root) + for sample in samples: + self.assertEqual(manager.evaluate_root(result.root, sample.bits), sample.output[0]) + + def test_constant_uses_width_one(self) -> None: + samples = [Sample((a, b), (1,)) for a in (0, 1) for b in (0, 1)] + manager = BDDManager() + result = learn_output( + samples, + [0, 1], + 0, + manager, + max_width=2, + conflict_budget=100000, + solver_name="cadical195", + ) + self.assertEqual(result.width, 1) + self.assertEqual(result.root, 1) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/test_decision_diagram_v2.py b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/test_decision_diagram_v2.py new file mode 100644 index 000000000..cc81a06c6 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/test_decision_diagram_v2.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + +import tempfile +import unittest +from pathlib import Path + +from decision_diagram_learn_v2 import ( + BDDManager, + Sample, + bits_for_xy, + compile_bdd, + learn_shared_bdd, + write_netlist, +) +from independent_bdd_audit import evaluate, parse_netlist + + +class BDDRouteTests(unittest.TestCase): + def test_complete_xor(self) -> None: + samples = [Sample((a, b), (a ^ b,)) for a in (0, 1) for b in (0, 1)] + manager, roots = learn_shared_bdd(samples, [0, 1], seed=42) + builder, outputs = compile_bdd(manager, roots, 2) + self.assertLessEqual(len(builder.lines), 1) + with tempfile.TemporaryDirectory() as directory: + path = Path(directory) / "xor.txt" + write_netlist(path, builder, outputs, metadata=["unit test"]) + n_inputs, gates, parsed_outputs = parse_netlist(path) + for sample in samples: + self.assertEqual( + evaluate(n_inputs, gates, parsed_outputs, sample.bits), sample.output + ) + + def test_partial_constraints_preserved(self) -> None: + samples = [ + Sample((0, 0, 0), (0, 1)), + Sample((0, 1, 1), (1, 0)), + Sample((1, 0, 1), (1, 1)), + Sample((1, 1, 0), (0, 0)), + ] + for order in ([0, 1, 2], [2, 0, 1], [1, 2, 0]): + manager, roots = learn_shared_bdd(samples, order, seed=42) + for sample in samples: + self.assertEqual(manager.evaluate(roots, sample.bits), sample.output) + + def test_phase_and_constant_extraction(self) -> None: + manager = BDDManager() + x0 = manager.intern(0, 0, 1) + not_x0 = manager.intern(0, 1, 0) + builder, outputs = compile_bdd(manager, [0, 1, x0, not_x0], 2) + with tempfile.TemporaryDirectory() as directory: + path = Path(directory) / "phase.txt" + write_netlist(path, builder, outputs, metadata=["unit test"]) + n_inputs, gates, parsed_outputs = parse_netlist(path) + for a in (0, 1): + for b in (0, 1): + self.assertEqual( + evaluate(n_inputs, gates, parsed_outputs, (a, b)), + (0, 1, a, 1 - a), + ) + + def test_bits_for_xy_encoding(self) -> None: + self.assertEqual(bits_for_xy(13, 4, 4), (1, 0, 1, 1, 0, 0, 1, 0)) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/test_zdd_shared_route_v2.py b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/test_zdd_shared_route_v2.py new file mode 100644 index 000000000..899f4fd42 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/test_zdd_shared_route_v2.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 + +import tempfile +import unittest +from pathlib import Path + +from decision_diagram_learn_v2 import Sample, compile_bdd, write_netlist +from independent_bitset_audit import parse +from zdd_shared_route import build_shared_zdd, expand_zdd_to_bdd + + +class SharedZDDTests(unittest.TestCase): + def test_zero_suppression_and_expansion(self) -> None: + samples = [ + Sample((0, 0), (1,)), + Sample((1, 0), (1,)), + Sample((0, 1), (0,)), + Sample((1, 1), (0,)), + ] + zdd, roots = build_shared_zdd(samples, [0, 1]) + self.assertLessEqual(len(zdd.reachable(roots)), 1) + bdd, bdd_roots = expand_zdd_to_bdd(zdd, roots) + for sample in samples: + self.assertEqual(bdd.evaluate(bdd_roots, sample.bits), sample.output) + builder, outputs = compile_bdd(bdd, bdd_roots, 2) + with tempfile.TemporaryDirectory() as directory: + path = Path(directory) / "zdd.txt" + write_netlist(path, builder, outputs, metadata=["zdd test"]) + n_inputs, _, parsed_outputs = parse(path) + self.assertEqual(n_inputs, 2) + self.assertEqual(parsed_outputs, ["~x2"]) + + def test_multiple_roots_share_nodes(self) -> None: + samples = [ + Sample((0, 0), (0, 0)), + Sample((1, 0), (1, 1)), + Sample((0, 1), (1, 1)), + Sample((1, 1), (0, 0)), + ] + zdd, roots = build_shared_zdd(samples, [0, 1]) + self.assertEqual(roots[0], roots[1]) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/zdd_shared_route.py b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/zdd_shared_route.py new file mode 100644 index 000000000..43cb27407 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/bdd_route/zdd_shared_route.py @@ -0,0 +1,362 @@ +#!/usr/bin/env python3 +"""Shared multi-root zero-suppressed decision diagram experiment for issue #71. + +Each output bit is represented by the family of training inputs labelled one. +Unseen assignments are absent and therefore zero by the ZDD convention. This +is the canonical zero-suppressed completion of the partial table, not a renamed +BDD. Skipped ZDD variables implicitly require zero; conversion to the challenge +netlist first expands those guards into an ordinary shared BDD. +""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import random +import time +from dataclasses import dataclass +from pathlib import Path +from typing import Sequence + +from decision_diagram_learn_v2 import ( + BDDManager, + INSTANCE_SPECS, + Sample, + base_orders, + compile_bdd, + evaluate_samples, + full_domain_audit, + read_training_csv, + write_netlist, +) + + +@dataclass(frozen=True) +class ZDDNode: + var: int + low: int + high: int + + +class ZDDManager: + """Canonical ZDD manager: a node with high=0 is suppressed.""" + + def __init__(self, order: Sequence[int]) -> None: + self.order = list(order) + self.position = {variable: depth for depth, variable in enumerate(order)} + self.nodes: dict[int, ZDDNode] = {} + self.unique: dict[ZDDNode, int] = {} + self.next_id = 2 + + def intern(self, var: int, low: int, high: int) -> int: + if high == 0: + return low + node = ZDDNode(var, low, high) + old = self.unique.get(node) + if old is not None: + return old + node_id = self.next_id + self.next_id += 1 + self.nodes[node_id] = node + self.unique[node] = node_id + return node_id + + def reachable(self, roots: Sequence[int]) -> set[int]: + todo = [root for root in roots if root >= 2] + found = set() + while todo: + node_id = todo.pop() + if node_id in found: + continue + found.add(node_id) + node = self.nodes[node_id] + if node.low >= 2: + todo.append(node.low) + if node.high >= 2: + todo.append(node.high) + return found + + def evaluate_root(self, root: int, bits: Sequence[int]) -> int: + depth = 0 + node_id = root + while node_id >= 2: + node = self.nodes[node_id] + node_depth = self.position[node.var] + if node_depth < depth: + raise AssertionError("ZDD variable order violation") + for skipped in range(depth, node_depth): + if bits[self.order[skipped]]: + return 0 + node_id = node.high if bits[node.var] else node.low + depth = node_depth + 1 + if node_id == 0: + return 0 + for skipped in range(depth, len(self.order)): + if bits[self.order[skipped]]: + return 0 + return 1 + + def evaluate(self, roots: Sequence[int], bits: Sequence[int]) -> tuple[int, ...]: + return tuple(self.evaluate_root(root, bits) for root in roots) + + +def ordered_code(bits: Sequence[int], order: Sequence[int]) -> int: + value = 0 + for depth, variable in enumerate(order): + value |= bits[variable] << depth + return value + + +def build_shared_zdd( + samples: Sequence[Sample], + order: Sequence[int], +) -> tuple[ZDDManager, list[int]]: + manager = ZDDManager(order) + codes = [ordered_code(sample.bits, order) for sample in samples] + roots = [] + for output_bit in range(len(samples[0].output)): + positive = tuple( + sorted( + { + code + for code, sample in zip(codes, samples) + if sample.output[output_bit] == 1 + } + ) + ) + memo: dict[tuple[int, tuple[int, ...]], int] = {} + + def build(depth: int, family: tuple[int, ...]) -> int: + if not family: + return 0 + if depth == len(order): + return 1 + key = (depth, family) + old = memo.get(key) + if old is not None: + return old + low = tuple(code for code in family if ((code >> depth) & 1) == 0) + high = tuple(code for code in family if ((code >> depth) & 1) == 1) + result = manager.intern( + order[depth], + build(depth + 1, low), + build(depth + 1, high), + ) + memo[key] = result + return result + + roots.append(build(0, positive)) + for sample in samples: + if manager.evaluate(roots, sample.bits) != sample.output: + raise AssertionError("ZDD failed to preserve a training row") + return manager, roots + + +def expand_zdd_to_bdd( + zdd: ZDDManager, + roots: Sequence[int], +) -> tuple[BDDManager, list[int]]: + """Make every implicit zero-suppressed guard explicit as a BDD node.""" + bdd = BDDManager() + memo: dict[tuple[int, int], int] = {} + + def expand(node_id: int, start_depth: int) -> int: + key = (node_id, start_depth) + old = memo.get(key) + if old is not None: + return old + if node_id == 0: + result = 0 + elif node_id == 1: + result = 1 + for depth in range(len(zdd.order) - 1, start_depth - 1, -1): + result = bdd.intern(zdd.order[depth], result, 0) + else: + node = zdd.nodes[node_id] + node_depth = zdd.position[node.var] + if node_depth < start_depth: + raise AssertionError("ZDD variable order violation") + low = expand(node.low, node_depth + 1) + high = expand(node.high, node_depth + 1) + result = bdd.intern(node.var, low, high) + for depth in range(node_depth - 1, start_depth - 1, -1): + result = bdd.intern(zdd.order[depth], result, 0) + memo[key] = result + return result + + bdd_roots = [expand(root, 0) for root in roots] + return bdd, bdd_roots + + +def zdd_score(samples: Sequence[Sample], order: Sequence[int]) -> tuple[int, int]: + manager, roots = build_shared_zdd(samples, order) + return len(manager.reachable(roots)), len(manager.nodes) + + +def choose_order( + samples: Sequence[Sample], + n: int, + *, + seed: int, + sift_variables: int, + adjacent_passes: int, +) -> tuple[list[int], dict[str, object]]: + rng = random.Random(seed) + cache: dict[tuple[int, ...], tuple[int, int]] = {} + trace = [] + + def assess(order: Sequence[int], tag: str) -> tuple[int, int]: + key = tuple(order) + if key not in cache: + cache[key] = zdd_score(samples, order) + reachable, allocated = cache[key] + trace.append( + { + "tag": tag, + "order": list(order), + "reachable_zdd_nodes": reachable, + "allocated_zdd_nodes": allocated, + } + ) + print( + f"[order] {tag} zdd_reachable={reachable} zdd_allocated={allocated}", + flush=True, + ) + return reachable, allocated + + bases = base_orders(n) + scored = [(assess(order, name), name, list(order)) for name, order in bases.items()] + best_score, base_name, best = min(scored, key=lambda item: (item[0], item[1])) + + variables = list(best) + rng.shuffle(variables) + for variable in variables[: min(sift_variables, len(variables))]: + old_position = best.index(variable) + without = [item for item in best if item != variable] + local = (best_score, old_position, list(best)) + positions = list(range(len(best))) + rng.shuffle(positions) + for position in positions: + trial = list(without) + trial.insert(position, variable) + score = assess(trial, f"sift-v{variable}-p{position}") + if (score, position) < (local[0], local[1]): + local = (score, position, trial) + best_score, _, best = local + + for pass_id in range(adjacent_passes): + positions = list(range(len(best) - 1)) + rng.shuffle(positions) + changed = False + for position in positions: + trial = list(best) + trial[position], trial[position + 1] = trial[position + 1], trial[position] + score = assess(trial, f"adj-{pass_id}-p{position}") + if score < best_score: + best_score = score + best = trial + changed = True + if not changed: + break + return best, { + "seed": seed, + "base_winner": base_name, + "selected_order": best, + "selected_zdd_score": list(best_score), + "evaluated_orders": len(cache), + "trace": trace, + } + + +def evaluate_zdd_samples( + manager: ZDDManager, + roots: Sequence[int], + samples: Sequence[Sample], +) -> tuple[int, int]: + rows = 0 + bits = 0 + for sample in samples: + predicted = manager.evaluate(roots, sample.bits) + rows += int(predicted == sample.output) + bits += sum(a == b for a, b in zip(predicted, sample.output)) + return rows, bits + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--dataset-root", type=Path, required=True) + parser.add_argument("--instance", choices=sorted(INSTANCE_SPECS), required=True) + parser.add_argument("--output-dir", type=Path, required=True) + parser.add_argument("--seed", type=int, default=42) + parser.add_argument("--sift-variables", type=int, default=6) + parser.add_argument("--adjacent-passes", type=int, default=2) + args = parser.parse_args() + + kind, n, m = INSTANCE_SPECS[args.instance] + training_path = args.dataset_root / args.instance / "train.csv" + samples = read_training_csv(training_path, 2 * n, m) + args.output_dir.mkdir(parents=True, exist_ok=True) + started = time.monotonic() + order, selection = choose_order( + samples, + n, + seed=args.seed, + sift_variables=args.sift_variables, + adjacent_passes=args.adjacent_passes, + ) + zdd, zdd_roots = build_shared_zdd(samples, order) + train_rows, train_bits = evaluate_zdd_samples(zdd, zdd_roots, samples) + bdd, bdd_roots = expand_zdd_to_bdd(zdd, zdd_roots) + bdd_train_rows, bdd_train_bits = evaluate_samples(bdd, bdd_roots, samples) + if (train_rows, train_bits) != (bdd_train_rows, bdd_train_bits): + raise AssertionError("ZDD-to-BDD expansion changed training semantics") + builder, signals = compile_bdd(bdd, bdd_roots, 2 * n) + audit = full_domain_audit(bdd, bdd_roots, kind=kind, n=n, m=m) + netlist = args.output_dir / f"{args.instance}.shared-zdd.txt" + write_netlist( + netlist, + builder, + signals, + metadata=[ + "shared multi-root zero-suppressed completion of train.csv", + f"instance={args.instance} seed={args.seed}", + "order_zero_based=" + ",".join(map(str, order)), + ], + ) + report = { + "schema": "occam71-shared-zdd-v1", + "instance": args.instance, + "completion_rule": "unseen assignments are absent from each output on-set", + "seed": args.seed, + "training_rows": len(samples), + "training_exact_rows": train_rows, + "training_correct_bits": train_bits, + "training_sha256": hashlib.sha256(training_path.read_bytes()).hexdigest(), + "order_selection": selection, + "zdd_reachable_nodes": len(zdd.reachable(zdd_roots)), + "zdd_allocated_nodes": len(zdd.nodes), + "expanded_bdd_reachable_nodes": len(bdd.reachable(bdd_roots)), + "expanded_bdd_allocated_nodes": len(bdd.nodes), + "gate_count": len(builder.lines), + "netlist": str(netlist), + "netlist_sha256": hashlib.sha256(netlist.read_bytes()).hexdigest(), + "full_domain_audit": audit, + "elapsed_seconds": time.monotonic() - started, + } + report_path = args.output_dir / f"{args.instance}.shared-zdd-report.json" + report_path.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", "ascii") + print( + f"[done] train={train_rows}/{len(samples)} " + f"full={audit['exact_rows']}/{audit['total_rows']} " + f"zdd={report['zdd_reachable_nodes']} " + f"bdd={report['expanded_bdd_reachable_nodes']} gates={report['gate_count']}", + flush=True, + ) + print(f"[artifact] {netlist}", flush=True) + print(f"[artifact] {report_path}", flush=True) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/bridge.py b/tracks/qcs/solutions/Genshin_Impact-71/search/bridge.py new file mode 100644 index 000000000..ccef91557 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/bridge.py @@ -0,0 +1,901 @@ +"""Strict, data-only bridge between Occam issue-71 netlists and BLIF. + +The module is deliberately self-contained. It never imports or executes a +submitted program: both formats are parsed as bounded, topologically ordered +data. BLIF is restricted to the combinational subset emitted and consumed by +eSLIM: .model, .inputs, .outputs, zero/one/two-input .names, and .end. + +Truth-table entries use BLIF order. For a two-input node they are +f(0,0), f(0,1), f(1,0), f(1,1), i.e. the first input is the most significant +index bit. Occam input and output sequences remain positional. +""" + +from __future__ import annotations + +import argparse +from dataclasses import dataclass +from pathlib import Path +import re +from typing import Iterable, Sequence + + +OPS = ("AND", "OR", "XOR", "NAND", "NOR", "XNOR") +OPS_SET = frozenset(OPS) +MAX_INPUTS = 20 +MAX_GATES = 1_000_000 +MAX_LOGICAL_LINE = 1_000_000 + +_OCCAM_SIGNAL_RE = re.compile(r"~?(?:x[1-9][0-9]*|w[1-9][0-9]*)\Z") +_OCCAM_WIRE_RE = re.compile(r"w[1-9][0-9]*\Z") +_BLIF_NAME_RE = re.compile( + r"(?:[A-Za-z_][A-Za-z0-9_.$-]*|[0-9]+)\Z" +) + + +def _split_occam_signal(token: str) -> tuple[str, bool]: + if not _OCCAM_SIGNAL_RE.fullmatch(token): + raise ValueError(f"invalid Occam signal token: {token!r}") + return (token[1:], True) if token.startswith("~") else (token, False) + + +def _token(base: str, negated: bool = False) -> str: + return f"~{base}" if negated else base + + +def _check_blif_name(name: str, context: str) -> str: + if not _BLIF_NAME_RE.fullmatch(name): + raise ValueError(f"invalid BLIF name in {context}: {name!r}") + return name + + +def _apply_op(op: str, left: bool, right: bool) -> bool: + if op == "AND": + return left and right + if op == "OR": + return left or right + if op == "XOR": + return left != right + if op == "NAND": + return not (left and right) + if op == "NOR": + return not (left or right) + if op == "XNOR": + return left == right + raise AssertionError(op) + + +@dataclass(frozen=True) +class OccamGate: + output: str + op: str + left: str + right: str + + +@dataclass(frozen=True) +class OccamCircuit: + ninputs: int + gates: tuple[OccamGate, ...] + outputs: tuple[str, ...] + + @classmethod + def parse(cls, path: str | Path) -> "OccamCircuit": + return cls.parse_text(Path(path).read_text(encoding="utf-8")) + + @classmethod + def parse_text(cls, text: str) -> "OccamCircuit": + if "\x00" in text: + raise ValueError("NUL byte in Occam netlist") + ninputs: int | None = None + gates: list[OccamGate] = [] + outputs: tuple[str, ...] | None = None + defined: set[str] = set() + + for lineno, raw in enumerate(text.splitlines(), start=1): + if len(raw) > MAX_LOGICAL_LINE: + raise ValueError(f"line {lineno}: line too long") + line = raw.split("#", 1)[0].strip() + if not line: + continue + fields = line.split() + if fields[0] == "INPUTS": + if ninputs is not None or gates or outputs is not None: + raise ValueError(f"line {lineno}: misplaced/duplicate INPUTS") + if len(fields) != 2 or not fields[1].isdigit(): + raise ValueError(f"line {lineno}: malformed INPUTS") + ninputs = int(fields[1]) + if not 1 <= ninputs <= MAX_INPUTS: + raise ValueError( + f"line {lineno}: INPUTS must be in 1..{MAX_INPUTS}" + ) + continue + + if ninputs is None: + raise ValueError(f"line {lineno}: INPUTS must come first") + if fields[0] == "OUTPUTS": + if outputs is not None or len(fields) < 2: + raise ValueError(f"line {lineno}: malformed/duplicate OUTPUTS") + parsed = tuple(fields[1:]) + for signal in parsed: + base, _ = _split_occam_signal(signal) + if base.startswith("x"): + if not 1 <= int(base[1:]) <= ninputs: + raise ValueError( + f"line {lineno}: input out of range: {base}" + ) + elif base not in defined: + raise ValueError( + f"line {lineno}: undefined output wire: {base}" + ) + outputs = parsed + continue + + if outputs is not None: + raise ValueError(f"line {lineno}: gate after OUTPUTS") + if len(gates) >= MAX_GATES: + raise ValueError(f"more than {MAX_GATES} gates") + if len(fields) != 5 or fields[1] != "=": + raise ValueError(f"line {lineno}: malformed gate") + output, op, left, right = fields[0], fields[2], fields[3], fields[4] + if not _OCCAM_WIRE_RE.fullmatch(output): + raise ValueError(f"line {lineno}: invalid output wire: {output}") + if output in defined: + raise ValueError(f"line {lineno}: duplicate output wire: {output}") + if op not in OPS_SET: + raise ValueError(f"line {lineno}: unsupported gate: {op}") + for signal in (left, right): + base, _ = _split_occam_signal(signal) + if base.startswith("x"): + if not 1 <= int(base[1:]) <= ninputs: + raise ValueError( + f"line {lineno}: input out of range: {base}" + ) + elif base not in defined: + raise ValueError( + f"line {lineno}: wire used before definition: {base}" + ) + gates.append(OccamGate(output, op, left, right)) + defined.add(output) + + if ninputs is None: + raise ValueError("missing INPUTS") + if outputs is None: + raise ValueError("missing OUTPUTS") + return cls(ninputs, tuple(gates), outputs) + + def to_text(self) -> str: + lines = [f"INPUTS {self.ninputs}"] + lines.extend( + f"{gate.output} = {gate.op} {gate.left} {gate.right}" + for gate in self.gates + ) + lines.append("OUTPUTS " + " ".join(self.outputs)) + return "\n".join(lines) + "\n" + + def write(self, path: str | Path) -> None: + destination = Path(path) + destination.parent.mkdir(parents=True, exist_ok=True) + destination.write_text(self.to_text(), encoding="utf-8", newline="\n") + + def evaluate(self, input_bits: str) -> str: + if len(input_bits) != self.ninputs or set(input_bits) - {"0", "1"}: + raise ValueError( + f"expected {self.ninputs} input bits, got {input_bits!r}" + ) + values: dict[str, bool] = { + f"x{idx + 1}": bit == "1" + for idx, bit in enumerate(input_bits) + } + + def value(signal: str) -> bool: + base, negated = _split_occam_signal(signal) + answer = values[base] + return not answer if negated else answer + + for gate in self.gates: + values[gate.output] = _apply_op( + gate.op, value(gate.left), value(gate.right) + ) + return "".join("1" if value(out) else "0" for out in self.outputs) + + def prune_and_renumber(self) -> "OccamCircuit": + live = { + base + for output in self.outputs + for base, _ in (_split_occam_signal(output),) + if base.startswith("w") + } + kept_rev: list[OccamGate] = [] + for gate in reversed(self.gates): + if gate.output not in live: + continue + kept_rev.append(gate) + for signal in (gate.left, gate.right): + base, _ = _split_occam_signal(signal) + if base.startswith("w"): + live.add(base) + kept = list(reversed(kept_rev)) + renaming = { + gate.output: f"w{idx}" for idx, gate in enumerate(kept, start=1) + } + + def remap(signal: str) -> str: + base, negated = _split_occam_signal(signal) + return _token(renaming.get(base, base), negated) + + result = OccamCircuit( + self.ninputs, + tuple( + OccamGate( + renaming[gate.output], + gate.op, + remap(gate.left), + remap(gate.right), + ) + for gate in kept + ), + tuple(remap(output) for output in self.outputs), + ) + return OccamCircuit.parse_text(result.to_text()) + + +@dataclass(frozen=True) +class BlifGate: + output: str + inputs: tuple[str, ...] + table: tuple[int, ...] + + def __post_init__(self) -> None: + if not 0 <= len(self.inputs) <= 2: + raise ValueError("BLIF gate fanin must be zero, one, or two") + if len(self.table) != 1 << len(self.inputs): + raise ValueError("BLIF truth-table size does not match fanin") + if any(bit not in (0, 1) for bit in self.table): + raise ValueError("BLIF truth table must contain only 0/1") + + +@dataclass(frozen=True) +class BlifNetwork: + model: str + inputs: tuple[str, ...] + outputs: tuple[str, ...] + gates: tuple[BlifGate, ...] + + @classmethod + def parse(cls, path: str | Path) -> "BlifNetwork": + return cls.parse_text(Path(path).read_text(encoding="utf-8")) + + @classmethod + def parse_text(cls, text: str) -> "BlifNetwork": + logical_lines = _logical_blif_lines(text) + model: str | None = None + inputs: tuple[str, ...] | None = None + outputs: tuple[str, ...] | None = None + gates: list[BlifGate] = [] + known: set[str] = set() + current_header: tuple[int, tuple[str, ...], str] | None = None + current_cubes: list[tuple[str, int, int]] = [] + ended = False + + def finish_gate() -> None: + nonlocal current_header, current_cubes + if current_header is None: + return + header_line, gate_inputs, gate_output = current_header + gate = _build_blif_gate( + header_line, gate_inputs, gate_output, current_cubes + ) + gates.append(gate) + known.add(gate_output) + current_header = None + current_cubes = [] + + for lineno, line in logical_lines: + if ended: + raise ValueError(f"line {lineno}: content after .end") + if line.startswith("."): + finish_gate() + fields = line.split() + directive = fields[0] + if directive == ".model": + if model is not None or len(fields) != 2 or inputs is not None: + raise ValueError(f"line {lineno}: malformed/misplaced .model") + model = _check_blif_name(fields[1], f"line {lineno}") + elif directive == ".inputs": + if inputs is not None or gates or current_header is not None: + raise ValueError( + f"line {lineno}: duplicate/misplaced .inputs" + ) + if len(fields) < 2: + raise ValueError(f"line {lineno}: empty .inputs") + parsed = tuple( + _check_blif_name(name, f"line {lineno}") + for name in fields[1:] + ) + if len(parsed) > MAX_INPUTS: + raise ValueError( + f"line {lineno}: more than {MAX_INPUTS} inputs" + ) + if len(set(parsed)) != len(parsed): + raise ValueError(f"line {lineno}: duplicate primary input") + inputs = parsed + known = set(parsed) + elif directive == ".outputs": + if ( + inputs is None + or outputs is not None + or gates + or current_header is not None + ): + raise ValueError( + f"line {lineno}: duplicate/misplaced .outputs" + ) + if len(fields) < 2: + raise ValueError(f"line {lineno}: empty .outputs") + outputs = tuple( + _check_blif_name(name, f"line {lineno}") + for name in fields[1:] + ) + elif directive == ".names": + if inputs is None or outputs is None: + raise ValueError( + f"line {lineno}: .inputs/.outputs must precede .names" + ) + if not 2 <= len(fields) <= 4: + raise ValueError( + f"line {lineno}: .names must have fanin 0..2" + ) + if len(gates) >= MAX_GATES: + raise ValueError(f"more than {MAX_GATES} BLIF gates") + names = tuple( + _check_blif_name(name, f"line {lineno}") + for name in fields[1:] + ) + gate_inputs, gate_output = names[:-1], names[-1] + if len(set(gate_inputs)) != len(gate_inputs): + # Repeated physical inputs are meaningful, but eSLIM's + # relation representation expects distinct formal inputs. + # Occam-to-BLIF collapses such nodes before writing. + raise ValueError( + f"line {lineno}: repeated .names formal input" + ) + missing = [name for name in gate_inputs if name not in known] + if missing: + raise ValueError( + f"line {lineno}: non-topological inputs: {missing}" + ) + if gate_output in known: + raise ValueError( + f"line {lineno}: redefined BLIF signal: {gate_output}" + ) + current_header = (lineno, gate_inputs, gate_output) + elif directive == ".end": + if len(fields) != 1: + raise ValueError(f"line {lineno}: malformed .end") + if inputs is None or outputs is None: + raise ValueError( + f"line {lineno}: missing .inputs or .outputs" + ) + ended = True + else: + raise ValueError( + f"line {lineno}: unsupported BLIF directive {directive!r}" + ) + else: + if current_header is None: + raise ValueError(f"line {lineno}: cube outside .names") + current_cubes.append((line, lineno, len(current_header[1]))) + + finish_gate() + if not ended: + raise ValueError("missing .end") + if model is None: + raise ValueError("missing .model") + assert inputs is not None and outputs is not None + missing_outputs = [name for name in outputs if name not in known] + if missing_outputs: + raise ValueError(f"undefined BLIF outputs: {missing_outputs}") + result = cls(model, inputs, outputs, tuple(gates)) + # Reparse the serializer in tests/clients when an additional audit is + # desired; construction itself has already checked all invariants. + return result + + def to_text(self) -> str: + _check_blif_name(self.model, "model") + lines = [ + f".model {self.model}", + ".inputs " + " ".join(self.inputs), + ".outputs " + " ".join(self.outputs), + ] + for gate in self.gates: + lines.append(".names " + " ".join((*gate.inputs, gate.output))) + width = len(gate.inputs) + for index, value in enumerate(gate.table): + if not value: + continue + if width: + lines.append(f"{index:0{width}b} 1") + else: + lines.append("1") + lines.append(".end") + return "\n".join(lines) + "\n" + + def write(self, path: str | Path) -> None: + destination = Path(path) + destination.parent.mkdir(parents=True, exist_ok=True) + destination.write_text(self.to_text(), encoding="utf-8", newline="\n") + + def evaluate(self, input_bits: str) -> str: + if len(input_bits) != len(self.inputs) or set(input_bits) - {"0", "1"}: + raise ValueError( + f"expected {len(self.inputs)} input bits, got {input_bits!r}" + ) + values = { + name: bit == "1" for name, bit in zip(self.inputs, input_bits) + } + for gate in self.gates: + index = 0 + for name in gate.inputs: + index = (index << 1) | int(values[name]) + values[gate.output] = bool(gate.table[index]) + return "".join("1" if values[name] else "0" for name in self.outputs) + + +def _logical_blif_lines(text: str) -> list[tuple[int, str]]: + if "\x00" in text: + raise ValueError("NUL byte in BLIF") + result: list[tuple[int, str]] = [] + pending = "" + pending_line = 0 + for lineno, raw in enumerate(text.splitlines(), start=1): + if len(raw) > MAX_LOGICAL_LINE: + raise ValueError(f"line {lineno}: physical line too long") + body = raw.split("#", 1)[0].rstrip() + continued = body.endswith("\\") + if continued: + body = body[:-1].rstrip() + if pending: + pending = f"{pending} {body.lstrip()}" + else: + pending = body.strip() + pending_line = lineno + if len(pending) > MAX_LOGICAL_LINE: + raise ValueError(f"line {pending_line}: logical line too long") + if not continued: + if pending: + result.append((pending_line, pending)) + pending = "" + pending_line = 0 + if pending: + raise ValueError(f"line {pending_line}: dangling BLIF continuation") + return result + + +def _build_blif_gate( + header_line: int, + inputs: tuple[str, ...], + output: str, + cube_lines: Sequence[tuple[str, int, int]], +) -> BlifGate: + plane: int | None = None + cubes: list[str] = [] + for raw, lineno, fanin in cube_lines: + fields = raw.split() + if fanin == 0: + if len(fields) != 1 or fields[0] not in {"0", "1"}: + raise ValueError(f"line {lineno}: malformed constant cube") + pattern = "" + value = int(fields[0]) + else: + if ( + len(fields) != 2 + or len(fields[0]) != fanin + or set(fields[0]) - {"0", "1", "-"} + or fields[1] not in {"0", "1"} + ): + raise ValueError(f"line {lineno}: malformed cube") + pattern, value = fields[0], int(fields[1]) + if plane is None: + plane = value + elif value != plane: + raise ValueError( + f"line {lineno}: mixed output planes in .names from " + f"line {header_line}" + ) + cubes.append(pattern) + + if plane is None: + return BlifGate(output, inputs, (0,) * (1 << len(inputs))) + table = [1 - plane] * (1 << len(inputs)) + for pattern in cubes: + for index in range(1 << len(inputs)): + bits = f"{index:0{len(inputs)}b}" if inputs else "" + if all(pat == "-" or pat == bit for pat, bit in zip(pattern, bits)): + table[index] = plane + return BlifGate(output, inputs, tuple(table)) + + +def _gate_table_over_unique_inputs( + op: str, + left_base: str, + left_negated: bool, + right_base: str, + right_negated: bool, + phases: dict[str, bool], + output_phase: bool, +) -> tuple[tuple[str, ...], tuple[int, ...]]: + names = ( + (left_base,) + if left_base == right_base + else (left_base, right_base) + ) + values: list[int] = [] + for index in range(1 << len(names)): + assignment = { + name: bool((index >> (len(names) - 1 - position)) & 1) + for position, name in enumerate(names) + } + left = ( + assignment[left_base] + ^ phases.get(left_base, False) + ^ left_negated + ) + right = ( + assignment[right_base] + ^ phases.get(right_base, False) + ^ right_negated + ) + output = _apply_op(op, left, right) ^ output_phase + values.append(int(output)) + return names, tuple(values) + + +def occam_to_blif( + circuit: OccamCircuit, model: str = "occam_issue71" +) -> BlifNetwork: + """Encode free inversions as BLIF node phases whenever possible. + + A wire used as a complemented primary output is represented in the matching + phase, so the usual case adds no unary output gate. Only a complemented + primary-input output or the same signal requested in both polarities needs + an explicit unary BLIF alias; the reverse bridge removes that alias for free. + """ + _check_blif_name(model, "model") + phases: dict[str, bool] = { + f"x{idx + 1}": False for idx in range(circuit.ninputs) + } + output_constraints: dict[str, bool] = {} + for output in circuit.outputs: + base, negated = _split_occam_signal(output) + if base.startswith("w") and base not in output_constraints: + output_constraints[base] = negated + for gate in circuit.gates: + phases[gate.output] = output_constraints.get(gate.output, False) + + occupied = set(phases) + output_names: list[str] = [] + aliases: list[tuple[str, str, bool]] = [] + + def fresh_output_alias(index: int) -> str: + serial = index + while True: + candidate = f"__occam_output_{serial}" + if candidate not in occupied: + occupied.add(candidate) + return candidate + serial += len(circuit.outputs) + 1 + + for index, output in enumerate(circuit.outputs, start=1): + base, desired_phase = _split_occam_signal(output) + actual_phase = phases[base] + if desired_phase == actual_phase: + output_names.append(base) + else: + alias = fresh_output_alias(index) + output_names.append(alias) + aliases.append((base, alias, actual_phase ^ desired_phase)) + + gates: list[BlifGate] = [] + for gate in circuit.gates: + left_base, left_negated = _split_occam_signal(gate.left) + right_base, right_negated = _split_occam_signal(gate.right) + names, table = _gate_table_over_unique_inputs( + gate.op, + left_base, + left_negated, + right_base, + right_negated, + phases, + phases[gate.output], + ) + gates.append(BlifGate(gate.output, names, table)) + for source, alias, invert in aliases: + gates.append( + BlifGate(alias, (source,), (1, 0) if invert else (0, 1)) + ) + + network = BlifNetwork( + model, + tuple(f"x{idx + 1}" for idx in range(circuit.ninputs)), + tuple(output_names), + tuple(gates), + ) + # The strict parser audits ordering, identifiers, and serialized semantics. + return BlifNetwork.parse_text(network.to_text()) + + +@dataclass(frozen=True) +class _Literal: + base: str + negated: bool = False + + def as_token(self) -> str: + return _token(self.base, self.negated) + + +@dataclass(frozen=True) +class _Constant: + value: bool + + +_Signal = _Literal | _Constant + + +def _truth_dependencies( + variables: Sequence[str], table: Sequence[int] +) -> list[int]: + dependencies: list[int] = [] + width = len(variables) + for position in range(width): + mask = 1 << (width - 1 - position) + if any( + table[index] != table[index | mask] + for index in range(1 << width) + if not index & mask + ): + dependencies.append(position) + return dependencies + + +def _binary_implementation( + table: tuple[int, int, int, int] +) -> tuple[str, bool, bool, bool]: + candidates: list[tuple[int, int, bool, bool, bool, str]] = [] + for op_index, op in enumerate(OPS): + for left_negated in (False, True): + for right_negated in (False, True): + for output_negated in (False, True): + candidate = tuple( + int( + _apply_op( + op, + bool(left ^ left_negated), + bool(right ^ right_negated), + ) + ^ output_negated + ) + for left in (0, 1) + for right in (0, 1) + ) + if candidate == table: + inversion_count = ( + int(left_negated) + + int(right_negated) + + int(output_negated) + ) + candidates.append( + ( + inversion_count, + op_index, + left_negated, + right_negated, + output_negated, + op, + ) + ) + if not candidates: + raise AssertionError(f"no one-gate implementation for table {table}") + _, _, left_negated, right_negated, output_negated, op = min(candidates) + return op, left_negated, right_negated, output_negated + + +def _compose_blif_gate( + gate: BlifGate, + arguments: Sequence[_Signal], + occam_gates: list[OccamGate], +) -> _Signal: + variables: list[str] = [] + for argument in arguments: + if isinstance(argument, _Literal) and argument.base not in variables: + variables.append(argument.base) + if len(variables) > 2: + raise AssertionError("a two-input BLIF gate cannot depend on >2 signals") + + effective: list[int] = [] + for index in range(1 << len(variables)): + assignment = { + name: bool((index >> (len(variables) - 1 - position)) & 1) + for position, name in enumerate(variables) + } + gate_index = 0 + for argument in arguments: + if isinstance(argument, _Constant): + value = argument.value + else: + value = assignment[argument.base] ^ argument.negated + gate_index = (gate_index << 1) | int(value) + effective.append(gate.table[gate_index]) + + dependencies = _truth_dependencies(variables, effective) + if not dependencies: + return _Constant(bool(effective[0])) + if len(dependencies) == 1: + position = dependencies[0] + mask = 1 << (len(variables) - 1 - position) + zero_value = bool(effective[0]) + one_value = bool(effective[mask]) + if zero_value == one_value: + raise AssertionError("dependency analysis inconsistency") + return _Literal(variables[position], negated=zero_value) + if len(dependencies) != 2 or len(variables) != 2: + raise AssertionError("unexpected dependency structure") + + binary_table = tuple(effective) + if len(binary_table) != 4: + raise AssertionError("binary table must contain four entries") + op, left_negated, right_negated, output_negated = ( + _binary_implementation(binary_table) # type: ignore[arg-type] + ) + output = f"w{len(occam_gates) + 1}" + occam_gates.append( + OccamGate( + output, + op, + _token(variables[0], left_negated), + _token(variables[1], right_negated), + ) + ) + return _Literal(output, output_negated) + + +def blif_to_occam(network: BlifNetwork) -> OccamCircuit: + """Convert arbitrary fanin-at-most-two .names into the Occam basis. + + Constants are propagated, and projections/inversions are represented as + aliases/free phases. Every genuine two-variable Boolean function is + realized by exactly one allowed gate plus free input/output inversions. + If a constant survives at a primary output, one shared XOR(x1,x1) gate is + materialized for all constant-zero/constant-one outputs. + """ + if not network.inputs: + raise ValueError("Occam circuits require at least one primary input") + if len(network.inputs) > MAX_INPUTS: + raise ValueError(f"more than {MAX_INPUTS} inputs") + signal_map: dict[str, _Signal] = { + name: _Literal(f"x{idx + 1}") + for idx, name in enumerate(network.inputs) + } + occam_gates: list[OccamGate] = [] + for gate in network.gates: + try: + arguments = [signal_map[name] for name in gate.inputs] + except KeyError as error: + raise ValueError( + f"non-topological BLIF signal: {error.args[0]}" + ) from None + if gate.output in signal_map: + raise ValueError(f"redefined BLIF signal: {gate.output}") + signal_map[gate.output] = _compose_blif_gate( + gate, arguments, occam_gates + ) + + output_signals: list[_Signal] = [] + for name in network.outputs: + if name not in signal_map: + raise ValueError(f"undefined BLIF output: {name}") + output_signals.append(signal_map[name]) + + constant_zero: _Literal | None = None + + def materialize(signal: _Signal) -> _Literal: + nonlocal constant_zero + if isinstance(signal, _Literal): + return signal + if constant_zero is None: + output = f"w{len(occam_gates) + 1}" + occam_gates.append(OccamGate(output, "XOR", "x1", "x1")) + constant_zero = _Literal(output) + return _Literal(constant_zero.base, negated=signal.value) + + outputs = tuple(materialize(signal).as_token() for signal in output_signals) + circuit = OccamCircuit( + len(network.inputs), tuple(occam_gates), outputs + ).prune_and_renumber() + return OccamCircuit.parse_text(circuit.to_text()) + + +def verify_equivalent( + occam: OccamCircuit, blif: BlifNetwork +) -> dict[str, int | bool]: + if occam.ninputs != len(blif.inputs): + raise ValueError("input-width mismatch") + if len(occam.outputs) != len(blif.outputs): + raise ValueError("output-width mismatch") + if occam.ninputs > MAX_INPUTS: + raise ValueError(f"refusing exhaustive check above {MAX_INPUTS} inputs") + mismatches = 0 + for assignment in range(1 << occam.ninputs): + bits = f"{assignment:0{occam.ninputs}b}" + if occam.evaluate(bits) != blif.evaluate(bits): + mismatches += 1 + return { + "assignments": 1 << occam.ninputs, + "outputs": len(occam.outputs), + "mismatching_assignments": mismatches, + "equivalent": mismatches == 0, + } + + +def verify_occam_equivalent( + left: OccamCircuit, right: OccamCircuit +) -> dict[str, int | bool]: + if left.ninputs != right.ninputs: + raise ValueError("input-width mismatch") + if len(left.outputs) != len(right.outputs): + raise ValueError("output-width mismatch") + mismatches = 0 + for assignment in range(1 << left.ninputs): + bits = f"{assignment:0{left.ninputs}b}" + if left.evaluate(bits) != right.evaluate(bits): + mismatches += 1 + return { + "assignments": 1 << left.ninputs, + "outputs": len(left.outputs), + "mismatching_assignments": mismatches, + "equivalent": mismatches == 0, + } + + +def _write_verified_blif(source: Path, destination: Path, model: str) -> None: + circuit = OccamCircuit.parse(source) + network = occam_to_blif(circuit, model) + audit = verify_equivalent(circuit, network) + if not audit["equivalent"]: + raise RuntimeError(f"Occam-to-BLIF verification failed: {audit}") + network.write(destination) + + +def _write_verified_occam(source: Path, destination: Path) -> None: + network = BlifNetwork.parse(source) + circuit = blif_to_occam(network) + audit = verify_equivalent(circuit, network) + if not audit["equivalent"]: + raise RuntimeError(f"BLIF-to-Occam verification failed: {audit}") + circuit.write(destination) + + +def main(argv: Sequence[str] | None = None) -> int: + parser = argparse.ArgumentParser( + description="Strict Occam issue-71 netlist / eSLIM BLIF bridge" + ) + subparsers = parser.add_subparsers(dest="command", required=True) + to_blif_parser = subparsers.add_parser("to-blif") + to_blif_parser.add_argument("source", type=Path) + to_blif_parser.add_argument("destination", type=Path) + to_blif_parser.add_argument("--model", default="occam_issue71") + to_occam_parser = subparsers.add_parser("to-occam") + to_occam_parser.add_argument("source", type=Path) + to_occam_parser.add_argument("destination", type=Path) + args = parser.parse_args(argv) + + if args.command == "to-blif": + _write_verified_blif(args.source, args.destination, args.model) + elif args.command == "to-occam": + _write_verified_occam(args.source, args.destination) + else: + raise AssertionError(args.command) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/c154_window_safe_ranked_array_v3.slurm b/tracks/qcs/solutions/Genshin_Impact-71/search/c154_window_safe_ranked_array_v3.slurm new file mode 100644 index 000000000..f21af667d --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/c154_window_safe_ranked_array_v3.slurm @@ -0,0 +1,80 @@ +#!/bin/bash +#SBATCH --job-name=occ71-c154win-v3 +#SBATCH --partition=home +#SBATCH --nodelist=n006 +#SBATCH --array=0-99%16 +#SBATCH --cpus-per-task=1 +#SBATCH --mem=8G +#SBATCH --time=01:00:00 +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/c154-window-safe-v3/logs/%A_%a.out +#SBATCH --error=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/c154-window-safe-v3/logs/%A_%a.err + +set -euo pipefail + +ROOT=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +SEARCH="$ROOT/tracks/qcs/solutions/Genshin_Impact-71/search" +INPUT="$ROOT/results/occam71/eslim/grid-build42598-seed42/05-C-size5-job43027/candidate.txt" +RANKING="$ROOT/results/occam71/c154-window-safe/c154_joint_window_ranking_safe_v3.json" +OUT_ROOT="$ROOT/results/occam71/c154-window-safe-v3" +PYSAT_VENV="${PYSAT_VENV:?submit with the audited PySAT venv}" +PYTHON_BIN="$PYSAT_VENV/bin/python" + +mkdir -p "$OUT_ROOT/logs" +test -x "$PYTHON_BIN" +test -s "$RANKING" +"$PYTHON_BIN" -c 'import pysat, pysat.solvers' + +read -r ROOT_A ROOT_B GATES < <( + "$PYTHON_BIN" -c ' +import json +import sys +payload = json.load(open(sys.argv[1], "r", encoding="utf-8")) +record = payload["records"][int(sys.argv[2])] +assert payload["safety"]["boundary_excludes_root_descendants"] is True +assert payload["safety"]["roots_functional_of_boundary"] is True +assert int(record["candidate_gates"]) == int(record["removed_count"]) - 1 +print(record["roots"][0], record["roots"][1], record["candidate_gates"]) +' "$RANKING" "$SLURM_ARRAY_TASK_ID" +) + +CASE_INDEX=$(printf "%03d" "$SLURM_ARRAY_TASK_ID") +CASE_NAME="${CASE_INDEX}_${ROOT_A}_${ROOT_B}_k${GATES}" +CASE_OUT="$OUT_ROOT/$CASE_NAME" +mkdir -p "$CASE_OUT" + +{ + printf 'job_id=%s\n' "$SLURM_ARRAY_JOB_ID" + printf 'array_task_id=%s\n' "$SLURM_ARRAY_TASK_ID" + printf 'node=%s\n' "$SLURMD_NODENAME" + printf 'seed=42\n' + printf 'parent_gates=154\n' + printf 'parent_sha256=f9396ee77462152c03fe061a4676944dc04e83787ef2a3915263bd2be8010bd9\n' + printf 'roots=%s,%s\n' "$ROOT_A" "$ROOT_B" + printf 'candidate_gates=%s\n' "$GATES" + sha256sum "$INPUT" "$RANKING" \ + "$SEARCH/c_window_rank_safe_v3.py" \ + "$SEARCH/d_window_sat_safe_remote.py" \ + "$SEARCH/d_window_sat_hpc_safe.py" +} > "$CASE_OUT/metadata.txt" + +"$PYTHON_BIN" -u "$SEARCH/d_window_sat_hpc_safe.py" "$INPUT" \ + --roots "$ROOT_A" "$ROOT_B" \ + --gates "$GATES" \ + --timeout 10 \ + --output-dir "$CASE_OUT" + +test -s "$CASE_OUT/report.json" +INPUT_STEM=$(basename "${INPUT%.txt}") +CANDIDATE="$CASE_OUT/${INPUT_STEM}.candidate.txt" +if [[ -s "$CANDIDATE" ]]; then + "$PYTHON_BIN" -u "$SEARCH/verify_candidate.py" \ + "$INPUT" "$CANDIDATE" \ + --report "$CASE_OUT/independent-full-domain-audit.json" + test -s "$CASE_OUT/independent-full-domain-audit.json" + "$PYTHON_BIN" -u "$SEARCH/audit_c_formula.py" \ + "$CANDIDATE" \ + --report "$CASE_OUT/direct-formula-audit.json" + test -s "$CASE_OUT/direct-formula-audit.json" + touch "$CASE_OUT/CANDIDATE_AUDITED" +fi +touch "$CASE_OUT/COMPLETE" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/c_window_rank.py b/tracks/qcs/solutions/Genshin_Impact-71/search/c_window_rank.py new file mode 100644 index 000000000..76881afcd --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/c_window_rank.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python3 +"""Rank promising two-root exact-resynthesis windows in mystery C. + +This is a read-only structural/truth-table diagnostic. It imports the +independently tested generic window implementation and never executes content +from a submitted netlist: the latter is parsed by the strict ASCII parser. +""" + +from __future__ import annotations + +import argparse +import importlib.util +import itertools +import json +import sys +from pathlib import Path + + +DEFAULT_MODULE = Path(r"C:\tmp\occam71_d_window\window_search.py") +DEFAULT_NETLIST = Path(r"C:\tmp\occam71_best_ref\mystery-C.txt") + + +def load_window_module(path: Path): + spec = importlib.util.spec_from_file_location("c_window_generic", path) + if spec is None or spec.loader is None: + raise RuntimeError(f"cannot load audited module {path}") + module = importlib.util.module_from_spec(spec) + sys.modules[spec.name] = module + spec.loader.exec_module(module) + return module + + +def natural_name_key(name: str) -> tuple[int, int]: + if len(name) < 2 or name[0] not in {"x", "w"}: + raise ValueError(f"unexpected signal name {name!r}") + return (0 if name[0] == "x" else 1, int(name[1:])) + + +def reachable_pattern_count( + names: tuple[str, ...], + values: dict[str, int], + rows: int, +) -> int: + seen: set[int] = set() + for row in range(rows): + code = 0 + for bit, name in enumerate(names): + code |= ((values[name] >> row) & 1) << bit + seen.add(code) + return len(seen) + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--module", type=Path, default=DEFAULT_MODULE) + parser.add_argument("--netlist", type=Path, default=DEFAULT_NETLIST) + parser.add_argument("--min-removed", type=int, default=6) + parser.add_argument("--max-removed", type=int, default=18) + parser.add_argument("--max-boundary", type=int, default=12) + parser.add_argument("--structural-limit", type=int, default=3000) + parser.add_argument("--output-limit", type=int, default=100) + parser.add_argument("--output", type=Path) + args = parser.parse_args() + + if not (2 <= args.min_removed <= args.max_removed): + raise ValueError("invalid removed-gate bounds") + if not (1 <= args.max_boundary <= 16): + raise ValueError("invalid boundary bound") + if args.structural_limit <= 0 or args.output_limit <= 0: + raise ValueError("limits must be positive") + + dws = load_window_module(args.module) + circuit = dws.parse_circuit(args.netlist) + _, values, _ = dws.evaluate(circuit) + gate_by_out, _ = dws.structural_maps(circuit) + gate_index = {gate.out: index for index, gate in enumerate(circuit.gates)} + rows = 1 << circuit.n_inputs + + structural: list[dict[str, object]] = [] + gate_names = tuple(gate.out for gate in circuit.gates) + for roots in itertools.combinations(gate_names, 2): + removed, _ = dws.removable_closure(circuit, roots) + removed_count = len(removed) + if not args.min_removed <= removed_count <= args.max_removed: + continue + boundary: set[str] = set() + for name in removed: + gate = gate_by_out[name] + for token in (gate.left, gate.right): + if token.name not in removed: + boundary.add(token.name) + if len(boundary) > args.max_boundary: + continue + ordered_boundary = tuple(sorted(boundary, key=natural_name_key)) + structural.append( + { + "roots": roots, + "removed_count": removed_count, + "boundary": ordered_boundary, + "boundary_count": len(ordered_boundary), + "removed": tuple(sorted(removed, key=gate_index.__getitem__)), + } + ) + + structural.sort( + key=lambda record: ( + -int(record["removed_count"]), + int(record["boundary_count"]), + tuple(record["roots"]), + ) + ) + structural = structural[: args.structural_limit] + + records: list[dict[str, object]] = [] + for record in structural: + boundary = tuple(record["boundary"]) + patterns = reachable_pattern_count(boundary, values, rows) + removed_count = int(record["removed_count"]) + candidate_gates = removed_count - 1 + boundary_count = int(record["boundary_count"]) + # A deterministic proxy for SAT effort. The exponent reflects the + # number of possible sources per synthesized gate; it is only used to + # rank pilots, never as a scientific result. + effort_proxy = patterns * candidate_gates * ( + boundary_count + candidate_gates + ) ** 2 + records.append( + { + **record, + "reachable_patterns": patterns, + "candidate_gates": candidate_gates, + "effort_proxy": effort_proxy, + } + ) + + records.sort( + key=lambda record: ( + int(record["effort_proxy"]), + -int(record["removed_count"]), + int(record["boundary_count"]), + tuple(record["roots"]), + ) + ) + payload = { + "netlist": str(args.netlist), + "inputs": circuit.n_inputs, + "gates": len(circuit.gates), + "rows": rows, + "filters": { + "min_removed": args.min_removed, + "max_removed": args.max_removed, + "max_boundary": args.max_boundary, + "structural_limit": args.structural_limit, + }, + "structural_candidates": len(structural), + "records": records[: args.output_limit], + } + rendered = json.dumps(payload, indent=2) + if args.output is None: + print(rendered) + else: + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(rendered + "\n", encoding="utf-8") + print(args.output) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/c_window_rank_safe_v2.py b/tracks/qcs/solutions/Genshin_Impact-71/search/c_window_rank_safe_v2.py new file mode 100644 index 000000000..e3f3ea1c3 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/c_window_rank_safe_v2.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python3 +"""Rank acyclic two-root exact-resynthesis windows in mystery C. + +Every possible boundary divisor is required to be outside both the removable +cone and the transitive fanout (descendant) cone of either replacement root. +The latter restriction prevents a synthesized root from depending on itself +through a surviving descendant. +""" + +from __future__ import annotations + +import argparse +import importlib.util +import itertools +import json +import sys +from pathlib import Path + + +DEFAULT_MODULE = Path( + "/home/user_milksang/private/homefile/quantum_harness/issue71_occam/" + "tracks/qcs/solutions/Genshin_Impact-71/search/window_search.py" +) +DEFAULT_NETLIST = Path( + "/home/user_milksang/private/homefile/quantum_harness/issue71_occam/" + "results/occam71/reference-355/mystery-C.txt" +) + + +def load_window_module(path: Path): + spec = importlib.util.spec_from_file_location("c_window_generic_safe_rank", path) + if spec is None or spec.loader is None: + raise RuntimeError(f"cannot load audited module {path}") + module = importlib.util.module_from_spec(spec) + sys.modules[spec.name] = module + spec.loader.exec_module(module) + return module + + +def natural_name_key(name: str) -> tuple[int, int]: + if len(name) < 2 or name[0] not in {"x", "w"}: + raise ValueError(f"unexpected signal name {name!r}") + return (0 if name[0] == "x" else 1, int(name[1:])) + + +def reachable_pattern_count( + names: tuple[str, ...], + values: dict[str, int], + rows: int, +) -> int: + seen: set[int] = set() + for row in range(rows): + code = 0 + for bit, name in enumerate(names): + code |= ((values[name] >> row) & 1) << bit + seen.add(code) + return len(seen) + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--module", type=Path, default=DEFAULT_MODULE) + parser.add_argument("--netlist", type=Path, default=DEFAULT_NETLIST) + parser.add_argument("--min-removed", type=int, default=6) + parser.add_argument("--max-removed", type=int, default=18) + parser.add_argument("--max-boundary", type=int, default=12) + parser.add_argument("--structural-limit", type=int, default=3000) + parser.add_argument("--output-limit", type=int, default=100) + parser.add_argument("--output", type=Path) + args = parser.parse_args() + + if not (2 <= args.min_removed <= args.max_removed): + raise ValueError("invalid removed-gate bounds") + if not (1 <= args.max_boundary <= 16): + raise ValueError("invalid boundary bound") + if args.structural_limit <= 0 or args.output_limit <= 0: + raise ValueError("limits must be positive") + + dws = load_window_module(args.module) + circuit = dws.parse_circuit(args.netlist) + _, values, _ = dws.evaluate(circuit) + gate_by_out, _ = dws.structural_maps(circuit) + gate_index = {gate.out: index for index, gate in enumerate(circuit.gates)} + rows = 1 << circuit.n_inputs + + structural: list[dict[str, object]] = [] + gate_names = tuple(gate.out for gate in circuit.gates) + for roots in itertools.combinations(gate_names, 2): + removed, descendants = dws.removable_closure(circuit, roots) + removed_count = len(removed) + if not args.min_removed <= removed_count <= args.max_removed: + continue + boundary: set[str] = set() + for name in removed: + gate = gate_by_out[name] + for token in (gate.left, gate.right): + if token.name not in removed and token.name not in descendants: + boundary.add(token.name) + if len(boundary) > args.max_boundary: + continue + ordered_boundary = tuple(sorted(boundary, key=natural_name_key)) + structural.append( + { + "roots": roots, + "removed_count": removed_count, + "boundary": ordered_boundary, + "boundary_count": len(ordered_boundary), + "removed": tuple(sorted(removed, key=gate_index.__getitem__)), + "descendant_count": len(descendants), + } + ) + + structural.sort( + key=lambda record: ( + -int(record["removed_count"]), + int(record["boundary_count"]), + tuple(record["roots"]), + ) + ) + structural = structural[: args.structural_limit] + + records: list[dict[str, object]] = [] + for record in structural: + boundary = tuple(record["boundary"]) + patterns = reachable_pattern_count(boundary, values, rows) + removed_count = int(record["removed_count"]) + candidate_gates = removed_count - 1 + boundary_count = int(record["boundary_count"]) + effort_proxy = patterns * candidate_gates * ( + boundary_count + candidate_gates + ) ** 2 + records.append( + { + **record, + "reachable_patterns": patterns, + "candidate_gates": candidate_gates, + "effort_proxy": effort_proxy, + } + ) + + records.sort( + key=lambda record: ( + int(record["effort_proxy"]), + -int(record["removed_count"]), + int(record["boundary_count"]), + tuple(record["roots"]), + ) + ) + payload = { + "netlist": str(args.netlist), + "inputs": circuit.n_inputs, + "gates": len(circuit.gates), + "rows": rows, + "safety": { + "acyclic_boundary": True, + "boundary_excludes_removed": True, + "boundary_excludes_root_descendants": True, + }, + "filters": { + "min_removed": args.min_removed, + "max_removed": args.max_removed, + "max_boundary": args.max_boundary, + "structural_limit": args.structural_limit, + }, + "structural_candidates": len(structural), + "records": records[: args.output_limit], + } + rendered = json.dumps(payload, indent=2) + if args.output is None: + print(rendered) + else: + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(rendered + "\n", encoding="utf-8") + print(args.output) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/c_window_rank_safe_v3.py b/tracks/qcs/solutions/Genshin_Impact-71/search/c_window_rank_safe_v3.py new file mode 100644 index 000000000..8a7ced7b9 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/c_window_rank_safe_v3.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python3 +"""Rank complete, acyclic two-root exact-resynthesis windows in mystery C. + +A valid fixed-boundary window must meet two independent conditions: +1. no boundary divisor lies in either replacement root's fanout cone; and +2. the retained boundary uniquely determines both root values. +The second condition rejects ancestor/descendant root pairs whose downstream +root needs a surviving internal signal that cannot safely be used as a divisor. +""" + +from __future__ import annotations + +import argparse +import importlib.util +import itertools +import json +import sys +from pathlib import Path + + +DEFAULT_MODULE = Path( + "/home/user_milksang/private/homefile/quantum_harness/issue71_occam/" + "tracks/qcs/solutions/Genshin_Impact-71/search/window_search.py" +) +DEFAULT_NETLIST = Path( + "/home/user_milksang/private/homefile/quantum_harness/issue71_occam/" + "results/occam71/reference-355/mystery-C.txt" +) + + +def load_window_module(path: Path): + spec = importlib.util.spec_from_file_location("c_window_generic_safe_rank_v3", path) + if spec is None or spec.loader is None: + raise RuntimeError(f"cannot load audited module {path}") + module = importlib.util.module_from_spec(spec) + sys.modules[spec.name] = module + spec.loader.exec_module(module) + return module + + +def natural_name_key(name: str) -> tuple[int, int]: + if len(name) < 2 or name[0] not in {"x", "w"}: + raise ValueError(f"unexpected signal name {name!r}") + return (0 if name[0] == "x" else 1, int(name[1:])) + + +def functional_relation_size( + boundary: tuple[str, ...], + roots: tuple[str, str], + values: dict[str, int], + rows: int, +) -> int | None: + relation: dict[int, int] = {} + for row in range(rows): + boundary_code = 0 + for bit, name in enumerate(boundary): + boundary_code |= ((values[name] >> row) & 1) << bit + root_code = 0 + for bit, name in enumerate(roots): + root_code |= ((values[name] >> row) & 1) << bit + previous = relation.setdefault(boundary_code, root_code) + if previous != root_code: + return None + return len(relation) + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--module", type=Path, default=DEFAULT_MODULE) + parser.add_argument("--netlist", type=Path, default=DEFAULT_NETLIST) + parser.add_argument("--min-removed", type=int, default=6) + parser.add_argument("--max-removed", type=int, default=18) + parser.add_argument("--max-boundary", type=int, default=12) + parser.add_argument("--structural-limit", type=int, default=3000) + parser.add_argument("--output-limit", type=int, default=100) + parser.add_argument("--output", type=Path) + args = parser.parse_args() + + if not (2 <= args.min_removed <= args.max_removed): + raise ValueError("invalid removed-gate bounds") + if not (1 <= args.max_boundary <= 16): + raise ValueError("invalid boundary bound") + if args.structural_limit <= 0 or args.output_limit <= 0: + raise ValueError("limits must be positive") + + dws = load_window_module(args.module) + circuit = dws.parse_circuit(args.netlist) + _, values, _ = dws.evaluate(circuit) + gate_by_out, _ = dws.structural_maps(circuit) + gate_index = {gate.out: index for index, gate in enumerate(circuit.gates)} + rows = 1 << circuit.n_inputs + + structural: list[dict[str, object]] = [] + gate_names = tuple(gate.out for gate in circuit.gates) + for roots_untyped in itertools.combinations(gate_names, 2): + roots = (roots_untyped[0], roots_untyped[1]) + removed, descendants = dws.removable_closure(circuit, roots) + removed_count = len(removed) + if not args.min_removed <= removed_count <= args.max_removed: + continue + boundary: set[str] = set() + for name in removed: + gate = gate_by_out[name] + for token in (gate.left, gate.right): + if token.name not in removed and token.name not in descendants: + boundary.add(token.name) + if len(boundary) > args.max_boundary: + continue + ordered_boundary = tuple(sorted(boundary, key=natural_name_key)) + relation_size = functional_relation_size( + ordered_boundary, roots, values, rows + ) + if relation_size is None: + continue + structural.append( + { + "roots": roots, + "removed_count": removed_count, + "boundary": ordered_boundary, + "boundary_count": len(ordered_boundary), + "removed": tuple(sorted(removed, key=gate_index.__getitem__)), + "descendant_count": len(descendants), + "reachable_patterns": relation_size, + } + ) + + structural.sort( + key=lambda record: ( + -int(record["removed_count"]), + int(record["boundary_count"]), + tuple(record["roots"]), + ) + ) + structural = structural[: args.structural_limit] + + records: list[dict[str, object]] = [] + for record in structural: + patterns = int(record["reachable_patterns"]) + removed_count = int(record["removed_count"]) + candidate_gates = removed_count - 1 + boundary_count = int(record["boundary_count"]) + effort_proxy = patterns * candidate_gates * ( + boundary_count + candidate_gates + ) ** 2 + records.append( + { + **record, + "candidate_gates": candidate_gates, + "effort_proxy": effort_proxy, + } + ) + + records.sort( + key=lambda record: ( + int(record["effort_proxy"]), + -int(record["removed_count"]), + int(record["boundary_count"]), + tuple(record["roots"]), + ) + ) + payload = { + "netlist": str(args.netlist), + "inputs": circuit.n_inputs, + "gates": len(circuit.gates), + "rows": rows, + "safety": { + "acyclic_boundary": True, + "boundary_excludes_removed": True, + "boundary_excludes_root_descendants": True, + "roots_functional_of_boundary": True, + }, + "filters": { + "min_removed": args.min_removed, + "max_removed": args.max_removed, + "max_boundary": args.max_boundary, + "structural_limit": args.structural_limit, + }, + "valid_structural_candidates": len(structural), + "records": records[: args.output_limit], + } + rendered = json.dumps(payload, indent=2) + if args.output is None: + print(rendered) + else: + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(rendered + "\n", encoding="utf-8") + print(args.output) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/c_window_safe_ranked_array_v2.slurm b/tracks/qcs/solutions/Genshin_Impact-71/search/c_window_safe_ranked_array_v2.slurm new file mode 100644 index 000000000..2165a78e1 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/c_window_safe_ranked_array_v2.slurm @@ -0,0 +1,71 @@ +#!/bin/bash +#SBATCH --job-name=occ71-cwin-v2 +#SBATCH --partition=home +#SBATCH --nodelist=n006 +#SBATCH --array=0-99%16 +#SBATCH --cpus-per-task=1 +#SBATCH --mem=8G +#SBATCH --time=01:00:00 +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/c-window-safe-v2/logs/%A_%a.out +#SBATCH --error=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/c-window-safe-v2/logs/%A_%a.err + +set -euo pipefail + +ROOT=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +SEARCH="$ROOT/tracks/qcs/solutions/Genshin_Impact-71/search" +INPUT="$ROOT/results/occam71/reference-355/mystery-C.txt" +RANKING="$ROOT/results/occam71/c-window-safe/c_joint_window_ranking_safe_v2.json" +OUT_ROOT="$ROOT/results/occam71/c-window-safe-v2" +PYSAT_VENV="${PYSAT_VENV:?submit with the audited PySAT venv}" +PYTHON_BIN="$PYSAT_VENV/bin/python" + +mkdir -p "$OUT_ROOT/logs" +test -x "$PYTHON_BIN" +test -s "$RANKING" +"$PYTHON_BIN" -c 'import pysat, pysat.solvers' + +read -r ROOT_A ROOT_B GATES < <( + "$PYTHON_BIN" -c ' +import json +import sys +payload = json.load(open(sys.argv[1], "r", encoding="utf-8")) +record = payload["records"][int(sys.argv[2])] +assert payload["safety"]["boundary_excludes_root_descendants"] is True +assert int(record["candidate_gates"]) == int(record["removed_count"]) - 1 +print(record["roots"][0], record["roots"][1], record["candidate_gates"]) +' "$RANKING" "$SLURM_ARRAY_TASK_ID" +) + +CASE_INDEX=$(printf "%03d" "$SLURM_ARRAY_TASK_ID") +CASE_NAME="${CASE_INDEX}_${ROOT_A}_${ROOT_B}_k${GATES}" +CASE_OUT="$OUT_ROOT/$CASE_NAME" +mkdir -p "$CASE_OUT" + +{ + printf 'job_id=%s\n' "$SLURM_ARRAY_JOB_ID" + printf 'array_task_id=%s\n' "$SLURM_ARRAY_TASK_ID" + printf 'node=%s\n' "$SLURMD_NODENAME" + printf 'seed=42\n' + printf 'roots=%s,%s\n' "$ROOT_A" "$ROOT_B" + printf 'candidate_gates=%s\n' "$GATES" + sha256sum "$INPUT" "$RANKING" \ + "$SEARCH/d_window_sat_safe_remote.py" \ + "$SEARCH/d_window_sat_hpc_safe.py" +} > "$CASE_OUT/metadata.txt" + +"$PYTHON_BIN" -u "$SEARCH/d_window_sat_hpc_safe.py" "$INPUT" \ + --roots "$ROOT_A" "$ROOT_B" \ + --gates "$GATES" \ + --timeout 10 \ + --output-dir "$CASE_OUT" + +test -s "$CASE_OUT/report.json" +CANDIDATE="$CASE_OUT/mystery-C.candidate.txt" +if [[ -s "$CANDIDATE" ]]; then + "$PYTHON_BIN" -u "$SEARCH/verify_candidate.py" \ + "$INPUT" "$CANDIDATE" \ + --report "$CASE_OUT/independent-full-domain-audit.json" + test -s "$CASE_OUT/independent-full-domain-audit.json" + touch "$CASE_OUT/CANDIDATE_AUDITED" +fi +touch "$CASE_OUT/COMPLETE" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/c_window_safe_ranked_array_v3.slurm b/tracks/qcs/solutions/Genshin_Impact-71/search/c_window_safe_ranked_array_v3.slurm new file mode 100644 index 000000000..01bdea525 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/c_window_safe_ranked_array_v3.slurm @@ -0,0 +1,73 @@ +#!/bin/bash +#SBATCH --job-name=occ71-cwin-v3 +#SBATCH --partition=home +#SBATCH --nodelist=n006 +#SBATCH --array=0-99%16 +#SBATCH --cpus-per-task=1 +#SBATCH --mem=8G +#SBATCH --time=01:00:00 +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/c-window-safe-v3/logs/%A_%a.out +#SBATCH --error=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/c-window-safe-v3/logs/%A_%a.err + +set -euo pipefail + +ROOT=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +SEARCH="$ROOT/tracks/qcs/solutions/Genshin_Impact-71/search" +INPUT="$ROOT/results/occam71/reference-355/mystery-C.txt" +RANKING="$ROOT/results/occam71/c-window-safe/c_joint_window_ranking_safe_v3.json" +OUT_ROOT="$ROOT/results/occam71/c-window-safe-v3" +PYSAT_VENV="${PYSAT_VENV:?submit with the audited PySAT venv}" +PYTHON_BIN="$PYSAT_VENV/bin/python" + +mkdir -p "$OUT_ROOT/logs" +test -x "$PYTHON_BIN" +test -s "$RANKING" +"$PYTHON_BIN" -c 'import pysat, pysat.solvers' + +read -r ROOT_A ROOT_B GATES < <( + "$PYTHON_BIN" -c ' +import json +import sys +payload = json.load(open(sys.argv[1], "r", encoding="utf-8")) +record = payload["records"][int(sys.argv[2])] +assert payload["safety"]["boundary_excludes_root_descendants"] is True +assert payload["safety"]["roots_functional_of_boundary"] is True +assert int(record["candidate_gates"]) == int(record["removed_count"]) - 1 +print(record["roots"][0], record["roots"][1], record["candidate_gates"]) +' "$RANKING" "$SLURM_ARRAY_TASK_ID" +) + +CASE_INDEX=$(printf "%03d" "$SLURM_ARRAY_TASK_ID") +CASE_NAME="${CASE_INDEX}_${ROOT_A}_${ROOT_B}_k${GATES}" +CASE_OUT="$OUT_ROOT/$CASE_NAME" +mkdir -p "$CASE_OUT" + +{ + printf 'job_id=%s\n' "$SLURM_ARRAY_JOB_ID" + printf 'array_task_id=%s\n' "$SLURM_ARRAY_TASK_ID" + printf 'node=%s\n' "$SLURMD_NODENAME" + printf 'seed=42\n' + printf 'roots=%s,%s\n' "$ROOT_A" "$ROOT_B" + printf 'candidate_gates=%s\n' "$GATES" + sha256sum "$INPUT" "$RANKING" \ + "$SEARCH/c_window_rank_safe_v3.py" \ + "$SEARCH/d_window_sat_safe_remote.py" \ + "$SEARCH/d_window_sat_hpc_safe.py" +} > "$CASE_OUT/metadata.txt" + +"$PYTHON_BIN" -u "$SEARCH/d_window_sat_hpc_safe.py" "$INPUT" \ + --roots "$ROOT_A" "$ROOT_B" \ + --gates "$GATES" \ + --timeout 10 \ + --output-dir "$CASE_OUT" + +test -s "$CASE_OUT/report.json" +CANDIDATE="$CASE_OUT/mystery-C.candidate.txt" +if [[ -s "$CANDIDATE" ]]; then + "$PYTHON_BIN" -u "$SEARCH/verify_candidate.py" \ + "$INPUT" "$CANDIDATE" \ + --report "$CASE_OUT/independent-full-domain-audit.json" + test -s "$CASE_OUT/independent-full-domain-audit.json" + touch "$CASE_OUT/CANDIDATE_AUDITED" +fi +touch "$CASE_OUT/COMPLETE" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/c_window_safe_rerun_t02_v1.slurm b/tracks/qcs/solutions/Genshin_Impact-71/search/c_window_safe_rerun_t02_v1.slurm new file mode 100644 index 000000000..a55c513b0 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/c_window_safe_rerun_t02_v1.slurm @@ -0,0 +1,49 @@ +#!/bin/bash +#SBATCH --job-name=occ71-cwin-safe +#SBATCH --partition=home +#SBATCH --nodelist=n006 +#SBATCH --array=0-1 +#SBATCH --cpus-per-task=1 +#SBATCH --mem=8G +#SBATCH --time=01:00:00 +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/c-window-safe/logs/%A_%a.out +#SBATCH --error=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/c-window-safe/logs/%A_%a.err + +set -euo pipefail + +ROOT=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +SEARCH="$ROOT/tracks/qcs/solutions/Genshin_Impact-71/search" +INPUT="$ROOT/results/occam71/reference-355/mystery-C.txt" +OUT_ROOT="$ROOT/results/occam71/c-window-safe" +PYSAT_VENV="${PYSAT_VENV:?submit with the audited PySAT venv}" +PYTHON_BIN="$PYSAT_VENV/bin/python" + +mkdir -p "$OUT_ROOT/logs" +test -x "$PYTHON_BIN" +"$PYTHON_BIN" -c 'import pysat, pysat.solvers' + +CASES=( + "w67 w71 5" + "w39 w43 5" +) +read -r ROOT_A ROOT_B GATES <<< "${CASES[$SLURM_ARRAY_TASK_ID]}" +CASE_NAME="${ROOT_A}_${ROOT_B}_k${GATES}" +CASE_OUT="$OUT_ROOT/$CASE_NAME" +mkdir -p "$CASE_OUT" + +"$PYTHON_BIN" -u "$SEARCH/d_window_sat_hpc_safe.py" "$INPUT" \ + --roots "$ROOT_A" "$ROOT_B" \ + --gates "$GATES" \ + --timeout 10 \ + --output-dir "$CASE_OUT" + +test -s "$CASE_OUT/report.json" +CANDIDATE="$CASE_OUT/mystery-C.candidate.txt" +if [[ -s "$CANDIDATE" ]]; then + "$PYTHON_BIN" -u "$SEARCH/verify_candidate.py" \ + "$INPUT" "$CANDIDATE" \ + --report "$CASE_OUT/independent-full-domain-audit.json" + test -s "$CASE_OUT/independent-full-domain-audit.json" + touch "$CASE_OUT/CANDIDATE_AUDITED" +fi +touch "$CASE_OUT/COMPLETE" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/c_window_t02_v1.slurm b/tracks/qcs/solutions/Genshin_Impact-71/search/c_window_t02_v1.slurm new file mode 100644 index 000000000..e97ea46d9 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/c_window_t02_v1.slurm @@ -0,0 +1,59 @@ +#!/bin/bash +#SBATCH --job-name=occam71-cwin +#SBATCH --partition=home +#SBATCH --nodelist=n006 +#SBATCH --array=0-11 +#SBATCH --cpus-per-task=1 +#SBATCH --mem=8G +#SBATCH --time=01:00:00 +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/c-window-slurm/logs/%A_%a.out +#SBATCH --error=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/c-window-slurm/logs/%A_%a.err + +set -euo pipefail + +ROOT=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +SEARCH="$ROOT/tracks/qcs/solutions/Genshin_Impact-71/search" +INPUT="$ROOT/results/occam71/reference-355/mystery-C.txt" +OUT_ROOT="$ROOT/results/occam71/c-window-slurm" +PYSAT_VENV="${PYSAT_VENV:?submit with the audited PySAT venv}" +PYTHON_BIN="$PYSAT_VENV/bin/python" + +mkdir -p "$OUT_ROOT/logs" +test -x "$PYTHON_BIN" +"$PYTHON_BIN" -c 'import pysat, pysat.solvers' + +CASES=( + "w67 w71 5" + "w88 w92 5" + "w110 w111 5" + "w130 w131 5" + "w143 w144 5" + "w53 w54 5" + "w64 w65 5" + "w80 w81 5" + "w51 w55 5" + "w108 w112 5" + "w128 w132 5" + "w39 w43 5" +) +read -r ROOT_A ROOT_B GATES <<< "${CASES[$SLURM_ARRAY_TASK_ID]}" +CASE_NAME="${ROOT_A}_${ROOT_B}_k${GATES}" +CASE_OUT="$OUT_ROOT/$CASE_NAME" +mkdir -p "$CASE_OUT" + +"$PYTHON_BIN" -u "$SEARCH/d_window_sat_hpc_remote.py" "$INPUT" \ + --roots "$ROOT_A" "$ROOT_B" \ + --gates "$GATES" \ + --timeout 10 \ + --output-dir "$CASE_OUT" + +test -s "$CASE_OUT/report.json" +CANDIDATE="$CASE_OUT/mystery-C.candidate.txt" +if [[ -s "$CANDIDATE" ]]; then + "$PYTHON_BIN" -u "$SEARCH/verify_candidate.py" \ + "$INPUT" "$CANDIDATE" \ + --report "$CASE_OUT/independent-full-domain-audit.json" + test -s "$CASE_OUT/independent-full-domain-audit.json" + touch "$CASE_OUT/CANDIDATE_AUDITED" +fi +touch "$CASE_OUT/COMPLETE" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/circuit.py b/tracks/qcs/solutions/Genshin_Impact-71/search/circuit.py new file mode 100644 index 000000000..d1946de8a --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/circuit.py @@ -0,0 +1,478 @@ +"""Independent Boolean-circuit tooling for quantum.harness issue #71. + +The parser is intentionally strict. Third-party netlists are treated as data: +this module never imports or executes code from a submission. +""" + +from __future__ import annotations + +from dataclasses import dataclass +from pathlib import Path +import csv +import hashlib +import re +from typing import Iterable, Sequence + + +OPS = frozenset({"AND", "OR", "XOR", "NAND", "NOR", "XNOR"}) +_TOKEN_RE = re.compile(r"~?(?:x[1-9][0-9]*|w[1-9][0-9]*)\Z") +_WIRE_RE = re.compile(r"w[1-9][0-9]*\Z") + + +def split_token(token: str) -> tuple[str, bool]: + """Return (base name, is_negated) after validating a signal token.""" + if not _TOKEN_RE.fullmatch(token): + raise ValueError(f"invalid signal token: {token!r}") + return (token[1:], True) if token.startswith("~") else (token, False) + + +def invert(token: str) -> str: + """Toggle a free inversion on a signal token.""" + base, negated = split_token(token) + return base if negated else f"~{base}" + + +def remap_token(token: str, mapping: dict[str, str]) -> str: + base, negated = split_token(token) + mapped = mapping.get(base, base) + return f"~{mapped}" if negated else mapped + + +@dataclass(frozen=True) +class Gate: + output: str + op: str + left: str + right: str + + +@dataclass(frozen=True) +class Circuit: + ninputs: int + gates: tuple[Gate, ...] + outputs: tuple[str, ...] + + @classmethod + def parse(cls, path: str | Path) -> "Circuit": + return cls.parse_text(Path(path).read_text(encoding="utf-8")) + + @classmethod + def parse_text(cls, text: str) -> "Circuit": + ninputs: int | None = None + gates: list[Gate] = [] + outputs: tuple[str, ...] | None = None + defined: set[str] = set() + + for lineno, raw_line in enumerate(text.splitlines(), start=1): + line = raw_line.split("#", 1)[0].strip() + if not line: + continue + fields = line.split() + + if fields[0] == "INPUTS": + if ninputs is not None or gates or outputs is not None: + raise ValueError(f"line {lineno}: misplaced/duplicate INPUTS") + if len(fields) != 2 or not fields[1].isdigit(): + raise ValueError(f"line {lineno}: malformed INPUTS") + ninputs = int(fields[1]) + if ninputs <= 0: + raise ValueError(f"line {lineno}: INPUTS must be positive") + continue + + if ninputs is None: + raise ValueError(f"line {lineno}: INPUTS must come first") + + if fields[0] == "OUTPUTS": + if outputs is not None or len(fields) < 2: + raise ValueError(f"line {lineno}: malformed/duplicate OUTPUTS") + parsed = tuple(fields[1:]) + for token in parsed: + base, _ = split_token(token) + if base.startswith("x"): + idx = int(base[1:]) + if not 1 <= idx <= ninputs: + raise ValueError(f"line {lineno}: input out of range: {base}") + elif base not in defined: + raise ValueError(f"line {lineno}: undefined output wire: {base}") + outputs = parsed + continue + + if outputs is not None: + raise ValueError(f"line {lineno}: gate appears after OUTPUTS") + if len(fields) != 5 or fields[1] != "=": + raise ValueError(f"line {lineno}: malformed gate") + output, op, left, right = fields[0], fields[2], fields[3], fields[4] + if not _WIRE_RE.fullmatch(output): + raise ValueError(f"line {lineno}: invalid output wire: {output}") + if output in defined: + raise ValueError(f"line {lineno}: duplicate output wire: {output}") + if op not in OPS: + raise ValueError(f"line {lineno}: unsupported gate: {op}") + for token in (left, right): + base, _ = split_token(token) + if base.startswith("x"): + idx = int(base[1:]) + if not 1 <= idx <= ninputs: + raise ValueError(f"line {lineno}: input out of range: {base}") + elif base not in defined: + raise ValueError( + f"line {lineno}: wire used before definition: {base}" + ) + gates.append(Gate(output, op, left, right)) + defined.add(output) + + if ninputs is None: + raise ValueError("missing INPUTS") + if outputs is None: + raise ValueError("missing OUTPUTS") + return cls(ninputs, tuple(gates), outputs) + + def to_text(self) -> str: + lines = [f"INPUTS {self.ninputs}"] + lines.extend( + f"{gate.output} = {gate.op} {gate.left} {gate.right}" + for gate in self.gates + ) + lines.append(f"OUTPUTS {' '.join(self.outputs)}") + return "\n".join(lines) + "\n" + + def write(self, path: str | Path) -> None: + destination = Path(path) + destination.parent.mkdir(parents=True, exist_ok=True) + destination.write_text(self.to_text(), encoding="utf-8", newline="\n") + + def evaluate(self, input_bits: str) -> str: + if len(input_bits) != self.ninputs or set(input_bits) - {"0", "1"}: + raise ValueError( + f"expected {self.ninputs} binary input characters, got {input_bits!r}" + ) + values: dict[str, bool] = { + f"x{i + 1}": bit == "1" for i, bit in enumerate(input_bits) + } + + def value(token: str) -> bool: + base, negated = split_token(token) + result = values[base] + return not result if negated else result + + for gate in self.gates: + left, right = value(gate.left), value(gate.right) + values[gate.output] = _apply_bool(gate.op, left, right) + return "".join("1" if value(token) else "0" for token in self.outputs) + + def truth_tables(self) -> tuple[dict[str, int], tuple[int, ...], int]: + """Evaluate all wires bit-parallel over the complete primary-input domain. + + Bit k of each returned integer is the value on assignment k. Assignment + bits follow x1, x2, ... order, matching the challenge's LSB-first layout. + """ + assignment_count = 1 << self.ninputs + mask = (1 << assignment_count) - 1 + values: dict[str, int] = {} + for input_idx in range(self.ninputs): + table = 0 + for assignment in range(assignment_count): + if (assignment >> input_idx) & 1: + table |= 1 << assignment + values[f"x{input_idx + 1}"] = table + + def value(token: str) -> int: + base, negated = split_token(token) + table = values[base] + return table ^ mask if negated else table + + for gate in self.gates: + values[gate.output] = _apply_table( + gate.op, value(gate.left), value(gate.right), mask + ) + return values, tuple(value(token) for token in self.outputs), mask + + def prune_and_renumber(self) -> "Circuit": + """Remove gates outside every output cone and densely renumber wires.""" + live: set[str] = { + base for token in self.outputs for base, _ in (split_token(token),) + if base.startswith("w") + } + kept_reversed: list[Gate] = [] + for gate in reversed(self.gates): + if gate.output not in live: + continue + kept_reversed.append(gate) + for token in (gate.left, gate.right): + base, _ = split_token(token) + if base.startswith("w"): + live.add(base) + kept = list(reversed(kept_reversed)) + mapping = {gate.output: f"w{idx}" for idx, gate in enumerate(kept, start=1)} + rewritten = tuple( + Gate( + mapping[gate.output], + gate.op, + remap_token(gate.left, mapping), + remap_token(gate.right, mapping), + ) + for gate in kept + ) + outputs = tuple(remap_token(token, mapping) for token in self.outputs) + return Circuit(self.ninputs, rewritten, outputs) + + def structural_audit(self) -> dict[str, int]: + values, _, mask = self.truth_tables() + seen: dict[int, str] = {} + constants = duplicates = complement_duplicates = 0 + for gate in self.gates: + table = values[gate.output] + if table in (0, mask): + constants += 1 + if table in seen: + duplicates += 1 + elif (table ^ mask) in seen: + complement_duplicates += 1 + seen.setdefault(table, gate.output) + pruned = self.prune_and_renumber() + return { + "gates": len(self.gates), + "dead_gates": len(self.gates) - len(pruned.gates), + "constant_wires": constants, + "duplicate_wires": duplicates, + "complement_duplicate_wires": complement_duplicates, + } + + +def _apply_bool(op: str, left: bool, right: bool) -> bool: + if op == "AND": + return left and right + if op == "OR": + return left or right + if op == "XOR": + return left != right + if op == "NAND": + return not (left and right) + if op == "NOR": + return not (left or right) + if op == "XNOR": + return left == right + raise AssertionError(op) + + +def _apply_table(op: str, left: int, right: int, mask: int) -> int: + if op == "AND": + return left & right + if op == "OR": + return left | right + if op == "XOR": + return left ^ right + if op == "NAND": + return (left & right) ^ mask + if op == "NOR": + return (left | right) ^ mask + if op == "XNOR": + return (left ^ right) ^ mask + raise AssertionError(op) + + +class CircuitBuilder: + def __init__(self, ninputs: int): + self.ninputs = ninputs + self._gates: list[Gate] = [] + + def gate(self, op: str, left: str, right: str) -> str: + if op not in OPS: + raise ValueError(op) + split_token(left) + split_token(right) + output = f"w{len(self._gates) + 1}" + self._gates.append(Gate(output, op, left, right)) + return output + + def half_adder(self, left: str, right: str) -> tuple[str, str]: + total = self.gate("XOR", left, right) + carry = self.gate("AND", left, right) + return total, carry + + def full_adder(self, a: str, b: str, carry_in: str) -> tuple[str, str]: + parity = self.gate("XOR", a, b) + total = self.gate("XOR", parity, carry_in) + generate = self.gate("AND", a, b) + propagate = self.gate("AND", parity, carry_in) + # The generate and propagate terms are disjoint, so XOR equals OR. + carry_out = self.gate("XOR", generate, propagate) + return total, carry_out + + def finish(self, outputs: Iterable[str], prune: bool = True) -> Circuit: + circuit = Circuit(self.ninputs, tuple(self._gates), tuple(outputs)) + # Round-trip through the strict parser before returning generated text. + circuit = Circuit.parse_text(circuit.to_text()) + return circuit.prune_and_renumber() if prune else circuit + + +def build_adder(nbits: int) -> Circuit: + if nbits < 1: + raise ValueError("nbits must be positive") + builder = CircuitBuilder(2 * nbits) + x = [f"x{i + 1}" for i in range(nbits)] + y = [f"x{nbits + i + 1}" for i in range(nbits)] + low_sum, carry = builder.half_adder(x[0], y[0]) + outputs = [low_sum] + for idx in range(1, nbits): + total, carry = builder.full_adder(x[idx], y[idx], carry) + outputs.append(total) + outputs.append(carry) + return builder.finish(outputs) + + +def build_multiplier(nbits: int) -> Circuit: + """Build a complete unsigned multiplier via column compression.""" + if nbits < 1: + raise ValueError("nbits must be positive") + builder = CircuitBuilder(2 * nbits) + columns: list[list[str]] = [[] for _ in range(2 * nbits + 1)] + for left_idx in range(nbits): + for right_idx in range(nbits): + partial = builder.gate( + "AND", + f"x{left_idx + 1}", + f"x{nbits + right_idx + 1}", + ) + columns[left_idx + right_idx].append(partial) + + outputs: list[str] = [] + for weight in range(2 * nbits): + column = columns[weight] + while len(column) >= 3: + a, b, c = column.pop(), column.pop(), column.pop() + total, carry = builder.full_adder(a, b, c) + column.append(total) + columns[weight + 1].append(carry) + if len(column) == 2: + total, carry = builder.half_adder(column[0], column[1]) + outputs.append(total) + columns[weight + 1].append(carry) + elif len(column) == 1: + outputs.append(column[0]) + else: + raise AssertionError(f"empty product column {weight}") + return builder.finish(outputs) + + +def decode_operands(input_bits: str) -> tuple[int, int]: + if len(input_bits) % 2: + raise ValueError("input width must be even") + nbits = len(input_bits) // 2 + left = sum((bit == "1") << idx for idx, bit in enumerate(input_bits[:nbits])) + right = sum((bit == "1") << idx for idx, bit in enumerate(input_bits[nbits:])) + return left, right + + +def encode_lsb(value: int, width: int) -> str: + if value < 0 or value >= 1 << width: + raise ValueError(f"value {value} does not fit in {width} bits") + return "".join("1" if (value >> idx) & 1 else "0" for idx in range(width)) + + +def arithmetic_output(kind: str, input_bits: str, width: int) -> str: + left, right = decode_operands(input_bits) + if kind == "add": + value = left + right + elif kind == "mul": + value = left * right + elif kind == "absdiff": + value = abs(left - right) + elif kind == "sos": + value = left * left + right * right + else: + raise ValueError(f"unknown arithmetic family: {kind}") + return encode_lsb(value, width) + + +def verify_formula(circuit: Circuit, kind: str) -> dict[str, int]: + checked = 1 << circuit.ninputs + width = len(circuit.outputs) + failures = 0 + for assignment in range(checked): + input_bits = encode_lsb(assignment, circuit.ninputs) + if circuit.evaluate(input_bits) != arithmetic_output(kind, input_bits, width): + failures += 1 + return {"checked": checked, "failures": failures} + + +def verify_dataset(circuit: Circuit, csv_path: str | Path) -> dict[str, int | float]: + rows = 0 + exact = 0 + correct_bits = 0 + total_bits = 0 + with Path(csv_path).open("r", encoding="utf-8", newline="") as stream: + reader = csv.DictReader(stream) + if reader.fieldnames != ["input", "output"]: + raise ValueError(f"unexpected CSV header in {csv_path}: {reader.fieldnames}") + for row in reader: + prediction = circuit.evaluate(row["input"]) + expected = row["output"] + if len(prediction) != len(expected): + raise ValueError("circuit/dataset output-width mismatch") + rows += 1 + exact += prediction == expected + correct_bits += sum(a == b for a, b in zip(prediction, expected)) + total_bits += len(expected) + return { + "rows": rows, + "exact": exact, + "exact_accuracy": exact / rows, + "correct_bits": correct_bits, + "total_bits": total_bits, + "bit_accuracy": correct_bits / total_bits, + } + + +def prediction_bytes(circuit: Circuit, test_inputs_path: str | Path) -> bytes: + with Path(test_inputs_path).open("r", encoding="utf-8", newline="") as stream: + reader = csv.reader(stream) + header = next(reader) + if header != ["input"]: + raise ValueError( + f"unexpected test-input header in {test_inputs_path}: {header}" + ) + lines = ["input,output"] + for fields in reader: + if len(fields) != 1: + raise ValueError(f"malformed test-input row: {fields}") + input_bits = fields[0] + lines.append(f"{input_bits},{circuit.evaluate(input_bits)}") + return ("\n".join(lines) + "\n").encode("ascii") + + +def write_predictions( + circuit: Circuit, test_inputs_path: str | Path, output_path: str | Path +) -> str: + payload = prediction_bytes(circuit, test_inputs_path) + destination = Path(output_path) + destination.parent.mkdir(parents=True, exist_ok=True) + destination.write_bytes(payload) + return hashlib.sha256(payload).hexdigest() + + +def read_commitment(path: str | Path) -> str: + fields = Path(path).read_text(encoding="ascii").split() + if not fields or not re.fullmatch(r"[0-9a-fA-F]{64}", fields[0]): + raise ValueError(f"malformed SHA-256 commitment: {path}") + return fields[0].lower() + + +def sha256_file(path: str | Path) -> str: + return hashlib.sha256(Path(path).read_bytes()).hexdigest() + + +def compare_truth_tables(left: Circuit, right: Circuit) -> dict[str, int | bool]: + if left.ninputs != right.ninputs: + raise ValueError("input-width mismatch") + if len(left.outputs) != len(right.outputs): + raise ValueError("output-width mismatch") + _, left_outputs, _ = left.truth_tables() + _, right_outputs, _ = right.truth_tables() + mismatching_outputs = sum(a != b for a, b in zip(left_outputs, right_outputs)) + return { + "assignments": 1 << left.ninputs, + "outputs": len(left.outputs), + "mismatching_outputs": mismatching_outputs, + "equivalent": mismatching_outputs == 0, + } diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/d109_window_safe_ranked_array_v3.slurm b/tracks/qcs/solutions/Genshin_Impact-71/search/d109_window_safe_ranked_array_v3.slurm new file mode 100644 index 000000000..36d820509 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/d109_window_safe_ranked_array_v3.slurm @@ -0,0 +1,78 @@ +#!/bin/bash +#SBATCH --job-name=occ71-d109win-v3 +#SBATCH --partition=home +#SBATCH --nodelist=n006 +#SBATCH --array=0-78%16 +#SBATCH --cpus-per-task=1 +#SBATCH --mem=8G +#SBATCH --time=01:00:00 +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/d109-window-safe-v3/logs/%A_%a.out +#SBATCH --error=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/d109-window-safe-v3/logs/%A_%a.err + +set -euo pipefail + +ROOT=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +SEARCH="$ROOT/tracks/qcs/solutions/Genshin_Impact-71/search" +INPUT="$ROOT/results/occam71/eslim/D-pilot-build42598-size6-seed42/job-42633/candidate.txt" +RANKING="$ROOT/results/occam71/d-window-safe/d_joint_window_ranking_safe_v3.json" +OUT_ROOT="$ROOT/results/occam71/d109-window-safe-v3" +PYSAT_VENV="${PYSAT_VENV:?submit with the audited PySAT venv}" +PYTHON_BIN="$PYSAT_VENV/bin/python" + +mkdir -p "$OUT_ROOT/logs" +test -x "$PYTHON_BIN" +test -s "$RANKING" +"$PYTHON_BIN" -c 'import pysat, pysat.solvers' + +read -r ROOT_A ROOT_B GATES < <( + "$PYTHON_BIN" -c ' +import json +import sys +payload = json.load(open(sys.argv[1], "r", encoding="utf-8")) +record = payload["records"][int(sys.argv[2])] +assert payload["safety"]["boundary_excludes_root_descendants"] is True +assert payload["safety"]["roots_functional_of_boundary"] is True +assert int(record["candidate_gates"]) == int(record["removed_count"]) - 1 +print(record["roots"][0], record["roots"][1], record["candidate_gates"]) +' "$RANKING" "$SLURM_ARRAY_TASK_ID" +) + +CASE_INDEX=$(printf "%03d" "$SLURM_ARRAY_TASK_ID") +CASE_NAME="${CASE_INDEX}_${ROOT_A}_${ROOT_B}_k${GATES}" +CASE_OUT="$OUT_ROOT/$CASE_NAME" +mkdir -p "$CASE_OUT" + +{ + printf 'job_id=%s\n' "$SLURM_ARRAY_JOB_ID" + printf 'array_task_id=%s\n' "$SLURM_ARRAY_TASK_ID" + printf 'node=%s\n' "$SLURMD_NODENAME" + printf 'seed=42\n' + printf 'roots=%s,%s\n' "$ROOT_A" "$ROOT_B" + printf 'candidate_gates=%s\n' "$GATES" + sha256sum "$INPUT" "$RANKING" \ + "$SEARCH/c_window_rank_safe_v3.py" \ + "$SEARCH/d_window_sat_safe_remote.py" \ + "$SEARCH/d_window_sat_hpc_safe.py" +} > "$CASE_OUT/metadata.txt" + +"$PYTHON_BIN" -u "$SEARCH/d_window_sat_hpc_safe.py" "$INPUT" \ + --roots "$ROOT_A" "$ROOT_B" \ + --gates "$GATES" \ + --timeout 10 \ + --output-dir "$CASE_OUT" + +test -s "$CASE_OUT/report.json" +INPUT_STEM=$(basename "${INPUT%.txt}") +CANDIDATE="$CASE_OUT/${INPUT_STEM}.candidate.txt" +if [[ -s "$CANDIDATE" ]]; then + "$PYTHON_BIN" -u "$SEARCH/verify_candidate.py" \ + "$INPUT" "$CANDIDATE" \ + --report "$CASE_OUT/independent-full-domain-audit.json" + test -s "$CASE_OUT/independent-full-domain-audit.json" + "$PYTHON_BIN" -u "$SEARCH/audit_d_formula.py" \ + "$CANDIDATE" \ + --report "$CASE_OUT/direct-formula-audit.json" + test -s "$CASE_OUT/direct-formula-audit.json" + touch "$CASE_OUT/CANDIDATE_AUDITED" +fi +touch "$CASE_OUT/COMPLETE" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/d_window_sat.py b/tracks/qcs/solutions/Genshin_Impact-71/search/d_window_sat.py new file mode 100644 index 000000000..7f694cb99 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/d_window_sat.py @@ -0,0 +1,614 @@ +#!/usr/bin/env python3 +"""Exact reachable-relation synthesis for two-root windows in mystery-D. + +This script consumes only the plain challenge netlist. It uses PySAT as a CNF +engine, but builds and audits the circuit semantics independently with complete +1024-row truth tables. The synthesis model permits every challenge gate, +arbitrary free input/output inversions, arbitrary acyclic topology, and +same-source fanins (so constants/projections are not accidentally excluded). +""" + +from __future__ import annotations + +import argparse +import hashlib +import importlib.util +import json +import random +import sys +import threading +import time +from dataclasses import dataclass +from pathlib import Path +from typing import Iterable, Sequence + +from pysat.solvers import Solver + + +SEED = 42 +MODULE_PATH = Path(r"C:\tmp\occam71_d_window\window_search.py") + + +def load_base_module(): + spec = importlib.util.spec_from_file_location("dws_exact_base", MODULE_PATH) + if spec is None or spec.loader is None: + raise RuntimeError(f"cannot load {MODULE_PATH}") + module = importlib.util.module_from_spec(spec) + sys.modules[spec.name] = module + spec.loader.exec_module(module) + return module + + +dws = load_base_module() + + +def truth_id(op: str, left_inv: bool, right_inv: bool) -> int: + result = 0 + for a in (0, 1): + for b in (0, 1): + aa = a ^ left_inv + bb = b ^ right_inv + if op == "AND": + out = aa & bb + elif op == "OR": + out = aa | bb + elif op == "XOR": + out = aa ^ bb + elif op == "NAND": + out = 1 ^ (aa & bb) + elif op == "NOR": + out = 1 ^ (aa | bb) + elif op == "XNOR": + out = 1 ^ (aa ^ bb) + else: + raise AssertionError(op) + result |= out << ((a << 1) | b) + return result + + +def gate_library() -> tuple[list[int], dict[int, tuple[str, bool, bool]]]: + mapping: dict[int, tuple[str, bool, bool]] = {} + preference = ["AND", "XOR", "OR", "NAND", "NOR", "XNOR"] + for op in preference: + for left_inv in (False, True): + for right_inv in (False, True): + table = truth_id(op, left_inv, right_inv) + depends_left = ( + ((table >> 0) & 1) != ((table >> 2) & 1) + or ((table >> 1) & 1) != ((table >> 3) & 1) + ) + depends_right = ( + ((table >> 0) & 1) != ((table >> 1) & 1) + or ((table >> 2) & 1) != ((table >> 3) & 1) + ) + if depends_left and depends_right and table not in mapping: + mapping[table] = (op, left_inv, right_inv) + if len(mapping) != 10: + raise AssertionError(f"expected 10 essential binary functions, got {mapping}") + return sorted(mapping), mapping + + +TABLES, TABLE_TO_GATE = gate_library() + + +class Pool: + def __init__(self) -> None: + self.top = 0 + + def one(self) -> int: + self.top += 1 + return self.top + + def many(self, count: int) -> list[int]: + return [self.one() for _ in range(count)] + + +def exactly_one(clauses: list[list[int]], variables: Sequence[int]) -> None: + if not variables: + clauses.append([]) + return + clauses.append(list(variables)) + for i, left in enumerate(variables): + for right in variables[i + 1 :]: + clauses.append([-left, -right]) + + +def force_selected_mux( + clauses: list[list[int]], + selector: int, + mux: int, + source: int | bool, +) -> None: + if isinstance(source, bool): + clauses.append([-selector, mux if source else -mux]) + else: + clauses.append([-selector, -source, mux]) + clauses.append([-selector, source, -mux]) + + +@dataclass +class Encoding: + clauses: list[list[int]] + pool: Pool + source_left: list[list[int]] + source_right: list[list[int]] + operations: list[list[int]] + gate_values: list[list[int]] + output_source: list[list[int]] + output_invert: list[int] + boundary_count: int + examples: list[tuple[tuple[int, ...], tuple[int, ...]]] + + +def encode_exact( + examples: list[tuple[tuple[int, ...], tuple[int, ...]]], + gate_count: int, +) -> Encoding: + boundary_count = len(examples[0][0]) + output_count = len(examples[0][1]) + row_count = len(examples) + pool = Pool() + clauses: list[list[int]] = [] + + gate_values = [pool.many(row_count) for _ in range(gate_count)] + source_left: list[list[int]] = [] + source_right: list[list[int]] = [] + operations: list[list[int]] = [] + + for gate_index in range(gate_count): + source_count = boundary_count + gate_index + left_sel = pool.many(source_count) + right_sel = pool.many(source_count) + op_sel = pool.many(len(TABLES)) + source_left.append(left_sel) + source_right.append(right_sel) + operations.append(op_sel) + exactly_one(clauses, left_sel) + exactly_one(clauses, right_sel) + exactly_one(clauses, op_sel) + + # The operation library is closed under swapping inputs, so left <= right + # is a complete symmetry break. Equality deliberately remains possible. + for left_index, left_var in enumerate(left_sel): + for right_index, right_var in enumerate(right_sel): + if left_index > right_index: + clauses.append([-left_var, -right_var]) + + for row_index, (boundary_bits, _) in enumerate(examples): + mux_left = pool.one() + mux_right = pool.one() + out_var = gate_values[gate_index][row_index] + for source_index, selector in enumerate(left_sel): + source: int | bool + if source_index < boundary_count: + source = bool(boundary_bits[source_index]) + else: + source = gate_values[source_index - boundary_count][row_index] + force_selected_mux(clauses, selector, mux_left, source) + for source_index, selector in enumerate(right_sel): + if source_index < boundary_count: + source = bool(boundary_bits[source_index]) + else: + source = gate_values[source_index - boundary_count][row_index] + force_selected_mux(clauses, selector, mux_right, source) + + for operation_index, table in enumerate(TABLES): + op_var = op_sel[operation_index] + for a in (0, 1): + for b in (0, 1): + value = (table >> ((a << 1) | b)) & 1 + # op ∧ (L=a) ∧ (R=b) → (out=value) + clause = [ + -op_var, + mux_left if a == 0 else -mux_left, + mux_right if b == 0 else -mux_right, + out_var if value else -out_var, + ] + clauses.append(clause) + + output_source: list[list[int]] = [] + output_invert = pool.many(output_count) + total_sources = boundary_count + gate_count + for output_index in range(output_count): + selectors = pool.many(total_sources) + output_source.append(selectors) + exactly_one(clauses, selectors) + invert = output_invert[output_index] + for row_index, (boundary_bits, target_bits) in enumerate(examples): + target = target_bits[output_index] + for source_index, selector in enumerate(selectors): + if source_index < boundary_count: + source_const = boundary_bits[source_index] + required_inv = source_const ^ target + clauses.append( + [-selector, invert if required_inv else -invert] + ) + continue + source_var = gate_values[source_index - boundary_count][row_index] + # selector → (source XOR invert = target) + for source_value in (0, 1): + for invert_value in (0, 1): + if (source_value ^ invert_value) == target: + continue + clauses.append( + [ + -selector, + source_var if source_value == 0 else -source_var, + invert if invert_value == 0 else -invert, + ] + ) + + # In a topological order of an irredundant circuit, the last gate is an + # output. This removes pure trailing padding without excluding any solution. + last_source = boundary_count + gate_count - 1 + clauses.append( + [output_source[index][last_source] for index in range(output_count)] + ) + return Encoding( + clauses, + pool, + source_left, + source_right, + operations, + gate_values, + output_source, + output_invert, + boundary_count, + examples, + ) + + +def model_bool(model_set: set[int], variable: int) -> bool: + return variable in model_set + + +def selected(model_set: set[int], variables: Sequence[int]) -> int: + choices = [index for index, var in enumerate(variables) if var in model_set] + if len(choices) != 1: + raise AssertionError(f"not one-hot in model: {choices}") + return choices[0] + + +@dataclass(frozen=True) +class SynthGate: + out: str + op: str + left: object + right: object + + +def decode( + encoding: Encoding, + model: Sequence[int], + boundary_names: Sequence[str], + root_names: Sequence[str], +): + model_set = {literal for literal in model if literal > 0} + new_names = [f"q{index + 1}" for index in range(len(encoding.source_left))] + sources = list(boundary_names) + gates = [] + for gate_index in range(len(encoding.source_left)): + left_index = selected(model_set, encoding.source_left[gate_index]) + right_index = selected(model_set, encoding.source_right[gate_index]) + operation_index = selected(model_set, encoding.operations[gate_index]) + table = TABLES[operation_index] + op, left_inv, right_inv = TABLE_TO_GATE[table] + left_name = sources[left_index] + right_name = sources[right_index] + gates.append( + dws.Gate( + new_names[gate_index], + op, + dws.Token(left_name, left_inv), + dws.Token(right_name, right_inv), + ) + ) + sources.append(new_names[gate_index]) + replacements = {} + for output_index, root in enumerate(root_names): + source_index = selected(model_set, encoding.output_source[output_index]) + replacements[root] = dws.Token( + sources[source_index], + model_bool(model_set, encoding.output_invert[output_index]), + ) + return gates, replacements + + +def token_rewrite(token, replacements): + replacement = replacements.get(token.name) + if replacement is None: + return token + return dws.Token(replacement.name, token.inv ^ replacement.inv) + + +def splice( + circuit, + roots: Sequence[str], + new_gates, + replacements, +): + removed, descendants = dws.removable_closure(circuit, roots) + gate_index = {gate.out: index for index, gate in enumerate(circuit.gates)} + boundary = set() + gate_by_out, _ = dws.structural_maps(circuit) + for name in removed: + gate = gate_by_out[name] + for token in (gate.left, gate.right): + if token.name not in removed: + boundary.add(token.name) + insert_after = max( + (gate_index[name] for name in boundary if name in gate_index), + default=-1, + ) + combined = [] + inserted = False + for index, gate in enumerate(circuit.gates): + if not inserted and index > insert_after: + combined.extend(new_gates) + inserted = True + if gate.out in removed: + continue + left = token_rewrite(gate.left, replacements) + right = token_rewrite(gate.right, replacements) + if left.name in removed or right.name in removed: + raise AssertionError( + f"non-root removed gate still referenced: {gate.out} " + f"{left.text()} {right.text()}" + ) + combined.append(dws.Gate(gate.out, gate.op, left, right)) + if not inserted: + combined.extend(new_gates) + outputs = [token_rewrite(token, replacements) for token in circuit.outputs] + + # Exact dead-code elimination, then deterministic topological renumbering. + by_out = {gate.out: gate for gate in combined} + needed = set() + todo = [token.name for token in outputs] + while todo: + name = todo.pop() + gate = by_out.get(name) + if gate is None or name in needed: + continue + needed.add(name) + todo.extend((gate.left.name, gate.right.name)) + live = [gate for gate in combined if gate.out in needed] + + rename = {} + renumbered = [] + for index, gate in enumerate(live, start=1): + left = dws.Token(rename.get(gate.left.name, gate.left.name), gate.left.inv) + right = dws.Token(rename.get(gate.right.name, gate.right.name), gate.right.inv) + out = f"w{index}" + rename[gate.out] = out + renumbered.append(dws.Gate(out, gate.op, left, right)) + new_outputs = [ + dws.Token(rename.get(token.name, token.name), token.inv) for token in outputs + ] + return dws.Circuit(circuit.n_inputs, renumbered, new_outputs), removed + + +def serialize(circuit) -> str: + lines = [f"INPUTS {circuit.n_inputs}"] + for gate in circuit.gates: + lines.append( + f"{gate.out} = {gate.op} {gate.left.text()} {gate.right.text()}" + ) + lines.append("OUTPUTS " + " ".join(token.text() for token in circuit.outputs)) + return "\n".join(lines) + "\n" + + +def reachable_examples(circuit, values, roots, removed): + gate_by_out, _ = dws.structural_maps(circuit) + boundary = set() + for name in removed: + gate = gate_by_out[name] + for token in (gate.left, gate.right): + if token.name not in removed: + boundary.add(token.name) + gate_index = {gate.out: index for index, gate in enumerate(circuit.gates)} + boundary_names = sorted( + boundary, + key=lambda name: ( + 0 if name.startswith("x") else 1, + int(name[1:]), + ), + ) + relation = {} + for row in range(1 << circuit.n_inputs): + key = tuple((values[name] >> row) & 1 for name in boundary_names) + target = tuple((values[name] >> row) & 1 for name in roots) + prior = relation.setdefault(key, target) + if prior != target: + raise AssertionError("window roots are not functions of boundary") + return boundary_names, sorted(relation.items()) + + +def solve_with_timeout( + encoding: Encoding, + timeout_seconds: float, + solver_preference: Sequence[str], +): + last_error = None + solver = None + chosen = None + for name in solver_preference: + try: + solver = Solver(name=name, bootstrap_with=encoding.clauses) + chosen = name + break + except Exception as exc: + last_error = repr(exc) + if solver is None: + raise RuntimeError(f"no SAT solver available; last error {last_error}") + try: + try: + solver.configure({"seed": SEED}) + except Exception: + pass + timer = threading.Timer(timeout_seconds, solver.interrupt) + timer.daemon = True + timer.start() + started = time.monotonic() + try: + result = solver.solve_limited(expect_interrupt=True) + finally: + elapsed = time.monotonic() - started + timer.cancel() + model = solver.get_model() if result is True else None + stats = solver.accum_stats() + return result, model, chosen, elapsed, stats + finally: + solver.delete() + + +def exhaustive_compare(reference, candidate): + _, _, expected = dws.evaluate(reference) + _, _, actual = dws.evaluate(candidate) + failures = [] + for row in range(1 << reference.n_inputs): + for output_index, (left, right) in enumerate(zip(expected, actual)): + if ((left ^ right) >> row) & 1: + failures.append((row, output_index)) + return failures + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("netlist", type=Path) + parser.add_argument("--roots", nargs=2, required=True) + parser.add_argument("--gates", type=int, required=True) + parser.add_argument("--timeout", type=float, default=60.0) + parser.add_argument("--output-dir", type=Path, required=True) + args = parser.parse_args() + if args.timeout <= 0 or args.timeout > 540: + raise ValueError("local pilot timeout must be in (0, 540] seconds") + random.seed(SEED) + started_all = time.monotonic() + + circuit = dws.parse_circuit(args.netlist) + mask, values, original_outputs = dws.evaluate(circuit) + root_names = tuple(args.roots) + for root in root_names: + if root not in {gate.out for gate in circuit.gates}: + raise ValueError(f"unknown root {root}") + removed, descendants = dws.removable_closure(circuit, root_names) + boundary_names, examples = reachable_examples( + circuit, values, root_names, removed + ) + print( + json.dumps( + { + "event": "encoding", + "roots": root_names, + "removed_gates": len(removed), + "candidate_gates": args.gates, + "boundary": boundary_names, + "reachable_patterns": len(examples), + "seed": SEED, + } + ), + flush=True, + ) + encoding_started = time.monotonic() + encoding = encode_exact(examples, args.gates) + encoding_elapsed = time.monotonic() - encoding_started + print( + json.dumps( + { + "event": "cnf", + "variables": encoding.pool.top, + "clauses": len(encoding.clauses), + "encoding_seconds": encoding_elapsed, + } + ), + flush=True, + ) + result, model, solver_name, solve_elapsed, stats = solve_with_timeout( + encoding, + args.timeout, + ("cadical195", "cadical153", "glucose42", "minisat22"), + ) + + args.output_dir.mkdir(parents=True, exist_ok=True) + report = { + "netlist_sha256": hashlib.sha256(args.netlist.read_bytes()).hexdigest(), + "roots": list(root_names), + "removed_gates": sorted( + removed, key=lambda name: int(name[1:]) + ), + "removed_gate_count": len(removed), + "requested_gate_count": args.gates, + "boundary": boundary_names, + "reachable_patterns": len(examples), + "full_input_rows": 1 << circuit.n_inputs, + "full_output_bits_checked": (1 << circuit.n_inputs) + * len(circuit.outputs), + "seed": SEED, + "solver": solver_name, + "sat_result": result, + "variables": encoding.pool.top, + "clauses": len(encoding.clauses), + "encoding_seconds": encoding_elapsed, + "solve_seconds": solve_elapsed, + "solver_stats": stats, + } + + if result is True and model is not None: + new_gates, replacements = decode( + encoding, model, boundary_names, root_names + ) + candidate, removed_again = splice( + circuit, root_names, new_gates, replacements + ) + failures = exhaustive_compare(circuit, candidate) + candidate_path = args.output_dir / "mystery-D.candidate.txt" + candidate_path.write_text(serialize(candidate), encoding="utf-8") + report.update( + { + "status": "SAT_VALID" if not failures else "SAT_INVALID", + "candidate_gate_count": len(candidate.gates), + "gate_saving": len(circuit.gates) - len(candidate.gates), + "replacement_gates": [ + { + "out": gate.out, + "op": gate.op, + "left": gate.left.text(), + "right": gate.right.text(), + } + for gate in new_gates + ], + "replacement_outputs": { + root: token.text() for root, token in replacements.items() + }, + "exhaustive_failure_count": len(failures), + "first_failures": failures[:20], + "candidate_sha256": hashlib.sha256( + candidate_path.read_bytes() + ).hexdigest(), + "candidate_path": str(candidate_path), + } + ) + if failures: + raise AssertionError(f"candidate failed exhaustive audit: {failures[:5]}") + elif result is False: + report["status"] = "UNSAT" + report["completeness"] = ( + "No acyclic circuit with the requested number of arbitrary allowed " + "two-input gates (including same-source fanins), free inversions, " + "this fixed boundary, and these two roots realizes the full " + "reachable boundary relation." + ) + else: + report["status"] = "TIMEOUT" + + report["total_seconds"] = time.monotonic() - started_all + report_path = args.output_dir / "report.json" + report_path.write_text( + json.dumps(report, indent=2, sort_keys=True) + "\n", + encoding="utf-8", + ) + print(json.dumps(report, indent=2, sort_keys=True), flush=True) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/d_window_sat_hpc_remote.py b/tracks/qcs/solutions/Genshin_Impact-71/search/d_window_sat_hpc_remote.py new file mode 100644 index 000000000..a34c472bd --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/d_window_sat_hpc_remote.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +"""Large deterministic-conflict-budget launcher intended for t02 Slurm.""" + +from __future__ import annotations + +import importlib.util +import sys +import time +from pathlib import Path + +from pysat.solvers import Solver + + +BASE = Path(__file__).with_name("d_window_sat_remote.py") +spec = importlib.util.spec_from_file_location("d_window_sat_hpc_remote_impl", BASE) +assert spec and spec.loader +impl = importlib.util.module_from_spec(spec) +sys.modules[spec.name] = impl +spec.loader.exec_module(impl) + + +def solve_hpc(encoding, timeout_seconds, solver_preference): + """Interpret the CLI timeout number as millions of conflicts on HPC.""" + solver = None + chosen = None + for name in solver_preference: + try: + solver = Solver(name=name, bootstrap_with=encoding.clauses) + chosen = name + break + except Exception: + pass + if solver is None: + raise RuntimeError("no supported PySAT backend") + try: + try: + solver.configure({"seed": impl.SEED}) + except Exception: + pass + conflict_budget = max(1, int(timeout_seconds * 1_000_000)) + solver.conf_budget(conflict_budget) + started = time.monotonic() + result = solver.solve_limited(expect_interrupt=False) + elapsed = time.monotonic() - started + model = solver.get_model() if result is True else None + stats = dict(solver.accum_stats()) + stats["configured_conflict_budget"] = conflict_budget + return result, model, chosen, elapsed, stats + finally: + solver.delete() + + +impl.solve_with_timeout = solve_hpc +impl.main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/d_window_sat_hpc_safe.py b/tracks/qcs/solutions/Genshin_Impact-71/search/d_window_sat_hpc_safe.py new file mode 100644 index 000000000..e39a1d3de --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/d_window_sat_hpc_safe.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +"""Conflict-budget launcher for the cycle-safe two-root SAT implementation.""" + +from __future__ import annotations + +import importlib.util +import sys +import time +from pathlib import Path + +from pysat.solvers import Solver + + +BASE = Path(__file__).with_name("d_window_sat_safe_remote.py") +spec = importlib.util.spec_from_file_location("d_window_sat_safe_impl", BASE) +if spec is None or spec.loader is None: + raise RuntimeError(f"cannot load {BASE}") +impl = importlib.util.module_from_spec(spec) +sys.modules[spec.name] = impl +spec.loader.exec_module(impl) + + +def solve_hpc(encoding, timeout_seconds, solver_preference): + """Interpret the CLI timeout number as millions of conflicts.""" + solver = None + chosen = None + for name in solver_preference: + try: + solver = Solver(name=name, bootstrap_with=encoding.clauses) + chosen = name + break + except Exception: + pass + if solver is None: + raise RuntimeError("no supported PySAT backend") + try: + try: + solver.configure({"seed": impl.SEED}) + except Exception: + pass + conflict_budget = max(1, int(timeout_seconds * 1_000_000)) + solver.conf_budget(conflict_budget) + started = time.monotonic() + result = solver.solve_limited(expect_interrupt=False) + elapsed = time.monotonic() - started + model = solver.get_model() if result is True else None + stats = dict(solver.accum_stats()) + stats["configured_conflict_budget"] = conflict_budget + return result, model, chosen, elapsed, stats + finally: + solver.delete() + + +impl.solve_with_timeout = solve_hpc +impl.main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/d_window_sat_remote.py b/tracks/qcs/solutions/Genshin_Impact-71/search/d_window_sat_remote.py new file mode 100644 index 000000000..050ec75ab --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/d_window_sat_remote.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +"""Linux-portable entry point for the independently audited D-window SAT code. + +The canonical implementation is kept byte-for-byte in ``d_window_sat.py``. +This loader changes exactly its host-specific module locator before compiling +it, so both direct execution and import by the HPC budget wrapper use the +sibling ``window_search.py`` on t02. +""" + +from __future__ import annotations + +from pathlib import Path + + +SOURCE_PATH = Path(__file__).with_name("d_window_sat.py") +MODULE_PATH = Path(__file__).with_name("window_search.py") +source = SOURCE_PATH.read_text(encoding="utf-8") +windows_locator = ( + 'MODULE_PATH = Path(r"C:\\tmp\\occam71_d_window\\window_search.py")' +) +portable_locator = 'MODULE_PATH = Path(__file__).with_name("window_search.py")' +if source.count(windows_locator) != 1: + raise RuntimeError( + "audited d_window_sat.py no longer has exactly one expected " + "host-specific MODULE_PATH" + ) +source = source.replace(windows_locator, portable_locator) +exec(compile(source, str(SOURCE_PATH), "exec"), globals(), globals()) diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/d_window_sat_safe_remote.py b/tracks/qcs/solutions/Genshin_Impact-71/search/d_window_sat_safe_remote.py new file mode 100644 index 000000000..8788920bd --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/d_window_sat_safe_remote.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +"""Cycle-safe Linux entry point for the audited two-root SAT synthesiser. + +The canonical implementation remains ``d_window_sat.py``. This loader makes +only exact, count-checked source transformations: + +1. use the sibling generic parser on Linux; +2. exclude descendants of either target root from the divisor boundary; +3. topologically sort a spliced candidate before canonical renumbering; +4. name the candidate from the input instance rather than hard-coding D. + +Every expected source fragment is checked before compilation, so source drift +fails closed. +""" + +from __future__ import annotations + +from pathlib import Path + + +SOURCE_PATH = Path(__file__).with_name("d_window_sat.py") +source = SOURCE_PATH.read_text(encoding="utf-8") + + +def replace_exact(old: str, new: str, count: int = 1) -> None: + global source + actual = source.count(old) + if actual != count: + raise RuntimeError( + f"audited source drift for {old[:60]!r}: " + f"expected {count}, found {actual}" + ) + source = source.replace(old, new) + + +replace_exact( + "import hashlib\nimport importlib.util", + "import hashlib\nimport heapq\nimport importlib.util", +) +replace_exact( + 'MODULE_PATH = Path(r"C:\\tmp\\occam71_d_window\\window_search.py")', + 'MODULE_PATH = Path(__file__).with_name("window_search.py")', +) +replace_exact( + " if token.name not in removed:\n" + " boundary.add(token.name)", + " if (\n" + " token.name not in removed\n" + " and token.name not in descendants\n" + " ):\n" + " boundary.add(token.name)", + count=2, +) +replace_exact( + "def reachable_examples(circuit, values, roots, removed):", + "def reachable_examples(\n" + " circuit, values, roots, removed, descendants\n" + "):", +) +replace_exact( + " boundary_names, examples = reachable_examples(\n" + " circuit, values, root_names, removed\n" + " )", + " boundary_names, examples = reachable_examples(\n" + " circuit, values, root_names, removed, descendants\n" + " )", +) +replace_exact( + ' candidate_path = args.output_dir / "mystery-D.candidate.txt"', + " candidate_path = (\n" + ' args.output_dir / f"{args.netlist.stem}.candidate.txt"\n' + " )", +) + +old_live_order = """\ + live = [gate for gate in combined if gate.out in needed] + + rename = {} +""" +new_live_order = """\ + # Joint synthesis may use an independent signal that appeared later than + # one old root. Recompute a deterministic topological order. Descendants + # of either target were excluded from the divisor boundary above. + original_position = { + gate.out: index for index, gate in enumerate(combined) + } + live_by_out = { + gate.out: gate for gate in combined if gate.out in needed + } + indegree = {name: 0 for name in live_by_out} + children = {name: set() for name in live_by_out} + for name, gate in live_by_out.items(): + dependencies = { + token.name + for token in (gate.left, gate.right) + if token.name in live_by_out + } + indegree[name] = len(dependencies) + for dependency in dependencies: + children[dependency].add(name) + ready = [ + (original_position[name], name) + for name, degree in indegree.items() + if degree == 0 + ] + heapq.heapify(ready) + live = [] + while ready: + _, name = heapq.heappop(ready) + live.append(live_by_out[name]) + for child in sorted( + children[name], key=original_position.__getitem__ + ): + indegree[child] -= 1 + if indegree[child] == 0: + heapq.heappush( + ready, (original_position[child], child) + ) + if len(live) != len(live_by_out): + cyclic = sorted( + (name for name, degree in indegree.items() if degree), + key=original_position.__getitem__, + ) + raise ValueError( + "replacement introduces a combinational cycle: " + + ", ".join(cyclic[:12]) + ) + + rename = {} +""" +replace_exact(old_live_order, new_live_order) + +exec(compile(source, str(SOURCE_PATH), "exec"), globals(), globals()) diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/d_window_t02_v3.slurm b/tracks/qcs/solutions/Genshin_Impact-71/search/d_window_t02_v3.slurm new file mode 100644 index 000000000..5cc9e474f --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/d_window_t02_v3.slurm @@ -0,0 +1,58 @@ +#!/bin/bash +#SBATCH --job-name=occam71-dwin +#SBATCH --partition=home +#SBATCH --nodelist=n006 +#SBATCH --array=0-9 +#SBATCH --cpus-per-task=1 +#SBATCH --mem=16G +#SBATCH --time=04:00:00 +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/d-window-slurm/logs/%A_%a.out +#SBATCH --error=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/d-window-slurm/logs/%A_%a.err + +set -euo pipefail + +ROOT=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +SEARCH="$ROOT/tracks/qcs/solutions/Genshin_Impact-71/search" +INPUT="$ROOT/results/occam71/reference-355/mystery-D.txt" +OUT_ROOT="$ROOT/results/occam71/d-window-slurm" +DEFAULT_VENV="$ROOT/results/occam71/tools/pysat-venv" +PYSAT_VENV="${PYSAT_VENV:-$DEFAULT_VENV}" +PYTHON_BIN="$PYSAT_VENV/bin/python" + +mkdir -p "$OUT_ROOT/logs" +test -x "$PYTHON_BIN" +"$PYTHON_BIN" -c 'import pysat, pysat.solvers' + +CASES=( + "w87 w97 12" + "w101 w106 11" + "w110 w113 11" + "w50 w62 10" + "w57 w71 10" + "w67 w78 10" + "w35 w38 9" + "w89 w94 9" + "w66 w76 7" + "w109 w111 6" +) +read -r ROOT_A ROOT_B GATES <<< "${CASES[$SLURM_ARRAY_TASK_ID]}" +CASE_NAME="${ROOT_A}_${ROOT_B}_k${GATES}" +CASE_OUT="$OUT_ROOT/$CASE_NAME" +mkdir -p "$CASE_OUT" + +"$PYTHON_BIN" -u "$SEARCH/d_window_sat_hpc_remote.py" "$INPUT" \ + --roots "$ROOT_A" "$ROOT_B" \ + --gates "$GATES" \ + --timeout 50 \ + --output-dir "$CASE_OUT" + +test -s "$CASE_OUT/report.json" +CANDIDATE="$CASE_OUT/mystery-D.candidate.txt" +if [[ -s "$CANDIDATE" ]]; then + "$PYTHON_BIN" -u "$SEARCH/verify_candidate.py" \ + "$INPUT" "$CANDIDATE" \ + --report "$CASE_OUT/independent-full-domain-audit.json" + test -s "$CASE_OUT/independent-full-domain-audit.json" + touch "$CASE_OUT/CANDIDATE_AUDITED" +fi +touch "$CASE_OUT/COMPLETE" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/divisor_resynth.py b/tracks/qcs/solutions/Genshin_Impact-71/search/divisor_resynth.py new file mode 100644 index 000000000..905422f5b --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/divisor_resynth.py @@ -0,0 +1,964 @@ +#!/usr/bin/env python3 +"""Exact phase-aware local resynthesis with external divisors. + +Security boundary: +* Input netlists are untrusted data, accepted only by a small ASCII grammar. +* No input text is evaluated, imported, interpolated into a command, or run. +* The only synthesized operations are AND/XOR with free literal negation; + this represents all six challenge gates modulo free input/output phase. + +For a root, the atom set is every primary input and every topologically prior +wire outside the root's MFFC. The search is complete for all connected +straight-line programs of at most three binary gates: + + 1 gate: g1(atom, atom) + 2 gates: g1(atom, atom); g2(g1, atom) + 3 gates: chain g3(g2, atom), reuse g3(g2, g1), or independent + branches g3(g1, g2). + +Semantically redundant self-gates only produce a constant or a phase of their +input and are covered at lower cost. Candidate circuits are exhaustively +verified over the full input domain, serialized, strictly reparsed, and +verified again. +""" + +from __future__ import annotations + +import argparse +import dataclasses +import json +import re +import time +from collections import Counter +from pathlib import Path +from typing import Iterable, Iterator, Sequence + + +OPS = ("AND", "OR", "XOR", "NAND", "NOR", "XNOR") +OPS_SET = frozenset(OPS) +INPUT_RE = re.compile(r"INPUTS ([1-9][0-9]*)\Z") +GATE_RE = re.compile( + r"w([1-9][0-9]*) = (AND|OR|XOR|NAND|NOR|XNOR) " + r"(~?)([xw])([1-9][0-9]*) (~?)([xw])([1-9][0-9]*)\Z" +) +OUTPUT_RE = re.compile(r"OUTPUTS( ~?[xw][1-9][0-9]*)+\Z") +TOKEN_RE = re.compile(r"(~?)([xw])([1-9][0-9]*)\Z") + + +@dataclasses.dataclass(frozen=True, slots=True) +class Lit: + kind: str + index: int + inv: bool = False + + def toggled(self) -> "Lit": + return Lit(self.kind, self.index, not self.inv) + + def text(self) -> str: + return ("~" if self.inv else "") + self.kind + str(self.index) + + +@dataclasses.dataclass(frozen=True, slots=True) +class Gate: + op: str + a: Lit + b: Lit + + +@dataclasses.dataclass(frozen=True, slots=True) +class Circuit: + ninputs: int + gates: tuple[Gate, ...] + outputs: tuple[Lit, ...] + + +@dataclasses.dataclass(frozen=True, slots=True) +class Ref: + kind: str # "base" or "local" + index: int # zero based + inv: bool = False + + def toggled(self) -> "Ref": + return Ref(self.kind, self.index, not self.inv) + + +@dataclasses.dataclass(frozen=True, slots=True) +class PGate: + op: str # canonical synthesis basis: AND or XOR + a: Ref + b: Ref + + +@dataclasses.dataclass(frozen=True, slots=True) +class Program: + # `out` denotes exactly the canonical phase representative `func`. + func: int + gates: tuple[PGate, ...] + out: Ref + + @property + def cost(self) -> int: + return len(self.gates) + + +class DeadlineExceeded(RuntimeError): + pass + + +def parse_lit(token: str) -> Lit: + match = TOKEN_RE.fullmatch(token) + if match is None: + raise ValueError(f"invalid literal token: {token!r}") + inv, kind, index = match.groups() + return Lit(kind, int(index), bool(inv)) + + +def validate_lit(lit: Lit, ninputs: int, max_wire: int, context: str) -> None: + if lit.kind == "x": + if not 1 <= lit.index <= ninputs: + raise ValueError(f"{context}: input out of range: {lit.text()}") + elif lit.kind == "w": + if not 1 <= lit.index <= max_wire: + raise ValueError( + f"{context}: non-prior wire {lit.text()} (max w{max_wire})" + ) + else: + raise ValueError(f"{context}: invalid literal kind") + + +def parse_circuit(path: Path) -> Circuit: + raw = path.read_bytes() + if len(raw) > 2_000_000: + raise ValueError("netlist exceeds strict 2 MB limit") + try: + text = raw.decode("ascii") + except UnicodeDecodeError as exc: + raise ValueError("netlist must be ASCII") from exc + lines = text.splitlines() + if not lines or any(len(line) > 4096 for line in lines): + raise ValueError("empty netlist or overlong line") + first = INPUT_RE.fullmatch(lines[0]) + if first is None: + raise ValueError("invalid INPUTS line") + ninputs = int(first.group(1)) + if not 1 <= ninputs <= 20: + raise ValueError("strict evaluator accepts 1..20 inputs") + if len(lines) < 2 or OUTPUT_RE.fullmatch(lines[-1]) is None: + raise ValueError("invalid or missing OUTPUTS line") + gates: list[Gate] = [] + for lineno, line in enumerate(lines[1:-1], start=2): + match = GATE_RE.fullmatch(line) + if match is None: + raise ValueError(f"line {lineno}: invalid gate assignment") + wi, op, ia, ka, xa, ib, kb, xb = match.groups() + expected = len(gates) + 1 + if int(wi) != expected: + raise ValueError( + f"line {lineno}: expected contiguous w{expected}, got w{wi}" + ) + a = Lit(ka, int(xa), bool(ia)) + b = Lit(kb, int(xb), bool(ib)) + validate_lit(a, ninputs, expected - 1, f"line {lineno}") + validate_lit(b, ninputs, expected - 1, f"line {lineno}") + gates.append(Gate(op, a, b)) + outputs = tuple(parse_lit(tok) for tok in lines[-1].split()[1:]) + if not outputs: + raise ValueError("at least one output is required") + for output in outputs: + validate_lit(output, ninputs, len(gates), "OUTPUTS") + return Circuit(ninputs, tuple(gates), outputs) + + +def serialize(circuit: Circuit) -> str: + lines = [f"INPUTS {circuit.ninputs}"] + for index, gate in enumerate(circuit.gates, start=1): + if gate.op not in OPS_SET: + raise AssertionError("invalid internal operation") + validate_lit(gate.a, circuit.ninputs, index - 1, f"w{index}") + validate_lit(gate.b, circuit.ninputs, index - 1, f"w{index}") + lines.append( + f"w{index} = {gate.op} {gate.a.text()} {gate.b.text()}" + ) + for output in circuit.outputs: + validate_lit(output, circuit.ninputs, len(circuit.gates), "OUTPUTS") + lines.append("OUTPUTS " + " ".join(x.text() for x in circuit.outputs)) + return "\n".join(lines) + "\n" + + +def input_truth_tables(ninputs: int) -> tuple[list[int], int]: + rows = 1 << ninputs + mask = (1 << rows) - 1 + values: list[int] = [] + for bit in range(ninputs): + half = 1 << bit + period = half << 1 + block = (1 << half) - 1 + value = 0 + for start in range(half, rows, period): + value |= block << start + values.append(value) + return values, mask + + +def apply_op(op: str, a: int, b: int, mask: int) -> int: + if op == "AND": + return a & b + if op == "OR": + return a | b + if op == "XOR": + return a ^ b + if op == "NAND": + return mask ^ (a & b) + if op == "NOR": + return mask ^ (a | b) + if op == "XNOR": + return mask ^ (a ^ b) + raise AssertionError(f"invalid operation {op!r}") + + +def literal_value( + lit: Lit, inputs: Sequence[int], wires: Sequence[int], mask: int +) -> int: + value = inputs[lit.index - 1] if lit.kind == "x" else wires[lit.index - 1] + return mask ^ value if lit.inv else value + + +def evaluate(circuit: Circuit) -> tuple[tuple[int, ...], tuple[int, ...], int]: + inputs, mask = input_truth_tables(circuit.ninputs) + wires: list[int] = [] + for gate in circuit.gates: + a = literal_value(gate.a, inputs, wires, mask) + b = literal_value(gate.b, inputs, wires, mask) + wires.append(apply_op(gate.op, a, b, mask)) + outputs = tuple( + literal_value(output, inputs, wires, mask) + for output in circuit.outputs + ) + return tuple(wires), outputs, mask + + +def live_wires(circuit: Circuit) -> set[int]: + live: set[int] = set() + stack = [x.index for x in circuit.outputs if x.kind == "w"] + while stack: + wire = stack.pop() + if wire in live: + continue + live.add(wire) + gate = circuit.gates[wire - 1] + if gate.a.kind == "w": + stack.append(gate.a.index) + if gate.b.kind == "w": + stack.append(gate.b.index) + return live + + +def prune_and_compact(circuit: Circuit) -> Circuit: + live = live_wires(circuit) + mapping: dict[int, int] = {} + gates: list[Gate] = [] + + def remap(lit: Lit) -> Lit: + if lit.kind == "x": + return lit + return Lit("w", mapping[lit.index], lit.inv) + + for old, gate in enumerate(circuit.gates, start=1): + if old not in live: + continue + mapping[old] = len(gates) + 1 + gates.append(Gate(gate.op, remap(gate.a), remap(gate.b))) + return Circuit( + circuit.ninputs, + tuple(gates), + tuple(remap(output) for output in circuit.outputs), + ) + + +def canonical(value: int, mask: int) -> tuple[int, bool]: + complement = mask ^ value + if complement < value: + return complement, True + return value, False + + +def fanout_counts(circuit: Circuit) -> list[int]: + counts = [0] * len(circuit.gates) + for gate in circuit.gates: + for lit in (gate.a, gate.b): + if lit.kind == "w": + counts[lit.index - 1] += 1 + for output in circuit.outputs: + if output.kind == "w": + counts[output.index - 1] += 1 + return counts + + +def mffc(circuit: Circuit, root: int, counts: Sequence[int]) -> set[int]: + remaining = list(counts) + removed: set[int] = set() + stack = [root] + while stack: + wire = stack.pop() + if wire in removed: + continue + removed.add(wire) + gate = circuit.gates[wire - 1] + for lit in (gate.a, gate.b): + if lit.kind != "w": + continue + remaining[lit.index - 1] -= 1 + if remaining[lit.index - 1] == 0: + stack.append(lit.index) + return removed + + +def shift_ref(ref: Ref, amount: int) -> Ref: + if ref.kind == "base": + return ref + if ref.kind != "local": + raise AssertionError("invalid program reference") + return Ref("local", ref.index + amount, ref.inv) + + +def shift_gate(gate: PGate, amount: int) -> PGate: + return PGate(gate.op, shift_ref(gate.a, amount), shift_ref(gate.b, amount)) + + +def matching_variant( + left: int, right: int, target: int, mask: int +) -> tuple[str, bool, bool, bool] | None: + """Return op, input phases, and output phase for target's phase class.""" + seen: set[int] = set() + for li in (False, True): + a = mask ^ left if li else left + for ri in (False, True): + b = mask ^ right if ri else right + for op in ("AND", "XOR"): + raw = apply_op(op, a, b, mask) + func, oi = canonical(raw, mask) + if func in seen: + continue + seen.add(func) + if func == target: + return op, li, ri, oi + return None + + +def merge_programs( + left: Program, + right: Program, + target: int, + mask: int, +) -> Program: + variant = matching_variant(left.func, right.func, target, mask) + if variant is None: + raise AssertionError("requested program merge does not match target") + op, li, ri, oi = variant + shift = left.cost + shifted_right_gates = tuple(shift_gate(x, shift) for x in right.gates) + right_out = shift_ref(right.out, shift) + prefix = left.gates + shifted_right_gates + gate = PGate( + op, + left.out.toggled() if li else left.out, + right_out.toggled() if ri else right_out, + ) + out = Ref("local", len(prefix), oi) + return Program(target, prefix + (gate,), out) + + +def append_existing( + prefix: Program, + other_func: int, + other_ref: Ref, + target: int, + mask: int, +) -> Program: + variant = matching_variant(prefix.func, other_func, target, mask) + if variant is None: + raise AssertionError("requested append does not match target") + op, li, ri, oi = variant + gate = PGate( + op, + prefix.out.toggled() if li else prefix.out, + other_ref.toggled() if ri else other_ref, + ) + return Program( + target, + prefix.gates + (gate,), + Ref("local", prefix.cost, oi), + ) + + +def pair_variants( + left: Program, right: Program, mask: int +) -> Iterator[Program]: + """Yield one witness for every output phase class of this pair.""" + seen: set[int] = set() + for li in (False, True): + a = mask ^ left.func if li else left.func + for ri in (False, True): + b = mask ^ right.func if ri else right.func + for op in ("AND", "XOR"): + raw = apply_op(op, a, b, mask) + func, oi = canonical(raw, mask) + if func in seen: + continue + seen.add(func) + # pair_variants is used for atom/atom and prefix/atom, so the + # right program has cost zero and no local-index shift is needed. + if right.cost: + raise AssertionError("pair_variants right side must be an atom") + gate = PGate( + op, + left.out.toggled() if li else left.out, + right.out.toggled() if ri else right.out, + ) + yield Program( + func, + left.gates + (gate,), + Ref("local", left.cost, oi), + ) + + +class TargetPairIndex: + """Target-directed exact pair query; avoids quadratic branch enumeration.""" + + def __init__( + self, + rights: Sequence[Program], + target: int, + mask: int, + ) -> None: + self.target = target + self.mask = mask + self.by_func = {x.func: x for x in rights} + self.cover: list[list[tuple[int, Program]]] = [] + for desired in (target, mask ^ target): + candidates: list[tuple[int, Program]] = [] + for program in rights: + for inv in (False, True): + value = mask ^ program.func if inv else program.func + if value & desired == desired: + candidates.append((value, program)) + self.cover.append(candidates) + + def find(self, left: int) -> Program | None: + # XOR: phases only change the output phase, so the partner phase class + # is uniquely determined. + wanted, _ = canonical(left ^ self.target, self.mask) + right = self.by_func.get(wanted) + if right is not None: + if matching_variant(left, right.func, self.target, self.mask): + return right + raise AssertionError("XOR index invariant failed") + + # AND family: U & V must equal target or its complement. Filter V by + # the necessary cover relation and test the remaining disjointness. + for desired_index, desired in enumerate( + (self.target, self.mask ^ self.target) + ): + for left_inv in (False, True): + u = self.mask ^ left if left_inv else left + if u & desired != desired: + continue + for v, candidate in self.cover[desired_index]: + if u & v == desired: + if matching_variant( + left, candidate.func, self.target, self.mask + ): + return candidate + raise AssertionError("AND index invariant failed") + return None + + def cover_sizes(self) -> list[int]: + return [len(x) for x in self.cover] + + +def make_atoms( + circuit: Circuit, + root: int, + removed: set[int], + inputs: Sequence[int], + wires: Sequence[int], + mask: int, +) -> tuple[list[Lit], list[Program]]: + physical: list[Lit] = [ + Lit("x", index) for index in range(1, circuit.ninputs + 1) + ] + physical.extend( + Lit("w", wire) + for wire in range(1, root) + if wire not in removed + ) + base_lits: list[Lit] = [] + atoms_by_func: dict[int, Program] = {} + for lit in physical: + raw = literal_value(lit, inputs, wires, mask) + func, inv = canonical(raw, mask) + if func in atoms_by_func: + continue + base_index = len(base_lits) + base_lits.append(lit) + atoms_by_func[func] = Program( + func, (), Ref("base", base_index, inv) + ) + return base_lits, list(atoms_by_func.values()) + + +def make_layer1( + atoms: Sequence[Program], + target: int, + mask: int, + deadline: float, +) -> tuple[dict[int, Program], Program | None, dict[str, int]]: + atom_funcs = {x.func for x in atoms} + layer: dict[int, Program] = {} + stats = { + "atom_pairs": 0, + "semantic_variants": 0, + "dominated_variants": 0, + "unique_layer1_functions": 0, + } + hit: Program | None = None + for left_index, left in enumerate(atoms): + if time.monotonic() > deadline: + raise DeadlineExceeded("deadline while building layer 1") + for right in atoms[: left_index + 1]: + stats["atom_pairs"] += 1 + for program in pair_variants(left, right, mask): + stats["semantic_variants"] += 1 + if program.func == target and hit is None: + hit = program + if program.func in atom_funcs: + stats["dominated_variants"] += 1 + continue + layer.setdefault(program.func, program) + stats["unique_layer1_functions"] = len(layer) + return layer, hit, stats + + +def synthesize( + atoms: Sequence[Program], + target_value: int, + mask: int, + max_cost: int, + deadline: float, +) -> tuple[Program | None, dict[str, object]]: + target, _ = canonical(target_value, mask) + atom_by_func = {x.func: x for x in atoms} + stats: dict[str, object] = { + "atoms": len(atoms), + "max_cost": max_cost, + "target_popcount_canonical": target.bit_count(), + } + if target in atom_by_func: + stats["result_tier"] = 0 + return atom_by_func[target], stats + if max_cost < 1: + stats["result_tier"] = None + return None, stats + + layer1, cost1_hit, layer1_stats = make_layer1( + atoms, target, mask, deadline + ) + stats["layer1"] = layer1_stats + if cost1_hit is not None: + stats["result_tier"] = 1 + return cost1_hit, stats + if max_cost < 2: + stats["result_tier"] = None + return None, stats + + atom_target_index = TargetPairIndex(atoms, target, mask) + stats["atom_target_and_cover_sizes"] = atom_target_index.cover_sizes() + cost2_queries = 0 + for first in layer1.values(): + cost2_queries += 1 + partner = atom_target_index.find(first.func) + if partner is not None: + result = merge_programs(first, partner, target, mask) + if result.cost != 2: + raise AssertionError("cost-2 construction mismatch") + stats["cost2_target_queries"] = cost2_queries + stats["result_tier"] = 2 + return result, stats + stats["cost2_target_queries"] = cost2_queries + if max_cost < 3: + stats["result_tier"] = None + return None, stats + + # Independent-branch topology, target-directed exact meet-in-the-middle. + layer1_values = list(layer1.values()) + branch_index = TargetPairIndex(layer1_values, target, mask) + stats["branch_target_and_cover_sizes"] = branch_index.cover_sizes() + branch_queries = 0 + for left in layer1_values: + branch_queries += 1 + right = branch_index.find(left.func) + if right is None: + continue + if left.func == right.func: + # Sharing the identical semantic signal costs two gates total. + result = append_existing( + left, left.func, left.out, target, mask + ) + if result.cost > 2: + raise AssertionError("shared branch should cost at most two") + else: + result = merge_programs(left, right, target, mask) + stats["branch_target_queries"] = branch_queries + stats["result_tier"] = result.cost + return result, stats + stats["branch_target_queries"] = branch_queries + + # Chain and reconvergent-reuse topologies. Enumerate every semantic + # g2(g1, atom) state. Each state is queried against all atoms through the + # exact target index and against its own g1 directly. + layer2_pairs = 0 + layer2_variants = 0 + chain_queries = 0 + reuse_queries = 0 + for first in layer1_values: + first_ref = first.out + for atom in atoms: + layer2_pairs += 1 + if (layer2_pairs & 4095) == 0 and time.monotonic() > deadline: + raise DeadlineExceeded("deadline in exact cost-3 enumeration") + for second in pair_variants(first, atom, mask): + layer2_variants += 1 + # Defensive catch: the target index above should already have + # found every cost-2 target. + if second.func == target: + stats["unexpected_direct_cost2"] = True + stats["result_tier"] = 2 + return second, stats + + reuse_queries += 1 + if matching_variant( + second.func, first.func, target, mask + ) is not None: + result = append_existing( + second, first.func, first_ref, target, mask + ) + stats.update( + { + "layer2_pairs": layer2_pairs, + "layer2_semantic_variants": layer2_variants, + "chain_target_queries": chain_queries, + "reuse_target_queries": reuse_queries, + "result_tier": 3, + "result_topology": "reuse", + } + ) + return result, stats + + chain_queries += 1 + partner = atom_target_index.find(second.func) + if partner is not None: + result = merge_programs( + second, partner, target, mask + ) + stats.update( + { + "layer2_pairs": layer2_pairs, + "layer2_semantic_variants": layer2_variants, + "chain_target_queries": chain_queries, + "reuse_target_queries": reuse_queries, + "result_tier": 3, + "result_topology": "chain", + } + ) + return result, stats + stats.update( + { + "layer2_pairs": layer2_pairs, + "layer2_semantic_variants": layer2_variants, + "chain_target_queries": chain_queries, + "reuse_target_queries": reuse_queries, + "result_tier": None, + } + ) + return None, stats + + +def convert_ref(ref: Ref, bases: Sequence[Lit], root: int) -> Lit: + if ref.kind == "base": + base = bases[ref.index] + return Lit(base.kind, base.index, base.inv ^ ref.inv) + if ref.kind == "local": + return Lit("w", root + ref.index, ref.inv) + raise AssertionError("invalid program reference") + + +def insert_program( + circuit: Circuit, + root: int, + bases: Sequence[Lit], + program: Program, + target_value: int, + mask: int, +) -> Circuit: + target, target_complemented = canonical(target_value, mask) + if program.func != target: + raise AssertionError("program phase class does not match root") + + inserted: list[Gate] = [] + for local_index, gate in enumerate(program.gates): + a = convert_ref(gate.a, bases, root) + b = convert_ref(gate.b, bases, root) + validate_lit(a, circuit.ninputs, root + local_index - 1, "synthesis") + validate_lit(b, circuit.ninputs, root + local_index - 1, "synthesis") + inserted.append(Gate(gate.op, a, b)) + + replacement_ref = program.out.toggled() if target_complemented else program.out + replacement = convert_ref(replacement_ref, bases, root) + shift = program.cost - 1 + + def remap(lit: Lit) -> Lit: + if lit.kind == "x": + return lit + if lit.index < root: + return lit + if lit.index == root: + return Lit( + replacement.kind, + replacement.index, + lit.inv ^ replacement.inv, + ) + return Lit("w", lit.index + shift, lit.inv) + + prefix = list(circuit.gates[: root - 1]) + suffix = [ + Gate(gate.op, remap(gate.a), remap(gate.b)) + for gate in circuit.gates[root:] + ] + expanded = Circuit( + circuit.ninputs, + tuple(prefix + inserted + suffix), + tuple(remap(output) for output in circuit.outputs), + ) + return prune_and_compact(expanded) + + +def full_domain_equal(reference: Circuit, candidate: Circuit) -> dict[str, object]: + if reference.ninputs != candidate.ninputs: + raise AssertionError("input width mismatch") + _, left, _ = evaluate(reference) + _, right, _ = evaluate(candidate) + if len(left) != len(right): + raise AssertionError("output width mismatch") + equal = [a == b for a, b in zip(left, right)] + return { + "rows": 1 << reference.ninputs, + "outputs": len(left), + "equal_each_output": equal, + "exact": all(equal), + } + + +def search_round( + circuit: Circuit, + deadline: float, + root_filter: set[int] | None, +) -> tuple[Circuit, dict[str, object] | None, dict[str, object]]: + circuit = prune_and_compact(circuit) + inputs, mask = input_truth_tables(circuit.ninputs) + wires, baseline_outputs, _ = evaluate(circuit) + counts = fanout_counts(circuit) + records: list[dict[str, object]] = [] + best = circuit + best_info: dict[str, object] | None = None + mffc_hist: Counter[int] = Counter() + + for root, target_value in enumerate(wires, start=1): + removed = mffc(circuit, root, counts) + size = len(removed) + mffc_hist[size] += 1 + if root_filter is not None and root not in root_filter: + continue + max_cost = min(3, size - 1) + record: dict[str, object] = { + "root": root, + "mffc_size": size, + "max_cost": max_cost, + } + if max_cost < 0: + record["status"] = "impossible" + records.append(record) + continue + bases, atoms = make_atoms( + circuit, root, removed, inputs, wires, mask + ) + record["physical_divisors_before_phase_dedup"] = ( + circuit.ninputs + (root - 1) - len( + [wire for wire in removed if wire < root] + ) + ) + record["semantic_divisor_phase_classes"] = len(atoms) + record["excluded_mffc_wires"] = sorted( + wire for wire in removed if wire < root + ) + try: + program, synth_stats = synthesize( + atoms, target_value, mask, max_cost, deadline + ) + except DeadlineExceeded as exc: + record["status"] = "deadline" + record["error"] = str(exc) + records.append(record) + raise + record["synthesis"] = synth_stats + if program is None: + record["status"] = "exhausted_no_match" + records.append(record) + continue + candidate = insert_program( + circuit, root, bases, program, target_value, mask + ) + _, outputs, _ = evaluate(candidate) + if outputs != baseline_outputs: + raise AssertionError("candidate failed full-domain output verification") + saved = len(circuit.gates) - len(candidate.gates) + if saved < 1: + # This can occur only if the semantic match did not eliminate the + # expected MFFC due to a bookkeeping error, so fail closed. + raise AssertionError("matched program did not save a gate") + record.update( + { + "status": "verified_match", + "program_cost": program.cost, + "candidate_gates": len(candidate.gates), + "saved_gates": saved, + } + ) + records.append(record) + if len(candidate.gates) < len(best.gates): + best = candidate + best_info = { + "kind": "phase_aware_external_divisor_resynthesis", + "root": root, + "mffc_size": size, + "program_cost": program.cost, + "before_gates": len(circuit.gates), + "after_gates": len(candidate.gates), + "saved_gates": saved, + "semantic_divisors": len(atoms), + } + + return best, best_info, { + "gate_count": len(circuit.gates), + "mffc_histogram": dict(sorted(mffc_hist.items())), + "records": records, + } + + +def optimize( + circuit: Circuit, + deadline: float, + root_filter: set[int] | None, +) -> tuple[Circuit, list[dict[str, object]], list[dict[str, object]]]: + current = prune_and_compact(circuit) + steps: list[dict[str, object]] = [] + rounds: list[dict[str, object]] = [] + while True: + candidate, info, report = search_round( + current, deadline, root_filter + ) + rounds.append(report) + if info is None: + return current, steps, rounds + steps.append(info) + current = candidate + # Explicit root filters refer to the original numbering and therefore + # are intentionally single-round diagnostic runs. + if root_filter is not None: + return current, steps, rounds + + +def parse_roots(text: str | None) -> set[int] | None: + if text is None: + return None + roots: set[int] = set() + for token in text.split(","): + if not token or not token.isascii() or not token.isdigit(): + raise ValueError("--roots must be comma-separated positive integers") + value = int(token) + if value < 1: + raise ValueError("--roots must be positive") + roots.add(value) + return roots + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("inputs", nargs="+", type=Path) + parser.add_argument("--out-dir", required=True, type=Path) + parser.add_argument("--seed", type=int, default=42) + parser.add_argument("--deadline-seconds", type=int, default=540) + parser.add_argument("--roots") + args = parser.parse_args() + if args.seed != 42: + raise ValueError("deterministic search is preregistered to seed 42") + if not 1 <= args.deadline_seconds <= 86400: + raise ValueError("deadline must be in 1..86400 seconds") + root_filter = parse_roots(args.roots) + args.out_dir.mkdir(parents=True, exist_ok=True) + started = time.monotonic() + deadline = started + args.deadline_seconds + summaries: list[dict[str, object]] = [] + + for input_path in args.inputs: + reference = parse_circuit(input_path) + initial = prune_and_compact(reference) + try: + optimized, steps, rounds = optimize( + initial, deadline, root_filter + ) + status = "complete" + except DeadlineExceeded as exc: + optimized = initial + steps = [] + rounds = [] + status = f"deadline: {exc}" + verification = full_domain_equal(reference, optimized) + if not verification["exact"]: + raise AssertionError("optimized circuit is not exactly equivalent") + output_path = args.out_dir / input_path.name + output_path.write_text( + serialize(optimized), encoding="ascii", newline="\n" + ) + reparsed = parse_circuit(output_path) + recheck = full_domain_equal(reference, reparsed) + if not recheck["exact"]: + raise AssertionError("serialized circuit failed strict recheck") + summary = { + "name": input_path.name, + "seed": args.seed, + "status": status, + "root_filter": sorted(root_filter) if root_filter else None, + "original_gates": len(reference.gates), + "initial_live_gates": len(initial.gates), + "optimized_gates": len(optimized.gates), + "saved_gates": len(reference.gates) - len(optimized.gates), + "elapsed_seconds": time.monotonic() - started, + "steps": steps, + "rounds": rounds, + "verification": recheck, + "output_path": str(output_path), + } + summaries.append(summary) + print(json.dumps(summary, indent=2, sort_keys=True)) + + report_path = args.out_dir / "report.json" + report_path.write_text( + json.dumps(summaries, indent=2, sort_keys=True) + "\n", + encoding="utf-8", + newline="\n", + ) + print(f"REPORT {report_path}") + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/exact_mffc.py b/tracks/qcs/solutions/Genshin_Impact-71/search/exact_mffc.py new file mode 100644 index 000000000..f39e8c017 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/exact_mffc.py @@ -0,0 +1,598 @@ +#!/usr/bin/env python3 +"""Exact 1--3 gate MFFC replacement over strict Occam netlists. + +This imports only the independently written strict parser/evaluator in the same +directory. Competitor code and prose files are never imported or executed. +""" + +from __future__ import annotations + +import argparse +import dataclasses +import json +from collections import Counter +from pathlib import Path +from typing import Iterable + +from reduce_netlist import ( + Circuit, + Gate, + Lit, + apply_op, + evaluate, + full_domain_verify, + input_truth_tables, + live_wires, + parse_circuit, + prune_and_compact, + serialize, + validate_lit, +) + + +@dataclasses.dataclass(frozen=True, slots=True) +class SRef: + kind: str # "base" or "local" + index: int # zero based + inv: bool = False + + def toggled(self) -> "SRef": + return SRef(self.kind, self.index, not self.inv) + + +@dataclasses.dataclass(frozen=True, slots=True) +class SGate: + op: str # canonical exact-synthesis basis: AND or XOR + a: SRef + b: SRef + + +@dataclasses.dataclass(frozen=True, slots=True) +class Synth: + # `func` is represented exactly by `out`, including out.inv. + func: int + gates: tuple[SGate, ...] + out: SRef + # Canonical functions/references for gate-produced signals, in local order. + signals: tuple[tuple[int, SRef], ...] + + @property + def cost(self) -> int: + return len(self.gates) + + +def canonical(value: int, mask: int) -> tuple[int, bool]: + complement = mask ^ value + if complement < value: + return complement, True + return value, False + + +def shift_ref(ref: SRef, local_shift: int) -> SRef: + if ref.kind == "base": + return ref + if ref.kind != "local": + raise AssertionError(f"bad SRef kind: {ref.kind}") + return SRef("local", ref.index + local_shift, ref.inv) + + +def shift_gate(gate: SGate, local_shift: int) -> SGate: + return SGate( + gate.op, + shift_ref(gate.a, local_shift), + shift_ref(gate.b, local_shift), + ) + + +def synth_signature(synth: Synth) -> tuple[object, ...]: + return ( + synth.func, + tuple( + ( + gate.op, + gate.a.kind, + gate.a.index, + gate.a.inv, + gate.b.kind, + gate.b.index, + gate.b.inv, + ) + for gate in synth.gates + ), + synth.out.kind, + synth.out.index, + synth.out.inv, + ) + + +def combine_disjoint_variants( + left: Synth, right: Synth, mask: int +) -> Iterable[Synth]: + """Combine two disjoint synthesized DAGs with one AND/XOR gate.""" + + shift = left.cost + right_gates = tuple(shift_gate(gate, shift) for gate in right.gates) + right_out = shift_ref(right.out, shift) + right_signals = tuple( + (func, shift_ref(ref, shift)) for func, ref in right.signals + ) + prefix_gates = left.gates + right_gates + prefix_signals = left.signals + right_signals + seen_functions: set[int] = set() + for left_inv in (False, True): + left_ref = left.out.toggled() if left_inv else left.out + left_value = mask ^ left.func if left_inv else left.func + for right_inv in (False, True): + right_ref = right_out.toggled() if right_inv else right_out + right_value = mask ^ right.func if right_inv else right.func + for op in ("AND", "XOR"): + raw = apply_op(op, left_value, right_value, mask) + func, output_inverted = canonical(raw, mask) + if func in seen_functions: + continue + seen_functions.add(func) + local_index = len(prefix_gates) + raw_ref = SRef("local", local_index, False) + out_ref = raw_ref.toggled() if output_inverted else raw_ref + gate = SGate(op, left_ref, right_ref) + yield Synth( + func, + prefix_gates + (gate,), + out_ref, + prefix_signals + ((func, out_ref),), + ) + + +def extend_with_existing_variants( + prefix: Synth, + other_func: int, + other_ref: SRef, + mask: int, +) -> Iterable[Synth]: + """Append a gate combining prefix.out with an already present signal.""" + + seen_functions: set[int] = set() + for prefix_inv in (False, True): + prefix_ref = prefix.out.toggled() if prefix_inv else prefix.out + prefix_value = mask ^ prefix.func if prefix_inv else prefix.func + for other_inv in (False, True): + ref = other_ref.toggled() if other_inv else other_ref + other_value = mask ^ other_func if other_inv else other_func + for op in ("AND", "XOR"): + raw = apply_op(op, prefix_value, other_value, mask) + func, output_inverted = canonical(raw, mask) + if func in seen_functions: + continue + seen_functions.add(func) + local_index = prefix.cost + raw_ref = SRef("local", local_index, False) + out_ref = raw_ref.toggled() if output_inverted else raw_ref + gate = SGate(op, prefix_ref, ref) + yield Synth( + func, + prefix.gates + (gate,), + out_ref, + prefix.signals + ((func, out_ref),), + ) + + +def fanout_counts(circuit: Circuit) -> list[int]: + counts = [0] * len(circuit.gates) + for gate in circuit.gates: + for lit in (gate.a, gate.b): + if lit.kind == "w": + counts[lit.index - 1] += 1 + for lit in circuit.outputs: + if lit.kind == "w": + counts[lit.index - 1] += 1 + return counts + + +def mffc_and_cut( + circuit: Circuit, root: int, reference_counts: list[int] +) -> tuple[set[int], tuple[Lit, ...]]: + """Return exact MFFC nodes and its unique phase-free boundary signals.""" + + remaining = list(reference_counts) + removed: set[int] = set() + stack = [root] + while stack: + wire = stack.pop() + if wire in removed: + continue + removed.add(wire) + gate = circuit.gates[wire - 1] + for lit in (gate.a, gate.b): + if lit.kind != "w": + continue + remaining[lit.index - 1] -= 1 + if remaining[lit.index - 1] == 0: + stack.append(lit.index) + + leaves_set: set[tuple[str, int]] = set() + for wire in removed: + gate = circuit.gates[wire - 1] + for lit in (gate.a, gate.b): + if lit.kind == "x" or lit.index not in removed: + leaves_set.add((lit.kind, lit.index)) + leaves = tuple( + Lit(kind, index, False) + for kind, index in sorted( + leaves_set, key=lambda item: (item[0] != "x", item[1]) + ) + ) + return removed, leaves + + +def literal_value( + lit: Lit, + input_values: list[int], + wire_values: tuple[int, ...], + mask: int, +) -> int: + value = ( + input_values[lit.index - 1] + if lit.kind == "x" + else wire_values[lit.index - 1] + ) + return mask ^ value if lit.inv else value + + +def make_atoms( + leaves: tuple[Lit, ...], + input_values: list[int], + wire_values: tuple[int, ...], + mask: int, +) -> list[Synth]: + atoms_by_function: dict[int, Synth] = {} + for base_index, leaf in enumerate(leaves): + raw = literal_value(leaf, input_values, wire_values, mask) + func, inverted = canonical(raw, mask) + out = SRef("base", base_index, inverted) + atoms_by_function.setdefault(func, Synth(func, (), out, ())) + return list(atoms_by_function.values()) + + +def synthesize_up_to_three( + target_value: int, + leaves: tuple[Lit, ...], + input_values: list[int], + wire_values: tuple[int, ...], + mask: int, + max_cost: int, +) -> tuple[Synth | None, dict[str, int]]: + """Exhaustively synthesize every 1--3 gate SLP topology over `leaves`.""" + + target_func, _ = canonical(target_value, mask) + atoms = make_atoms(leaves, input_values, wire_values, mask) + atom_functions = {atom.func for atom in atoms} + stats = { + "atoms": len(atoms), + "cost1_functions": 0, + "cost2_states": 0, + "cost3_chain_checks": 0, + "cost3_reuse_checks": 0, + "cost3_branch_checks": 0, + } + if target_func in atom_functions: + # Normally caught by zero-gate global resubstitution, but retaining this + # makes the exact cut search logically complete. + atom = next(atom for atom in atoms if atom.func == target_func) + return atom, stats + if max_cost < 1: + return None, stats + + layer1: dict[int, Synth] = {} + for left_index, left in enumerate(atoms): + for right in atoms[: left_index + 1]: + for synth in combine_disjoint_variants(left, right, mask): + if synth.func in atom_functions: + continue + layer1.setdefault(synth.func, synth) + stats["cost1_functions"] = len(layer1) + if target_func in layer1: + return layer1[target_func], stats + if max_cost < 2: + return None, stats + + # A cost-2 SLP is necessarily g1=f(base,base), g2=f(g1,base). + # Preserve (g2 function, g1 function), which is the complete semantic state + # needed for the cost-3 topology that reuses g1. + layer2: dict[tuple[int, int], Synth] = {} + cheap_functions = atom_functions | set(layer1) + for first_func, first in layer1.items(): + for atom in atoms: + for synth in combine_disjoint_variants(first, atom, mask): + stats["cost2_states"] += 1 + if synth.func == target_func: + return synth, stats + if synth.func in cheap_functions: + continue + layer2.setdefault((synth.func, first_func), synth) + if max_cost < 3: + return None, stats + + # Topology 1: chain, g3=f(g2, base). + # Topology 2: reconvergent reuse, g3=f(g2, g1). + for (second_func, first_func), second in layer2.items(): + for atom in atoms: + for synth in combine_disjoint_variants(second, atom, mask): + stats["cost3_chain_checks"] += 1 + if synth.func == target_func: + return synth, stats + # In a chain-built cost-2 Synth the first local gate's canonical signal + # is stored first. Its reference may carry a free phase. + first_signal_func, first_signal_ref = second.signals[0] + if first_signal_func != first_func: + raise AssertionError("cost-2 semantic-state bookkeeping mismatch") + for synth in extend_with_existing_variants( + second, first_signal_func, first_signal_ref, mask + ): + stats["cost3_reuse_checks"] += 1 + if synth.func == target_func: + return synth, stats + + # Topology 3: independent branches, g1=f(base,base), + # g2=f(base,base), g3=f(g1,g2). + layer1_items = list(layer1.values()) + for left_index, left in enumerate(layer1_items): + for right in layer1_items[: left_index + 1]: + # Identical synthesized branches should be shared and therefore + # belong to a <=2 gate topology already covered above. + if synth_signature(left) == synth_signature(right): + continue + for synth in combine_disjoint_variants(left, right, mask): + stats["cost3_branch_checks"] += 1 + if synth.func == target_func: + return synth, stats + return None, stats + + +def convert_sref( + ref: SRef, leaves: tuple[Lit, ...], insertion_root: int +) -> Lit: + if ref.kind == "base": + leaf = leaves[ref.index] + return Lit(leaf.kind, leaf.index, leaf.inv ^ ref.inv) + if ref.kind == "local": + return Lit("w", insertion_root + ref.index, ref.inv) + raise AssertionError(f"bad SRef kind: {ref.kind}") + + +def insert_replacement( + circuit: Circuit, + root: int, + leaves: tuple[Lit, ...], + synth: Synth, + target_value: int, + mask: int, +) -> Circuit: + if synth.cost < 1: + raise ValueError("zero-cost synthesis uses the dedicated substitution path") + target_func, target_is_complement = canonical(target_value, mask) + if synth.func != target_func: + raise ValueError("synth function does not match root modulo phase") + + inserted: list[Gate] = [] + for local_index, sgate in enumerate(synth.gates): + if sgate.op not in ("AND", "XOR"): + raise AssertionError("unexpected synthesis op") + a = convert_sref(sgate.a, leaves, root) + b = convert_sref(sgate.b, leaves, root) + # At local gate j, only inserted wires w_root..w_(root+j-1) + # and old wires < root may be referenced. + validate_lit(a, circuit.ninputs, root + local_index - 1, "synthesis") + validate_lit(b, circuit.ninputs, root + local_index - 1, "synthesis") + inserted.append(Gate(sgate.op, a, b)) + + replacement_ref = convert_sref( + synth.out.toggled() if target_is_complement else synth.out, + leaves, + root, + ) + shift = synth.cost - 1 + + def remap_old_lit(lit: Lit) -> Lit: + if lit.kind == "x": + return lit + if lit.index < root: + return lit + if lit.index == root: + return Lit( + replacement_ref.kind, + replacement_ref.index, + lit.inv ^ replacement_ref.inv, + ) + return Lit("w", lit.index + shift, lit.inv) + + prefix = list(circuit.gates[: root - 1]) + suffix = [ + Gate(gate.op, remap_old_lit(gate.a), remap_old_lit(gate.b)) + for gate in circuit.gates[root:] + ] + outputs = tuple(remap_old_lit(lit) for lit in circuit.outputs) + expanded = Circuit( + circuit.ninputs, tuple(prefix + inserted + suffix), outputs + ) + return prune_and_compact(expanded) + + +def search_best_mffc_rewrite( + circuit: Circuit, + max_cut_leaves: int, +) -> tuple[Circuit, dict[str, object] | None, dict[str, object]]: + circuit = prune_and_compact(circuit) + wire_values, baseline_outputs, mask = evaluate(circuit) + input_values, _ = input_truth_tables(circuit.ninputs) + counts = fanout_counts(circuit) + best = circuit + best_info: dict[str, object] | None = None + root_records: list[dict[str, object]] = [] + mffc_hist: Counter[int] = Counter() + cut_hist: Counter[int] = Counter() + searched_roots = 0 + + for root, target_value in enumerate(wire_values, start=1): + removed, leaves = mffc_and_cut(circuit, root, counts) + mffc_size = len(removed) + cut_size = len(leaves) + mffc_hist[mffc_size] += 1 + cut_hist[cut_size] += 1 + max_cost = min(3, mffc_size - 1) + record: dict[str, object] = { + "root": root, + "mffc_size": mffc_size, + "cut_size": cut_size, + "max_cost": max_cost, + } + if max_cost < 1: + record["status"] = "cannot_save_with_positive_cost" + root_records.append(record) + continue + if cut_size > max_cut_leaves: + record["status"] = "cut_limit" + root_records.append(record) + continue + searched_roots += 1 + synth, synth_stats = synthesize_up_to_three( + target_value, + leaves, + input_values, + wire_values, + mask, + max_cost, + ) + record["synth_stats"] = synth_stats + if synth is None: + record["status"] = "exhausted_no_match" + root_records.append(record) + continue + if synth.cost == 0: + # The global zero-gate pass has already proven there are no such + # matches in these circuits. Keep this guard for logical clarity. + record["status"] = "zero_cost_match_deferred" + root_records.append(record) + continue + candidate = insert_replacement( + circuit, root, leaves, synth, target_value, mask + ) + _, candidate_outputs, _ = evaluate(candidate) + if candidate_outputs != baseline_outputs: + raise AssertionError("MFFC replacement failed full-domain verification") + saved = len(circuit.gates) - len(candidate.gates) + if saved < 1: + raise AssertionError("accepted MFFC synthesis did not save a gate") + record["status"] = "match" + record["synth_cost"] = synth.cost + record["saved_gates"] = saved + root_records.append(record) + if len(candidate.gates) < len(best.gates): + best = candidate + best_info = { + "kind": "exact_mffc_cut_replacement", + "root": f"w{root}", + "mffc_size": mffc_size, + "cut_leaves": [lit.text() for lit in leaves], + "synth_cost": synth.cost, + "before_gates": len(circuit.gates), + "after_gates": len(candidate.gates), + "saved_gates": saved, + } + + stats: dict[str, object] = { + "roots": len(circuit.gates), + "searched_roots": searched_roots, + "max_cut_leaves": max_cut_leaves, + "mffc_histogram": dict(sorted(mffc_hist.items())), + "cut_histogram": dict(sorted(cut_hist.items())), + "root_records": root_records, + } + return best, best_info, stats + + +def optimize_fixpoint( + circuit: Circuit, max_cut_leaves: int +) -> tuple[Circuit, list[dict[str, object]], list[dict[str, object]]]: + current = prune_and_compact(circuit) + steps: list[dict[str, object]] = [] + rounds: list[dict[str, object]] = [] + while True: + candidate, info, stats = search_best_mffc_rewrite( + current, max_cut_leaves + ) + rounds.append(stats) + if info is None: + return current, steps, rounds + steps.append(info) + current = candidate + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("inputs", nargs="+", type=Path) + parser.add_argument("--out-dir", required=True, type=Path) + parser.add_argument("--seed", type=int, default=42) + parser.add_argument("--max-cut-leaves", type=int, default=10) + args = parser.parse_args() + if args.seed != 42: + raise ValueError("this deterministic search is preregistered to root seed 42") + if not 2 <= args.max_cut_leaves <= 16: + raise ValueError("max cut leaves must be in 2..16") + args.out_dir.mkdir(parents=True, exist_ok=True) + summaries: list[dict[str, object]] = [] + for input_path in args.inputs: + reference = parse_circuit(input_path) + baseline = prune_and_compact(reference) + optimized, steps, rounds = optimize_fixpoint( + baseline, args.max_cut_leaves + ) + verification = full_domain_verify(reference, optimized) + if not verification["exact"]: + raise AssertionError("optimized circuit failed full-domain equivalence") + output_path = args.out_dir / input_path.name + output_path.write_text( + serialize(optimized), encoding="ascii", newline="\n" + ) + reparsed = parse_circuit(output_path) + second_verification = full_domain_verify(reference, reparsed) + if not second_verification["exact"]: + raise AssertionError("serialized circuit failed exact verification") + summaries.append( + { + "name": input_path.name, + "seed": args.seed, + "original_gates": len(reference.gates), + "initial_live_gates": len(baseline.gates), + "optimized_gates": len(optimized.gates), + "saved_gates": len(reference.gates) - len(optimized.gates), + "steps": steps, + "rounds": rounds, + "verification": second_verification, + "output_path": str(output_path), + } + ) + report_path = args.out_dir / "report.json" + report_path.write_text( + json.dumps(summaries, indent=2, sort_keys=True) + "\n", + encoding="utf-8", + newline="\n", + ) + # Keep console output concise; the exhaustive per-root audit is in report. + concise = [ + { + "name": item["name"], + "original_gates": item["original_gates"], + "optimized_gates": item["optimized_gates"], + "saved_gates": item["saved_gates"], + "steps": item["steps"], + "verification": item["verification"], + "output_path": item["output_path"], + "report_path": str(report_path), + } + for item in summaries + ] + print(json.dumps(concise, indent=2, sort_keys=True)) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/generate_mystery_predictions.py b/tracks/qcs/solutions/Genshin_Impact-71/search/generate_mystery_predictions.py new file mode 100644 index 000000000..68aebb16b --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/generate_mystery_predictions.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +"""Generate and commitment-check issue #71 mystery predictions.""" + +from __future__ import annotations + +import argparse +import hashlib +import json +from pathlib import Path + +from circuit import Circuit, prediction_bytes + + +EXPECTED_SHA256 = { + "A": "51e3f026def41778ecd0d7dcaee9f970b9937488e6716891932b73824c16d4c7", + "B": "e2c9d0e23ee36bfc0f12d7f39fdfe2ca5a8abe8eb194fec56500733694b75c28", + "C": "c7b37413844bf0b10ebad0010046469f500354a22cc2ba95cbe42709f8e8337d", + "D": "b445a717483303fa3c5d8a1f7abe81888b267b7c472121c9c464fa9766808580", +} + + +def sha256_file(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as stream: + for block in iter(lambda: stream.read(1 << 20), b""): + digest.update(block) + return digest.hexdigest() + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--datasets", type=Path, required=True) + parser.add_argument("--netlists", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + + records: dict[str, dict[str, object]] = {} + for instance, expected in EXPECTED_SHA256.items(): + name = f"mystery-{instance}" + netlist = args.netlists / f"{name}.txt" + test_inputs = args.datasets / name / "test_inputs.csv" + destination = args.output / name / "test_outputs.csv" + temporary = destination.with_suffix(".csv.tmp") + + circuit = Circuit.parse(netlist) + payload = prediction_bytes(circuit, test_inputs) + actual = hashlib.sha256(payload).hexdigest() + if actual != expected: + raise RuntimeError( + f"{name}: prediction commitment mismatch: {actual} != {expected}" + ) + + destination.parent.mkdir(parents=True, exist_ok=True) + temporary.write_bytes(payload) + temporary.replace(destination) + records[instance] = { + "gates": len(circuit.gates), + "netlist": str(netlist), + "netlist_sha256": sha256_file(netlist), + "prediction": str(destination), + "prediction_sha256": actual, + "official_commitment_sha256": expected, + "commitment_matches": True, + } + + manifest = { + "challenge": "QuantumBFS/quantum.harness issue 71", + "all_official_commitments_match": True, + "instances": records, + } + manifest_path = args.output / "prediction_manifest.json" + temporary_manifest = manifest_path.with_suffix(".json.tmp") + temporary_manifest.write_text( + json.dumps(manifest, indent=2, sort_keys=True) + "\n", + encoding="utf-8", + newline="\n", + ) + temporary_manifest.replace(manifest_path) + print(json.dumps(manifest, sort_keys=True)) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/README.md b/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/README.md new file mode 100644 index 000000000..0a319656c --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/README.md @@ -0,0 +1,49 @@ +# Issue #71: independent IP/MILP experiment arm + +This directory implements a genuine 0–1 integer-programming route. It does +not call the SAT encodings used by the other search arms. + +## Formulation + +For every synthesized gate, the model has one-hot binary variables for: + +- the left and right input literals (both phases of every available signal); +- one of AND, OR, XOR, NAND, NOR, and XNOR; +- the gate's Boolean value on every truth-table row. + +The product between a topology selector and an earlier unknown gate value is +linearized with the usual three binary McCormick inequalities. Conditional +truth-table inequalities impose the selected gate operation. The last gate is +constrained to the target on every supplied row. All operations are +commutative, so the model removes the port-swap symmetry. + +HiGHS status `Infeasible` is a proof that no circuit of the requested size can +match the supplied rows within this topology and divisor boundary. In the +local-window experiments, an infeasible deterministic subset is already a +valid obstruction to an exact full-domain replacement: every exact +replacement would also have to match that subset. A feasible subset solution +is never accepted directly; it is independently evaluated on the entire input +domain, spliced into the full circuit, and all circuit outputs are compared. + +## Reproduction + +The environment is locked to CPython 3.13 Linux wheels: + +- NumPy 2.3.2 +- SciPy 1.16.1 (bundled HiGHS MILP backend) + +Both wheel SHA256 hashes are in `requirements.lock`. On `t02-server`, submit: + +```bash +sbatch setup_scipy_home.sbatch +sbatch --dependency=afterok: run_ip_milp_home.sbatch +``` + +The canonical root seed is 42. The run writes an incremental JSON record after +every solved instance, a final `results.json`, source hashes, Slurm metadata, +and a `COMPLETE` sentinel. + +## Security boundary + +Reference netlists are parsed as strict inert ASCII data. No code, scripts, or +instructions from competing pull requests are imported or executed. diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/REPORT.md b/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/REPORT.md new file mode 100644 index 000000000..71e471f9b --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/REPORT.md @@ -0,0 +1,105 @@ +# Issue #71 IP/MILP experiment report + +## Outcome + +The independent 0–1 MILP arm is complete. It produced exact warmup minima +for four low-output cones and 13 completed local no-improvement proofs across +A–D. It found no new candidate circuit; five harder 4→3 windows reached their +solver limit and remain explicitly unresolved. + +## Exact formulation + +For every unknown gate, binary one-hot variables choose: + +- each input literal, including free phase; +- one operation from AND, OR, XOR, NAND, NOR, XNOR; +- the gate value on every supplied truth-table row. + +For an earlier unknown signal, the selected value is linearized as +`z = selector ∧ value` using `z ≤ selector`, `z ≤ value`, and +`z ≥ selector + value − 1` (with the corresponding complemented form). +Conditional linear inequalities impose all four rows of the chosen gate truth +table. The two commutative ports are ordered to remove a symmetry. This is a +MILP implemented through SciPy 1.16.1's bundled HiGHS backend, not the SAT +encoding used by the other experiment arm. + +## Warmup output cones + +All 16 assignments of `(x₀,x₁,y₀,y₁)` were constrained. + +| Function | Infeasible size | Feasible size | Independent audit | +|---|---:|---:|---| +| add bit 0 | 0 | 1 | full 16-row truth table | +| add bit 1 | 2 | 3 | full truth table + exhaustive 2-gate enumeration | +| multiply bit 0 | 0 | 1 | full 16-row truth table | +| multiply bit 1 | 2 | 3 | full truth table + exhaustive 2-gate enumeration | + +The independent enumeration imports neither SciPy nor the MILP source. It +checked 71,280 commutativity-reduced two-gate configurations for each of add +bit 1 and multiply bit 1; neither target was reachable. + +## A–D local exact synthesis + +Every test asks whether an MFFC can be replaced by one fewer gate while using +the same safe, earlier-signal divisor boundary. The 128-row primary batch uses +deterministic seed-42 witnesses. The separate C/D batch uses 64 rows and +selects the smallest distinct MFFCs. Infeasibility on a row subset is already +a valid obstruction to an exact full-domain replacement: an exact replacement +would necessarily satisfy every selected row. A feasible subset result would +only be accepted after full-domain root and complete-circuit output audits. + +| Circuit | Window | Attempt | Status | +|---|---|---:|---| +| A | w7 | 3→2 | proven infeasible | +| A | w12 | 3→2 | proven infeasible | +| A | w17 | 3→2 | proven infeasible | +| B | w49 | 4→3 | limit | +| B | w6 | 3→2 | proven infeasible | +| B | w10 | 3→2 | proven infeasible | +| C | w84 | 4→3 | limit | +| C | w102 | 4→3 | limit | +| C | w122 | 4→3 | limit | +| C | w39 | 2→1 | proven infeasible | +| C | w41 | 2→1 | proven infeasible | +| C | w46 | 2→1 | proven infeasible | +| D | w46 | 4→3 | limit | +| D | w34 | 3→2 | proven infeasible | +| D | w38 | 3→2 | proven infeasible | +| D | w11 | 2→1 | proven infeasible | +| D | w28 | 2→1 | proven infeasible | +| D | w30 | 2→1 | proven infeasible | + +Summary: 13 `PROVEN_INFEASIBLE`, 5 `LIMIT`, 0 feasible local replacements, +and therefore 0 candidate netlists from this arm. + +## Reproducibility + +- Root seed: 42. +- Environment setup job: 43025, completed with exit code 0. +- Primary job: 43026 on n002, 4 CPU / 24 GiB, completed with exit code 0 in + 48:39; result wall time 2917.893 s. +- Distinct C/D small-window job: 43205 on n002, 4 CPU / 16 GiB, completed with + exit code 0 in 10.736 s. +- An earlier submission, 43203, failed before execution because its Slurm log + parent directory did not yet exist. The directory was created and 43205 is + the successful, complete rerun. +- NumPy 2.3.2 wheel SHA256: + `938065908d1d869c7d75d8ec45f735a034771c6ea07088867f713d1cd3bbbe4f`. +- SciPy 1.16.1 wheel SHA256: + `fedc2cbd1baed37474b1924c331b97bdff611d762c196fac1a9b71e67b813b1b`. + +Primary artifact hashes: + +- `results.json`: + `e140e06abe40d08e3ffd932af32462999c2b1e635f446c415d030789e1f4ff78` +- `manifest.json`: + `cfefe83bbcfdddb6a831f4e5e7cf1607250a9c968b7a49e1d868236ab761e23a` +- independent enumeration: + `fd27f19a66f2b9604d71f9dc4b704074d3e37555e385d9a9a22e770b4950068c` +- C/D small-window results: + `d732ed19ad34f3535bbd29cfbe7157ae0ae1656cf4f11985346d0426a1156d88` + +## Security + +Competing pull-request code and prose were never executed. Reference +netlists were accepted only as strict inert ASCII data by a standalone parser. diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/exact_milp.py b/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/exact_milp.py new file mode 100644 index 000000000..6741938a8 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/exact_milp.py @@ -0,0 +1,413 @@ +"""Auditable 0-1 MILP for exact synthesis over a fixed Boolean sample set. + +The formulation is intentionally independent of the SAT search used elsewhere +for issue #71. Each synthesized gate has: + +* one-hot selectors for two (commutative) input literals; +* a one-hot selector for AND/OR/XOR/NAND/NOR/XNOR; +* a Boolean value on every supplied truth-table row. + +Free input/output phase is represented by offering both polarities of every +available signal. Products between topology selectors and earlier unknown +gate values are linearized with standard binary McCormick constraints. +""" + +from __future__ import annotations + +from dataclasses import dataclass +import time +from typing import Mapping, Sequence + +import numpy as np +from scipy.optimize import Bounds, LinearConstraint, milp +from scipy.sparse import coo_matrix + + +OPS = ("AND", "OR", "XOR", "NAND", "NOR", "XNOR") + + +def apply_op(op: str, left: np.ndarray, right: np.ndarray) -> np.ndarray: + if op == "AND": + return left & right + if op == "OR": + return left | right + if op == "XOR": + return left ^ right + if op == "NAND": + return ~(left & right) + if op == "NOR": + return ~(left | right) + if op == "XNOR": + return ~(left ^ right) + raise ValueError(op) + + +@dataclass(frozen=True) +class Literal: + name: str + negated: bool + dynamic_gate: int | None + + @property + def token(self) -> str: + return f"~{self.name}" if self.negated else self.name + + +@dataclass(frozen=True) +class SynthGate: + op: str + left: str + right: str + + +@dataclass +class MilpResult: + status: str + solver_status: int + message: str + wall_seconds: float + variable_count: int + constraint_count: int + nonzero_count: int + mip_node_count: int | None + mip_gap: float | None + gates: list[SynthGate] | None + + def as_dict(self) -> dict: + return { + "status": self.status, + "solver_status": self.solver_status, + "message": self.message, + "wall_seconds": self.wall_seconds, + "variable_count": self.variable_count, + "constraint_count": self.constraint_count, + "nonzero_count": self.nonzero_count, + "mip_node_count": self.mip_node_count, + "mip_gap": self.mip_gap, + "gates": None + if self.gates is None + else [ + {"op": gate.op, "left": gate.left, "right": gate.right} + for gate in self.gates + ], + } + + +class Model: + def __init__(self) -> None: + self.var_names: list[str] = [] + self.var_index: dict[str, int] = {} + self.rows: list[int] = [] + self.cols: list[int] = [] + self.data: list[float] = [] + self.lower: list[float] = [] + self.upper: list[float] = [] + + def var(self, name: str) -> int: + if name in self.var_index: + raise ValueError(f"duplicate variable {name}") + index = len(self.var_names) + self.var_names.append(name) + self.var_index[name] = index + return index + + def add( + self, + coefficients: Mapping[int, float], + lower: float = -np.inf, + upper: float = np.inf, + ) -> None: + row = len(self.lower) + for column, coefficient in coefficients.items(): + if coefficient: + self.rows.append(row) + self.cols.append(column) + self.data.append(float(coefficient)) + self.lower.append(float(lower)) + self.upper.append(float(upper)) + + def constraint(self) -> LinearConstraint: + matrix = coo_matrix( + (self.data, (self.rows, self.cols)), + shape=(len(self.lower), len(self.var_names)), + dtype=float, + ).tocsr() + return LinearConstraint( + matrix, np.asarray(self.lower), np.asarray(self.upper) + ) + + +def _truth(op: str, left: int, right: int) -> int: + if op == "AND": + return left & right + if op == "OR": + return left | right + if op == "XOR": + return left ^ right + if op == "NAND": + return 1 - (left & right) + if op == "NOR": + return 1 - (left | right) + if op == "XNOR": + return 1 - (left ^ right) + raise ValueError(op) + + +def zero_gate_literal( + static_signals: Mapping[str, Sequence[bool]], target: Sequence[bool] +) -> str | None: + target_array = np.asarray(target, dtype=bool) + for name, raw in static_signals.items(): + values = np.asarray(raw, dtype=bool) + if np.array_equal(values, target_array): + return name + if np.array_equal(~values, target_array): + return f"~{name}" + return None + + +def solve_exact( + static_signals: Mapping[str, Sequence[bool]], + target: Sequence[bool], + gate_count: int, + *, + time_limit: float = 600.0, + symmetry_breaking: bool = True, + solver_display: bool = False, +) -> MilpResult: + """Find a gate_count circuit, or prove the supplied rows infeasible.""" + if gate_count <= 0: + raise ValueError("gate_count must be positive") + names = list(static_signals) + if not names: + raise ValueError("at least one static signal is required") + arrays = {name: np.asarray(static_signals[name], dtype=bool) for name in names} + target_array = np.asarray(target, dtype=bool) + row_count = len(target_array) + if row_count == 0: + raise ValueError("empty sample set") + if any(len(values) != row_count for values in arrays.values()): + raise ValueError("signal and target row counts differ") + + model = Model() + selectors: dict[tuple[int, int, int], int] = {} + literals: dict[int, list[Literal]] = {} + op_vars: dict[tuple[int, int], int] = {} + value_vars: dict[tuple[int, int], int] = {} + port_vars: dict[tuple[int, int, int], int] = {} + product_vars: dict[tuple[int, int, int, int], int] = {} + + for gate in range(gate_count): + available = [ + Literal(name, negated, None) + for name in names + for negated in (False, True) + ] + available.extend( + Literal(f"g{earlier}", negated, earlier) + for earlier in range(gate) + for negated in (False, True) + ) + literals[gate] = available + for port in range(2): + for literal_index in range(len(available)): + selectors[(gate, port, literal_index)] = model.var( + f"sel_g{gate}_p{port}_l{literal_index}" + ) + model.add( + { + selectors[(gate, port, literal_index)]: 1 + for literal_index in range(len(available)) + }, + 1, + 1, + ) + if symmetry_breaking: + # Every allowed operation is commutative, so order literal indices. + coefficients: dict[int, float] = {} + for literal_index in range(len(available)): + coefficients[selectors[(gate, 0, literal_index)]] = literal_index + coefficients[selectors[(gate, 1, literal_index)]] = -literal_index + model.add(coefficients, upper=0) + for op_index in range(len(OPS)): + op_vars[(gate, op_index)] = model.var(f"op_g{gate}_{OPS[op_index]}") + model.add( + {op_vars[(gate, op_index)]: 1 for op_index in range(len(OPS))}, + 1, + 1, + ) + for row in range(row_count): + value_vars[(gate, row)] = model.var(f"value_g{gate}_r{row}") + for port in range(2): + port_vars[(gate, port, row)] = model.var( + f"port_g{gate}_p{port}_r{row}" + ) + + # Port-value multiplexers. + for gate in range(gate_count): + for port in range(2): + for row in range(row_count): + coefficients: dict[int, float] = { + port_vars[(gate, port, row)]: 1 + } + for literal_index, literal in enumerate(literals[gate]): + selector = selectors[(gate, port, literal_index)] + if literal.dynamic_gate is None: + value = bool(arrays[literal.name][row]) ^ literal.negated + if value: + coefficients[selector] = ( + coefficients.get(selector, 0) - 1 + ) + continue + product = model.var( + f"prod_g{gate}_p{port}_l{literal_index}_r{row}" + ) + product_vars[(gate, port, literal_index, row)] = product + coefficients[product] = coefficients.get(product, 0) - 1 + source = value_vars[(literal.dynamic_gate, row)] + # product = selector AND (source XOR literal.negated) + model.add({product: 1, selector: -1}, upper=0) + if not literal.negated: + model.add({product: 1, source: -1}, upper=0) + model.add( + {product: 1, selector: -1, source: -1}, lower=-1 + ) + else: + model.add({product: 1, source: 1}, upper=1) + model.add( + {product: 1, selector: -1, source: 1}, lower=0 + ) + model.add(coefficients, 0, 0) + + # Conditional truth-table constraints for the selected operation. + for gate in range(gate_count): + for row in range(row_count): + left_var = port_vars[(gate, 0, row)] + right_var = port_vars[(gate, 1, row)] + output_var = value_vars[(gate, row)] + for op_index, op in enumerate(OPS): + op_var = op_vars[(gate, op_index)] + for left in (0, 1): + for right in (0, 1): + # match(bit, variable) = constant + coefficient*variable. + left_constant, left_coefficient = ( + (0, 1) if left else (1, -1) + ) + right_constant, right_coefficient = ( + (0, 1) if right else (1, -1) + ) + constant = left_constant + right_constant + if _truth(op, left, right): + # output - op - match(left) - match(right) >= -2 + model.add( + { + output_var: 1, + op_var: -1, + left_var: -left_coefficient, + right_var: -right_coefficient, + }, + lower=-2 + constant, + ) + else: + # output + op + match(left) + match(right) <= 3 + model.add( + { + output_var: 1, + op_var: 1, + left_var: left_coefficient, + right_var: right_coefficient, + }, + upper=3 - constant, + ) + + # The last gate is the requested output; complementary output phases are + # already expressible by the complementary gate operations. + for row, target_value in enumerate(target_array): + model.add( + {value_vars[(gate_count - 1, row)]: 1}, + int(target_value), + int(target_value), + ) + + constraint = model.constraint() + variable_count = len(model.var_names) + started = time.monotonic() + result = milp( + c=np.zeros(variable_count), + integrality=np.ones(variable_count), + bounds=Bounds(np.zeros(variable_count), np.ones(variable_count)), + constraints=constraint, + options={ + "disp": solver_display, + "presolve": True, + "time_limit": float(time_limit), + "mip_rel_gap": 0.0, + "random_seed": 42, + }, + ) + elapsed = time.monotonic() - started + status_names = { + 0: "OPTIMAL_FEASIBLE", + 1: "LIMIT", + 2: "PROVEN_INFEASIBLE", + 3: "UNBOUNDED", + 4: "SOLVER_ERROR", + } + gates: list[SynthGate] | None = None + if result.x is not None and result.status == 0: + gates = [] + for gate in range(gate_count): + chosen_literals = [] + for port in range(2): + chosen = max( + range(len(literals[gate])), + key=lambda index: result.x[selectors[(gate, port, index)]], + ) + chosen_literals.append(literals[gate][chosen].token) + chosen_op = max( + range(len(OPS)), key=lambda index: result.x[op_vars[(gate, index)]] + ) + gates.append( + SynthGate(OPS[chosen_op], chosen_literals[0], chosen_literals[1]) + ) + return MilpResult( + status=status_names.get(result.status, f"UNKNOWN_{result.status}"), + solver_status=int(result.status), + message=str(result.message), + wall_seconds=elapsed, + variable_count=variable_count, + constraint_count=len(model.lower), + nonzero_count=len(model.data), + mip_node_count=( + None + if getattr(result, "mip_node_count", None) is None + else int(result.mip_node_count) + ), + mip_gap=( + None + if getattr(result, "mip_gap", None) is None + else float(result.mip_gap) + ), + gates=gates, + ) + + +def evaluate_synth( + gates: Sequence[SynthGate], static_signals: Mapping[str, Sequence[bool]] +) -> np.ndarray: + values = { + name: np.asarray(raw, dtype=bool).copy() + for name, raw in static_signals.items() + } + + def literal(token: str) -> np.ndarray: + negated = token.startswith("~") + name = token[1:] if negated else token + value = values[name] + return ~value if negated else value + + for index, gate in enumerate(gates): + values[f"g{index}"] = apply_op( + gate.op, literal(gate.left), literal(gate.right) + ) + return values[f"g{len(gates) - 1}"] diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/independent_warmup_audit_v2.py b/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/independent_warmup_audit_v2.py new file mode 100644 index 000000000..00737e7ce --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/independent_warmup_audit_v2.py @@ -0,0 +1,101 @@ +"""Independent exhaustive audit of the warmup MILP minimum-size claims. + +This file deliberately does not import SciPy or exact_milp.py. +""" + +from __future__ import annotations + +import argparse +import hashlib +import itertools +import json +from pathlib import Path + + +OPS = ("AND", "OR", "XOR", "NAND", "NOR", "XNOR") +MASK = (1 << 16) - 1 + + +def op(name: str, left: int, right: int) -> int: + if name == "AND": + return left & right + if name == "OR": + return left | right + if name == "XOR": + return left ^ right + if name == "NAND": + return MASK ^ (left & right) + if name == "NOR": + return MASK ^ (left | right) + if name == "XNOR": + return MASK ^ (left ^ right) + raise ValueError(name) + + +def table(bit: int) -> int: + return sum(((row >> bit) & 1) << row for row in range(16)) + + +def literal_tables(bases: list[int]) -> list[int]: + return [value for base in bases for value in (base, MASK ^ base)] + + +def reachable_with_two_gates(target: int, static_bases: list[int]) -> tuple[bool, int]: + static_literals = literal_tables(static_bases) + checked = 0 + for left_index, right_index in itertools.combinations_with_replacement( + range(len(static_literals)), 2 + ): + left, right = static_literals[left_index], static_literals[right_index] + for first_op in OPS: + first = op(first_op, left, right) + second_literals = static_literals + [first, MASK ^ first] + for second_left, second_right in itertools.combinations_with_replacement( + range(len(second_literals)), 2 + ): + for second_op in OPS: + checked += 1 + if op( + second_op, + second_literals[second_left], + second_literals[second_right], + ) == target: + return True, checked + return False, checked + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + x0, x1, y0, y1 = map(table, range(4)) + targets = { + "add_bit1": x1 ^ y1 ^ (x0 & y0), + "mul_bit1": (x1 & y0) ^ (x0 & y1), + } + records = {} + for name, target in targets.items(): + reachable, checked = reachable_with_two_gates( + target, [x0, x1, y0, y1] + ) + records[name] = { + "reachable_with_at_most_two_gates": reachable, + "complete_second_gate_configurations_checked": checked, + } + print(name, reachable, checked, flush=True) + source_hash = hashlib.sha256(Path(__file__).read_bytes()).hexdigest() + result = { + "schema": "issue71-independent-warmup-enumeration-v2", + "domain_rows": 16, + "basis": list(OPS), + "free_literal_phase": True, + "commutative_port_ordering": True, + "source_sha256": source_hash, + "records": records, + } + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(json.dumps(result, indent=2, sort_keys=True) + "\n") + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/requirements.lock b/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/requirements.lock new file mode 100644 index 000000000..57e944fff --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/requirements.lock @@ -0,0 +1,4 @@ +numpy==2.3.2 \ + --hash=sha256:938065908d1d869c7d75d8ec45f735a034771c6ea07088867f713d1cd3bbbe4f +scipy==1.16.1 \ + --hash=sha256:fedc2cbd1baed37474b1924c331b97bdff611d762c196fac1a9b71e67b813b1b diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/run_experiments.py b/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/run_experiments.py new file mode 100644 index 000000000..0c7558bb2 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/run_experiments.py @@ -0,0 +1,518 @@ +"""Run the issue #71 IP/MILP experiment arm and persist auditable artifacts.""" + +from __future__ import annotations + +import argparse +from dataclasses import dataclass +import hashlib +import json +import os +from pathlib import Path +import platform +import sys +import time + +import numpy as np +import scipy + +from exact_milp import MilpResult, SynthGate, evaluate_synth, solve_exact, zero_gate_literal + + +OPS = {"AND", "OR", "XOR", "NAND", "NOR", "XNOR"} + + +@dataclass(frozen=True) +class Gate: + output: str + op: str + left: str + right: str + + +@dataclass(frozen=True) +class Circuit: + ninputs: int + gates: tuple[Gate, ...] + outputs: tuple[str, ...] + + +def split_token(token: str) -> tuple[str, bool]: + negated = token.startswith("~") + base = token[1:] if negated else token + if not base or base[0] not in {"x", "w"} or not base[1:].isdigit(): + raise ValueError(f"invalid token {token!r}") + return base, negated + + +def parse_netlist(path: Path) -> Circuit: + """Parse netlist as inert ASCII data; never import or execute submissions.""" + ninputs = None + gates: list[Gate] = [] + outputs = None + defined: set[str] = set() + for line_number, raw in enumerate(path.read_text("ascii").splitlines(), 1): + line = raw.split("#", 1)[0].strip() + if not line: + continue + fields = line.split() + if fields[0] == "INPUTS": + if ninputs is not None or len(fields) != 2: + raise ValueError(f"{path}:{line_number}: bad INPUTS") + ninputs = int(fields[1]) + elif fields[0] == "OUTPUTS": + if outputs is not None or ninputs is None: + raise ValueError(f"{path}:{line_number}: bad OUTPUTS") + outputs = tuple(fields[1:]) + else: + if ninputs is None or outputs is not None or len(fields) != 5: + raise ValueError(f"{path}:{line_number}: bad gate") + output, equals, op, left, right = fields + if equals != "=" or op not in OPS or output in defined: + raise ValueError(f"{path}:{line_number}: invalid gate") + for token in (left, right): + base, _ = split_token(token) + if base.startswith("w") and base not in defined: + raise ValueError(f"{path}:{line_number}: forward reference") + if base.startswith("x") and not 1 <= int(base[1:]) <= ninputs: + raise ValueError(f"{path}:{line_number}: input range") + gates.append(Gate(output, op, left, right)) + defined.add(output) + if ninputs is None or outputs is None: + raise ValueError(f"{path}: incomplete") + return Circuit(ninputs, tuple(gates), outputs) + + +def apply_bits(op: str, left: int, right: int, mask: int) -> int: + if op == "AND": + return left & right + if op == "OR": + return left | right + if op == "XOR": + return left ^ right + if op == "NAND": + return mask ^ (left & right) + if op == "NOR": + return mask ^ (left | right) + if op == "XNOR": + return mask ^ (left ^ right) + raise ValueError(op) + + +def circuit_tables(circuit: Circuit) -> tuple[dict[str, int], tuple[int, ...], int]: + count = 1 << circuit.ninputs + mask = (1 << count) - 1 + values: dict[str, int] = {} + for input_index in range(circuit.ninputs): + table = 0 + for assignment in range(count): + table |= ((assignment >> input_index) & 1) << assignment + values[f"x{input_index + 1}"] = table + + def literal(token: str) -> int: + base, negated = split_token(token) + return values[base] ^ (mask if negated else 0) + + for gate in circuit.gates: + values[gate.output] = apply_bits( + gate.op, literal(gate.left), literal(gate.right), mask + ) + return ( + values, + tuple(literal(token) for token in circuit.outputs), + mask, + ) + + +def sha256(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as handle: + for chunk in iter(lambda: handle.read(1 << 20), b""): + digest.update(chunk) + return digest.hexdigest() + + +def evaluate_result( + result: MilpResult, + signals: dict[str, np.ndarray], + target: np.ndarray, +) -> bool | None: + if result.gates is None: + return None + return bool(np.array_equal(evaluate_synth(result.gates, signals), target)) + + +def warmup_experiments(output_dir: Path, time_limit: float) -> list[dict]: + rows = np.arange(16, dtype=np.uint16) + signals = { + "x0": ((rows >> 0) & 1).astype(bool), + "x1": ((rows >> 1) & 1).astype(bool), + "y0": ((rows >> 2) & 1).astype(bool), + "y1": ((rows >> 3) & 1).astype(bool), + } + targets = { + "add_bit0": signals["x0"] ^ signals["y0"], + "add_bit1": signals["x1"] ^ signals["y1"] ^ (signals["x0"] & signals["y0"]), + "mul_bit0": signals["x0"] & signals["y0"], + "mul_bit1": (signals["x1"] & signals["y0"]) + ^ (signals["x0"] & signals["y1"]), + } + plans = { + "add_bit0": (0, 1), + "add_bit1": (2, 3), + "mul_bit0": (0, 1), + "mul_bit1": (2, 3), + } + records: list[dict] = [] + for name, counts in plans.items(): + target = targets[name] + for gate_count in counts: + print(f"WARMUP name={name} gates={gate_count}", flush=True) + if gate_count == 0: + literal = zero_gate_literal(signals, target) + record = { + "kind": "warmup", + "name": name, + "gate_count": 0, + "status": ( + "OPTIMAL_FEASIBLE" if literal is not None else "PROVEN_INFEASIBLE" + ), + "zero_gate_literal": literal, + "rows": 16, + "independent_truth_audit": literal is not None, + } + else: + result = solve_exact( + signals, target, gate_count, time_limit=time_limit + ) + record = { + "kind": "warmup", + "name": name, + "gate_count": gate_count, + "rows": 16, + **result.as_dict(), + "independent_truth_audit": evaluate_result( + result, signals, target + ), + } + records.append(record) + print( + f"RESULT name={name} gates={gate_count} " + f"status={record['status']} audit={record['independent_truth_audit']}", + flush=True, + ) + (output_dir / "warmup_incremental.json").write_text( + json.dumps(records, indent=2, sort_keys=True) + "\n" + ) + return records + + +def structural_maps(circuit: Circuit) -> tuple[dict[str, Gate], dict[str, set[str]]]: + by_output = {gate.output: gate for gate in circuit.gates} + fanout: dict[str, set[str]] = {} + for gate in circuit.gates: + for token in (gate.left, gate.right): + base, _ = split_token(token) + fanout.setdefault(base, set()).add(gate.output) + for token in circuit.outputs: + base, _ = split_token(token) + fanout.setdefault(base, set()).add("$OUTPUT") + return by_output, fanout + + +def mffc(circuit: Circuit, root: str) -> set[str]: + by_output, fanout = structural_maps(circuit) + removed = {root} + changed = True + while changed: + changed = False + for wire in tuple(removed): + gate = by_output[wire] + for token in (gate.left, gate.right): + base, _ = split_token(token) + if base in by_output and base not in removed: + if fanout.get(base, set()) <= removed: + removed.add(base) + changed = True + return removed + + +def choose_windows(circuit: Circuit, limit: int) -> list[tuple[str, set[str]]]: + candidates = [] + for gate_index, gate in enumerate(circuit.gates): + removed = mffc(circuit, gate.output) + if 2 <= len(removed) <= 4: + # Prefer larger MFFCs, then earlier roots (fewer divisors). + candidates.append(((-len(removed), gate_index), gate.output, removed)) + candidates.sort(key=lambda item: item[0]) + return [(root, removed) for _, root, removed in candidates[:limit]] + + +def witness_assignments(ninputs: int, count: int, stage_index: int) -> np.ndarray: + domain = 1 << ninputs + if domain <= count: + return np.arange(domain, dtype=np.int64) + seed = np.random.SeedSequence([42, stage_index]) + rng = np.random.default_rng(seed) + chosen = rng.choice(domain, size=count, replace=False) + chosen.sort() + return chosen + + +def bits_from_table(table: int, assignments: np.ndarray) -> np.ndarray: + return np.fromiter( + ((table >> int(assignment)) & 1 for assignment in assignments), + dtype=bool, + count=len(assignments), + ) + + +def canonical_prune( + named_tables: list[tuple[str, int]], assignments: np.ndarray +) -> dict[str, np.ndarray]: + kept: dict[bytes, str] = {} + signals: dict[str, np.ndarray] = {} + for name, table in named_tables: + values = bits_from_table(table, assignments) + packed = np.packbits(values, bitorder="little").tobytes() + complement = np.packbits(~values, bitorder="little").tobytes() + key = min(packed, complement) + if key in kept: + continue + kept[key] = name + signals[name] = values + return signals + + +def remap_synth_gates( + synth: list[SynthGate], root: str, existing: set[str] +) -> tuple[list[Gate], dict[str, str]]: + max_wire = max( + (int(name[1:]) for name in existing if name.startswith("w")), default=0 + ) + mapping: dict[str, str] = {} + for index in range(len(synth)): + mapping[f"g{index}"] = ( + root if index == len(synth) - 1 else f"w{max_wire + index + 1}" + ) + + def token(raw: str) -> str: + negated = raw.startswith("~") + base = raw[1:] if negated else raw + mapped = mapping.get(base, base) + return f"~{mapped}" if negated else mapped + + gates = [ + Gate(mapping[f"g{index}"], gate.op, token(gate.left), token(gate.right)) + for index, gate in enumerate(synth) + ] + return gates, mapping + + +def splice(circuit: Circuit, root: str, removed: set[str], synth: list[SynthGate]) -> Circuit: + replacements, _ = remap_synth_gates( + synth, + root, + {gate.output for gate in circuit.gates}, + ) + rewritten: list[Gate] = [] + inserted = False + for gate in circuit.gates: + if gate.output == root: + rewritten.extend(replacements) + inserted = True + if gate.output not in removed: + rewritten.append(gate) + if not inserted: + raise ValueError(root) + return Circuit(circuit.ninputs, tuple(rewritten), circuit.outputs) + + +def write_netlist(circuit: Circuit, path: Path) -> None: + lines = [f"INPUTS {circuit.ninputs}"] + lines.extend( + f"{gate.output} = {gate.op} {gate.left} {gate.right}" + for gate in circuit.gates + ) + lines.append("OUTPUTS " + " ".join(circuit.outputs)) + path.write_text("\n".join(lines) + "\n", encoding="ascii") + + +def local_experiments( + reference_dir: Path, + output_dir: Path, + time_limit: float, + witness_count: int, + windows_per_circuit: int, +) -> list[dict]: + records: list[dict] = [] + for label_index, label in enumerate("ABCD"): + path = reference_dir / f"mystery-{label}.txt" + circuit = parse_netlist(path) + tables, reference_outputs, _ = circuit_tables(circuit) + gate_index = {gate.output: index for index, gate in enumerate(circuit.gates)} + assignments = witness_assignments( + circuit.ninputs, witness_count, label_index + ) + windows = choose_windows(circuit, windows_per_circuit) + print( + f"LOCAL label={label} gates={len(circuit.gates)} " + f"windows={len(windows)} witness_rows={len(assignments)}", + flush=True, + ) + for window_index, (root, removed) in enumerate(windows): + root_index = gate_index[root] + static_names = [f"x{i + 1}" for i in range(circuit.ninputs)] + static_names.extend( + gate.output + for gate in circuit.gates[:root_index] + if gate.output not in removed + ) + static = canonical_prune( + [(name, tables[name]) for name in static_names], assignments + ) + target = bits_from_table(tables[root], assignments) + gate_count = len(removed) - 1 + print( + f"WINDOW label={label} index={window_index} root={root} " + f"mffc={len(removed)} replacement={gate_count} " + f"divisors={len(static)}", + flush=True, + ) + result = solve_exact( + static, target, gate_count, time_limit=time_limit + ) + record = { + "kind": "local_window", + "label": label, + "window_index": window_index, + "root": root, + "removed": sorted(removed, key=lambda x: int(x[1:])), + "removed_gate_count": len(removed), + "replacement_gate_count": gate_count, + "witness_rows": len(assignments), + "witness_assignment_sha256": hashlib.sha256( + assignments.astype(" None: + parser = argparse.ArgumentParser() + parser.add_argument("--reference-dir", type=Path, required=True) + parser.add_argument("--output-dir", type=Path, required=True) + parser.add_argument("--time-limit", type=float, default=300.0) + parser.add_argument("--witness-count", type=int, default=128) + parser.add_argument("--windows-per-circuit", type=int, default=3) + parser.add_argument( + "--mode", choices=("warmup", "local", "all"), default="all" + ) + args = parser.parse_args() + args.output_dir.mkdir(parents=True, exist_ok=True) + started = time.time() + manifest = { + "schema": "issue71-ip-milp-v1", + "root_seed": 42, + "mode": args.mode, + "reference_dir": str(args.reference_dir), + "reference_sha256": { + label: sha256(args.reference_dir / f"mystery-{label}.txt") + for label in "ABCD" + }, + "python": sys.version, + "platform": platform.platform(), + "numpy": np.__version__, + "scipy": scipy.__version__, + "scipy_milp_backend": "HiGHS bundled with SciPy", + "environment": { + key: os.environ.get(key) + for key in ("SLURM_JOB_ID", "SLURM_ARRAY_TASK_ID", "HOSTNAME") + }, + "parameters": { + "time_limit": args.time_limit, + "witness_count": args.witness_count, + "windows_per_circuit": args.windows_per_circuit, + }, + } + manifest["source_sha256"] = { + path.name: sha256(path) + for path in (Path(__file__), Path(__file__).with_name("exact_milp.py")) + } + (args.output_dir / "manifest.json").write_text( + json.dumps(manifest, indent=2, sort_keys=True) + "\n" + ) + results: dict[str, list[dict]] = {} + if args.mode in {"warmup", "all"}: + results["warmup"] = warmup_experiments( + args.output_dir, args.time_limit + ) + if args.mode in {"local", "all"}: + results["local"] = local_experiments( + args.reference_dir, + args.output_dir, + args.time_limit, + args.witness_count, + args.windows_per_circuit, + ) + results["wall_seconds"] = time.time() - started + (args.output_dir / "results.json").write_text( + json.dumps(results, indent=2, sort_keys=True) + "\n" + ) + (args.output_dir / "COMPLETE").write_text( + f"IP_MILP_COMPLETE wall_seconds={results['wall_seconds']:.6f}\n" + ) + print( + f"COMPLETE output={args.output_dir} " + f"wall_seconds={results['wall_seconds']:.3f}", + flush=True, + ) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/run_ip_milp.sbatch b/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/run_ip_milp.sbatch new file mode 100644 index 000000000..4995ae473 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/run_ip_milp.sbatch @@ -0,0 +1,31 @@ +#!/bin/bash +#SBATCH --job-name=occam71-ip-milp +#SBATCH --partition=CPU +#SBATCH --nodes=1 +#SBATCH --ntasks=1 +#SBATCH --cpus-per-task=4 +#SBATCH --mem=24G +#SBATCH --time=02:00:00 +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/ip-milp/run-%j.log + +set -euo pipefail +ROOT=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +CODE="$ROOT/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp" +VENV="$ROOT/results/occam71/tools/scipy-milp-venv" +OUT="$ROOT/results/occam71/ip-milp/job-${SLURM_JOB_ID}" +REF="$ROOT/results/occam71/reference-355" +export OMP_NUM_THREADS="${SLURM_CPUS_PER_TASK}" +export OPENBLAS_NUM_THREADS="${SLURM_CPUS_PER_TASK}" +export MKL_NUM_THREADS="${SLURM_CPUS_PER_TASK}" +mkdir -p "$OUT" +sha256sum "$CODE/exact_milp.py" "$CODE/run_experiments.py" \ + "$CODE/requirements.lock" "$0" > "$OUT/source-sha256.txt" +printf 'job_id=%s\nnode=%s\nsubmitted_seed=42\n' \ + "$SLURM_JOB_ID" "$(hostname)" > "$OUT/submission.txt" +"$VENV/bin/python" -u "$CODE/run_experiments.py" \ + --reference-dir "$REF" \ + --output-dir "$OUT" \ + --mode all \ + --time-limit 300 \ + --witness-count 128 \ + --windows-per-circuit 3 diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/run_ip_milp_home.sbatch b/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/run_ip_milp_home.sbatch new file mode 100644 index 000000000..5c4dbf773 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/run_ip_milp_home.sbatch @@ -0,0 +1,31 @@ +#!/bin/bash +#SBATCH --job-name=occam71-ip-milp +#SBATCH --partition=home +#SBATCH --nodes=1 +#SBATCH --ntasks=1 +#SBATCH --cpus-per-task=4 +#SBATCH --mem=24G +#SBATCH --time=02:00:00 +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/ip-milp/run-%j.log + +set -euo pipefail +ROOT=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +CODE="$ROOT/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp" +VENV="$ROOT/results/occam71/tools/scipy-milp-venv" +OUT="$ROOT/results/occam71/ip-milp/job-${SLURM_JOB_ID}" +REF="$ROOT/results/occam71/reference-355" +export OMP_NUM_THREADS="${SLURM_CPUS_PER_TASK}" +export OPENBLAS_NUM_THREADS="${SLURM_CPUS_PER_TASK}" +export MKL_NUM_THREADS="${SLURM_CPUS_PER_TASK}" +mkdir -p "$OUT" +sha256sum "$CODE/exact_milp.py" "$CODE/run_experiments.py" \ + "$CODE/requirements.lock" "$0" > "$OUT/source-sha256.txt" +printf 'job_id=%s\nnode=%s\nsubmitted_seed=42\n' \ + "$SLURM_JOB_ID" "$(hostname)" > "$OUT/submission.txt" +"$VENV/bin/python" -u "$CODE/run_experiments.py" \ + --reference-dir "$REF" \ + --output-dir "$OUT" \ + --mode all \ + --time-limit 300 \ + --witness-count 128 \ + --windows-per-circuit 3 diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/run_ip_milp_normal.sbatch b/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/run_ip_milp_normal.sbatch new file mode 100644 index 000000000..3e161a737 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/run_ip_milp_normal.sbatch @@ -0,0 +1,31 @@ +#!/bin/bash +#SBATCH --job-name=occam71-ip-milp +#SBATCH --partition=normal +#SBATCH --nodes=1 +#SBATCH --ntasks=1 +#SBATCH --cpus-per-task=4 +#SBATCH --mem=24G +#SBATCH --time=02:00:00 +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/ip-milp/run-%j.log + +set -euo pipefail +ROOT=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +CODE="$ROOT/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp" +VENV="$ROOT/results/occam71/tools/scipy-milp-venv" +OUT="$ROOT/results/occam71/ip-milp/job-${SLURM_JOB_ID}" +REF="$ROOT/results/occam71/reference-355" +export OMP_NUM_THREADS="${SLURM_CPUS_PER_TASK}" +export OPENBLAS_NUM_THREADS="${SLURM_CPUS_PER_TASK}" +export MKL_NUM_THREADS="${SLURM_CPUS_PER_TASK}" +mkdir -p "$OUT" +sha256sum "$CODE/exact_milp.py" "$CODE/run_experiments.py" \ + "$CODE/requirements.lock" "$0" > "$OUT/source-sha256.txt" +printf 'job_id=%s\nnode=%s\nsubmitted_seed=42\n' \ + "$SLURM_JOB_ID" "$(hostname)" > "$OUT/submission.txt" +"$VENV/bin/python" -u "$CODE/run_experiments.py" \ + --reference-dir "$REF" \ + --output-dir "$OUT" \ + --mode all \ + --time-limit 300 \ + --witness-count 128 \ + --windows-per-circuit 3 diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/run_small_windows_cd.py b/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/run_small_windows_cd.py new file mode 100644 index 000000000..0db386ba7 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/run_small_windows_cd.py @@ -0,0 +1,73 @@ +"""Second MILP batch: deliberately easy, distinct C/D MFFCs only. + +The primary run ranks large MFFCs first. This complementary batch ranks +2- and 3-gate cones first so that the independent IP arm also obtains completed +proof statuses on C and D rather than spending every slot on a 4→3 timeout. +""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path +import time + +import run_experiments as experiment + + +def choose_small_cd( + circuit: experiment.Circuit, limit: int +) -> list[tuple[str, set[str]]]: + if len(circuit.gates) not in {113, 156}: + return [] + candidates = [] + for gate_index, gate in enumerate(circuit.gates): + removed = experiment.mffc(circuit, gate.output) + if 2 <= len(removed) <= 3: + candidates.append(((len(removed), gate_index), gate.output, removed)) + candidates.sort(key=lambda item: item[0]) + return [(root, removed) for _, root, removed in candidates[:limit]] + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--reference-dir", type=Path, required=True) + parser.add_argument("--output-dir", type=Path, required=True) + parser.add_argument("--time-limit", type=float, default=180) + parser.add_argument("--witness-count", type=int, default=64) + parser.add_argument("--windows-per-circuit", type=int, default=3) + args = parser.parse_args() + args.output_dir.mkdir(parents=True, exist_ok=True) + experiment.choose_windows = choose_small_cd + started = time.time() + records = experiment.local_experiments( + args.reference_dir, + args.output_dir, + args.time_limit, + args.witness_count, + args.windows_per_circuit, + ) + result = { + "schema": "issue71-ip-milp-small-cd-v1", + "root_seed": 42, + "selection": "C/D only; MFFC size ascending then root index", + "parameters": { + "time_limit": args.time_limit, + "witness_count": args.witness_count, + "windows_per_circuit": args.windows_per_circuit, + }, + "records": records, + "wall_seconds": time.time() - started, + } + (args.output_dir / "results.json").write_text( + json.dumps(result, indent=2, sort_keys=True) + "\n" + ) + (args.output_dir / "COMPLETE").write_text("IP_MILP_SMALL_CD_COMPLETE\n") + print( + f"COMPLETE output={args.output_dir} records={len(records)}", + flush=True, + ) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/run_small_windows_cd_home.sbatch b/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/run_small_windows_cd_home.sbatch new file mode 100644 index 000000000..35b0f4a14 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/run_small_windows_cd_home.sbatch @@ -0,0 +1,30 @@ +#!/bin/bash +#SBATCH --job-name=occ71-ip-smallcd +#SBATCH --partition=home +#SBATCH --nodes=1 +#SBATCH --ntasks=1 +#SBATCH --cpus-per-task=4 +#SBATCH --mem=16G +#SBATCH --time=01:00:00 +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/ip-milp-small-cd/run-%j.log + +set -euo pipefail +ROOT=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +CODE="$ROOT/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp" +VENV="$ROOT/results/occam71/tools/scipy-milp-venv" +OUT="$ROOT/results/occam71/ip-milp-small-cd/job-${SLURM_JOB_ID}" +REF="$ROOT/results/occam71/reference-355" +export OMP_NUM_THREADS="${SLURM_CPUS_PER_TASK}" +export OPENBLAS_NUM_THREADS="${SLURM_CPUS_PER_TASK}" +export MKL_NUM_THREADS="${SLURM_CPUS_PER_TASK}" +mkdir -p "$OUT" +sha256sum "$CODE/exact_milp.py" "$CODE/run_experiments.py" \ + "$CODE/run_small_windows_cd.py" "$0" > "$OUT/source-sha256.txt" +printf 'job_id=%s\nnode=%s\nsubmitted_seed=42\n' \ + "$SLURM_JOB_ID" "$(hostname)" > "$OUT/submission.txt" +"$VENV/bin/python" -u "$CODE/run_small_windows_cd.py" \ + --reference-dir "$REF" \ + --output-dir "$OUT" \ + --time-limit 180 \ + --witness-count 64 \ + --windows-per-circuit 3 diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/setup_scipy.sbatch b/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/setup_scipy.sbatch new file mode 100644 index 000000000..8b8bba93e --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/setup_scipy.sbatch @@ -0,0 +1,22 @@ +#!/bin/bash +#SBATCH --job-name=occam71-scipy +#SBATCH --partition=CPU +#SBATCH --nodes=1 +#SBATCH --ntasks=1 +#SBATCH --cpus-per-task=2 +#SBATCH --mem=8G +#SBATCH --time=00:15:00 +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/ip-milp/setup-%j.log + +set -euo pipefail +ROOT=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +VENV="$ROOT/results/occam71/tools/scipy-milp-venv" +mkdir -p "$ROOT/results/occam71/ip-milp" +python3 -m venv "$VENV" +"$VENV/bin/python" -m pip install --disable-pip-version-check \ + --require-hashes -r "$ROOT/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/requirements.lock" +"$VENV/bin/python" - <<'PY' +import numpy, scipy +from scipy.optimize import milp +print("SCIPY_MILP_READY", numpy.__version__, scipy.__version__, milp.__module__, flush=True) +PY diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/setup_scipy_home.sbatch b/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/setup_scipy_home.sbatch new file mode 100644 index 000000000..fc5ecb8cd --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/setup_scipy_home.sbatch @@ -0,0 +1,22 @@ +#!/bin/bash +#SBATCH --job-name=occam71-scipy +#SBATCH --partition=home +#SBATCH --nodes=1 +#SBATCH --ntasks=1 +#SBATCH --cpus-per-task=2 +#SBATCH --mem=8G +#SBATCH --time=00:15:00 +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/ip-milp/setup-%j.log + +set -euo pipefail +ROOT=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +VENV="$ROOT/results/occam71/tools/scipy-milp-venv" +mkdir -p "$ROOT/results/occam71/ip-milp" +python3 -m venv "$VENV" +"$VENV/bin/python" -m pip install --disable-pip-version-check \ + --require-hashes -r "$ROOT/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/requirements.lock" +"$VENV/bin/python" - <<'PY' +import numpy, scipy +from scipy.optimize import milp +print("SCIPY_MILP_READY", numpy.__version__, scipy.__version__, milp.__module__, flush=True) +PY diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/setup_scipy_normal.sbatch b/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/setup_scipy_normal.sbatch new file mode 100644 index 000000000..5f1ae1a6b --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/setup_scipy_normal.sbatch @@ -0,0 +1,22 @@ +#!/bin/bash +#SBATCH --job-name=occam71-scipy +#SBATCH --partition=normal +#SBATCH --nodes=1 +#SBATCH --ntasks=1 +#SBATCH --cpus-per-task=2 +#SBATCH --mem=8G +#SBATCH --time=00:15:00 +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/ip-milp/setup-%j.log + +set -euo pipefail +ROOT=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +VENV="$ROOT/results/occam71/tools/scipy-milp-venv" +mkdir -p "$ROOT/results/occam71/ip-milp" +python3 -m venv "$VENV" +"$VENV/bin/python" -m pip install --disable-pip-version-check \ + --require-hashes -r "$ROOT/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/requirements.lock" +"$VENV/bin/python" - <<'PY' +import numpy, scipy +from scipy.optimize import milp +print("SCIPY_MILP_READY", numpy.__version__, scipy.__version__, milp.__module__, flush=True) +PY diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/test_exact_milp.py b/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/test_exact_milp.py new file mode 100644 index 000000000..9a066e7fc --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/ip_milp/test_exact_milp.py @@ -0,0 +1,47 @@ +import unittest + +import numpy as np + +from exact_milp import evaluate_synth, solve_exact, zero_gate_literal + + +class ExactMilpTests(unittest.TestCase): + def setUp(self): + rows = np.arange(8) + self.signals = { + "a": ((rows >> 0) & 1).astype(bool), + "b": ((rows >> 1) & 1).astype(bool), + "c": ((rows >> 2) & 1).astype(bool), + } + + def test_zero_gate_literal(self): + self.assertEqual( + zero_gate_literal(self.signals, ~self.signals["b"]), "~b" + ) + self.assertIsNone( + zero_gate_literal( + self.signals, self.signals["a"] ^ self.signals["b"] + ) + ) + + def test_one_gate_and(self): + target = self.signals["a"] & self.signals["b"] + result = solve_exact(self.signals, target, 1, time_limit=30) + self.assertEqual(result.status, "OPTIMAL_FEASIBLE") + self.assertTrue( + np.array_equal(evaluate_synth(result.gates, self.signals), target) + ) + + def test_two_gate_infeasible_for_three_way_xor(self): + target = self.signals["a"] ^ self.signals["b"] ^ self.signals["c"] + one = solve_exact(self.signals, target, 1, time_limit=30) + self.assertEqual(one.status, "PROVEN_INFEASIBLE") + two = solve_exact(self.signals, target, 2, time_limit=30) + self.assertEqual(two.status, "OPTIMAL_FEASIBLE") + self.assertTrue( + np.array_equal(evaluate_synth(two.gates, self.signals), target) + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/make_frontier_manifest.py b/tracks/qcs/solutions/Genshin_Impact-71/search/make_frontier_manifest.py new file mode 100644 index 000000000..414b288d3 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/make_frontier_manifest.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 +"""Create a deterministic audited manifest for an Occam frontier directory.""" + +from __future__ import annotations + +import argparse +import hashlib +import json +from pathlib import Path + + +def sha256(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as stream: + for chunk in iter(lambda: stream.read(1 << 20), b""): + digest.update(chunk) + return digest.hexdigest() + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("frontier", type=Path) + parser.add_argument("--output", type=Path) + args = parser.parse_args() + + origins = { + "A": { + "source": "reference-355/mystery-A.txt", + "synthesis": "37-gate ripple-carry adder; independently audited", + }, + "B": { + "source": "reference-355/mystery-B.txt", + "synthesis": "49-gate absolute-difference circuit; independently audited", + }, + "C": { + "source": "eSLIM grid job 43027 task 5", + "synthesis": "relation-SAT local resynthesis, size 5, seed 42", + }, + "D": { + "source": "eSLIM pilot job 42633", + "synthesis": "relation-SAT local resynthesis, size 6, seed 42", + }, + } + instances: dict[str, object] = {} + total = 0 + total_rows = 0 + total_bits = 0 + for name in ("A", "B", "C", "D"): + netlist = args.frontier / "netlists" / f"mystery-{name}.txt" + audit_path = args.frontier / f"audit-{name}.json" + audit = json.loads(audit_path.read_text(encoding="utf-8")) + netlist_hash = sha256(netlist) + if audit["instance"] != name: + raise ValueError(f"audit instance mismatch for {name}") + if audit["candidate_sha256"] != netlist_hash: + raise ValueError(f"audit/netlist hash mismatch for {name}") + if audit["equivalent"] is not True or audit["mismatches"]: + raise ValueError(f"formula audit failed for {name}") + structure = audit["structure"] + for key in ( + "dead_gates", + "constant_gates", + "duplicate_gate_functions", + "complement_gate_functions", + ): + if structure[key]: + raise ValueError(f"{name} has nonempty structural issue {key}") + gates = int(audit["gates"]) + total += gates + total_rows += int(audit["assignments"]) + total_bits += int(audit["output_bits_checked"]) + instances[name] = { + **origins[name], + "formula": audit["formula"], + "inputs": int(audit["inputs"]), + "outputs": int(audit["outputs"]), + "gates": gates, + "assignments_exhaustively_checked": int(audit["assignments"]), + "output_bits_checked": int(audit["output_bits_checked"]), + "netlist_sha256": netlist_hash, + "formula_audit_sha256": sha256(audit_path), + } + + payload = { + "challenge": "QuantumBFS/quantum.harness issue 71", + "root_seed": 42, + "gate_basis": ["AND", "OR", "XOR", "NAND", "NOR", "XNOR"], + "inverters_free": True, + "instances": instances, + "total_gates": total, + "total_assignments_exhaustively_checked": total_rows, + "total_output_bits_checked": total_bits, + "all_formula_audits_exact": True, + "trust_boundary": ( + "Competitor material was never executed. Pure netlists were parsed " + "as strict ASCII data and all frontier circuits were exhaustively " + "checked directly against independently encoded arithmetic formulas." + ), + } + if total != 349: + raise ValueError(f"expected frontier total 349, got {total}") + output = args.output or args.frontier / "manifest.json" + rendered = json.dumps(payload, indent=2, sort_keys=True) + "\n" + temporary = output.with_suffix(output.suffix + ".tmp") + temporary.write_text(rendered, encoding="utf-8") + temporary.replace(output) + print( + json.dumps( + { + "manifest": str(output), + "manifest_sha256": sha256(output), + "total_gates": total, + "total_assignments": total_rows, + "total_output_bits_checked": total_bits, + }, + sort_keys=True, + ) + ) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/make_frontier_manifest_v2.py b/tracks/qcs/solutions/Genshin_Impact-71/search/make_frontier_manifest_v2.py new file mode 100644 index 000000000..5a245c52f --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/make_frontier_manifest_v2.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 +"""Create a deterministic audited manifest for an Occam frontier directory.""" + +from __future__ import annotations + +import argparse +import hashlib +import json +from pathlib import Path + + +def sha256(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as stream: + for chunk in iter(lambda: stream.read(1 << 20), b""): + digest.update(chunk) + return digest.hexdigest() + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("frontier", type=Path) + parser.add_argument("--output", type=Path) + parser.add_argument("--expected-total", type=int, required=True) + parser.add_argument("--c-source", required=True) + parser.add_argument("--c-synthesis", required=True) + args = parser.parse_args() + + origins = { + "A": { + "source": "reference-355/mystery-A.txt", + "synthesis": "37-gate ripple-carry adder; independently audited", + }, + "B": { + "source": "reference-355/mystery-B.txt", + "synthesis": "49-gate absolute-difference circuit; independently audited", + }, + "C": { + "source": args.c_source, + "synthesis": args.c_synthesis, + }, + "D": { + "source": "eSLIM pilot job 42633", + "synthesis": "relation-SAT local resynthesis, size 6, seed 42", + }, + } + instances: dict[str, object] = {} + total = 0 + total_rows = 0 + total_bits = 0 + for name in ("A", "B", "C", "D"): + netlist = args.frontier / "netlists" / f"mystery-{name}.txt" + audit_path = args.frontier / f"audit-{name}.json" + audit = json.loads(audit_path.read_text(encoding="utf-8")) + netlist_hash = sha256(netlist) + if audit["instance"] != name: + raise ValueError(f"audit instance mismatch for {name}") + if audit["candidate_sha256"] != netlist_hash: + raise ValueError(f"audit/netlist hash mismatch for {name}") + if audit["equivalent"] is not True or audit["mismatches"]: + raise ValueError(f"formula audit failed for {name}") + structure = audit["structure"] + for key in ( + "dead_gates", + "constant_gates", + "duplicate_gate_functions", + "complement_gate_functions", + ): + if structure[key]: + raise ValueError(f"{name} has nonempty structural issue {key}") + gates = int(audit["gates"]) + total += gates + total_rows += int(audit["assignments"]) + total_bits += int(audit["output_bits_checked"]) + instances[name] = { + **origins[name], + "formula": audit["formula"], + "inputs": int(audit["inputs"]), + "outputs": int(audit["outputs"]), + "gates": gates, + "assignments_exhaustively_checked": int(audit["assignments"]), + "output_bits_checked": int(audit["output_bits_checked"]), + "netlist_sha256": netlist_hash, + "formula_audit_sha256": sha256(audit_path), + } + + payload = { + "challenge": "QuantumBFS/quantum.harness issue 71", + "root_seed": 42, + "gate_basis": ["AND", "OR", "XOR", "NAND", "NOR", "XNOR"], + "inverters_free": True, + "instances": instances, + "total_gates": total, + "total_assignments_exhaustively_checked": total_rows, + "total_output_bits_checked": total_bits, + "all_formula_audits_exact": True, + "trust_boundary": ( + "Competitor material was never executed. Pure netlists were parsed " + "as strict ASCII data and all frontier circuits were exhaustively " + "checked directly against independently encoded arithmetic formulas." + ), + } + if total != args.expected_total: + raise ValueError( + f"expected frontier total {args.expected_total}, got {total}" + ) + output = args.output or args.frontier / "manifest.json" + rendered = json.dumps(payload, indent=2, sort_keys=True) + "\n" + temporary = output.with_suffix(output.suffix + ".tmp") + temporary.write_text(rendered, encoding="utf-8") + temporary.replace(output) + print( + json.dumps( + { + "manifest": str(output), + "manifest_sha256": sha256(output), + "total_gates": total, + "total_assignments": total_rows, + "total_output_bits_checked": total_bits, + }, + sort_keys=True, + ) + ) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/precheck_c154_window_safe_v3.sh b/tracks/qcs/solutions/Genshin_Impact-71/search/precheck_c154_window_safe_v3.sh new file mode 100644 index 000000000..a348e1238 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/precheck_c154_window_safe_v3.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +set -euo pipefail + +root=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +search_dir="$root/tracks/qcs/solutions/Genshin_Impact-71/search" +ranking="$root/results/occam71/c154-window-safe/c154_joint_window_ranking_safe_v3.json" +parent="$root/results/occam71/eslim/grid-build42598-seed42/05-C-size5-job43027/candidate.txt" + +sinfo -o "%P %a %l %D %t %N %G" +squeue -u user_milksang -o "%.18i %.9P %.20j %.2t %.10M %.6D %R" +scontrol show node n006 | grep -E "NodeName=|State=|CfgTRES=|AllocTRES=" + +cd "$search_dir" +bash -n c154_window_safe_ranked_array_v3.slurm +test -s "$ranking" +python -c ' +import json +import sys +p = json.load(open(sys.argv[1], "r", encoding="utf-8")) +assert p["safety"] == { + "acyclic_boundary": True, + "boundary_excludes_removed": True, + "boundary_excludes_root_descendants": True, + "roots_functional_of_boundary": True, +} +assert p["valid_structural_candidates"] >= 100 +assert len(p["records"]) == 100 +for r in p["records"]: + assert len(r["roots"]) == 2 + assert r["candidate_gates"] == r["removed_count"] - 1 +' "$ranking" +test "$(sha256sum "$parent" | cut -d' ' -f1)" = \ + "f9396ee77462152c03fe061a4676944dc04e83787ef2a3915263bd2be8010bd9" +python audit_c_formula.py "$parent" > /dev/null +sha256sum \ + c154_window_safe_ranked_array_v3.slurm \ + c_window_rank_safe_v3.py \ + d_window_sat_safe_remote.py \ + d_window_sat_hpc_safe.py \ + audit_c_formula.py \ + "$ranking" \ + "$parent" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/precheck_c_window_safe.sh b/tracks/qcs/solutions/Genshin_Impact-71/search/precheck_c_window_safe.sh new file mode 100644 index 000000000..d50c72728 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/precheck_c_window_safe.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -euo pipefail + +root=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +search_dir="$root/tracks/qcs/solutions/Genshin_Impact-71/search" + +sinfo -o "%P %a %l %D %t %N %G" +squeue -u user_milksang -o "%.18i %.9P %.20j %.2t %.10M %.6D %R" +scontrol show node n006 | grep -E "NodeName=|State=|CfgTRES=|AllocTRES=" + +cd "$search_dir" +bash -n c_window_safe_rerun_t02_v1.slurm +sha256sum \ + d_window_sat_safe_remote.py \ + d_window_sat_hpc_safe.py \ + c_window_safe_rerun_t02_v1.slurm diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/precheck_c_window_safe_v2.sh b/tracks/qcs/solutions/Genshin_Impact-71/search/precheck_c_window_safe_v2.sh new file mode 100644 index 000000000..2f2d5a1fc --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/precheck_c_window_safe_v2.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -euo pipefail + +root=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +search_dir="$root/tracks/qcs/solutions/Genshin_Impact-71/search" +ranking="$root/results/occam71/c-window-safe/c_joint_window_ranking_safe_v2.json" + +sinfo -o "%P %a %l %D %t %N %G" +squeue -u user_milksang -o "%.18i %.9P %.20j %.2t %.10M %.6D %R" +scontrol show node n006 | grep -E "NodeName=|State=|CfgTRES=|AllocTRES=" + +cd "$search_dir" +bash -n c_window_safe_ranked_array_v2.slurm +python -m py_compile c_window_rank_safe_v2.py +test -s "$ranking" +python -c ' +import json +import sys +p = json.load(open(sys.argv[1], "r", encoding="utf-8")) +assert p["safety"] == { + "acyclic_boundary": True, + "boundary_excludes_removed": True, + "boundary_excludes_root_descendants": True, +} +assert len(p["records"]) == 100 +for r in p["records"]: + assert len(r["roots"]) == 2 + assert r["candidate_gates"] == r["removed_count"] - 1 +' "$ranking" +sha256sum \ + c_window_rank_safe_v2.py \ + d_window_sat_safe_remote.py \ + d_window_sat_hpc_safe.py \ + c_window_safe_ranked_array_v2.slurm \ + "$ranking" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/precheck_c_window_safe_v3.sh b/tracks/qcs/solutions/Genshin_Impact-71/search/precheck_c_window_safe_v3.sh new file mode 100644 index 000000000..95ff60cf3 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/precheck_c_window_safe_v3.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +set -euo pipefail + +root=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +search_dir="$root/tracks/qcs/solutions/Genshin_Impact-71/search" +ranking="$root/results/occam71/c-window-safe/c_joint_window_ranking_safe_v3.json" + +sinfo -o "%P %a %l %D %t %N %G" +squeue -u user_milksang -o "%.18i %.9P %.20j %.2t %.10M %.6D %R" +scontrol show node n006 | grep -E "NodeName=|State=|CfgTRES=|AllocTRES=" + +cd "$search_dir" +bash -n c_window_safe_ranked_array_v3.slurm +python -m py_compile c_window_rank_safe_v3.py +test -s "$ranking" +python -c ' +import json +import sys +p = json.load(open(sys.argv[1], "r", encoding="utf-8")) +assert p["safety"] == { + "acyclic_boundary": True, + "boundary_excludes_removed": True, + "boundary_excludes_root_descendants": True, + "roots_functional_of_boundary": True, +} +assert p["valid_structural_candidates"] >= 100 +assert len(p["records"]) == 100 +for r in p["records"]: + assert len(r["roots"]) == 2 + assert r["candidate_gates"] == r["removed_count"] - 1 +' "$ranking" +sha256sum \ + c_window_rank_safe_v3.py \ + d_window_sat_safe_remote.py \ + d_window_sat_hpc_safe.py \ + c_window_safe_ranked_array_v3.slurm \ + "$ranking" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/precheck_d109_window_safe_v3.sh b/tracks/qcs/solutions/Genshin_Impact-71/search/precheck_d109_window_safe_v3.sh new file mode 100644 index 000000000..ada075ead --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/precheck_d109_window_safe_v3.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +set -euo pipefail + +root=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +search_dir="$root/tracks/qcs/solutions/Genshin_Impact-71/search" +ranking="$root/results/occam71/d-window-safe/d_joint_window_ranking_safe_v3.json" + +sinfo -o "%P %a %l %D %t %N %G" +squeue -u user_milksang -o "%.18i %.9P %.20j %.2t %.10M %.6D %R" +scontrol show node n006 | grep -E "NodeName=|State=|CfgTRES=|AllocTRES=" + +cd "$search_dir" +bash -n d109_window_safe_ranked_array_v3.slurm +test -s "$ranking" +python -c ' +import json +import sys +p = json.load(open(sys.argv[1], "r", encoding="utf-8")) +assert p["safety"] == { + "acyclic_boundary": True, + "boundary_excludes_removed": True, + "boundary_excludes_root_descendants": True, + "roots_functional_of_boundary": True, +} +assert p["valid_structural_candidates"] == 79 +assert len(p["records"]) == 79 +for r in p["records"]: + assert len(r["roots"]) == 2 + assert r["candidate_gates"] == r["removed_count"] - 1 +' "$ranking" +sha256sum \ + d109_window_safe_ranked_array_v3.slurm \ + c_window_rank_safe_v3.py \ + d_window_sat_safe_remote.py \ + d_window_sat_hpc_safe.py \ + audit_d_formula.py \ + "$ranking" \ + "$root/results/occam71/eslim/D-pilot-build42598-size6-seed42/job-42633/candidate.txt" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/precheck_eslim_C_stage2_from154.sh b/tracks/qcs/solutions/Genshin_Impact-71/search/precheck_eslim_C_stage2_from154.sh new file mode 100644 index 000000000..25a9625cb --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/precheck_eslim_C_stage2_from154.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -euo pipefail + +root=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +search_dir="$root/tracks/qcs/solutions/Genshin_Impact-71/search" +build_run="$root/results/occam71/tools/eslim-build-51e9f774/job-42598" +parent="$root/results/occam71/eslim/grid-build42598-seed42/05-C-size5-job43027/candidate.txt" + +sinfo -o "%P %a %l %D %t %N %G" +squeue -u user_milksang -o "%.18i %.9P %.20j %.2t %.10M %.6D %R" +scontrol show node n006 | grep -E "NodeName=|State=|CfgTRES=|AllocTRES=" + +cd "$search_dir" +bash -n slurm_eslim_C_stage2_from154_grid.sh +python -m py_compile audit_c_formula.py +grep -qx SUCCESS "$build_run/BUILD_READY" +sha256sum --check "$build_run/built_modules_sha256.txt" +test "$(sha256sum "$parent" | cut -d' ' -f1)" = \ + "f9396ee77462152c03fe061a4676944dc04e83787ef2a3915263bd2be8010bd9" +python audit_c_formula.py "$parent" \ + --report "$root/results/occam71/eslim/grid-build42598-seed42/05-C-size5-job43027/pre-stage2-formula-audit.json" \ + > /dev/null +sha256sum \ + slurm_eslim_C_stage2_from154_grid.sh \ + audit_c_formula.py \ + bridge.py \ + verify_candidate.py \ + "$parent" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/precheck_eslim_C_stage3_from152.sh b/tracks/qcs/solutions/Genshin_Impact-71/search/precheck_eslim_C_stage3_from152.sh new file mode 100644 index 000000000..104e6db0b --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/precheck_eslim_C_stage3_from152.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -euo pipefail + +root=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +search_dir="$root/tracks/qcs/solutions/Genshin_Impact-71/search" +build_run="$root/results/occam71/tools/eslim-build-51e9f774/job-42598" +parent="$root/results/occam71/eslim/C-stage2-from154-grid-seed42/03-size7-job43188/candidate.txt" + +sinfo -o "%P %a %l %D %t %N %G" +squeue -u user_milksang -o "%.18i %.9P %.20j %.2t %.10M %.6D %R" +scontrol show node n006 | grep -E "NodeName=|State=|CfgTRES=|AllocTRES=" + +cd "$search_dir" +bash -n slurm_eslim_C_stage3_from152_grid.sh +python -m py_compile audit_c_formula.py +grep -qx SUCCESS "$build_run/BUILD_READY" +sha256sum --check "$build_run/built_modules_sha256.txt" +test "$(sha256sum "$parent" | cut -d' ' -f1)" = \ + "67540307369fedfffdb2b1a6473eff5e0bbfeb0e4873d03fddbeceb653cd071c" +python audit_c_formula.py "$parent" \ + --report "$root/results/occam71/eslim/grid-build42598-seed42/05-C-size5-job43027/pre-stage3-formula-audit.json" \ + > /dev/null +sha256sum \ + slurm_eslim_C_stage3_from152_grid.sh \ + audit_c_formula.py \ + bridge.py \ + verify_candidate.py \ + "$parent" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/precheck_eslim_grid_abc_d109.sh b/tracks/qcs/solutions/Genshin_Impact-71/search/precheck_eslim_grid_abc_d109.sh new file mode 100644 index 000000000..2d23857ee --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/precheck_eslim_grid_abc_d109.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -euo pipefail + +root=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +search_dir="$root/tracks/qcs/solutions/Genshin_Impact-71/search" +build_run="$root/results/occam71/tools/eslim-build-51e9f774/job-42598" + +sinfo -o "%P %a %l %D %t %N %G" +squeue -u user_milksang -o "%.18i %.9P %.20j %.2t %.10M %.6D %R" +scontrol show node n006 | grep -E "NodeName=|State=|CfgTRES=|AllocTRES=" + +cd "$search_dir" +bash -n slurm_eslim_grid_abc_d109_seed42.sh +grep -qx SUCCESS "$build_run/BUILD_READY" +sha256sum --check "$build_run/built_modules_sha256.txt" +test -s "$root/results/occam71/reference-355/mystery-A.txt" +test -s "$root/results/occam71/reference-355/mystery-B.txt" +test -s "$root/results/occam71/reference-355/mystery-C.txt" +test -s "$root/results/occam71/eslim/D-pilot-build42598-size6-seed42/job-42633/candidate.txt" +sha256sum \ + slurm_eslim_grid_abc_d109_seed42.sh \ + bridge.py \ + verify_candidate.py \ + audit_d_formula.py \ + "$root/results/occam71/reference-355/mystery-A.txt" \ + "$root/results/occam71/reference-355/mystery-B.txt" \ + "$root/results/occam71/reference-355/mystery-C.txt" \ + "$root/results/occam71/eslim/D-pilot-build42598-size6-seed42/job-42633/candidate.txt" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/reduce_netlist.py b/tracks/qcs/solutions/Genshin_Impact-71/search/reduce_netlist.py new file mode 100644 index 000000000..97c0dd653 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/reduce_netlist.py @@ -0,0 +1,549 @@ +#!/usr/bin/env python3 +"""Strict, self-contained truth-table optimizer for the Occam netlist format. + +The input is treated as untrusted data. Only a tiny line-oriented grammar is +accepted; no input text is evaluated, imported, interpolated, or executed. +""" + +from __future__ import annotations + +import argparse +import dataclasses +import json +import re +from collections import defaultdict +from pathlib import Path +from typing import Iterable + + +OPS = ("AND", "OR", "XOR", "NAND", "NOR", "XNOR") +OPS_SET = frozenset(OPS) +INPUT_RE = re.compile(r"INPUTS ([1-9][0-9]*)\Z") +GATE_RE = re.compile( + r"w([1-9][0-9]*) = (AND|OR|XOR|NAND|NOR|XNOR) " + r"(~?)([xw])([1-9][0-9]*) (~?)([xw])([1-9][0-9]*)\Z" +) +OUTPUT_RE = re.compile( + r"OUTPUTS( ~?[xw][1-9][0-9]*)+\Z" +) +TOKEN_RE = re.compile(r"(~?)([xw])([1-9][0-9]*)\Z") + + +@dataclasses.dataclass(frozen=True, slots=True) +class Lit: + kind: str + index: int + inv: bool = False + + def toggled(self) -> "Lit": + return Lit(self.kind, self.index, not self.inv) + + def text(self) -> str: + return ("~" if self.inv else "") + self.kind + str(self.index) + + +@dataclasses.dataclass(frozen=True, slots=True) +class Gate: + op: str + a: Lit + b: Lit + + +@dataclasses.dataclass(frozen=True, slots=True) +class Circuit: + ninputs: int + gates: tuple[Gate, ...] + outputs: tuple[Lit, ...] + + +def parse_lit_token(token: str) -> Lit: + match = TOKEN_RE.fullmatch(token) + if match is None: + raise ValueError(f"invalid literal token: {token!r}") + tilde, kind, index_text = match.groups() + return Lit(kind, int(index_text), bool(tilde)) + + +def validate_lit(lit: Lit, ninputs: int, max_wire: int, context: str) -> None: + if lit.kind == "x": + if not 1 <= lit.index <= ninputs: + raise ValueError(f"{context}: input index out of range: {lit.text()}") + elif lit.kind == "w": + if not 1 <= lit.index <= max_wire: + raise ValueError( + f"{context}: wire must already exist (max w{max_wire}): {lit.text()}" + ) + else: + raise ValueError(f"{context}: unknown literal kind: {lit.kind!r}") + + +def parse_circuit(path: Path) -> Circuit: + raw = path.read_bytes() + if len(raw) > 2_000_000: + raise ValueError("input exceeds strict 2 MB size limit") + try: + text = raw.decode("ascii") + except UnicodeDecodeError as exc: + raise ValueError("netlist must be ASCII") from exc + # Normalize only the platform newline representation. Empty lines and + # surrounding whitespace are deliberately not accepted. + lines = text.splitlines() + if not lines: + raise ValueError("empty netlist") + if any(len(line) > 4096 for line in lines): + raise ValueError("line exceeds strict 4096-byte limit") + first = INPUT_RE.fullmatch(lines[0]) + if first is None: + raise ValueError("line 1 must be exactly: INPUTS ") + ninputs = int(first.group(1)) + if not 1 <= ninputs <= 20: + raise ValueError("strict evaluator accepts 1..20 inputs") + if len(lines) < 2: + raise ValueError("missing OUTPUTS line") + if OUTPUT_RE.fullmatch(lines[-1]) is None: + raise ValueError("last line is not a valid OUTPUTS declaration") + gates: list[Gate] = [] + for lineno, line in enumerate(lines[1:-1], start=2): + match = GATE_RE.fullmatch(line) + if match is None: + raise ValueError(f"line {lineno}: not a whitelisted gate assignment") + ( + wire_text, + op, + inv_a, + kind_a, + index_a, + inv_b, + kind_b, + index_b, + ) = match.groups() + wire_index = int(wire_text) + expected = len(gates) + 1 + if wire_index != expected: + raise ValueError( + f"line {lineno}: expected contiguous w{expected}, got w{wire_index}" + ) + a = Lit(kind_a, int(index_a), bool(inv_a)) + b = Lit(kind_b, int(index_b), bool(inv_b)) + validate_lit(a, ninputs, expected - 1, f"line {lineno}") + validate_lit(b, ninputs, expected - 1, f"line {lineno}") + gates.append(Gate(op, a, b)) + output_tokens = lines[-1].split()[1:] + if not output_tokens: + raise ValueError("at least one output is required") + outputs = tuple(parse_lit_token(token) for token in output_tokens) + for lit in outputs: + validate_lit(lit, ninputs, len(gates), "OUTPUTS") + return Circuit(ninputs, tuple(gates), outputs) + + +def serialize(circuit: Circuit) -> str: + lines = [f"INPUTS {circuit.ninputs}"] + for index, gate in enumerate(circuit.gates, start=1): + if gate.op not in OPS_SET: + raise AssertionError(f"internal invalid op: {gate.op}") + validate_lit(gate.a, circuit.ninputs, index - 1, f"internal w{index}") + validate_lit(gate.b, circuit.ninputs, index - 1, f"internal w{index}") + lines.append(f"w{index} = {gate.op} {gate.a.text()} {gate.b.text()}") + for lit in circuit.outputs: + validate_lit(lit, circuit.ninputs, len(circuit.gates), "internal OUTPUTS") + lines.append("OUTPUTS " + " ".join(lit.text() for lit in circuit.outputs)) + return "\n".join(lines) + "\n" + + +def input_truth_tables(ninputs: int) -> tuple[list[int], int]: + nrows = 1 << ninputs + mask = (1 << nrows) - 1 + values: list[int] = [] + for input_index in range(ninputs): + half_period = 1 << input_index + period = half_period << 1 + block = (1 << half_period) - 1 + value = 0 + for start in range(half_period, nrows, period): + value |= block << start + values.append(value) + return values, mask + + +def apply_op(op: str, a: int, b: int, mask: int) -> int: + if op == "AND": + return a & b + if op == "OR": + return a | b + if op == "XOR": + return a ^ b + if op == "NAND": + return mask ^ (a & b) + if op == "NOR": + return mask ^ (a | b) + if op == "XNOR": + return mask ^ (a ^ b) + raise AssertionError(f"unknown op: {op}") + + +def eval_lit( + lit: Lit, input_values: list[int], wire_values: list[int], mask: int +) -> int: + value = ( + input_values[lit.index - 1] + if lit.kind == "x" + else wire_values[lit.index - 1] + ) + return mask ^ value if lit.inv else value + + +def evaluate(circuit: Circuit) -> tuple[tuple[int, ...], tuple[int, ...], int]: + input_values, mask = input_truth_tables(circuit.ninputs) + wire_values: list[int] = [] + for gate in circuit.gates: + a = eval_lit(gate.a, input_values, wire_values, mask) + b = eval_lit(gate.b, input_values, wire_values, mask) + wire_values.append(apply_op(gate.op, a, b, mask)) + outputs = tuple( + eval_lit(lit, input_values, wire_values, mask) for lit in circuit.outputs + ) + return tuple(wire_values), outputs, mask + + +def live_wires(circuit: Circuit) -> set[int]: + live: set[int] = set() + stack = [lit.index for lit in circuit.outputs if lit.kind == "w"] + while stack: + index = stack.pop() + if index in live: + continue + live.add(index) + gate = circuit.gates[index - 1] + if gate.a.kind == "w": + stack.append(gate.a.index) + if gate.b.kind == "w": + stack.append(gate.b.index) + return live + + +def prune_and_compact(circuit: Circuit) -> Circuit: + live = live_wires(circuit) + mapping: dict[int, int] = {} + new_gates: list[Gate] = [] + + def remap(lit: Lit) -> Lit: + if lit.kind == "x": + return lit + return Lit("w", mapping[lit.index], lit.inv) + + for old_index, gate in enumerate(circuit.gates, start=1): + if old_index not in live: + continue + new_index = len(new_gates) + 1 + mapping[old_index] = new_index + new_gates.append(Gate(gate.op, remap(gate.a), remap(gate.b))) + new_outputs = tuple(remap(lit) for lit in circuit.outputs) + return Circuit(circuit.ninputs, tuple(new_gates), new_outputs) + + +def compose_inv(container_inv: bool, replacement: Lit) -> Lit: + return Lit(replacement.kind, replacement.index, container_inv ^ replacement.inv) + + +def substitute_wire(circuit: Circuit, target: int, replacement: Lit) -> Circuit: + if replacement.kind == "w" and replacement.index >= target: + raise ValueError("replacement must be topologically earlier than target") + + def sub(lit: Lit) -> Lit: + if lit.kind == "w" and lit.index == target: + return compose_inv(lit.inv, replacement) + return lit + + gates = tuple(Gate(g.op, sub(g.a), sub(g.b)) for g in circuit.gates) + outputs = tuple(sub(lit) for lit in circuit.outputs) + return prune_and_compact(Circuit(circuit.ninputs, gates, outputs)) + + +def replace_gate(circuit: Circuit, target: int, replacement: Gate) -> Circuit: + validate_lit(replacement.a, circuit.ninputs, target - 1, "replacement") + validate_lit(replacement.b, circuit.ninputs, target - 1, "replacement") + gates = list(circuit.gates) + gates[target - 1] = replacement + return prune_and_compact( + Circuit(circuit.ninputs, tuple(gates), circuit.outputs) + ) + + +def source_truth( + lit: Lit, + input_values: list[int], + wire_values: tuple[int, ...], + mask: int, +) -> int: + value = ( + input_values[lit.index - 1] + if lit.kind == "x" + else wire_values[lit.index - 1] + ) + return mask ^ value if lit.inv else value + + +def structural_key(gate: Gate) -> tuple[str, str, str]: + # All allowed operations are commutative. + left, right = sorted((gate.a.text(), gate.b.text())) + return gate.op, left, right + + +def all_phases(source: Lit) -> tuple[Lit, Lit]: + plain = Lit(source.kind, source.index, False) + return plain, plain.toggled() + + +def find_best_zero_or_one_gate_rewrite( + circuit: Circuit, +) -> tuple[Circuit, dict[str, object] | None, dict[str, int]]: + """Exhaustively search zero- and one-gate resubstitution over all prior nodes. + + Every source pair, both input phases, and every whitelisted gate operation is + considered. Only full truth-table matches are candidates. + """ + + circuit = prune_and_compact(circuit) + wire_values, baseline_outputs, mask = evaluate(circuit) + input_values, _ = input_truth_tables(circuit.ninputs) + target_functions = set(wire_values) + + # Map exact function to every topologically available source. This drives + # exhaustive zero-gate resubstitution. + available_by_function: dict[int, list[Lit]] = defaultdict(list) + base_sources = [Lit("x", i) for i in range(1, circuit.ninputs + 1)] + for source in base_sources: + value = source_truth(source, input_values, wire_values, mask) + available_by_function[value].append(source) + available_by_function[mask ^ value].append(source.toggled()) + + # One-gate expression index, populated incrementally so every expression is + # guaranteed to use wires earlier than the target. We retain only results + # equal to some actual node function, avoiding a large untrusted-data-driven + # memory footprint. + expr_by_function: dict[int, list[Gate]] = defaultdict(list) + expr_seen: dict[int, set[tuple[str, str, str]]] = defaultdict(set) + sources: list[Lit] = list(base_sources) + best = circuit + best_info: dict[str, object] | None = None + stats = { + "targets": len(circuit.gates), + "zero_matches": 0, + "one_gate_function_hits": 0, + "one_gate_structural_forms": 0, + "verified_candidates": 0, + } + + def consider(candidate: Circuit, info: dict[str, object]) -> None: + nonlocal best, best_info + if len(candidate.gates) >= len(best.gates): + return + _, outputs, _ = evaluate(candidate) + stats["verified_candidates"] += 1 + if outputs != baseline_outputs: + raise AssertionError("internal rewrite failed full-domain verification") + best = candidate + best_info = info | { + "before_gates": len(circuit.gates), + "after_gates": len(candidate.gates), + } + + def add_new_source(new_source: Lit) -> None: + # Include self-pairs as identities such as XOR a a; they can matter for + # constants even though constants are uncommon in these instances. + previous_plus_self = sources + [new_source] + phase_new = all_phases(new_source) + for other in previous_plus_self: + for a in phase_new: + for b in all_phases(other): + av = source_truth(a, input_values, wire_values, mask) + bv = source_truth(b, input_values, wire_values, mask) + for op in OPS: + result = apply_op(op, av, bv, mask) + if result not in target_functions: + continue + gate = Gate(op, a, b) + key = structural_key(gate) + if key in expr_seen[result]: + continue + expr_seen[result].add(key) + expr_by_function[result].append(gate) + stats["one_gate_structural_forms"] += 1 + + # Seed the one-gate index with every unordered pair of inputs exactly once. + for input_position, new_source in enumerate(base_sources): + prior = base_sources[: input_position + 1] + for other in prior: + for a in all_phases(new_source): + for b in all_phases(other): + av = source_truth(a, input_values, wire_values, mask) + bv = source_truth(b, input_values, wire_values, mask) + for op in OPS: + result = apply_op(op, av, bv, mask) + if result not in target_functions: + continue + gate = Gate(op, a, b) + key = structural_key(gate) + if key in expr_seen[result]: + continue + expr_seen[result].add(key) + expr_by_function[result].append(gate) + stats["one_gate_structural_forms"] += 1 + + # At target w_i, indexes contain inputs and wires w_1..w_(i-1). + for target, target_value in enumerate(wire_values, start=1): + for replacement in available_by_function.get(target_value, ()): + stats["zero_matches"] += 1 + candidate = substitute_wire(circuit, target, replacement) + consider( + candidate, + { + "kind": "zero_gate_resubstitution", + "target": f"w{target}", + "replacement": replacement.text(), + }, + ) + + expressions = expr_by_function.get(target_value, ()) + stats["one_gate_function_hits"] += len(expressions) + original_key = structural_key(circuit.gates[target - 1]) + for gate in expressions: + if structural_key(gate) == original_key: + continue + candidate = replace_gate(circuit, target, gate) + consider( + candidate, + { + "kind": "one_gate_resubstitution", + "target": f"w{target}", + "replacement": ( + f"{gate.op} {gate.a.text()} {gate.b.text()}" + ), + }, + ) + + current_source = Lit("w", target) + current_value = target_value + available_by_function[current_value].append(current_source) + available_by_function[mask ^ current_value].append( + current_source.toggled() + ) + # Generate only pairs that include this newly available wire. Input + # pairs were seeded above, so each structural pair is covered once. + for other in sources: + for a in all_phases(current_source): + for b in all_phases(other): + av = source_truth(a, input_values, wire_values, mask) + bv = source_truth(b, input_values, wire_values, mask) + for op in OPS: + result = apply_op(op, av, bv, mask) + if result not in target_functions: + continue + gate = Gate(op, a, b) + key = structural_key(gate) + if key in expr_seen[result]: + continue + expr_seen[result].add(key) + expr_by_function[result].append(gate) + stats["one_gate_structural_forms"] += 1 + # Self-pair for the new source. + for a in all_phases(current_source): + for b in all_phases(current_source): + av = source_truth(a, input_values, wire_values, mask) + bv = source_truth(b, input_values, wire_values, mask) + for op in OPS: + result = apply_op(op, av, bv, mask) + if result not in target_functions: + continue + gate = Gate(op, a, b) + key = structural_key(gate) + if key in expr_seen[result]: + continue + expr_seen[result].add(key) + expr_by_function[result].append(gate) + stats["one_gate_structural_forms"] += 1 + sources.append(current_source) + + return best, best_info, stats + + +def optimize_fixpoint(circuit: Circuit) -> tuple[Circuit, list[dict[str, object]], list[dict[str, int]]]: + current = prune_and_compact(circuit) + steps: list[dict[str, object]] = [] + round_stats: list[dict[str, int]] = [] + while True: + rewritten, info, stats = find_best_zero_or_one_gate_rewrite(current) + round_stats.append(stats) + if info is None: + break + steps.append(info) + current = rewritten + return current, steps, round_stats + + +def full_domain_verify(reference: Circuit, candidate: Circuit) -> dict[str, object]: + if reference.ninputs != candidate.ninputs: + raise AssertionError("input count mismatch") + _, ref_outputs, _ = evaluate(reference) + _, cand_outputs, _ = evaluate(candidate) + if len(ref_outputs) != len(cand_outputs): + raise AssertionError("output count mismatch") + equal_each = [a == b for a, b in zip(ref_outputs, cand_outputs)] + return { + "rows": 1 << reference.ninputs, + "outputs": len(ref_outputs), + "equal_each_output": equal_each, + "exact": all(equal_each), + } + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("inputs", nargs="+", type=Path) + parser.add_argument("--out-dir", required=True, type=Path) + parser.add_argument("--seed", type=int, default=42) + args = parser.parse_args() + if args.seed != 42: + raise ValueError("this reproducible search is preregistered to root seed 42") + args.out_dir.mkdir(parents=True, exist_ok=True) + summaries: list[dict[str, object]] = [] + for input_path in args.inputs: + reference = parse_circuit(input_path) + baseline = prune_and_compact(reference) + optimized, steps, round_stats = optimize_fixpoint(baseline) + verification = full_domain_verify(reference, optimized) + if not verification["exact"]: + raise AssertionError("candidate is not exactly equivalent") + output_path = args.out_dir / input_path.name + output_path.write_text(serialize(optimized), encoding="ascii", newline="\n") + # Reparse serialized bytes as a second syntax/topology check. + reparsed = parse_circuit(output_path) + second_verification = full_domain_verify(reference, reparsed) + if not second_verification["exact"]: + raise AssertionError("serialized candidate failed exact equivalence") + summaries.append( + { + "name": input_path.name, + "seed": args.seed, + "original_gates": len(reference.gates), + "initial_live_gates": len(baseline.gates), + "optimized_gates": len(optimized.gates), + "saved_gates": len(reference.gates) - len(optimized.gates), + "steps": steps, + "round_stats": round_stats, + "verification": second_verification, + "output_path": str(output_path), + } + ) + report_path = args.out_dir / "report.json" + report_path.write_text( + json.dumps(summaries, indent=2, sort_keys=True) + "\n", + encoding="utf-8", + newline="\n", + ) + print(json.dumps(summaries, indent=2, sort_keys=True)) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/setup_pysat_t02.slurm b/tracks/qcs/solutions/Genshin_Impact-71/search/setup_pysat_t02.slurm new file mode 100644 index 000000000..d737c8662 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/setup_pysat_t02.slurm @@ -0,0 +1,53 @@ +#!/bin/bash +#SBATCH --job-name=occ71-pysat-env +#SBATCH --partition=home +#SBATCH --nodelist=n006 +#SBATCH --cpus-per-task=1 +#SBATCH --mem=4G +#SBATCH --time=00:15:00 +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/tools/logs/pysat-setup-%j.out +#SBATCH --error=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/tools/logs/pysat-setup-%j.err + +set -euo pipefail + +ROOT=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +TOOLS="$ROOT/results/occam71/tools" +WHEELS="$TOOLS/pysat-wheels-cp313" +FINAL="$TOOLS/pysat-venv-job-$SLURM_JOB_ID" +STAGING="$TOOLS/.pysat-venv-job-$SLURM_JOB_ID.staging" + +mkdir -p "$TOOLS/logs" +test ! -e "$FINAL" +test ! -e "$STAGING" +test -s "$WHEELS/python_sat-1.8.dev20-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" +test -s "$WHEELS/six-1.17.0-py2.py3-none-any.whl" + +export PYTHONHASHSEED=42 +python3 -m venv "$STAGING" +"$STAGING/bin/python" -m pip install \ + --no-index \ + --disable-pip-version-check \ + --find-links "$WHEELS" \ + python-sat six + +"$STAGING/bin/python" - <<'PY' +from pysat.solvers import Solver + +available = [] +for name in ("cadical195", "cadical153", "glucose42", "minisat22"): + try: + with Solver(name=name, bootstrap_with=[[1]]) as solver: + if solver.solve() is not True: + raise RuntimeError(f"{name} failed positive control") + available.append(name) + except Exception: + pass +if not available: + raise RuntimeError("none of the audited SAT backends is available") +print("PYSAT_BACKENDS_OK", ",".join(available), flush=True) +PY + +mv "$STAGING" "$FINAL" +printf 'job_id=%s\nseed=42\n' "$SLURM_JOB_ID" > "$FINAL/READY" +test -s "$FINAL/READY" +printf 'PYSAT_VENV=%s\n' "$FINAL" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/setup_pysat_t02_v2.slurm b/tracks/qcs/solutions/Genshin_Impact-71/search/setup_pysat_t02_v2.slurm new file mode 100644 index 000000000..b8d7315c1 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/setup_pysat_t02_v2.slurm @@ -0,0 +1,53 @@ +#!/bin/bash +#SBATCH --job-name=occ71-pysat-env +#SBATCH --partition=home +#SBATCH --nodelist=n006 +#SBATCH --cpus-per-task=1 +#SBATCH --mem=4G +#SBATCH --time=00:15:00 +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/tools/logs/pysat-setup-%j.out +#SBATCH --error=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/tools/logs/pysat-setup-%j.err + +set -euo pipefail + +ROOT=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +TOOLS="$ROOT/results/occam71/tools" +WHEELS="$TOOLS/pysat-wheels-cp313" +FINAL="$TOOLS/pysat-venv-job-$SLURM_JOB_ID" +STAGING="$TOOLS/.pysat-venv-job-$SLURM_JOB_ID.staging" + +mkdir -p "$TOOLS/logs" +test ! -e "$FINAL" +test ! -e "$STAGING" +test -s "$WHEELS/python_sat-1.8.dev20-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl" +test -s "$WHEELS/six-1.17.0-py2.py3-none-any.whl" + +export PYTHONHASHSEED=42 +python3 -m venv "$STAGING" +"$STAGING/bin/python" -m pip install \ + --no-index \ + --disable-pip-version-check \ + --find-links "$WHEELS" \ + python-sat six + +"$STAGING/bin/python" - <<'PY' +from pysat.solvers import Solver + +available = [] +for name in ("cadical195", "cadical153", "glucose42", "minisat22"): + try: + with Solver(name=name, bootstrap_with=[[1]]) as solver: + if solver.solve() is not True: + raise RuntimeError(f"{name} failed positive control") + available.append(name) + except Exception: + pass +if not available: + raise RuntimeError("none of the audited SAT backends is available") +print("PYSAT_BACKENDS_OK", ",".join(available), flush=True) +PY + +mv "$STAGING" "$FINAL" +printf 'job_id=%s\nseed=42\n' "$SLURM_JOB_ID" > "$FINAL/READY" +test -s "$FINAL/READY" +printf 'PYSAT_VENV=%s\n' "$FINAL" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/slurm_build_eslim.sh b/tracks/qcs/solutions/Genshin_Impact-71/search/slurm_build_eslim.sh new file mode 100644 index 000000000..5077c2641 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/slurm_build_eslim.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash +#SBATCH --job-name=occ71-build-eslim +#SBATCH --partition=home +#SBATCH --nodelist=n006 +#SBATCH --nodes=1 +#SBATCH --ntasks=1 +#SBATCH --cpus-per-task=4 +#SBATCH --mem=16G +#SBATCH --time=00:30:00 +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/logs/build-eslim-%j.out + +set -euo pipefail +umask 027 + +readonly PROJECT_ROOT="/home/user_milksang/private/homefile/quantum_harness/issue71_occam" +readonly TOOLS_ROOT="${PROJECT_ROOT}/results/occam71/tools" +readonly ARCHIVE="${TOOLS_ROOT}/eSLIM-51e9f774.tar.gz" +readonly WHEEL_DIR="${TOOLS_ROOT}/wheels-cp313" +readonly RUN_ROOT="${TOOLS_ROOT}/eslim-build-51e9f774" +readonly RUN_DIR="${RUN_ROOT}/job-${SLURM_JOB_ID}" +readonly SOURCE_DIR="${RUN_DIR}/eSLIM" +readonly BUILD_DIR="${SOURCE_DIR}/src/bindings/build" +readonly VENV_DIR="${RUN_DIR}/venv" +readonly CADICAL_DIR="${SOURCE_DIR}/src/bindings/cadical" + +mkdir -p "${RUN_DIR}" +{ + printf 'job_id=%s\n' "${SLURM_JOB_ID}" + printf 'node=%s\n' "$(hostname)" + printf 'start_utc=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" + printf 'source_commit=%s\n' "51e9f77429627473db623058157b66a1192cbb59" + printf 'seed=%s\n' "42" + printf 'python=%s\n' "$(python3 --version 2>&1)" + printf 'cmake=%s\n' "$(cmake --version | head -n 1)" + printf 'compiler=%s\n' "$(g++ --version | head -n 1)" +} > "${RUN_DIR}/metadata.txt" + +sha256sum "${ARCHIVE}" "${WHEEL_DIR}"/*.whl \ + > "${RUN_DIR}/input_sha256.txt" + +python3 -m venv "${VENV_DIR}" +"${VENV_DIR}/bin/python" -m pip install \ + --no-index \ + --find-links "${WHEEL_DIR}" \ + pybind11==3.0.4 \ + bitarray==3.9.2 + +tar -xzf "${ARCHIVE}" -C "${RUN_DIR}" +# The Windows checkout used for transport has CRLF text and no Unix execute +# metadata. Normalize every CaDiCaL build shell script plus its makefile +# template/version file; source bytes remain pinned by the archived hash. +sed -i 's/\r$//' \ + "${CADICAL_DIR}/configure" \ + "${CADICAL_DIR}/makefile.in" \ + "${CADICAL_DIR}/VERSION" \ + "${CADICAL_DIR}/scripts/"*.sh +chmod 0755 \ + "${CADICAL_DIR}/configure" \ + "${CADICAL_DIR}/scripts/make-build-header.sh" \ + "${CADICAL_DIR}/scripts/get-git-id.sh" +readonly PYBIND11_CMAKE="$("${VENV_DIR}/bin/python" -m pybind11 --cmakedir)" + +cmake \ + -S "${SOURCE_DIR}/src/bindings" \ + -B "${BUILD_DIR}" \ + -DCMAKE_BUILD_TYPE=Release \ + -Dpybind11_DIR="${PYBIND11_CMAKE}" \ + -DPYTHON_EXECUTABLE="${VENV_DIR}/bin/python" +cmake --build "${BUILD_DIR}" --parallel "${SLURM_CPUS_PER_TASK}" + +for module in aiger cadical relationSynthesiser; do + module_path="$(find "${BUILD_DIR}" -maxdepth 1 -type f \ + -name "${module}*.so" -print -quit)" + test -n "${module_path}" + cp "${module_path}" "${SOURCE_DIR}/src/bindings/" +done + +( + cd "${SOURCE_DIR}/src" + "${VENV_DIR}/bin/python" -c \ + 'from bindings import aiger, cadical, relationSynthesiser; import bitarray; print("IMPORT_OK", bitarray.__version__)' +) + +sha256sum "${SOURCE_DIR}/src/bindings/"*.so \ + > "${RUN_DIR}/built_modules_sha256.txt" +printf 'end_utc=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ + >> "${RUN_DIR}/metadata.txt" +printf 'SUCCESS\n' > "${RUN_DIR}/BUILD_READY" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/slurm_c_remaining.sbatch b/tracks/qcs/solutions/Genshin_Impact-71/search/slurm_c_remaining.sbatch new file mode 100644 index 000000000..a35802d95 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/slurm_c_remaining.sbatch @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +#SBATCH --job-name=occam71-div-C +#SBATCH --partition=home +#SBATCH --nodelist=n006 +#SBATCH --array=0-3 +#SBATCH --cpus-per-task=1 +#SBATCH --mem=12G +#SBATCH --time=06:00:00 +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/divisor-search/logs/C-%A_%a.out +#SBATCH --error=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/divisor-search/logs/C-%A_%a.err + +set -euo pipefail + +readonly PROJECT=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +readonly SEARCH="${PROJECT}/tracks/qcs/solutions/Genshin_Impact-71/search" +readonly INPUT="${PROJECT}/results/occam71/reference-355/mystery-C.txt" +readonly RESULTS="${PROJECT}/results/occam71/divisor-search" +readonly ROOTS=(102 122 140 153) +readonly ROOT="${ROOTS[${SLURM_ARRAY_TASK_ID}]}" +readonly OUT="${RESULTS}/C/root-${ROOT}" + +mkdir -p "${OUT}" +python3 -u "${SEARCH}/divisor_resynth.py" \ + "${INPUT}" \ + --out-dir "${OUT}" \ + --seed 42 \ + --deadline-seconds 21000 \ + --roots "${ROOT}" +test -s "${OUT}/report.json" +touch "${OUT}/COMPLETE" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/slurm_eslim_C_stage2_from154_grid.sh b/tracks/qcs/solutions/Genshin_Impact-71/search/slurm_eslim_C_stage2_from154_grid.sh new file mode 100644 index 000000000..77d285bed --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/slurm_eslim_C_stage2_from154_grid.sh @@ -0,0 +1,195 @@ +#!/usr/bin/env bash +# Pinned eSLIM continuation from the independently audited 154-gate multiplier. +# +#SBATCH --job-name=occ71-eslim-C154 +#SBATCH --partition=home +#SBATCH --nodelist=n006 +#SBATCH --array=0-4%5 +#SBATCH --nodes=1 +#SBATCH --ntasks=1 +#SBATCH --cpus-per-task=1 +#SBATCH --mem=16G +#SBATCH --time=00:25:00 +#SBATCH --no-requeue +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/eslim/C-stage2-from154-grid-seed42/logs/%A_%a.out +#SBATCH --error=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/eslim/C-stage2-from154-grid-seed42/logs/%A_%a.err + +set -Eeuo pipefail +umask 027 + +export LC_ALL=C +export TZ=UTC +export PYTHONHASHSEED=42 +export OMP_NUM_THREADS=1 +export OPENBLAS_NUM_THREADS=1 + +readonly PROJECT_ROOT="/home/user_milksang/private/homefile/quantum_harness/issue71_occam" +readonly SEARCH_DIR="${PROJECT_ROOT}/tracks/qcs/solutions/Genshin_Impact-71/search" +readonly BUILD_RUN="${PROJECT_ROOT}/results/occam71/tools/eslim-build-51e9f774/job-42598" +readonly ESLIM_SRC="${BUILD_RUN}/eSLIM/src" +readonly PYTHON="${BUILD_RUN}/venv/bin/python" +readonly REFERENCE="${PROJECT_ROOT}/results/occam71/eslim/grid-build42598-seed42/05-C-size5-job43027/candidate.txt" +readonly RUN_ROOT="${PROJECT_ROOT}/results/occam71/eslim/C-stage2-from154-grid-seed42" + +SIZES=(4 5 6 7 8) +readonly SIZE="${SIZES[$SLURM_ARRAY_TASK_ID]}" +readonly LIMIT_SECONDS=900 +TASK_INDEX=$(printf "%02d" "${SLURM_ARRAY_TASK_ID}") +readonly TASK_INDEX +readonly RUN_DIR="${RUN_ROOT}/${TASK_INDEX}-size${SIZE}-job${SLURM_ARRAY_JOB_ID}" + +mkdir -p "${RUN_ROOT}/logs" +mkdir "${RUN_DIR}" + +CURRENT_STAGE="initialization" +record_failure() { + local exit_code="$1" + local source_line="$2" + trap - ERR INT TERM + { + printf 'status=FAILED\n' + printf 'stage=%s\n' "${CURRENT_STAGE}" + printf 'exit_code=%s\n' "${exit_code}" + printf 'source_line=%s\n' "${source_line}" + printf 'end_utc=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" + } > "${RUN_DIR}/FAILED" + exit "${exit_code}" +} +trap 'record_failure "$?" "${LINENO}"' ERR +trap 'record_failure 130 "${LINENO}"' INT +trap 'record_failure 143 "${LINENO}"' TERM + +{ + printf 'array_job_id=%s\n' "${SLURM_ARRAY_JOB_ID}" + printf 'array_task_id=%s\n' "${SLURM_ARRAY_TASK_ID}" + printf 'node=%s\n' "$(hostname)" + printf 'start_utc=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" + printf 'source_commit=%s\n' "51e9f77429627473db623058157b66a1192cbb59" + printf 'build_job_id=%s\n' "42598" + printf 'parent_job_id=%s\n' "43027_5" + printf 'parent_gates=%s\n' "154" + printf 'parent_sha256=%s\n' "f9396ee77462152c03fe061a4676944dc04e83787ef2a3915263bd2be8010bd9" + printf 'seed=%s\n' "42" + printf 'limit_seconds=%s\n' "${LIMIT_SECONDS}" + printf 'initial_subcircuit_size=%s\n' "${SIZE}" + printf 'max_subcircuit_inputs=%s\n' "10" + printf 'synthesis_mode=%s\n' "relation_sat" + printf 'solver_base_timeout_seconds=%s\n' "30" + printf 'relation_timeout_seconds=%s\n' "30" +} > "${RUN_DIR}/metadata.txt" + +CURRENT_STAGE="build-integrity-check" +grep -qx SUCCESS "${BUILD_RUN}/BUILD_READY" +test -x "${PYTHON}" +sha256sum --check "${BUILD_RUN}/built_modules_sha256.txt" \ + > "${RUN_DIR}/built_modules_check.txt" + +CURRENT_STAGE="parent-integrity-and-formula-audit" +test -s "${REFERENCE}" +test "$(sha256sum "${REFERENCE}" | cut -d' ' -f1)" = \ + "f9396ee77462152c03fe061a4676944dc04e83787ef2a3915263bd2be8010bd9" +"${PYTHON}" -u "${SEARCH_DIR}/audit_c_formula.py" \ + "${REFERENCE}" \ + --report "${RUN_DIR}/parent-formula-audit.json" \ + > "${RUN_DIR}/parent-formula-audit.stdout.txt" + +CURRENT_STAGE="input-integrity-check" +sha256sum \ + "${REFERENCE}" \ + "${SEARCH_DIR}/bridge.py" \ + "${SEARCH_DIR}/circuit.py" \ + "${SEARCH_DIR}/verify_candidate.py" \ + "${SEARCH_DIR}/audit_c_formula.py" \ + "${ESLIM_SRC}/reduce.py" \ + "${ESLIM_SRC}/synthesisManager.py" \ + "${ESLIM_SRC}/subcircuitSynthesiser.py" \ + "${ESLIM_SRC}/relationFromSubcircuit.py" \ + "${ESLIM_SRC}/blifIO.py" \ + > "${RUN_DIR}/input_sha256.txt" + +CURRENT_STAGE="occam-to-blif" +"${PYTHON}" -u "${SEARCH_DIR}/bridge.py" to-blif \ + "${REFERENCE}" \ + "${RUN_DIR}/input.partial.blif" \ + --model "mystery_C_154_stage2" +test -s "${RUN_DIR}/input.partial.blif" +mv "${RUN_DIR}/input.partial.blif" "${RUN_DIR}/input.blif" + +CURRENT_STAGE="eslim-relation-sat" +SECONDS=0 +( + cd "${ESLIM_SRC}" + "${PYTHON}" -u reduce.py \ + "${RUN_DIR}/input.blif" \ + "${RUN_DIR}/reduced.partial.blif" \ + "${LIMIT_SECONDS}" \ + --gs 2 \ + --seed 42 \ + --syn-mode sat \ + --size "${SIZE}" \ + --limit-inputs 10 \ + --solverTO 30 \ + --relTO 30 \ + --require-reduction +) > "${RUN_DIR}/eslim.stdout.txt" 2> "${RUN_DIR}/eslim.stderr.txt" +printf 'eslim_elapsed_seconds=%s\n' "${SECONDS}" > "${RUN_DIR}/timing.txt" +test -s "${RUN_DIR}/reduced.partial.blif" +mv "${RUN_DIR}/reduced.partial.blif" "${RUN_DIR}/reduced.blif" + +CURRENT_STAGE="blif-to-occam" +"${PYTHON}" -u "${SEARCH_DIR}/bridge.py" to-occam \ + "${RUN_DIR}/reduced.blif" \ + "${RUN_DIR}/candidate.partial.txt" +test -s "${RUN_DIR}/candidate.partial.txt" +mv "${RUN_DIR}/candidate.partial.txt" "${RUN_DIR}/candidate.txt" + +CURRENT_STAGE="independent-reference-audit" +PYTHONPATH="${SEARCH_DIR}" "${PYTHON}" -u \ + "${SEARCH_DIR}/verify_candidate.py" \ + "${REFERENCE}" \ + "${RUN_DIR}/candidate.txt" \ + --report "${RUN_DIR}/verification.json" \ + > "${RUN_DIR}/verification.stdout.txt" +test -s "${RUN_DIR}/verification.json" + +SAVED_GATES="$( + "${PYTHON}" -c \ + 'import json,sys; print(json.load(open(sys.argv[1], encoding="utf-8"))["saved_gates"])' \ + "${RUN_DIR}/verification.json" +)" +if [[ ! "${SAVED_GATES}" =~ ^-?[0-9]+$ ]] || (( SAVED_GATES < 0 )); then + record_failure 22 "${LINENO}" +fi + +CURRENT_STAGE="independent-direct-formula-audit" +"${PYTHON}" -u "${SEARCH_DIR}/audit_c_formula.py" \ + "${RUN_DIR}/candidate.txt" \ + --report "${RUN_DIR}/formula-audit.json" \ + > "${RUN_DIR}/formula-audit.stdout.txt" +test -s "${RUN_DIR}/formula-audit.json" + +CURRENT_STAGE="finalization" +sha256sum \ + "${RUN_DIR}/reduced.blif" \ + "${RUN_DIR}/candidate.txt" \ + "${RUN_DIR}/verification.json" \ + "${RUN_DIR}/formula-audit.json" \ + > "${RUN_DIR}/output_sha256.txt" +{ + printf 'end_utc=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" + printf 'saved_gates=%s\n' "${SAVED_GATES}" + if (( SAVED_GATES > 0 )); then + printf 'result=%s\n' "VALIDATED_IMPROVEMENT" + else + printf 'result=%s\n' "VALIDATED_NO_IMPROVEMENT" + fi +} >> "${RUN_DIR}/metadata.txt" + +if (( SAVED_GATES > 0 )); then + printf 'VALIDATED_IMPROVEMENT saved_gates=%s\n' "${SAVED_GATES}" \ + > "${RUN_DIR}/COMPLETE" +else + printf 'VALIDATED_NO_IMPROVEMENT saved_gates=0\n' \ + > "${RUN_DIR}/COMPLETE" +fi +trap - ERR INT TERM diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/slurm_eslim_C_stage3_from152_grid.sh b/tracks/qcs/solutions/Genshin_Impact-71/search/slurm_eslim_C_stage3_from152_grid.sh new file mode 100644 index 000000000..49281e861 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/slurm_eslim_C_stage3_from152_grid.sh @@ -0,0 +1,195 @@ +#!/usr/bin/env bash +# Pinned eSLIM continuation from the independently audited 152-gate multiplier. +# +#SBATCH --job-name=occ71-eslim-C152 +#SBATCH --partition=home +#SBATCH --nodelist=n006 +#SBATCH --array=0-4%5 +#SBATCH --nodes=1 +#SBATCH --ntasks=1 +#SBATCH --cpus-per-task=1 +#SBATCH --mem=16G +#SBATCH --time=00:25:00 +#SBATCH --no-requeue +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/eslim/C-stage3-from152-grid-seed42/logs/%A_%a.out +#SBATCH --error=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/eslim/C-stage3-from152-grid-seed42/logs/%A_%a.err + +set -Eeuo pipefail +umask 027 + +export LC_ALL=C +export TZ=UTC +export PYTHONHASHSEED=42 +export OMP_NUM_THREADS=1 +export OPENBLAS_NUM_THREADS=1 + +readonly PROJECT_ROOT="/home/user_milksang/private/homefile/quantum_harness/issue71_occam" +readonly SEARCH_DIR="${PROJECT_ROOT}/tracks/qcs/solutions/Genshin_Impact-71/search" +readonly BUILD_RUN="${PROJECT_ROOT}/results/occam71/tools/eslim-build-51e9f774/job-42598" +readonly ESLIM_SRC="${BUILD_RUN}/eSLIM/src" +readonly PYTHON="${BUILD_RUN}/venv/bin/python" +readonly REFERENCE="${PROJECT_ROOT}/results/occam71/eslim/C-stage2-from154-grid-seed42/03-size7-job43188/candidate.txt" +readonly RUN_ROOT="${PROJECT_ROOT}/results/occam71/eslim/C-stage3-from152-grid-seed42" + +SIZES=(4 5 6 7 8) +readonly SIZE="${SIZES[$SLURM_ARRAY_TASK_ID]}" +readonly LIMIT_SECONDS=900 +TASK_INDEX=$(printf "%02d" "${SLURM_ARRAY_TASK_ID}") +readonly TASK_INDEX +readonly RUN_DIR="${RUN_ROOT}/${TASK_INDEX}-size${SIZE}-job${SLURM_ARRAY_JOB_ID}" + +mkdir -p "${RUN_ROOT}/logs" +mkdir "${RUN_DIR}" + +CURRENT_STAGE="initialization" +record_failure() { + local exit_code="$1" + local source_line="$2" + trap - ERR INT TERM + { + printf 'status=FAILED\n' + printf 'stage=%s\n' "${CURRENT_STAGE}" + printf 'exit_code=%s\n' "${exit_code}" + printf 'source_line=%s\n' "${source_line}" + printf 'end_utc=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" + } > "${RUN_DIR}/FAILED" + exit "${exit_code}" +} +trap 'record_failure "$?" "${LINENO}"' ERR +trap 'record_failure 130 "${LINENO}"' INT +trap 'record_failure 143 "${LINENO}"' TERM + +{ + printf 'array_job_id=%s\n' "${SLURM_ARRAY_JOB_ID}" + printf 'array_task_id=%s\n' "${SLURM_ARRAY_TASK_ID}" + printf 'node=%s\n' "$(hostname)" + printf 'start_utc=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" + printf 'source_commit=%s\n' "51e9f77429627473db623058157b66a1192cbb59" + printf 'build_job_id=%s\n' "42598" + printf 'parent_job_id=%s\n' "43188_3" + printf 'parent_gates=%s\n' "152" + printf 'parent_sha256=%s\n' "67540307369fedfffdb2b1a6473eff5e0bbfeb0e4873d03fddbeceb653cd071c" + printf 'seed=%s\n' "42" + printf 'limit_seconds=%s\n' "${LIMIT_SECONDS}" + printf 'initial_subcircuit_size=%s\n' "${SIZE}" + printf 'max_subcircuit_inputs=%s\n' "10" + printf 'synthesis_mode=%s\n' "relation_sat" + printf 'solver_base_timeout_seconds=%s\n' "30" + printf 'relation_timeout_seconds=%s\n' "30" +} > "${RUN_DIR}/metadata.txt" + +CURRENT_STAGE="build-integrity-check" +grep -qx SUCCESS "${BUILD_RUN}/BUILD_READY" +test -x "${PYTHON}" +sha256sum --check "${BUILD_RUN}/built_modules_sha256.txt" \ + > "${RUN_DIR}/built_modules_check.txt" + +CURRENT_STAGE="parent-integrity-and-formula-audit" +test -s "${REFERENCE}" +test "$(sha256sum "${REFERENCE}" | cut -d' ' -f1)" = \ + "67540307369fedfffdb2b1a6473eff5e0bbfeb0e4873d03fddbeceb653cd071c" +"${PYTHON}" -u "${SEARCH_DIR}/audit_c_formula.py" \ + "${REFERENCE}" \ + --report "${RUN_DIR}/parent-formula-audit.json" \ + > "${RUN_DIR}/parent-formula-audit.stdout.txt" + +CURRENT_STAGE="input-integrity-check" +sha256sum \ + "${REFERENCE}" \ + "${SEARCH_DIR}/bridge.py" \ + "${SEARCH_DIR}/circuit.py" \ + "${SEARCH_DIR}/verify_candidate.py" \ + "${SEARCH_DIR}/audit_c_formula.py" \ + "${ESLIM_SRC}/reduce.py" \ + "${ESLIM_SRC}/synthesisManager.py" \ + "${ESLIM_SRC}/subcircuitSynthesiser.py" \ + "${ESLIM_SRC}/relationFromSubcircuit.py" \ + "${ESLIM_SRC}/blifIO.py" \ + > "${RUN_DIR}/input_sha256.txt" + +CURRENT_STAGE="occam-to-blif" +"${PYTHON}" -u "${SEARCH_DIR}/bridge.py" to-blif \ + "${REFERENCE}" \ + "${RUN_DIR}/input.partial.blif" \ + --model "mystery_C_152_stage3" +test -s "${RUN_DIR}/input.partial.blif" +mv "${RUN_DIR}/input.partial.blif" "${RUN_DIR}/input.blif" + +CURRENT_STAGE="eslim-relation-sat" +SECONDS=0 +( + cd "${ESLIM_SRC}" + "${PYTHON}" -u reduce.py \ + "${RUN_DIR}/input.blif" \ + "${RUN_DIR}/reduced.partial.blif" \ + "${LIMIT_SECONDS}" \ + --gs 2 \ + --seed 42 \ + --syn-mode sat \ + --size "${SIZE}" \ + --limit-inputs 10 \ + --solverTO 30 \ + --relTO 30 \ + --require-reduction +) > "${RUN_DIR}/eslim.stdout.txt" 2> "${RUN_DIR}/eslim.stderr.txt" +printf 'eslim_elapsed_seconds=%s\n' "${SECONDS}" > "${RUN_DIR}/timing.txt" +test -s "${RUN_DIR}/reduced.partial.blif" +mv "${RUN_DIR}/reduced.partial.blif" "${RUN_DIR}/reduced.blif" + +CURRENT_STAGE="blif-to-occam" +"${PYTHON}" -u "${SEARCH_DIR}/bridge.py" to-occam \ + "${RUN_DIR}/reduced.blif" \ + "${RUN_DIR}/candidate.partial.txt" +test -s "${RUN_DIR}/candidate.partial.txt" +mv "${RUN_DIR}/candidate.partial.txt" "${RUN_DIR}/candidate.txt" + +CURRENT_STAGE="independent-reference-audit" +PYTHONPATH="${SEARCH_DIR}" "${PYTHON}" -u \ + "${SEARCH_DIR}/verify_candidate.py" \ + "${REFERENCE}" \ + "${RUN_DIR}/candidate.txt" \ + --report "${RUN_DIR}/verification.json" \ + > "${RUN_DIR}/verification.stdout.txt" +test -s "${RUN_DIR}/verification.json" + +SAVED_GATES="$( + "${PYTHON}" -c \ + 'import json,sys; print(json.load(open(sys.argv[1], encoding="utf-8"))["saved_gates"])' \ + "${RUN_DIR}/verification.json" +)" +if [[ ! "${SAVED_GATES}" =~ ^-?[0-9]+$ ]] || (( SAVED_GATES < 0 )); then + record_failure 22 "${LINENO}" +fi + +CURRENT_STAGE="independent-direct-formula-audit" +"${PYTHON}" -u "${SEARCH_DIR}/audit_c_formula.py" \ + "${RUN_DIR}/candidate.txt" \ + --report "${RUN_DIR}/formula-audit.json" \ + > "${RUN_DIR}/formula-audit.stdout.txt" +test -s "${RUN_DIR}/formula-audit.json" + +CURRENT_STAGE="finalization" +sha256sum \ + "${RUN_DIR}/reduced.blif" \ + "${RUN_DIR}/candidate.txt" \ + "${RUN_DIR}/verification.json" \ + "${RUN_DIR}/formula-audit.json" \ + > "${RUN_DIR}/output_sha256.txt" +{ + printf 'end_utc=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" + printf 'saved_gates=%s\n' "${SAVED_GATES}" + if (( SAVED_GATES > 0 )); then + printf 'result=%s\n' "VALIDATED_IMPROVEMENT" + else + printf 'result=%s\n' "VALIDATED_NO_IMPROVEMENT" + fi +} >> "${RUN_DIR}/metadata.txt" + +if (( SAVED_GATES > 0 )); then + printf 'VALIDATED_IMPROVEMENT saved_gates=%s\n' "${SAVED_GATES}" \ + > "${RUN_DIR}/COMPLETE" +else + printf 'VALIDATED_NO_IMPROVEMENT saved_gates=0\n' \ + > "${RUN_DIR}/COMPLETE" +fi +trap - ERR INT TERM diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/slurm_eslim_D_pilot.sh b/tracks/qcs/solutions/Genshin_Impact-71/search/slurm_eslim_D_pilot.sh new file mode 100644 index 000000000..9bbee8f33 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/slurm_eslim_D_pilot.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +#SBATCH --job-name=occ71-eslim-D +#SBATCH --partition=home +#SBATCH --nodelist=n006 +#SBATCH --nodes=1 +#SBATCH --ntasks=1 +#SBATCH --cpus-per-task=1 +#SBATCH --mem=16G +#SBATCH --time=00:25:00 +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/logs/eslim-D-%j.out + +set -euo pipefail +umask 027 + +readonly PROJECT_ROOT="/home/user_milksang/private/homefile/quantum_harness/issue71_occam" +readonly SEARCH_DIR="${PROJECT_ROOT}/tracks/qcs/solutions/Genshin_Impact-71/search" +readonly BUILD_RUN="${PROJECT_ROOT}/results/occam71/tools/eslim-build-51e9f774/job-42566" +readonly ESLIM_SRC="${BUILD_RUN}/eSLIM/src" +readonly PYTHON="${BUILD_RUN}/venv/bin/python" +readonly REFERENCE="${PROJECT_ROOT}/results/occam71/reference-355/mystery-D.txt" +readonly INPUT_BLIF="${PROJECT_ROOT}/results/occam71/eslim/inputs/mystery-D.blif" +readonly RUN_ROOT="${PROJECT_ROOT}/results/occam71/eslim/D-size6-seed42" +readonly RUN_DIR="${RUN_ROOT}/job-${SLURM_JOB_ID}" + +test -f "${BUILD_RUN}/BUILD_READY" +test -x "${PYTHON}" +test -s "${INPUT_BLIF}" +mkdir -p "${RUN_DIR}" +{ + printf 'job_id=%s\n' "${SLURM_JOB_ID}" + printf 'node=%s\n' "$(hostname)" + printf 'start_utc=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" + printf 'seed=42\n' + printf 'limit_seconds=900\n' + printf 'gate_size=2\n' + printf 'initial_subcircuit_size=6\n' + printf 'max_subcircuit_inputs=10\n' + printf 'synthesis_mode=relation_sat\n' + printf 'solver_timeout=30\n' + printf 'relation_timeout=30\n' +} > "${RUN_DIR}/metadata.txt" +sha256sum \ + "${REFERENCE}" \ + "${INPUT_BLIF}" \ + "${SEARCH_DIR}/bridge.py" \ + "${SEARCH_DIR}/verify_candidate.py" \ + "${ESLIM_SRC}/reduce.py" \ + > "${RUN_DIR}/input_sha256.txt" + +SECONDS=0 +( + cd "${ESLIM_SRC}" + "${PYTHON}" -u reduce.py \ + "${INPUT_BLIF}" \ + "${RUN_DIR}/reduced.blif" \ + 900 \ + --gs 2 \ + --seed 42 \ + --syn-mode sat \ + --size 6 \ + --limit-inputs 10 \ + --solverTO 30 \ + --relTO 30 \ + --require-reduction +) +printf 'elapsed_seconds=%s\n' "${SECONDS}" > "${RUN_DIR}/timing.txt" + +python3 -u "${SEARCH_DIR}/bridge.py" to-occam \ + "${RUN_DIR}/reduced.blif" \ + "${RUN_DIR}/candidate.txt" +PYTHONPATH="${SEARCH_DIR}" python3 -u "${SEARCH_DIR}/verify_candidate.py" \ + "${REFERENCE}" \ + "${RUN_DIR}/candidate.txt" \ + --report "${RUN_DIR}/verification.json" + +printf 'end_utc=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ + >> "${RUN_DIR}/metadata.txt" +printf 'SUCCESS\n' > "${RUN_DIR}/COMPLETE" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/slurm_eslim_D_pilot_42598.sh b/tracks/qcs/solutions/Genshin_Impact-71/search/slurm_eslim_D_pilot_42598.sh new file mode 100644 index 000000000..7437df769 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/slurm_eslim_D_pilot_42598.sh @@ -0,0 +1,198 @@ +#!/usr/bin/env bash +# Reproducible eSLIM relation-SAT pilot for mystery D. +# +# This script is intentionally pinned to the successful, immutable build +# artifact from Slurm job 42598. It creates one durable run directory per +# Slurm job and writes COMPLETE only after two independent exhaustive audits. +# It does not read or execute any competitor submission code. +# +#SBATCH --job-name=occ71-eslim-D-42598 +#SBATCH --partition=home +#SBATCH --nodelist=n006 +#SBATCH --nodes=1 +#SBATCH --ntasks=1 +#SBATCH --cpus-per-task=1 +#SBATCH --mem=16G +#SBATCH --time=00:25:00 +#SBATCH --no-requeue +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/logs/eslim-D-42598-%j.out + +set -Eeuo pipefail +umask 027 + +export LC_ALL=C +export TZ=UTC +export PYTHONHASHSEED=42 +export OMP_NUM_THREADS=1 +export OPENBLAS_NUM_THREADS=1 + +readonly PROJECT_ROOT="/home/user_milksang/private/homefile/quantum_harness/issue71_occam" +readonly SEARCH_DIR="${PROJECT_ROOT}/tracks/qcs/solutions/Genshin_Impact-71/search" +readonly BUILD_RUN="${PROJECT_ROOT}/results/occam71/tools/eslim-build-51e9f774/job-42598" +readonly ESLIM_SRC="${BUILD_RUN}/eSLIM/src" +readonly PYTHON="${BUILD_RUN}/venv/bin/python" +readonly REFERENCE="${PROJECT_ROOT}/results/occam71/reference-355/mystery-D.txt" +readonly RUN_ROOT="${PROJECT_ROOT}/results/occam71/eslim/D-pilot-build42598-size6-seed42" +readonly RUN_DIR="${RUN_ROOT}/job-${SLURM_JOB_ID}" + +mkdir -p "${RUN_ROOT}" +mkdir "${RUN_DIR}" + +CURRENT_STAGE="initialization" +record_failure() { + local exit_code="$1" + local source_line="$2" + trap - ERR INT TERM + { + printf 'status=FAILED\n' + printf 'stage=%s\n' "${CURRENT_STAGE}" + printf 'exit_code=%s\n' "${exit_code}" + printf 'source_line=%s\n' "${source_line}" + printf 'end_utc=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" + } > "${RUN_DIR}/FAILED" + exit "${exit_code}" +} +trap 'record_failure "$?" "${LINENO}"' ERR +trap 'record_failure 130 "${LINENO}"' INT +trap 'record_failure 143 "${LINENO}"' TERM + +{ + printf 'job_id=%s\n' "${SLURM_JOB_ID}" + printf 'node=%s\n' "$(hostname)" + printf 'start_utc=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" + printf 'source_commit=%s\n' "51e9f77429627473db623058157b66a1192cbb59" + printf 'build_job_id=%s\n' "42598" + printf 'seed=%s\n' "42" + printf 'python_hash_seed=%s\n' "${PYTHONHASHSEED}" + printf 'limit_seconds=%s\n' "900" + printf 'gate_size=%s\n' "2" + printf 'initial_subcircuit_size=%s\n' "6" + printf 'fixed_subcircuit_size=%s\n' "true" + printf 'max_subcircuit_inputs=%s\n' "10" + printf 'synthesis_mode=%s\n' "relation_sat" + printf 'dynamic_timeouts=%s\n' "true" + printf 'solver_base_timeout_seconds=%s\n' "30" + printf 'relation_timeout_seconds=%s\n' "30" + printf 'require_strict_local_reduction=%s\n' "true" + printf 'windowing=%s\n' "false" + printf 'abc=%s\n' "false" + printf 'aig=%s\n' "false" + printf 'restarts=%s\n' "0" + printf 'python=%s\n' "$("${PYTHON}" --version 2>&1)" +} > "${RUN_DIR}/metadata.txt" + +CURRENT_STAGE="build-integrity-check" +grep -qx 'SUCCESS' "${BUILD_RUN}/BUILD_READY" +test -x "${PYTHON}" +test -s "${ESLIM_SRC}/bindings/aiger.cpython-313-x86_64-linux-gnu.so" +test -s "${ESLIM_SRC}/bindings/cadical.cpython-313-x86_64-linux-gnu.so" +test -s \ + "${ESLIM_SRC}/bindings/relationSynthesiser.cpython-313-x86_64-linux-gnu.so" +sha256sum --check "${BUILD_RUN}/built_modules_sha256.txt" \ + > "${RUN_DIR}/built_modules_check.txt" + +CURRENT_STAGE="input-integrity-check" +test -s "${REFERENCE}" +test -s "${SEARCH_DIR}/bridge.py" +test -s "${SEARCH_DIR}/circuit.py" +test -s "${SEARCH_DIR}/verify_candidate.py" +test -s "${ESLIM_SRC}/reduce.py" +sha256sum \ + "${REFERENCE}" \ + "${SEARCH_DIR}/bridge.py" \ + "${SEARCH_DIR}/circuit.py" \ + "${SEARCH_DIR}/verify_candidate.py" \ + "${ESLIM_SRC}/reduce.py" \ + "${ESLIM_SRC}/synthesisManager.py" \ + "${ESLIM_SRC}/subcircuitSynthesiser.py" \ + "${ESLIM_SRC}/relationFromSubcircuit.py" \ + "${ESLIM_SRC}/blifIO.py" \ + > "${RUN_DIR}/input_sha256.txt" + +CURRENT_STAGE="occam-to-blif" +"${PYTHON}" -u "${SEARCH_DIR}/bridge.py" to-blif \ + "${REFERENCE}" \ + "${RUN_DIR}/input.partial.blif" \ + --model "mystery_D_reference_355" +test -s "${RUN_DIR}/input.partial.blif" +mv "${RUN_DIR}/input.partial.blif" "${RUN_DIR}/input.blif" +sha256sum "${RUN_DIR}/input.blif" > "${RUN_DIR}/generated_input_sha256.txt" + +cat > "${RUN_DIR}/eslim_command.txt" < "${RUN_DIR}/eslim.stdout.txt" 2> "${RUN_DIR}/eslim.stderr.txt" +printf 'eslim_elapsed_seconds=%s\n' "${SECONDS}" \ + > "${RUN_DIR}/timing.txt" +test -s "${RUN_DIR}/reduced.partial.blif" +mv "${RUN_DIR}/reduced.partial.blif" "${RUN_DIR}/reduced.blif" + +CURRENT_STAGE="blif-to-occam-audit" +"${PYTHON}" -u "${SEARCH_DIR}/bridge.py" to-occam \ + "${RUN_DIR}/reduced.blif" \ + "${RUN_DIR}/candidate.partial.txt" +test -s "${RUN_DIR}/candidate.partial.txt" +mv "${RUN_DIR}/candidate.partial.txt" "${RUN_DIR}/candidate.txt" + +CURRENT_STAGE="independent-full-domain-audit" +PYTHONPATH="${SEARCH_DIR}" "${PYTHON}" -u \ + "${SEARCH_DIR}/verify_candidate.py" \ + "${REFERENCE}" \ + "${RUN_DIR}/candidate.txt" \ + --report "${RUN_DIR}/verification.json" \ + > "${RUN_DIR}/verification.stdout.txt" +test -s "${RUN_DIR}/verification.json" + +SAVED_GATES="$( + "${PYTHON}" -c \ + 'import json,sys; print(json.load(open(sys.argv[1], encoding="utf-8"))["saved_gates"])' \ + "${RUN_DIR}/verification.json" +)" +if [[ ! "${SAVED_GATES}" =~ ^-?[0-9]+$ ]]; then + record_failure 21 "${LINENO}" +fi +if (( SAVED_GATES < 0 )); then + record_failure 22 "${LINENO}" +fi + +CURRENT_STAGE="finalization" +sha256sum \ + "${RUN_DIR}/reduced.blif" \ + "${RUN_DIR}/candidate.txt" \ + "${RUN_DIR}/verification.json" \ + > "${RUN_DIR}/output_sha256.txt" +{ + printf 'end_utc=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" + printf 'saved_gates=%s\n' "${SAVED_GATES}" + if (( SAVED_GATES > 0 )); then + printf 'result=%s\n' "VALIDATED_IMPROVEMENT" + else + printf 'result=%s\n' "VALIDATED_NO_IMPROVEMENT" + fi +} >> "${RUN_DIR}/metadata.txt" + +if (( SAVED_GATES > 0 )); then + printf 'VALIDATED_IMPROVEMENT saved_gates=%s\n' "${SAVED_GATES}" \ + > "${RUN_DIR}/COMPLETE" +else + printf 'VALIDATED_NO_IMPROVEMENT saved_gates=0\n' \ + > "${RUN_DIR}/COMPLETE" +fi +trap - ERR INT TERM diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/slurm_eslim_D_stage2_from109.sh b/tracks/qcs/solutions/Genshin_Impact-71/search/slurm_eslim_D_stage2_from109.sh new file mode 100644 index 000000000..223527b00 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/slurm_eslim_D_stage2_from109.sh @@ -0,0 +1,199 @@ +#!/usr/bin/env bash +# Continue eSLIM from the independently audited 109-gate mystery-D candidate. +# +# The input was produced by job 42633 and is pinned by SHA-256 below. COMPLETE +# is written only after reference equivalence and direct x*x+y*y audits pass. +# +#SBATCH --job-name=occ71-eslim-D-s2 +#SBATCH --partition=home +#SBATCH --nodelist=n006 +#SBATCH --nodes=1 +#SBATCH --ntasks=1 +#SBATCH --cpus-per-task=1 +#SBATCH --mem=16G +#SBATCH --time=00:25:00 +#SBATCH --no-requeue +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/logs/eslim-D-stage2-%j.out + +set -Eeuo pipefail +umask 027 + +export LC_ALL=C +export TZ=UTC +export PYTHONHASHSEED=42 +export OMP_NUM_THREADS=1 +export OPENBLAS_NUM_THREADS=1 + +readonly ROOT="/home/user_milksang/private/homefile/quantum_harness/issue71_occam" +readonly SEARCH="${ROOT}/tracks/qcs/solutions/Genshin_Impact-71/search" +readonly BUILD="${ROOT}/results/occam71/tools/eslim-build-51e9f774/job-42598" +readonly ESLIM="${BUILD}/eSLIM/src" +readonly PYTHON="${BUILD}/venv/bin/python" +readonly INPUT="${ROOT}/results/occam71/eslim/D-pilot-build42598-size6-seed42/job-42633/candidate.txt" +readonly EXPECTED_INPUT_SHA="cd3f317f4a0b88818e54869e40b4550fd67549f8eb4a5eb02c74db4ec6864dbd" +readonly RUN_ROOT="${ROOT}/results/occam71/eslim/D-stage2-from109-size6-seed42" +readonly RUN="${RUN_ROOT}/job-${SLURM_JOB_ID}" + +mkdir -p "${RUN_ROOT}" +mkdir "${RUN}" + +STAGE="initialization" +record_failure() { + local code="$1" + local line="$2" + trap - ERR INT TERM + { + printf 'status=FAILED\n' + printf 'stage=%s\n' "${STAGE}" + printf 'exit_code=%s\n' "${code}" + printf 'source_line=%s\n' "${line}" + printf 'end_utc=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" + } > "${RUN}/FAILED" + exit "${code}" +} +trap 'record_failure "$?" "${LINENO}"' ERR +trap 'record_failure 130 "${LINENO}"' INT +trap 'record_failure 143 "${LINENO}"' TERM + +{ + printf 'job_id=%s\n' "${SLURM_JOB_ID}" + printf 'node=%s\n' "$(hostname)" + printf 'start_utc=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" + printf 'source_commit=%s\n' "51e9f77429627473db623058157b66a1192cbb59" + printf 'build_job_id=%s\n' "42598" + printf 'parent_job_id=%s\n' "42633" + printf 'parent_gates=%s\n' "109" + printf 'seed=%s\n' "42" + printf 'limit_seconds=%s\n' "900" + printf 'gate_size=%s\n' "2" + printf 'initial_subcircuit_size=%s\n' "6" + printf 'fixed_subcircuit_size=%s\n' "true" + printf 'max_subcircuit_inputs=%s\n' "10" + printf 'synthesis_mode=%s\n' "relation_sat" + printf 'solver_base_timeout_seconds=%s\n' "30" + printf 'relation_timeout_seconds=%s\n' "30" +} > "${RUN}/metadata.txt" + +STAGE="integrity-check" +grep -qx 'SUCCESS' "${BUILD}/BUILD_READY" +test -x "${PYTHON}" +test -s "${INPUT}" +test -s "${SEARCH}/bridge.py" +test -s "${SEARCH}/verify_candidate.py" +test -s "${SEARCH}/audit_d_formula.py" +printf '%s %s\n' "${EXPECTED_INPUT_SHA}" "${INPUT}" \ + | sha256sum --check - > "${RUN}/parent_check.txt" +sha256sum --check "${BUILD}/built_modules_sha256.txt" \ + > "${RUN}/built_modules_check.txt" +sha256sum \ + "${INPUT}" \ + "${SEARCH}/bridge.py" \ + "${SEARCH}/verify_candidate.py" \ + "${SEARCH}/audit_d_formula.py" \ + "${ESLIM}/reduce.py" \ + > "${RUN}/input_sha256.txt" + +STAGE="parent-formula-audit" +python3 "${SEARCH}/audit_d_formula.py" \ + "${INPUT}" \ + --report "${RUN}/parent-formula-audit.json" \ + > "${RUN}/parent-formula-audit.stdout.txt" + +STAGE="occam-to-blif" +"${PYTHON}" -u "${SEARCH}/bridge.py" to-blif \ + "${INPUT}" \ + "${RUN}/input.partial.blif" \ + --model "mystery_D_stage2_parent_109" +test -s "${RUN}/input.partial.blif" +mv "${RUN}/input.partial.blif" "${RUN}/input.blif" + +cat > "${RUN}/eslim_command.txt" < "${RUN}/eslim.stdout.txt" 2> "${RUN}/eslim.stderr.txt" +printf 'eslim_elapsed_seconds=%s\n' "${SECONDS}" > "${RUN}/timing.txt" +test -s "${RUN}/reduced.partial.blif" +mv "${RUN}/reduced.partial.blif" "${RUN}/reduced.blif" + +STAGE="blif-to-occam" +"${PYTHON}" -u "${SEARCH}/bridge.py" to-occam \ + "${RUN}/reduced.blif" \ + "${RUN}/candidate.partial.txt" +test -s "${RUN}/candidate.partial.txt" +mv "${RUN}/candidate.partial.txt" "${RUN}/candidate.txt" + +STAGE="reference-equivalence-audit" +"${PYTHON}" -u "${SEARCH}/verify_candidate.py" \ + "${INPUT}" \ + "${RUN}/candidate.txt" \ + --report "${RUN}/verification.json" \ + > "${RUN}/verification.stdout.txt" +test -s "${RUN}/verification.json" + +STAGE="direct-formula-audit" +python3 "${SEARCH}/audit_d_formula.py" \ + "${RUN}/candidate.txt" \ + --report "${RUN}/formula-audit.json" \ + > "${RUN}/formula-audit.stdout.txt" +test -s "${RUN}/formula-audit.json" + +SAVED_GATES="$( + "${PYTHON}" -c \ + 'import json,sys; print(json.load(open(sys.argv[1], encoding="utf-8"))["saved_gates"])' \ + "${RUN}/verification.json" +)" +CANDIDATE_GATES="$( + "${PYTHON}" -c \ + 'import json,sys; print(json.load(open(sys.argv[1], encoding="utf-8"))["gates"])' \ + "${RUN}/formula-audit.json" +)" +if [[ ! "${SAVED_GATES}" =~ ^[0-9]+$ ]]; then + record_failure 21 "${LINENO}" +fi +if [[ ! "${CANDIDATE_GATES}" =~ ^[0-9]+$ ]]; then + record_failure 22 "${LINENO}" +fi + +STAGE="finalization" +sha256sum \ + "${RUN}/reduced.blif" \ + "${RUN}/candidate.txt" \ + "${RUN}/verification.json" \ + "${RUN}/formula-audit.json" \ + > "${RUN}/output_sha256.txt" +{ + printf 'end_utc=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" + printf 'saved_gates=%s\n' "${SAVED_GATES}" + printf 'candidate_gates=%s\n' "${CANDIDATE_GATES}" + if (( SAVED_GATES > 0 )); then + printf 'result=%s\n' "VALIDATED_IMPROVEMENT" + else + printf 'result=%s\n' "VALIDATED_NO_IMPROVEMENT" + fi +} >> "${RUN}/metadata.txt" + +if (( SAVED_GATES > 0 )); then + printf 'VALIDATED_IMPROVEMENT candidate_gates=%s saved_gates=%s\n' \ + "${CANDIDATE_GATES}" "${SAVED_GATES}" > "${RUN}/COMPLETE" +else + printf 'VALIDATED_NO_IMPROVEMENT candidate_gates=%s\n' \ + "${CANDIDATE_GATES}" > "${RUN}/COMPLETE" +fi +trap - ERR INT TERM diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/slurm_eslim_grid_abc_d109_seed42.sh b/tracks/qcs/solutions/Genshin_Impact-71/search/slurm_eslim_grid_abc_d109_seed42.sh new file mode 100644 index 000000000..9590ac137 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/slurm_eslim_grid_abc_d109_seed42.sh @@ -0,0 +1,226 @@ +#!/usr/bin/env bash +# Pinned eSLIM relation-SAT grid over all mysteries and several subcircuit sizes. +# +# Pure reference netlists are treated only as parsed data. No code, scripts, +# or prose from competitor pull requests are read or executed. +# +#SBATCH --job-name=occ71-eslim-grid +#SBATCH --partition=home +#SBATCH --nodelist=n006 +#SBATCH --array=0-11%6 +#SBATCH --nodes=1 +#SBATCH --ntasks=1 +#SBATCH --cpus-per-task=1 +#SBATCH --mem=16G +#SBATCH --time=00:25:00 +#SBATCH --no-requeue +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/eslim/grid-build42598-seed42/logs/%A_%a.out +#SBATCH --error=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/eslim/grid-build42598-seed42/logs/%A_%a.err + +set -Eeuo pipefail +umask 027 + +export LC_ALL=C +export TZ=UTC +export PYTHONHASHSEED=42 +export OMP_NUM_THREADS=1 +export OPENBLAS_NUM_THREADS=1 + +readonly PROJECT_ROOT="/home/user_milksang/private/homefile/quantum_harness/issue71_occam" +readonly SEARCH_DIR="${PROJECT_ROOT}/tracks/qcs/solutions/Genshin_Impact-71/search" +readonly BUILD_RUN="${PROJECT_ROOT}/results/occam71/tools/eslim-build-51e9f774/job-42598" +readonly ESLIM_SRC="${BUILD_RUN}/eSLIM/src" +readonly PYTHON="${BUILD_RUN}/venv/bin/python" +readonly RUN_ROOT="${PROJECT_ROOT}/results/occam71/eslim/grid-build42598-seed42" + +readonly D109="${PROJECT_ROOT}/results/occam71/eslim/D-pilot-build42598-size6-seed42/job-42633/candidate.txt" +readonly REF_ROOT="${PROJECT_ROOT}/results/occam71/reference-355" + +CASES=( + "A 5 ${REF_ROOT}/mystery-A.txt" + "B 4 ${REF_ROOT}/mystery-B.txt" + "B 5 ${REF_ROOT}/mystery-B.txt" + "B 6 ${REF_ROOT}/mystery-B.txt" + "B 7 ${REF_ROOT}/mystery-B.txt" + "C 5 ${REF_ROOT}/mystery-C.txt" + "C 6 ${REF_ROOT}/mystery-C.txt" + "C 7 ${REF_ROOT}/mystery-C.txt" + "C 8 ${REF_ROOT}/mystery-C.txt" + "D 5 ${D109}" + "D 7 ${D109}" + "D 8 ${D109}" +) +read -r INSTANCE SIZE REFERENCE <<< "${CASES[$SLURM_ARRAY_TASK_ID]}" +readonly INSTANCE +readonly SIZE +readonly REFERENCE + +LIMIT_SECONDS=900 +if [[ "${INSTANCE}" == "A" ]]; then + LIMIT_SECONDS=600 +fi +readonly LIMIT_SECONDS + +TASK_INDEX=$(printf "%02d" "${SLURM_ARRAY_TASK_ID}") +readonly TASK_INDEX +readonly RUN_DIR="${RUN_ROOT}/${TASK_INDEX}-${INSTANCE}-size${SIZE}-job${SLURM_ARRAY_JOB_ID}" + +mkdir -p "${RUN_ROOT}/logs" +mkdir "${RUN_DIR}" + +CURRENT_STAGE="initialization" +record_failure() { + local exit_code="$1" + local source_line="$2" + trap - ERR INT TERM + { + printf 'status=FAILED\n' + printf 'stage=%s\n' "${CURRENT_STAGE}" + printf 'exit_code=%s\n' "${exit_code}" + printf 'source_line=%s\n' "${source_line}" + printf 'end_utc=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" + } > "${RUN_DIR}/FAILED" + exit "${exit_code}" +} +trap 'record_failure "$?" "${LINENO}"' ERR +trap 'record_failure 130 "${LINENO}"' INT +trap 'record_failure 143 "${LINENO}"' TERM + +{ + printf 'array_job_id=%s\n' "${SLURM_ARRAY_JOB_ID}" + printf 'array_task_id=%s\n' "${SLURM_ARRAY_TASK_ID}" + printf 'node=%s\n' "$(hostname)" + printf 'start_utc=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" + printf 'instance=%s\n' "${INSTANCE}" + printf 'source_commit=%s\n' "51e9f77429627473db623058157b66a1192cbb59" + printf 'build_job_id=%s\n' "42598" + printf 'seed=%s\n' "42" + printf 'limit_seconds=%s\n' "${LIMIT_SECONDS}" + printf 'gate_size=%s\n' "2" + printf 'initial_subcircuit_size=%s\n' "${SIZE}" + printf 'fixed_subcircuit_size=%s\n' "true" + printf 'max_subcircuit_inputs=%s\n' "10" + printf 'synthesis_mode=%s\n' "relation_sat" + printf 'solver_base_timeout_seconds=%s\n' "30" + printf 'relation_timeout_seconds=%s\n' "30" + printf 'reference=%s\n' "${REFERENCE}" +} > "${RUN_DIR}/metadata.txt" + +CURRENT_STAGE="build-integrity-check" +grep -qx 'SUCCESS' "${BUILD_RUN}/BUILD_READY" +test -x "${PYTHON}" +sha256sum --check "${BUILD_RUN}/built_modules_sha256.txt" \ + > "${RUN_DIR}/built_modules_check.txt" + +CURRENT_STAGE="input-integrity-check" +test -s "${REFERENCE}" +test -s "${SEARCH_DIR}/bridge.py" +test -s "${SEARCH_DIR}/verify_candidate.py" +test -s "${ESLIM_SRC}/reduce.py" +sha256sum \ + "${REFERENCE}" \ + "${SEARCH_DIR}/bridge.py" \ + "${SEARCH_DIR}/circuit.py" \ + "${SEARCH_DIR}/verify_candidate.py" \ + "${ESLIM_SRC}/reduce.py" \ + "${ESLIM_SRC}/synthesisManager.py" \ + "${ESLIM_SRC}/subcircuitSynthesiser.py" \ + "${ESLIM_SRC}/relationFromSubcircuit.py" \ + "${ESLIM_SRC}/blifIO.py" \ + > "${RUN_DIR}/input_sha256.txt" + +CURRENT_STAGE="occam-to-blif" +"${PYTHON}" -u "${SEARCH_DIR}/bridge.py" to-blif \ + "${REFERENCE}" \ + "${RUN_DIR}/input.partial.blif" \ + --model "mystery_${INSTANCE}_grid" +test -s "${RUN_DIR}/input.partial.blif" +mv "${RUN_DIR}/input.partial.blif" "${RUN_DIR}/input.blif" + +printf '%q ' \ + "${PYTHON}" -u reduce.py \ + "${RUN_DIR}/input.blif" "${RUN_DIR}/reduced.partial.blif" \ + "${LIMIT_SECONDS}" --gs 2 --seed 42 --syn-mode sat --size "${SIZE}" \ + --limit-inputs 10 --solverTO 30 --relTO 30 --require-reduction \ + > "${RUN_DIR}/eslim_command.txt" +printf '\n' >> "${RUN_DIR}/eslim_command.txt" + +CURRENT_STAGE="eslim-relation-sat" +SECONDS=0 +( + cd "${ESLIM_SRC}" + "${PYTHON}" -u reduce.py \ + "${RUN_DIR}/input.blif" \ + "${RUN_DIR}/reduced.partial.blif" \ + "${LIMIT_SECONDS}" \ + --gs 2 \ + --seed 42 \ + --syn-mode sat \ + --size "${SIZE}" \ + --limit-inputs 10 \ + --solverTO 30 \ + --relTO 30 \ + --require-reduction +) > "${RUN_DIR}/eslim.stdout.txt" 2> "${RUN_DIR}/eslim.stderr.txt" +printf 'eslim_elapsed_seconds=%s\n' "${SECONDS}" > "${RUN_DIR}/timing.txt" +test -s "${RUN_DIR}/reduced.partial.blif" +mv "${RUN_DIR}/reduced.partial.blif" "${RUN_DIR}/reduced.blif" + +CURRENT_STAGE="blif-to-occam" +"${PYTHON}" -u "${SEARCH_DIR}/bridge.py" to-occam \ + "${RUN_DIR}/reduced.blif" \ + "${RUN_DIR}/candidate.partial.txt" +test -s "${RUN_DIR}/candidate.partial.txt" +mv "${RUN_DIR}/candidate.partial.txt" "${RUN_DIR}/candidate.txt" + +CURRENT_STAGE="independent-full-domain-audit" +PYTHONPATH="${SEARCH_DIR}" "${PYTHON}" -u \ + "${SEARCH_DIR}/verify_candidate.py" \ + "${REFERENCE}" \ + "${RUN_DIR}/candidate.txt" \ + --report "${RUN_DIR}/verification.json" \ + > "${RUN_DIR}/verification.stdout.txt" +test -s "${RUN_DIR}/verification.json" + +SAVED_GATES="$( + "${PYTHON}" -c \ + 'import json,sys; print(json.load(open(sys.argv[1], encoding="utf-8"))["saved_gates"])' \ + "${RUN_DIR}/verification.json" +)" +if [[ ! "${SAVED_GATES}" =~ ^-?[0-9]+$ ]] || (( SAVED_GATES < 0 )); then + record_failure 22 "${LINENO}" +fi + +if [[ "${INSTANCE}" == "D" ]]; then + CURRENT_STAGE="independent-direct-formula-audit" + "${PYTHON}" -u "${SEARCH_DIR}/audit_d_formula.py" \ + "${RUN_DIR}/candidate.txt" \ + --report "${RUN_DIR}/formula-audit.json" \ + > "${RUN_DIR}/formula-audit.stdout.txt" + test -s "${RUN_DIR}/formula-audit.json" +fi + +CURRENT_STAGE="finalization" +sha256sum \ + "${RUN_DIR}/reduced.blif" \ + "${RUN_DIR}/candidate.txt" \ + "${RUN_DIR}/verification.json" \ + > "${RUN_DIR}/output_sha256.txt" +{ + printf 'end_utc=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" + printf 'saved_gates=%s\n' "${SAVED_GATES}" + if (( SAVED_GATES > 0 )); then + printf 'result=%s\n' "VALIDATED_IMPROVEMENT" + else + printf 'result=%s\n' "VALIDATED_NO_IMPROVEMENT" + fi +} >> "${RUN_DIR}/metadata.txt" + +if (( SAVED_GATES > 0 )); then + printf 'VALIDATED_IMPROVEMENT saved_gates=%s\n' "${SAVED_GATES}" \ + > "${RUN_DIR}/COMPLETE" +else + printf 'VALIDATED_NO_IMPROVEMENT saved_gates=0\n' \ + > "${RUN_DIR}/COMPLETE" +fi +trap - ERR INT TERM diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/slurm_exact_mffc_C.sh b/tracks/qcs/solutions/Genshin_Impact-71/search/slurm_exact_mffc_C.sh new file mode 100644 index 000000000..8f2988bc6 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/slurm_exact_mffc_C.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +#SBATCH --job-name=occ71-mffc-C +#SBATCH --partition=home +#SBATCH --nodelist=n006 +#SBATCH --nodes=1 +#SBATCH --ntasks=1 +#SBATCH --cpus-per-task=1 +#SBATCH --mem=8G +#SBATCH --time=02:00:00 +#SBATCH --output=/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/logs/mffc-C-%j.out + +set -euo pipefail +umask 027 + +readonly PROJECT_ROOT="/home/user_milksang/private/homefile/quantum_harness/issue71_occam" +readonly SEARCH_DIR="${PROJECT_ROOT}/tracks/qcs/solutions/Genshin_Impact-71/search" +readonly INPUT_NETLIST="${PROJECT_ROOT}/results/occam71/reference-355/mystery-C.txt" +readonly CUT_LIMIT="${1:-6}" +readonly RUN_ROOT="${PROJECT_ROOT}/results/occam71/search/mffc-C-seed42-cut${CUT_LIMIT}" +readonly RUN_DIR="${RUN_ROOT}/job-${SLURM_JOB_ID}" + +mkdir -p "${RUN_DIR}" +{ + printf 'job_id=%s\n' "${SLURM_JOB_ID}" + printf 'node=%s\n' "$(hostname)" + printf 'start_utc=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" + printf 'seed=42\n' + printf 'max_cut_leaves=%s\n' "${CUT_LIMIT}" + printf 'python=%s\n' "$(python3 --version 2>&1)" +} > "${RUN_DIR}/metadata.txt" + +sha256sum \ + "${SEARCH_DIR}/exact_mffc.py" \ + "${SEARCH_DIR}/reduce_netlist.py" \ + "${INPUT_NETLIST}" > "${RUN_DIR}/input_sha256.txt" + +SECONDS=0 +python3 -u "${SEARCH_DIR}/exact_mffc.py" \ + "${INPUT_NETLIST}" \ + --out-dir "${RUN_DIR}/candidate" \ + --seed 42 \ + --max-cut-leaves "${CUT_LIMIT}" +printf 'elapsed_seconds=%s\n' "${SECONDS}" > "${RUN_DIR}/timing.txt" + +printf 'end_utc=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ + >> "${RUN_DIR}/metadata.txt" +printf 'SUCCESS\n' > "${RUN_DIR}/COMPLETE" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/slurm_finalize_frontier347.sbatch b/tracks/qcs/solutions/Genshin_Impact-71/search/slurm_finalize_frontier347.sbatch new file mode 100644 index 000000000..d727a7a44 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/slurm_finalize_frontier347.sbatch @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +#SBATCH --job-name=occ71-f347-audit +#SBATCH --partition=home +#SBATCH --nodes=1 +#SBATCH --ntasks=1 +#SBATCH --cpus-per-task=1 +#SBATCH --mem=4G +#SBATCH --time=00:15:00 + +set -euo pipefail + +repo=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +search="$repo/tracks/qcs/solutions/Genshin_Impact-71/search" +target="$repo/results/occam71/frontier-347" +stage="$target/.finalize-${SLURM_JOB_ID}" + +cd "$repo" +mkdir "$stage" + +publish_once() { + src=$1 + dst=$2 + if test -e "$dst"; then + cmp -s "$src" "$dst" || { + printf 'refusing to replace different existing artifact: %s\n' "$dst" >&2 + exit 1 + } + rm -- "$src" + else + mv "$src" "$dst" + fi +} + +for instance in A B C D; do + python "$search/audit_arithmetic_formula.py" \ + "$instance" \ + "results/occam71/frontier-347/netlists/mystery-${instance}.txt" \ + --report "$stage/audit-${instance}.json" + publish_once "$stage/audit-${instance}.json" "$target/audit-${instance}.json" +done + +python "$search/make_frontier_manifest_v2.py" \ + "$target" \ + --expected-total 347 \ + --c-source 'eSLIM stage2 job 43188 task 3' \ + --c-synthesis 'relation-SAT cascade from C154, window size 7, root seed 42' \ + --output "$stage/manifest.json" +publish_once "$stage/manifest.json" "$target/manifest.json" + +sha256sum \ + "$target"/netlists/mystery-{A,B,C,D}.txt \ + "$target"/audit-{A,B,C,D}.json \ + "$target"/manifest.json \ + > "$stage/SHA256SUMS" +publish_once "$stage/SHA256SUMS" "$target/SHA256SUMS" + +manifest_sha=$(sha256sum "$target/manifest.json" | cut -d' ' -f1) +printf 'status=FRONTIER_347_COMPLETE\nmanifest_sha256=%s\n' "$manifest_sha" \ + > "$stage/COMPLETE" +publish_once "$stage/COMPLETE" "$target/COMPLETE" +rmdir "$stage" + +sha256sum -c "$target/SHA256SUMS" +printf 'FRONTIER_347_COMPLETE\n' diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/solve_warmups.py b/tracks/qcs/solutions/Genshin_Impact-71/search/solve_warmups.py new file mode 100644 index 000000000..62d376813 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/solve_warmups.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +"""Build and independently verify both disclosed issue #71 warm-ups.""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +from circuit import ( + build_adder, + build_multiplier, + read_commitment, + verify_dataset, + verify_formula, + write_predictions, +) + + +def solve_one( + *, + instance: str, + family: str, + circuit, + data_root: Path, + output_root: Path, +) -> dict: + instance_data = data_root / "datasets" / instance + circuit_path = output_root / "circuits" / f"{instance}.txt" + prediction_path = ( + output_root / "predictions" / instance / "test_outputs.csv" + ) + circuit.write(circuit_path) + training = verify_dataset(circuit, instance_data / "train.csv") + exhaustive = verify_formula(circuit, family) + actual_hash = write_predictions( + circuit, instance_data / "test_inputs.csv", prediction_path + ) + expected_hash = read_commitment(instance_data / "commitment.sha256") + result = { + "instance": instance, + "family": family, + "gates": len(circuit.gates), + "training": training, + "exhaustive": exhaustive, + "prediction_sha256": actual_hash, + "commitment_sha256": expected_hash, + "commitment_match": actual_hash == expected_hash, + "circuit": str(circuit_path), + "predictions": str(prediction_path), + } + if training["exact"] != training["rows"]: + raise RuntimeError(f"{instance}: training verification failed") + if exhaustive["failures"]: + raise RuntimeError(f"{instance}: exhaustive formula verification failed") + if actual_hash != expected_hash: + raise RuntimeError(f"{instance}: prediction commitment mismatch") + return result + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--data-root", type=Path, required=True) + parser.add_argument("--output-root", type=Path, required=True) + args = parser.parse_args() + + results = [ + solve_one( + instance="practice-add-n4", + family="add", + circuit=build_adder(4), + data_root=args.data_root, + output_root=args.output_root, + ), + solve_one( + instance="practice-mul-n4", + family="mul", + circuit=build_multiplier(4), + data_root=args.data_root, + output_root=args.output_root, + ), + ] + manifest = { + "schema": "occam71-warmup-v1", + "root_seed": 42, + "note": "Deterministic construction; no random stream was required.", + "results": results, + } + args.output_root.mkdir(parents=True, exist_ok=True) + manifest_path = args.output_root / "warmup_manifest.json" + manifest_path.write_text( + json.dumps(manifest, indent=2, sort_keys=True) + "\n", + encoding="utf-8", + newline="\n", + ) + print(json.dumps(manifest, indent=2, sort_keys=True), flush=True) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/summarize_c_window_safe_v2.py b/tracks/qcs/solutions/Genshin_Impact-71/search/summarize_c_window_safe_v2.py new file mode 100644 index 000000000..6c0d6800c --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/summarize_c_window_safe_v2.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +"""Audit the ranked acyclic mystery-C SAT window array.""" + +from __future__ import annotations + +import argparse +import hashlib +import json +from collections import Counter +from pathlib import Path + + +def sha256(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as stream: + for chunk in iter(lambda: stream.read(1 << 20), b""): + digest.update(chunk) + return digest.hexdigest() + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--root", type=Path, required=True) + parser.add_argument("--ranking", type=Path, required=True) + parser.add_argument("--expected", type=int, default=100) + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + + ranking = json.loads(args.ranking.read_text(encoding="utf-8")) + assert ranking["safety"]["acyclic_boundary"] is True + assert ranking["safety"]["boundary_excludes_root_descendants"] is True + records = ranking["records"][: args.expected] + if len(records) != args.expected: + raise RuntimeError( + f"ranking has {len(records)} records, expected {args.expected}" + ) + + cells: list[dict[str, object]] = [] + status_counts: Counter[str] = Counter() + missing: list[int] = [] + malformed: list[dict[str, object]] = [] + candidates: list[dict[str, object]] = [] + + for index, ranked in enumerate(records): + roots = [str(root) for root in ranked["roots"]] + gates = int(ranked["candidate_gates"]) + expected_name = f"{index:03d}_{roots[0]}_{roots[1]}_k{gates}" + cell = args.root / expected_name + report_path = cell / "report.json" + if not report_path.is_file(): + missing.append(index) + continue + try: + report = json.loads(report_path.read_text(encoding="utf-8")) + if list(report["roots"]) != roots: + raise ValueError("roots disagree with frozen ranking") + if int(report["requested_gate_count"]) != gates: + raise ValueError("gate count disagrees with frozen ranking") + if int(report["removed_gate_count"]) != int(ranked["removed_count"]): + raise ValueError("removed count disagrees with frozen ranking") + status = str(report["status"]) + except (KeyError, TypeError, ValueError, json.JSONDecodeError) as exc: + malformed.append({"index": index, "error": str(exc)}) + continue + + status_counts[status] += 1 + candidate_path = cell / "mystery-C.candidate.txt" + audit_path = cell / "independent-full-domain-audit.json" + entry: dict[str, object] = { + "index": index, + "cell": expected_name, + "roots": roots, + "removed_gate_count": int(report["removed_gate_count"]), + "requested_gate_count": gates, + "boundary": list(report["boundary"]), + "reachable_patterns": int(report["reachable_patterns"]), + "status": status, + "solve_seconds": float(report["solve_seconds"]), + "conflicts": int(report["solver_stats"]["conflicts"]), + "report_sha256": sha256(report_path), + } + if candidate_path.is_file(): + entry["candidate_sha256"] = sha256(candidate_path) + entry["candidate_bytes"] = candidate_path.stat().st_size + if not audit_path.is_file(): + entry["audit_error"] = "candidate exists without independent audit" + else: + audit = json.loads(audit_path.read_text(encoding="utf-8")) + entry["audit_sha256"] = sha256(audit_path) + entry["audit"] = audit + candidates.append(entry) + cells.append(entry) + + payload = { + "ranking": str(args.ranking), + "ranking_sha256": sha256(args.ranking), + "expected_cells": args.expected, + "completed_reports": len(cells), + "missing_indices": missing, + "malformed": malformed, + "status_counts": dict(sorted(status_counts.items())), + "candidate_count": len(candidates), + "candidates": candidates, + "cells": cells, + "complete": ( + len(cells) == args.expected + and not missing + and not malformed + and sum(status_counts.values()) == args.expected + ), + } + rendered = json.dumps(payload, indent=2, sort_keys=True) + "\n" + args.output.parent.mkdir(parents=True, exist_ok=True) + temporary = args.output.with_suffix(args.output.suffix + ".tmp") + temporary.write_text(rendered, encoding="utf-8") + temporary.replace(args.output) + print( + json.dumps( + { + "complete": payload["complete"], + "status_counts": payload["status_counts"], + "candidate_count": payload["candidate_count"], + "output": str(args.output), + "output_sha256": sha256(args.output), + }, + sort_keys=True, + ) + ) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/summarize_window_safe_v3.py b/tracks/qcs/solutions/Genshin_Impact-71/search/summarize_window_safe_v3.py new file mode 100644 index 000000000..7c7aa1f97 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/summarize_window_safe_v3.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 +"""Audit an acyclic, functional-boundary ranked SAT window array.""" + +from __future__ import annotations + +import argparse +import hashlib +import json +from collections import Counter +from pathlib import Path + + +def sha256(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as stream: + for chunk in iter(lambda: stream.read(1 << 20), b""): + digest.update(chunk) + return digest.hexdigest() + + +def load_json(path: Path) -> dict[str, object]: + value = json.loads(path.read_text(encoding="utf-8")) + if not isinstance(value, dict): + raise TypeError(f"{path} does not contain a JSON object") + return value + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--root", type=Path, required=True) + parser.add_argument("--ranking", type=Path, required=True) + parser.add_argument("--expected", type=int, required=True) + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + + ranking = load_json(args.ranking) + safety = ranking["safety"] + assert isinstance(safety, dict) + assert safety["acyclic_boundary"] is True + assert safety["boundary_excludes_root_descendants"] is True + assert safety["roots_functional_of_boundary"] is True + records = ranking["records"] + assert isinstance(records, list) + records = records[: args.expected] + if len(records) != args.expected: + raise RuntimeError( + f"ranking has {len(records)} records, expected {args.expected}" + ) + + candidate_filename = Path(str(ranking["netlist"])).stem + ".candidate.txt" + cells: list[dict[str, object]] = [] + status_counts: Counter[str] = Counter() + missing: list[int] = [] + malformed: list[dict[str, object]] = [] + candidates: list[dict[str, object]] = [] + + for index, ranked_untyped in enumerate(records): + assert isinstance(ranked_untyped, dict) + ranked = ranked_untyped + roots = [str(root) for root in ranked["roots"]] + gates = int(ranked["candidate_gates"]) + expected_name = f"{index:03d}_{roots[0]}_{roots[1]}_k{gates}" + cell = args.root / expected_name + report_path = cell / "report.json" + if not report_path.is_file(): + missing.append(index) + continue + try: + report = load_json(report_path) + if list(report["roots"]) != roots: + raise ValueError("roots disagree with frozen ranking") + if int(report["requested_gate_count"]) != gates: + raise ValueError("gate count disagrees with frozen ranking") + if int(report["removed_gate_count"]) != int(ranked["removed_count"]): + raise ValueError("removed count disagrees with frozen ranking") + status = str(report["status"]) + except (KeyError, TypeError, ValueError, json.JSONDecodeError) as exc: + malformed.append({"index": index, "error": str(exc)}) + continue + + status_counts[status] += 1 + entry: dict[str, object] = { + "index": index, + "cell": expected_name, + "roots": roots, + "removed_gate_count": int(report["removed_gate_count"]), + "requested_gate_count": gates, + "boundary": list(report["boundary"]), + "reachable_patterns": int(report["reachable_patterns"]), + "status": status, + "solve_seconds": float(report["solve_seconds"]), + "conflicts": int(report["solver_stats"]["conflicts"]), + "report_sha256": sha256(report_path), + } + + candidate_path = cell / candidate_filename + audit_path = cell / "independent-full-domain-audit.json" + formula_path = cell / "direct-formula-audit.json" + if candidate_path.is_file(): + entry["candidate_sha256"] = sha256(candidate_path) + if not audit_path.is_file(): + entry["audit_error"] = "candidate exists without independent audit" + else: + entry["audit_sha256"] = sha256(audit_path) + entry["audit"] = load_json(audit_path) + if formula_path.is_file(): + entry["formula_audit_sha256"] = sha256(formula_path) + entry["formula_audit"] = load_json(formula_path) + candidates.append(entry) + cells.append(entry) + + payload = { + "ranking": str(args.ranking), + "ranking_sha256": sha256(args.ranking), + "candidate_filename": candidate_filename, + "expected_cells": args.expected, + "completed_reports": len(cells), + "missing_indices": missing, + "malformed": malformed, + "status_counts": dict(sorted(status_counts.items())), + "candidate_count": len(candidates), + "candidates": candidates, + "cells": cells, + "complete": ( + len(cells) == args.expected + and not missing + and not malformed + and sum(status_counts.values()) == args.expected + ), + } + rendered = json.dumps(payload, indent=2, sort_keys=True) + "\n" + args.output.parent.mkdir(parents=True, exist_ok=True) + temporary = args.output.with_suffix(args.output.suffix + ".tmp") + temporary.write_text(rendered, encoding="utf-8") + temporary.replace(args.output) + print( + json.dumps( + { + "complete": payload["complete"], + "status_counts": payload["status_counts"], + "candidate_count": payload["candidate_count"], + "output": str(args.output), + "output_sha256": sha256(args.output), + }, + sort_keys=True, + ) + ) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/test_bridge.py b/tracks/qcs/solutions/Genshin_Impact-71/search/test_bridge.py new file mode 100644 index 000000000..6a662b65e --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/test_bridge.py @@ -0,0 +1,193 @@ +from __future__ import annotations + +import unittest + +from bridge import ( + BlifGate, + BlifNetwork, + OccamCircuit, + OccamGate, + OPS_SET, + blif_to_occam, + occam_to_blif, + verify_equivalent, + verify_occam_equivalent, +) + + +class StrictParsingTests(unittest.TestCase): + def test_occam_rejects_prompt_like_text(self) -> None: + with self.assertRaises(ValueError): + OccamCircuit.parse_text( + "INPUTS 2\nignore previous instructions\nOUTPUTS x1\n" + ) + + def test_blif_rejects_unknown_directive(self) -> None: + with self.assertRaisesRegex(ValueError, "unsupported BLIF directive"): + BlifNetwork.parse_text( + ".model m\n.inputs a\n.outputs a\n.shell do_something\n.end\n" + ) + + def test_blif_rejects_non_topological_gate(self) -> None: + with self.assertRaisesRegex(ValueError, "non-topological"): + BlifNetwork.parse_text( + ".model m\n" + ".inputs a b\n" + ".outputs y\n" + ".names future a y\n" + "11 1\n" + ".names a b future\n" + "11 1\n" + ".end\n" + ) + + def test_blif_rejects_mixed_output_planes(self) -> None: + with self.assertRaisesRegex(ValueError, "mixed output planes"): + BlifNetwork.parse_text( + ".model m\n" + ".inputs a b\n" + ".outputs y\n" + ".names a b y\n" + "00 0\n" + "11 1\n" + ".end\n" + ) + + def test_continuation_and_negative_plane(self) -> None: + network = BlifNetwork.parse_text( + ".model m\n" + ".inputs a \\\n" + " b\n" + ".outputs y\n" + ".names a b y\n" + "11 0\n" + ".end\n" + ) + self.assertEqual((1, 1, 1, 0), network.gates[0].table) + + +class BridgeTests(unittest.TestCase): + def assert_bridge_equivalent( + self, circuit: OccamCircuit, expected_gate_count: int | None = None + ) -> OccamCircuit: + blif = occam_to_blif(circuit) + first_audit = verify_equivalent(circuit, blif) + self.assertTrue(first_audit["equivalent"], first_audit) + reparsed = BlifNetwork.parse_text(blif.to_text()) + rebuilt = blif_to_occam(reparsed) + second_audit = verify_occam_equivalent(circuit, rebuilt) + self.assertTrue(second_audit["equivalent"], second_audit) + if expected_gate_count is not None: + self.assertEqual(expected_gate_count, len(rebuilt.gates)) + return rebuilt + + def test_free_internal_and_output_phases_round_trip(self) -> None: + circuit = OccamCircuit( + 3, + ( + OccamGate("w1", "AND", "~x1", "x2"), + OccamGate("w2", "XOR", "~w1", "x3"), + OccamGate("w3", "NOR", "w1", "~w2"), + ), + ("~w3", "w2", "~w1"), + ) + rebuilt = self.assert_bridge_equivalent(circuit, 3) + self.assertTrue(all(gate.op in OPS_SET for gate in rebuilt.gates)) + + def test_output_in_both_polarities_costs_no_occam_gate(self) -> None: + circuit = OccamCircuit( + 2, + (OccamGate("w1", "AND", "x1", "x2"),), + ("w1", "~w1"), + ) + blif = occam_to_blif(circuit) + self.assertEqual(2, len(blif.gates)) + rebuilt = blif_to_occam(blif) + self.assertEqual(1, len(rebuilt.gates)) + self.assertTrue(verify_occam_equivalent(circuit, rebuilt)["equivalent"]) + + def test_complemented_primary_output_is_free_after_return(self) -> None: + circuit = OccamCircuit(2, (), ("~x1", "x1", "~x2")) + blif = occam_to_blif(circuit) + rebuilt = blif_to_occam(blif) + self.assertEqual(0, len(rebuilt.gates)) + self.assertTrue(verify_occam_equivalent(circuit, rebuilt)["equivalent"]) + + def test_numeric_eslim_style_identifiers(self) -> None: + network = BlifNetwork.parse_text( + ".model spec\n" + ".inputs 1 2\n" + ".outputs 4 5\n" + ".names 1 2 3\n" + "01 1\n" + "10 1\n" + ".names 3 4\n" + "0 1\n" + ".names 3 5\n" + "1 1\n" + ".end\n" + ) + circuit = blif_to_occam(network) + self.assertEqual(1, len(circuit.gates)) + self.assertTrue(verify_equivalent(circuit, network)["equivalent"]) + + def test_all_sixteen_binary_functions(self) -> None: + for mask in range(16): + with self.subTest(mask=mask): + table = tuple((mask >> index) & 1 for index in range(4)) + network = BlifNetwork( + "all_functions", + ("a", "b"), + ("y",), + (BlifGate("y", ("a", "b"), table),), + ) + circuit = blif_to_occam(network) + dependencies = [] + if table[0] != table[2] or table[1] != table[3]: + dependencies.append("a") + if table[0] != table[1] or table[2] != table[3]: + dependencies.append("b") + expected = 1 if len(dependencies) in {0, 2} else 0 + self.assertEqual(expected, len(circuit.gates)) + self.assertTrue( + verify_equivalent(circuit, network)["equivalent"] + ) + self.assertTrue( + all(gate.op in OPS_SET for gate in circuit.gates) + ) + + def test_constants_are_propagated_and_shared(self) -> None: + network = BlifNetwork( + "constants", + ("a", "b"), + ("zero", "one", "projection"), + ( + BlifGate("zero", (), (0,)), + BlifGate("one", (), (1,)), + BlifGate("projection", ("a", "zero"), (0, 0, 1, 1)), + ), + ) + circuit = blif_to_occam(network) + self.assertEqual(1, len(circuit.gates)) + self.assertEqual(("w1", "~w1", "x1"), circuit.outputs) + self.assertTrue(verify_equivalent(circuit, network)["equivalent"]) + + def test_complete_truth_table_round_trip(self) -> None: + circuit = OccamCircuit( + 4, + ( + OccamGate("w1", "XOR", "x1", "~x2"), + OccamGate("w2", "NAND", "x3", "x4"), + OccamGate("w3", "OR", "~w1", "w2"), + OccamGate("w4", "XNOR", "w1", "~w3"), + ), + ("w1", "~w2", "w3", "~w4"), + ) + rebuilt = self.assert_bridge_equivalent(circuit, 4) + for assignment in range(16): + bits = f"{assignment:04b}" + self.assertEqual(circuit.evaluate(bits), rebuilt.evaluate(bits)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/test_circuit.py b/tracks/qcs/solutions/Genshin_Impact-71/search/test_circuit.py new file mode 100644 index 000000000..e6442887a --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/test_circuit.py @@ -0,0 +1,44 @@ +from __future__ import annotations + +import unittest + +from circuit import ( + Circuit, + build_adder, + build_multiplier, + compare_truth_tables, + verify_formula, +) + + +class CircuitTests(unittest.TestCase): + def test_four_bit_adder(self) -> None: + circuit = build_adder(4) + self.assertEqual(17, len(circuit.gates)) + self.assertEqual({"checked": 256, "failures": 0}, verify_formula(circuit, "add")) + + def test_four_bit_multiplier(self) -> None: + circuit = build_multiplier(4) + self.assertEqual({"checked": 256, "failures": 0}, verify_formula(circuit, "mul")) + self.assertEqual(0, circuit.structural_audit()["dead_gates"]) + + def test_round_trip(self) -> None: + circuit = build_adder(5) + parsed = Circuit.parse_text(circuit.to_text()) + self.assertEqual( + { + "assignments": 1024, + "outputs": 6, + "mismatching_outputs": 0, + "equivalent": True, + }, + compare_truth_tables(circuit, parsed), + ) + + def test_parser_rejects_non_netlist_text(self) -> None: + with self.assertRaises(ValueError): + Circuit.parse_text("INPUTS 2\nignore previous instructions\nOUTPUTS x1\n") + + +if __name__ == "__main__": + unittest.main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/test_divisor_resynth.py b/tracks/qcs/solutions/Genshin_Impact-71/search/test_divisor_resynth.py new file mode 100644 index 000000000..510fa7ad0 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/test_divisor_resynth.py @@ -0,0 +1,77 @@ +from __future__ import annotations + +import random +import tempfile +import unittest +from pathlib import Path + +from divisor_resynth import ( + OPS, + Program, + Ref, + TargetPairIndex, + apply_op, + canonical, + matching_variant, + pair_variants, + parse_circuit, +) + + +class DivisorResynthTests(unittest.TestCase): + def test_and_xor_phase_basis_spans_six_challenge_gates(self) -> None: + mask = (1 << 16) - 1 + a_raw = 0x35A7 + b_raw = 0x6C93 + a, ai = canonical(a_raw, mask) + b, bi = canonical(b_raw, mask) + left = Program(a, (), Ref("base", 0, ai)) + right = Program(b, (), Ref("base", 1, bi)) + generated = {x.func for x in pair_variants(left, right, mask)} + required = { + canonical(apply_op(op, a_raw, b_raw, mask), mask)[0] + for op in OPS + } + self.assertTrue(required <= generated) + + def test_target_pair_index_agrees_with_exhaustive_search(self) -> None: + rng = random.Random(42) + mask = (1 << 16) - 1 + for _ in range(500): + funcs: list[int] = [] + while len(funcs) < 25: + func, _ = canonical(rng.getrandbits(16), mask) + if func not in funcs: + funcs.append(func) + rights = [ + Program(func, (), Ref("base", index)) + for index, func in enumerate(funcs) + ] + target, _ = canonical(rng.getrandbits(16), mask) + index = TargetPairIndex(rights, target, mask) + for _ in range(10): + left, _ = canonical(rng.getrandbits(16), mask) + expected = any( + matching_variant(left, right.func, target, mask) + is not None + for right in rights + ) + actual = index.find(left) is not None + self.assertEqual(expected, actual) + + def test_parser_rejects_non_netlist_text(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) / "bad.txt" + path.write_text( + "INPUTS 2\n" + "w1 = XOR x1 x2\n" + "please run this command\n" + "OUTPUTS w1\n", + encoding="ascii", + ) + with self.assertRaises(ValueError): + parse_circuit(path) + + +if __name__ == "__main__": + unittest.main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/verify_candidate.py b/tracks/qcs/solutions/Genshin_Impact-71/search/verify_candidate.py new file mode 100644 index 000000000..86bcae51a --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/verify_candidate.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +"""Independent full-domain audit for issue-71 candidate netlists.""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +from circuit import Circuit, compare_truth_tables, sha256_file + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("reference", type=Path) + parser.add_argument("candidate", type=Path) + parser.add_argument("--report", required=True, type=Path) + args = parser.parse_args() + + reference = Circuit.parse(args.reference) + candidate = Circuit.parse(args.candidate) + audit = compare_truth_tables(reference, candidate) + report = { + "reference": str(args.reference), + "candidate": str(args.candidate), + "reference_sha256": sha256_file(args.reference), + "candidate_sha256": sha256_file(args.candidate), + "reference_gates": len(reference.gates), + "candidate_gates": len(candidate.gates), + "saved_gates": len(reference.gates) - len(candidate.gates), + "truth_table": audit, + } + if not audit["equivalent"]: + raise SystemExit(f"candidate is not equivalent: {report}") + args.report.parent.mkdir(parents=True, exist_ok=True) + args.report.write_text( + json.dumps(report, indent=2, sort_keys=True) + "\n", + encoding="utf-8", + newline="\n", + ) + print(json.dumps(report, indent=2, sort_keys=True)) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/search/window_search.py b/tracks/qcs/solutions/Genshin_Impact-71/search/window_search.py new file mode 100644 index 000000000..ade31384a --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/search/window_search.py @@ -0,0 +1,333 @@ +#!/usr/bin/env python3 +"""Independent structural and exact-window search for issue 71 mystery-D. + +The only external input consumed by this program is the plain circuit netlist. +All equivalence checks are exhaustive over the 2**10 primary-input assignments. +Negation is treated as free, exactly as in the challenge cost model. +""" + +from __future__ import annotations + +import argparse +import hashlib +import itertools +import json +import random +import time +from collections import Counter, defaultdict, deque +from dataclasses import dataclass +from pathlib import Path +from typing import Iterable, Iterator, Sequence + + +OPS = {"AND", "OR", "XOR", "NAND", "NOR", "XNOR"} + + +@dataclass(frozen=True) +class Token: + name: str + inv: bool = False + + @classmethod + def parse(cls, text: str) -> "Token": + return cls(text[1:], True) if text.startswith("~") else cls(text, False) + + def text(self) -> str: + return ("~" if self.inv else "") + self.name + + +@dataclass(frozen=True) +class Gate: + out: str + op: str + left: Token + right: Token + + +@dataclass +class Circuit: + n_inputs: int + gates: list[Gate] + outputs: list[Token] + + @property + def names(self) -> list[str]: + return [f"x{i}" for i in range(1, self.n_inputs + 1)] + [ + gate.out for gate in self.gates + ] + + +def parse_circuit(path: Path) -> Circuit: + lines = [ + line.strip() + for line in path.read_text(encoding="utf-8").splitlines() + if line.strip() and not line.lstrip().startswith("#") + ] + if not lines or not lines[0].startswith("INPUTS "): + raise ValueError("missing INPUTS header") + n_inputs = int(lines[0].split()[1]) + gates: list[Gate] = [] + outputs: list[Token] | None = None + defined = {f"x{i}" for i in range(1, n_inputs + 1)} + for lineno, line in enumerate(lines[1:], start=2): + if line.startswith("OUTPUTS "): + if outputs is not None: + raise ValueError(f"duplicate OUTPUTS at line {lineno}") + outputs = [Token.parse(item) for item in line.split()[1:]] + continue + if outputs is not None: + raise ValueError(f"gate after OUTPUTS at line {lineno}") + fields = line.split() + if len(fields) != 5 or fields[1] != "=" or fields[2] not in OPS: + raise ValueError(f"bad gate syntax at line {lineno}: {line!r}") + out, _, op, left_s, right_s = fields + left, right = Token.parse(left_s), Token.parse(right_s) + if out in defined: + raise ValueError(f"redefinition at line {lineno}: {out}") + if left.name not in defined or right.name not in defined: + raise ValueError(f"forward/unknown reference at line {lineno}") + gates.append(Gate(out, op, left, right)) + defined.add(out) + if outputs is None: + raise ValueError("missing OUTPUTS footer") + for token in outputs: + if token.name not in defined: + raise ValueError(f"unknown output {token.text()}") + return Circuit(n_inputs, gates, outputs) + + +def input_truth_tables(n_inputs: int) -> tuple[int, dict[str, int]]: + rows = 1 << n_inputs + mask = (1 << rows) - 1 + tables: dict[str, int] = {} + for bit in range(n_inputs): + value = 0 + for row in range(rows): + if (row >> bit) & 1: + value |= 1 << row + tables[f"x{bit + 1}"] = value + return mask, tables + + +def apply_gate(op: str, a: int, b: int, mask: int) -> int: + if op == "AND": + return a & b + if op == "OR": + return a | b + if op == "XOR": + return a ^ b + if op == "NAND": + return mask ^ (a & b) + if op == "NOR": + return mask ^ (a | b) + if op == "XNOR": + return mask ^ (a ^ b) + raise AssertionError(op) + + +def evaluate(circuit: Circuit) -> tuple[int, dict[str, int], tuple[int, ...]]: + mask, values = input_truth_tables(circuit.n_inputs) + for gate in circuit.gates: + a = values[gate.left.name] ^ (mask if gate.left.inv else 0) + b = values[gate.right.name] ^ (mask if gate.right.inv else 0) + values[gate.out] = apply_gate(gate.op, a, b, mask) + outputs = tuple( + values[token.name] ^ (mask if token.inv else 0) + for token in circuit.outputs + ) + return mask, values, outputs + + +def canonical(value: int, mask: int) -> tuple[int, bool]: + """Return phase-canonical truth table and whether value was complemented.""" + inv = mask ^ value + if inv < value: + return inv, True + return value, False + + +def structural_maps(circuit: Circuit): + gate_by_out = {gate.out: gate for gate in circuit.gates} + fanout: dict[str, set[str]] = defaultdict(set) + for gate in circuit.gates: + fanout[gate.left.name].add(gate.out) + fanout[gate.right.name].add(gate.out) + for index, output in enumerate(circuit.outputs): + fanout[output.name].add(f"@o{index}") + return gate_by_out, fanout + + +def removable_closure( + circuit: Circuit, roots: Iterable[str] +) -> tuple[set[str], set[str]]: + """Gates deleted by replacing all roots; returns (removed, descendants). + + A predecessor becomes removable exactly when every one of its fanouts is + already removable. Output pseudo-fanouts prevent accidental output deletion. + Descendants cannot be used as divisors because that would create a cycle. + """ + gate_by_out, fanout = structural_maps(circuit) + removed = set(roots) + queue = deque(removed) + while queue: + name = queue.popleft() + gate = gate_by_out.get(name) + if gate is None: + continue + for pred in {gate.left.name, gate.right.name}: + if pred not in gate_by_out or pred in removed: + continue + if fanout[pred] <= removed: + removed.add(pred) + queue.append(pred) + + children: dict[str, set[str]] = defaultdict(set) + for gate in circuit.gates: + children[gate.left.name].add(gate.out) + children[gate.right.name].add(gate.out) + descendants = set(roots) + queue = deque(roots) + while queue: + name = queue.popleft() + for child in children[name]: + if child not in descendants: + descendants.add(child) + queue.append(child) + return removed, descendants + + +def support_sets(circuit: Circuit) -> dict[str, frozenset[str]]: + supports = { + f"x{i}": frozenset({f"x{i}"}) + for i in range(1, circuit.n_inputs + 1) + } + for gate in circuit.gates: + supports[gate.out] = supports[gate.left.name] | supports[gate.right.name] + return supports + + +def cone(circuit: Circuit, root: str) -> set[str]: + gate_by_out, _ = structural_maps(circuit) + seen: set[str] = set() + todo = [root] + while todo: + name = todo.pop() + gate = gate_by_out.get(name) + if gate is None or name in seen: + continue + seen.add(name) + todo.extend((gate.left.name, gate.right.name)) + return seen + + +def analyse(circuit: Circuit, values: dict[str, int], mask: int) -> dict: + gate_by_out, fanout = structural_maps(circuit) + supports = support_sets(circuit) + output_names = [token.name for token in circuit.outputs] + mffcs = {} + for gate in circuit.gates: + removed, descendants = removable_closure(circuit, [gate.out]) + mffcs[gate.out] = len(removed) + + output_cones = {name: cone(circuit, name) for name in output_names} + overlaps = [] + for i, left in enumerate(output_names): + for right in output_names[i + 1 :]: + common = output_cones[left] & output_cones[right] + if common: + overlaps.append( + { + "left": left, + "right": right, + "common_gates": len(common), + "union_gates": len(output_cones[left] | output_cones[right]), + } + ) + + # Candidate pairs whose simultaneous replacement exposes more logic than + # either independent MFFC. These are the useful multi-output windows. + synergy = [] + gates = [gate.out for gate in circuit.gates] + for i, left in enumerate(gates): + left_removed, _ = removable_closure(circuit, [left]) + for right in gates[i + 1 :]: + if right in cone(circuit, left) or left in cone(circuit, right): + continue + right_removed, _ = removable_closure(circuit, [right]) + both, descendants = removable_closure(circuit, [left, right]) + bonus = len(both) - len(left_removed | right_removed) + if len(both) >= 3 and (bonus > 0 or len(left_removed) + len(right_removed) >= 4): + synergy.append( + { + "left": left, + "right": right, + "removed": len(both), + "independent_union": len(left_removed | right_removed), + "synergy": bonus, + "left_mffc": len(left_removed), + "right_mffc": len(right_removed), + } + ) + synergy.sort( + key=lambda item: ( + item["synergy"], + item["removed"], + item["left_mffc"] + item["right_mffc"], + ), + reverse=True, + ) + + phase_classes: dict[int, list[str]] = defaultdict(list) + for name, value in values.items(): + phase_classes[canonical(value, mask)[0]].append(name) + duplicates = [names for names in phase_classes.values() if len(names) > 1] + + return { + "n_inputs": circuit.n_inputs, + "n_gates": len(circuit.gates), + "n_outputs": len(circuit.outputs), + "outputs": [token.text() for token in circuit.outputs], + "gate_type_counts": dict(Counter(gate.op for gate in circuit.gates)), + "fanout_histogram": dict( + sorted(Counter(len(fanout[gate.out]) for gate in circuit.gates).items()) + ), + "support_histogram": dict( + sorted(Counter(len(supports[gate.out]) for gate in circuit.gates).items()) + ), + "largest_mffcs": sorted( + ({"root": name, "size": size} for name, size in mffcs.items()), + key=lambda item: item["size"], + reverse=True, + )[:30], + "output_cone_sizes": { + name: len(output_cones[name]) for name in output_names + }, + "output_cone_overlaps": sorted( + overlaps, key=lambda item: item["common_gates"], reverse=True + ), + "multi_root_candidates": synergy[:100], + "phase_duplicate_classes": duplicates, + } + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("netlist", type=Path) + parser.add_argument("--report", type=Path) + args = parser.parse_args() + circuit = parse_circuit(args.netlist) + mask, values, outputs = evaluate(circuit) + report = analyse(circuit, values, mask) + digest = hashlib.sha256() + for table in outputs: + digest.update(table.to_bytes((mask.bit_length() + 7) // 8, "little")) + report["semantic_sha256"] = digest.hexdigest() + text = json.dumps(report, indent=2, sort_keys=True) + print(text) + if args.report: + args.report.parent.mkdir(parents=True, exist_ok=True) + args.report.write_text(text + "\n", encoding="utf-8") + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/TN_DESIGN.md b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/TN_DESIGN.md new file mode 100644 index 000000000..b6c80ddf6 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/TN_DESIGN.md @@ -0,0 +1,47 @@ +# Issue 71 tensor-network arm: preregistered pilot + +## Security and evidence boundary + +- Only official `train.csv` files are read by training and rank diagnostics. +- Strict ASCII/shape validation rejects malformed CSV rows and duplicate inputs. +- Training code contains no `eval`, `exec`, subprocess, shell, network, pickle, + or dynamic import path. +- No competitor PR prose or code is consumed. +- Full arithmetic truth is isolated in `tn_truth.py`; `train_mps.py` and + `rank_diag.py` never import it. `audit_mps.py` imports it only after a model + artifact and SHA-256 are frozen. + +## Pilot hypotheses + +1. LSB-interleaved order should expose a lower train-unfolding completion rank + and better low-bond MPS generalization for addition than blocked order. +2. Even the 4-by-4 multiplication warmup should require a larger effective + bond than addition, foreshadowing the known one-dimensional MPS/OBDD + bottleneck for multiplication. +3. A continuous MPS can be a useful hypothesis-discovery diagnostic, but a + thresholded continuous fit is not by itself a legal small Boolean circuit. + +## Algorithms + +- Rank proxy: for every order/cut/output bit, fit real low-rank matrix + completions at ranks 1, 2, 4, and 8 using only an 80% split of official + training coordinates. Report held-out sign accuracy, RMSE, and category + coverage. This is explicitly a proxy, not an exact hidden-rank certificate. +- MPS baseline: one scalar real MPS per output bit, one-site alternating ridge + least squares, QR gauge transport, deterministic PCG inner solves, root seed + 42, and an 80/20 train-only split. +- Pilot sweep: both warmups, blocked-LSB and interleaved-LSB order, maximum bond + 2 and 4, ridge 1e-5, at most 6 sweeps, patience 2. +- Audit: exhaustive 256-input evaluation after each frozen model. Exact + full-target Boolean and signed TT ranks are computed only as audit diagnostics. + +## Promotion criteria + +The full A/B/C/D sweep is promoted only if the pilot: + +- completes without non-finite values or parser/audit failures; +- shows at least one configuration above chance on held-out rows; +- writes complete JSON reports, model hashes, and a completion sentinel. + +The full sweep will compare all four fixed variable orders and bonds +2, 4, 8, and 16. No full-domain metric will select a hyperparameter. diff --git a/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/TN_DISTILL_DESIGN.md b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/TN_DISTILL_DESIGN.md new file mode 100644 index 000000000..3d387308e --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/TN_DISTILL_DESIGN.md @@ -0,0 +1,19 @@ +# Issue 71: continuous MPS to ROBDD to gate-netlist distillation + +This audit-only stage completes the conversion path suggested by the issue. + +- Select exactly one frozen MPS per mystery instance using only validation + exact accuracy, validation bit accuracy, lower validation RMSE, then smaller + bond. Full-domain accuracy is not part of selection. +- Exhaustively threshold that already-frozen MPS. +- Reduce the resulting multi-output truth table into a shared ordered BDD using + the selected MPS variable order. +- Convert every BDD node to simplified AND/OR MUX logic, with free inversions, + and write a syntactically legal issue-71 netlist. +- Independently parse and bit-parallel simulate the serialized netlist over the + complete domain. It must exactly equal the thresholded MPS predictions. +- Separately report mismatches to the arithmetic truth. Any nonzero mismatch + makes the netlist an analysis artifact, not a challenge candidate. + +The four cells use root seed 42 indirectly through the immutable selected MPS +artifacts from Slurm array 42698. Each requests one CPU and 8 GiB. diff --git a/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/TN_FULL_DESIGN.md b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/TN_FULL_DESIGN.md new file mode 100644 index 000000000..8f7bde145 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/TN_FULL_DESIGN.md @@ -0,0 +1,31 @@ +# Issue 71 tensor-network arm: promoted full sweep + +Pilot job 42645 met the preregistered promotion criteria. The full sweep uses +only official training coordinates and self-written, audited code. + +## Fixed matrix + +- 16 train-only unfolding-rank cells: four mystery instances by four fixed + orders, completion ranks 1, 2, 4, 8, and 16, eight ALS iterations. +- 64 train-only MPS cells: four instances by four fixed orders by maximum bond + 2, 4, 8, and 16, ridge 1e-5, six sweeps, patience two, root seed 42. +- Every MPS cell freezes a model and SHA-256 before launching the independent + full-domain audit process. +- Model choice inside each instance/order uses validation exact accuracy, then + validation bit accuracy, then lower validation RMSE, then smaller bond. + Full-domain metrics never select a model or hyperparameter. + +## Resource estimate + +The warmup pilot completed 8 MPS configurations and 4 rank diagnostics in +under one minute on 4 requested CPU cores. The promoted instances have at most +2000 samples, 16 sites, 12 outputs, and maximum bond 16. The largest one-site +feature block is at most about 1000 by 256 float64 values (about 2 MiB); +rank-factor arrays and 256 by 256 audit matricizations are also small. Each +independent task requests 1 CPU and 4--6 GiB, with at most 16 concurrent tasks. +The 1 h rank and 2 h MPS limits are conservative relative to the pilot. + +## Interpretation guardrail + +A continuous thresholded MPS with any full-domain mismatch is not exact Boolean +recovery and is not a legal candidate circuit, regardless of its bit accuracy. diff --git a/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/TN_FULL_DESIGN_V2.md b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/TN_FULL_DESIGN_V2.md new file mode 100644 index 000000000..2c05a3ebc --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/TN_FULL_DESIGN_V2.md @@ -0,0 +1,31 @@ +# Issue 71 tensor-network arm: promoted full sweep (v2) + +Pilot job 42645 met the preregistered promotion criteria. The full sweep uses +only official training coordinates and self-written, audited code. + +## Fixed matrix + +- 16 train-only unfolding-rank cells: four mystery instances by four fixed + orders, completion ranks 1, 2, 4, 8, and 16, eight ALS iterations. +- 64 train-only MPS cells: four instances by four fixed orders by maximum bond + 2, 4, 8, and 16, ridge 1e-5, six sweeps, patience two, root seed 42. +- Every MPS cell freezes a model and SHA-256 before launching the independent + full-domain audit process. +- Model choice inside each instance/order uses validation exact accuracy, then + validation bit accuracy, then lower validation RMSE, then smaller bond. + Full-domain metrics never select a model or hyperparameter. + +## Resource estimate + +The warmup pilot completed 8 MPS configurations and 4 rank diagnostics in +about 84 seconds on 4 requested CPU cores. The promoted instances have at most +2000 samples, 16 sites, 12 outputs, and maximum bond 16. The largest one-site +feature block is at most about 1000 by 256 float64 values (about 2 MiB); +rank-factor arrays and 256 by 256 audit matricizations are also small. Each +independent task requests 1 CPU and 4--6 GiB, with at most 16 concurrent tasks. +The 1 h rank and 2 h MPS limits are conservative relative to the pilot. + +## Interpretation guardrail + +A continuous thresholded MPS with any full-domain mismatch is not exact Boolean +recovery and is not a legal candidate circuit, regardless of its bit accuracy. diff --git a/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/TN_RESULTS.md b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/TN_RESULTS.md new file mode 100644 index 000000000..c0dd4c3fe --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/TN_RESULTS.md @@ -0,0 +1,73 @@ +# Issue 71 张量网络实验结果 + +结论:题面提示的 MPS/BDD 方向对结构诊断很有效,但本次严格 +train-only 的连续 MPS/ALS 没有精确恢复 A/B/C/D 中任何一个函数, +也没有产生可提交的挑战电路。 + +## 运行与证据 + +- warmup pilot:Slurm 42645。 +- train-only unfolding rank:42681,16/16 cells 成功。 +- continuous MPS:42698,64/64 cells 成功。 +- `afterok` 哈希汇总:42747。 +- MPS→共享 ROBDD→门网表:42790,4/4 cells 成功。 +- 所有随机流从根种子 42 稳定派生。 +- 每次提交前均记录 `sinfo`、`squeue` 和 n006 的 + `State`、`CfgTRES`、`AllocTRES`。 + +完整记录见 `submission_record.json` 与 +`distill-job-42790/distill_submission_record.json`。 + +## 精确 TT 秩诊断 + +下表是完整算术真值张量在相应变量序下,各输出位、各 cut 的最大 +Boolean TT rank。它只在训练和模型冻结后的 audit 阶段计算。 + +| 实例 | blocked LSB/MSB | interleaved LSB/MSB | 解释 | +|---|---:|---:|---| +| A:加法 | 255 / 255 | 3 / 3 | 按位交错把 carry 暴露为小状态 | +| B:绝对差 | 128 / 128 | 5 / 5 | 按位交错把 borrow/比较状态暴露出来 | +| C:乘法 | 62 / 62 | 61 / 61 | 一维链序无法消除乘法高秩瓶颈 | +| D:平方和 | 19 / 19 | 30 / 30 | blocked 顺序反而更适合两个独立平方子函数 | + +## Train-only 连续 MPS + +模型选择只使用训练集内部 80/20 划分:先最大化 validation exact, +再最大化 validation bit,随后最小化 validation RMSE,最后偏好较小 χ。 +完整真值准确率没有参与选择。 + +| 实例 | 训练选择的顺序、χ | float64 参数 | validation exact / bit | full exact / bit | 冻结模型 SHA-256 前缀 | +|---|---|---:|---:|---:|---| +| A | interleaved MSB,8 | 13,032 | 0.2225 / 0.87861 | 0.23730 / 0.87882 | `aaaa0b9f` | +| B | interleaved LSB,8 | 8,344 | 0.61333 / 0.93190 | 0.64178 / 0.93243 | `66dd77e3` | +| C | interleaved LSB,8 | 11,232 | 0.05000 / 0.78785 | 0.17383 / 0.81724 | `00adc239` | +| D | blocked MSB,4 | 2,552 | 0.20000 / 0.88750 | 0.34277 / 0.90146 | `d1a50f33` | + +64 个 MPS 配置中,完整定义域 exact 的模型数为 0。高 bit accuracy +不能替代逐行 exact;这些模型不是挑战候选。 + +## 实际 MPS→BDD→电路转换 + +每个实例仅使用上表由 train validation 选出的冻结模型。完整枚举其 +阈值函数,bottom-up hash 成共享多输出 ROBDD,再把每个节点转换成 +简化 MUX 的 AND/OR 网表。独立严格解析器已在完整定义域证明序列化 +网表与对应 MPS 阈值函数完全等价。 + +| 实例 | ROBDD 非终端节点 | 网表门数 | 对真值错误行 | 网表 SHA-256 前缀 | +|---|---:|---:|---:|---| +| A | 13,454 | 27,262 | 49,984 / 65,536 | `15311b03` | +| B | 4,057 | 8,362 | 5,869 / 16,384 | `25e903c1` | +| C | 3,476 | 7,143 | 3,384 / 4,096 | `7845c251` | +| D | 727 | 1,479 | 673 / 1,024 | `442557c7` | + +这些是“近似 MPS 的精确电路”,不是“算术真值的精确电路”; +四个 artifact 均明确标记 `challenge_candidate=false`。 + +## 可复现产物 + +- 主 JSON:`summary-rank42681-mps42698/summary.json` + (SHA-256 `e8b35a5d43f1617484ccbd9d240d1952b8f423c514a412461f06c4c629256d84`)。 +- 图:`summary-rank42681-mps42698/tn-results.png`。 +- 64 个冻结 MPS:`mps-job-42698/cells/`。 +- 16 组 rank 诊断:`rank-job-42681/cells/`。 +- 四个 ROBDD/netlist 报告和网表:`distill-job-42790/cells/`。 diff --git a/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/audit_mps.py b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/audit_mps.py new file mode 100644 index 000000000..05a8fdce0 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/audit_mps.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +"""Full-domain audit of a frozen train-only MPS model.""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +import numpy as np + +from tn_common import ( + atomic_json, + classification_metrics, + load_models, + predict_scores, + sha256_file, +) +from tn_truth import enumerate_full_domain, tt_rank_vectors + + +def rank_summary(rank_vectors: list[list[int]]) -> dict: + if not rank_vectors: + return {"per_output": [], "max_across_outputs_by_cut": []} + return { + "per_output": rank_vectors, + "max_across_outputs_by_cut": np.max( + np.asarray(rank_vectors, dtype=np.int64), axis=0 + ).astype(int).tolist(), + } + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--model", required=True, type=Path) + parser.add_argument("--report-out", required=True, type=Path) + parser.add_argument( + "--oracle-ranks", + action="store_true", + help="also compute exact full-target TT ranks; audit-only, never model selection", + ) + args = parser.parse_args() + saved = load_models(args.model) + metadata = saved.metadata + instance = str(metadata["instance"]) + order = [int(value) for value in metadata["order_original_axes"]] + x_bits, y_bits = enumerate_full_domain(instance) + x_ordered = x_bits[:, order] + metrics = classification_metrics(saved.models, x_ordered, y_bits) + scores = np.column_stack( + [predict_scores(output_model, x_ordered) for output_model in saved.models] + ) + predictions = (scores >= 0.0).astype(np.int8) + mismatching_rows = np.flatnonzero(np.any(predictions != y_bits, axis=1)) + report = { + "schema": "occam71-mps-full-domain-audit-v1", + "model_path": str(args.model), + "model_sha256": sha256_file(args.model), + "instance": instance, + "order_name": metadata["order_name"], + "max_bond": metadata["max_bond"], + "full_domain_metrics": metrics, + "mismatching_rows": int(mismatching_rows.size), + "first_mismatching_indices": mismatching_rows[:32].astype(int).tolist(), + "selection_firewall": ( + "Full-domain truth imported only by audit_mps.py after model freeze; " + "these metrics were not used for training or hyperparameter selection." + ), + } + if args.oracle_ranks: + boolean_ranks = tt_rank_vectors(y_bits.astype(np.float64), order) + signed_ranks = tt_rank_vectors( + 2.0 * y_bits.astype(np.float64) - 1.0, order + ) + predicted_ranks = tt_rank_vectors(predictions.astype(np.float64), order) + report["oracle_tt_ranks_audit_only"] = { + "boolean_01": rank_summary(boolean_ranks), + "signed_pm1": rank_summary(signed_ranks), + "thresholded_prediction_01": rank_summary(predicted_ranks), + } + atomic_json(args.report_out, report) + print(json.dumps(report, sort_keys=True), flush=True) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/distill_mps_bdd.py b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/distill_mps_bdd.py new file mode 100644 index 000000000..0a2db40ba --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/distill_mps_bdd.py @@ -0,0 +1,343 @@ +#!/usr/bin/env python3 +"""Distill one train-selected continuous MPS into a shared ROBDD and netlist.""" + +from __future__ import annotations + +import argparse +import hashlib +import json +from collections import Counter +from pathlib import Path + +import numpy as np + +from tn_common import ( + INSTANCE_SPECS, + atomic_json, + load_models, + predict_scores, + sha256_file, +) +from tn_truth import enumerate_full_domain + + +INSTANCES = ("mystery-A", "mystery-B", "mystery-C", "mystery-D") +ORDERS = ( + "blocked_lsb", + "blocked_msb", + "interleaved_lsb", + "interleaved_msb", +) +BONDS = (2, 4, 8, 16) + + +def load_json(path: Path) -> dict: + return json.loads(path.read_text(encoding="utf-8")) + + +def selection_key(configuration: dict) -> tuple[float, float, float, int]: + validation = configuration["validation"] + return ( + float(validation["exact_accuracy"]), + float(validation["bit_accuracy"]), + -float(validation["rmse_pm1"]), + -int(configuration["bond"]), + ) + + +def selected_configuration(summary: dict, instance: str) -> dict: + candidates = [ + item + for item in summary["all_mps_configurations"] + if item["instance"] == instance + ] + if len(candidates) != 16: + raise RuntimeError(f"expected 16 configurations for {instance}") + return max(candidates, key=selection_key) + + +def cell_for_configuration(mps_root: Path, configuration: dict) -> Path: + instance_index = INSTANCES.index(configuration["instance"]) + order_index = ORDERS.index(configuration["order"]) + bond_index = BONDS.index(int(configuration["bond"])) + task_id = instance_index * 16 + order_index * 4 + bond_index + matches = sorted((mps_root / "cells").glob(f"{task_id:02d}-*")) + if len(matches) != 1: + raise RuntimeError(f"could not resolve task {task_id}: {matches}") + return matches[0] + + +def build_shared_robdd( + predictions: np.ndarray, order: list[int] +) -> tuple[list[tuple[int, int, int]], list[int]]: + """Return nodes (level, low, high) and roots; terminals are IDs 0 and 1.""" + n_sites = len(order) + if predictions.shape[0] != 2**n_sites: + raise ValueError("prediction table is not a complete Boolean domain") + unique: dict[tuple[int, int, int], int] = {} + nodes: list[tuple[int, int, int]] = [] + roots: list[int] = [] + for output_index in range(predictions.shape[1]): + original_tensor = predictions[:, output_index].reshape( + (2,) * n_sites, order="F" + ) + ordered_tensor = np.transpose(original_tensor, axes=order) + current = ordered_tensor.reshape(-1).astype(np.int64) + for level in range(n_sites - 1, -1, -1): + if current.size % 2: + raise AssertionError("odd ROBDD frontier") + next_ids = np.empty(current.size // 2, dtype=np.int64) + for position, (low, high) in enumerate( + zip(current[0::2].tolist(), current[1::2].tolist()) + ): + low_id, high_id = int(low), int(high) + if low_id == high_id: + node_id = low_id + else: + key = (level, low_id, high_id) + node_id = unique.get(key, -1) + if node_id < 0: + node_id = len(nodes) + 2 + unique[key] = node_id + nodes.append(key) + next_ids[position] = node_id + current = next_ids + if current.size != 1: + raise AssertionError("ROBDD did not reduce to one root") + roots.append(int(current[0])) + return nodes, roots + + +def invert(reference: str) -> str: + return reference[1:] if reference.startswith("~") else f"~{reference}" + + +def emit_mux_netlist( + path: Path, + n_inputs: int, + order: list[int], + nodes: list[tuple[int, int, int]], + roots: list[int], +) -> dict: + """Emit simplified AND/OR MUX logic, structurally shared across all roots.""" + gates: list[tuple[str, str, str, str]] = [] + gate_cache: dict[tuple[str, str, str], str] = {} + + def gate(operation: str, left: str, right: str) -> str: + operands = tuple(sorted((left, right))) + key = (operation, operands[0], operands[1]) + existing = gate_cache.get(key) + if existing is not None: + return existing + wire = f"w{len(gates) + 1}" + gates.append((wire, operation, operands[0], operands[1])) + gate_cache[key] = wire + return wire + + false_reference = gate("AND", "x1", "~x1") + references: dict[int, str] = {0: false_reference, 1: invert(false_reference)} + simplified = Counter() + for node_id, (level, low_id, high_id) in enumerate(nodes, start=2): + variable = f"x{order[level] + 1}" + low = references[low_id] + high = references[high_id] + if low_id == 0 and high_id == 1: + result = variable + simplified["identity"] += 1 + elif low_id == 1 and high_id == 0: + result = invert(variable) + simplified["inversion"] += 1 + elif low_id == 0: + result = gate("AND", variable, high) + simplified["low_false"] += 1 + elif high_id == 0: + result = gate("AND", invert(variable), low) + simplified["high_false"] += 1 + elif low_id == 1: + result = gate("OR", invert(variable), high) + simplified["low_true"] += 1 + elif high_id == 1: + result = gate("OR", variable, low) + simplified["high_true"] += 1 + else: + low_term = gate("AND", invert(variable), low) + high_term = gate("AND", variable, high) + result = gate("OR", low_term, high_term) + simplified["general_mux"] += 1 + references[node_id] = result + output_references = [references[root] for root in roots] + lines = [f"INPUTS {n_inputs}"] + lines.extend( + f"{wire} = {operation} {left} {right}" + for wire, operation, left, right in gates + ) + lines.append(f"OUTPUTS {' '.join(output_references)}") + payload = ("\n".join(lines) + "\n").encode("ascii") + path.parent.mkdir(parents=True, exist_ok=True) + temporary = path.with_suffix(path.suffix + ".tmp") + temporary.write_bytes(payload) + temporary.replace(path) + return { + "gates": len(gates), + "outputs": len(output_references), + "bytes": len(payload), + "simplification_counts": dict(sorted(simplified.items())), + } + + +def token_value(token: str, values: dict[str, int], mask: int) -> int: + negated = token.startswith("~") + base = token[1:] if negated else token + if base not in values: + raise ValueError(f"undefined token {token!r}") + return values[base] ^ mask if negated else values[base] + + +def bit_table(bits: np.ndarray) -> int: + packed = np.packbits(bits.astype(np.uint8), bitorder="little") + return int.from_bytes(packed.tobytes(), byteorder="little", signed=False) + + +def verify_serialized_netlist( + path: Path, predictions: np.ndarray +) -> dict[str, int | bool]: + """Strictly parse and bit-parallel simulate the generated serialized file.""" + raw_lines = path.read_text(encoding="ascii").splitlines() + if not raw_lines or len(raw_lines[0].split()) != 2: + raise ValueError("malformed INPUTS line") + input_fields = raw_lines[0].split() + if input_fields[0] != "INPUTS" or not input_fields[1].isdigit(): + raise ValueError("malformed INPUTS line") + n_inputs = int(input_fields[1]) + assignment_count = 2**n_inputs + if predictions.shape[0] != assignment_count: + raise ValueError("prediction/netlist domain mismatch") + mask = (1 << assignment_count) - 1 + values: dict[str, int] = {} + for input_index in range(n_inputs): + table = 0 + for assignment in range(assignment_count): + table |= ((assignment >> input_index) & 1) << assignment + values[f"x{input_index + 1}"] = table + output_tokens: list[str] | None = None + gate_count = 0 + for line in raw_lines[1:]: + fields = line.split() + if fields and fields[0] == "OUTPUTS": + if output_tokens is not None or len(fields) < 2: + raise ValueError("malformed OUTPUTS line") + output_tokens = fields[1:] + continue + if output_tokens is not None: + raise ValueError("gate after OUTPUTS") + if len(fields) != 5 or fields[1] != "=": + raise ValueError(f"malformed gate line: {line!r}") + wire, operation, left, right = fields[0], fields[2], fields[3], fields[4] + if wire != f"w{gate_count + 1}" or operation not in {"AND", "OR"}: + raise ValueError(f"invalid gate line: {line!r}") + left_value = token_value(left, values, mask) + right_value = token_value(right, values, mask) + values[wire] = ( + left_value & right_value + if operation == "AND" + else left_value | right_value + ) + gate_count += 1 + if output_tokens is None or len(output_tokens) != predictions.shape[1]: + raise ValueError("missing/mismatched outputs") + mismatch_outputs = 0 + for output_index, token in enumerate(output_tokens): + actual = token_value(token, values, mask) + expected = bit_table(predictions[:, output_index]) + mismatch_outputs += actual != expected + return { + "assignments": assignment_count, + "outputs": predictions.shape[1], + "gates": gate_count, + "mismatching_output_truth_tables": mismatch_outputs, + "equivalent_to_thresholded_mps": mismatch_outputs == 0, + } + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--instance", required=True, choices=INSTANCES) + parser.add_argument("--summary", required=True, type=Path) + parser.add_argument("--mps-root", required=True, type=Path) + parser.add_argument("--netlist-out", required=True, type=Path) + parser.add_argument("--report-out", required=True, type=Path) + args = parser.parse_args() + summary = load_json(args.summary) + configuration = selected_configuration(summary, args.instance) + cell = cell_for_configuration(args.mps_root, configuration) + saved = load_models(cell / "model.npz") + model_hash = sha256_file(cell / "model.npz") + if model_hash != configuration["model_sha256"]: + raise RuntimeError("selected model hash mismatch") + order = [int(value) for value in saved.metadata["order_original_axes"]] + x_bits, y_truth = enumerate_full_domain(args.instance) + x_ordered = x_bits[:, order] + scores = np.column_stack( + [predict_scores(model, x_ordered) for model in saved.models] + ) + predictions = (scores >= 0.0).astype(np.int8) + nodes, roots = build_shared_robdd(predictions, order) + netlist = emit_mux_netlist( + args.netlist_out, x_bits.shape[1], order, nodes, roots + ) + netlist_verification = verify_serialized_netlist( + args.netlist_out, predictions + ) + if not netlist_verification["equivalent_to_thresholded_mps"]: + raise RuntimeError("serialized netlist failed full-domain equivalence") + wrong_rows = np.any(predictions != y_truth, axis=1) + parameter_count = int( + sum(core.size for model in saved.models for core in model) + ) + nodes_by_level = Counter(level for level, _, _ in nodes) + report = { + "schema": "occam71-tn-bdd-distillation-v1", + "instance": args.instance, + "selection_rule": ( + "train-only validation exact accuracy, then bit accuracy, then " + "lower RMSE, then smaller bond" + ), + "selected_configuration": configuration, + "source_cell": str(cell), + "source_model_sha256": model_hash, + "mps_parameter_count_float64": parameter_count, + "mps_parameter_bytes_float64": 8 * parameter_count, + "robdd": { + "variable_order_original_axes": order, + "shared_nonterminal_nodes": len(nodes), + "nodes_by_level": { + str(level): int(nodes_by_level[level]) + for level in range(len(order)) + }, + "roots": roots, + }, + "netlist": { + **netlist, + "path": str(args.netlist_out), + "sha256": sha256_file(args.netlist_out), + "verification": netlist_verification, + }, + "arithmetic_truth_audit": { + "rows": int(y_truth.shape[0]), + "bit_accuracy": float(np.mean(predictions == y_truth)), + "exact_accuracy": float(np.mean(~wrong_rows)), + "mismatching_rows": int(np.count_nonzero(wrong_rows)), + }, + "challenge_candidate": False, + "rejection_reason": ( + "The distilled circuit exactly implements the thresholded MPS, " + "but that function has nonzero full-domain mismatches to the " + "arithmetic target." + ), + } + atomic_json(args.report_out, report) + print(json.dumps(report, sort_keys=True), flush=True) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/oracle_rank_audit.py b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/oracle_rank_audit.py new file mode 100644 index 000000000..f900f5917 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/oracle_rank_audit.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +"""Audit-only exact full-domain TT ranks for one instance/order.""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +import numpy as np + +from tn_common import INSTANCE_SPECS, ORDER_NAMES, atomic_json, variable_order +from tn_truth import enumerate_full_domain, tt_rank_vectors + + +def summarize(vectors: list[list[int]]) -> dict: + values = np.asarray(vectors, dtype=np.int64) + return { + "per_output": vectors, + "max_across_outputs_by_cut": np.max(values, axis=0).astype(int).tolist(), + } + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--instance", required=True, choices=sorted(INSTANCE_SPECS)) + parser.add_argument("--order", required=True, choices=ORDER_NAMES) + parser.add_argument("--report-out", required=True, type=Path) + args = parser.parse_args() + n = int(INSTANCE_SPECS[args.instance]["n"]) + order = variable_order(n, args.order) + _, y_bits = enumerate_full_domain(args.instance) + report = { + "schema": "occam71-oracle-tt-rank-audit-v1", + "instance": args.instance, + "order_name": args.order, + "order_original_axes": order, + "domain_rows": int(y_bits.shape[0]), + "boolean_01": summarize( + tt_rank_vectors(y_bits.astype(np.float64), order) + ), + "signed_pm1": summarize( + tt_rank_vectors(2.0 * y_bits.astype(np.float64) - 1.0, order) + ), + "selection_firewall": ( + "Computed only in audit process; unavailable to rank completion " + "and MPS training." + ), + } + atomic_json(args.report_out, report) + print(json.dumps(report, sort_keys=True), flush=True) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/plot_tn_results.py b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/plot_tn_results.py new file mode 100644 index 000000000..baa6d4664 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/plot_tn_results.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +"""Plot exact TT rank structure and train-selected MPS audit accuracy.""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +import matplotlib + +matplotlib.use("Agg") +import matplotlib.pyplot as plt +import numpy as np + + +INSTANCES = ("mystery-A", "mystery-B", "mystery-C", "mystery-D") +ORDERS = ( + "blocked_lsb", + "blocked_msb", + "interleaved_lsb", + "interleaved_msb", +) +ORDER_LABELS = ("blocked LSB", "blocked MSB", "interleaved LSB", "interleaved MSB") + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("summary", type=Path) + parser.add_argument("output", type=Path) + args = parser.parse_args() + data = json.loads(args.summary.read_text(encoding="utf-8")) + rank_lookup = { + (item["instance"], item["order"]): max( + item["oracle_audit_only"]["boolean_max_rank_by_cut"] + ) + for item in data["rank_diagnostics"] + } + accuracy_lookup = { + (item["instance"], item["order"]): item["full_domain_audit"][ + "exact_accuracy" + ] + for item in data["train_validation_selected"] + } + x_locations = np.arange(len(INSTANCES), dtype=np.float64) + bar_width = 0.19 + colors = ("#4C78A8", "#72B7B2", "#F58518", "#E45756") + figure, axes = plt.subplots(2, 1, figsize=(10.5, 8.2), constrained_layout=True) + for order_index, (order, label, color) in enumerate( + zip(ORDERS, ORDER_LABELS, colors) + ): + offsets = x_locations + (order_index - 1.5) * bar_width + ranks = [rank_lookup[(instance, order)] for instance in INSTANCES] + accuracies = [ + accuracy_lookup[(instance, order)] for instance in INSTANCES + ] + axes[0].bar(offsets, ranks, width=bar_width, label=label, color=color) + axes[1].bar(offsets, accuracies, width=bar_width, label=label, color=color) + for x_value, rank in zip(offsets, ranks): + axes[0].annotate( + str(rank), + (x_value, rank), + xytext=(0, 3), + textcoords="offset points", + ha="center", + va="bottom", + fontsize=7, + ) + for x_value, accuracy in zip(offsets, accuracies): + axes[1].annotate( + f"{accuracy:.3f}", + (x_value, accuracy), + xytext=(0, 3), + textcoords="offset points", + ha="center", + va="bottom", + fontsize=7, + rotation=90, + ) + axes[0].set_yscale("log", base=2) + axes[0].set_ylabel("max exact Boolean TT rank (log₂)") + axes[0].set_title("Variable order changes TT complexity sharply") + axes[0].grid(axis="y", alpha=0.25) + axes[0].legend(ncol=2, fontsize=8) + axes[1].set_ylim(0.0, 1.05) + axes[1].axhline(1.0, color="black", linestyle="--", linewidth=1) + axes[1].set_ylabel("full-domain exact-match accuracy") + axes[1].set_title("Train-validation-selected continuous MPS remains inexact") + axes[1].grid(axis="y", alpha=0.25) + for axis in axes: + axis.set_xticks(x_locations) + axis.set_xticklabels(INSTANCES) + figure.suptitle( + "Issue 71 tensor-network arm — seed 42; 0 exact models, 0 legal circuits", + fontsize=13, + ) + args.output.parent.mkdir(parents=True, exist_ok=True) + temporary = args.output.with_suffix(".tmp.png") + figure.savefig(temporary, dpi=180) + plt.close(figure) + temporary.replace(args.output) + print(args.output, flush=True) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/rank_diag.py b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/rank_diag.py new file mode 100644 index 000000000..326426665 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/rank_diag.py @@ -0,0 +1,228 @@ +#!/usr/bin/env python3 +"""Train-only variable-order TT unfolding rank diagnostics. + +For every cut, this fits low-rank matrix completions to observed coordinates +only. It is a diagnostic proxy, not a claim about the exact hidden TT rank. +The independent audit later computes exact full-domain ranks for comparison. +""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +import numpy as np + +from tn_common import ( + INSTANCE_SPECS, + ORDER_NAMES, + atomic_json, + comma_ints, + load_train_csv, + sha256_file, + stable_seed, + variable_order, +) + + +def binary_codes(bits: np.ndarray) -> np.ndarray: + if bits.shape[1] == 0: + return np.zeros(bits.shape[0], dtype=np.int64) + weights = (np.int64(1) << np.arange(bits.shape[1], dtype=np.int64)) + return bits.astype(np.int64) @ weights + + +def split_observations(n_rows: int, seed: int, fraction: float = 0.2) -> tuple[np.ndarray, np.ndarray]: + rng = np.random.default_rng(seed) + permutation = rng.permutation(n_rows) + n_validation = max(1, min(n_rows - 1, int(round(fraction * n_rows)))) + return permutation[n_validation:], permutation[:n_validation] + + +def fit_matrix_completion( + row_ids: np.ndarray, + column_ids: np.ndarray, + targets: np.ndarray, + train_indices: np.ndarray, + validation_indices: np.ndarray, + rank: int, + ridge: float, + iterations: int, + seed: int, +) -> dict: + n_rows = int(row_ids.max()) + 1 + n_columns = int(column_ids.max()) + 1 + rng = np.random.default_rng(seed) + left = rng.normal(0.0, 0.1 / np.sqrt(rank), size=(n_rows, rank)) + right = rng.normal(0.0, 0.1 / np.sqrt(rank), size=(n_columns, rank)) + + train_by_row: list[list[int]] = [[] for _ in range(n_rows)] + train_by_column: list[list[int]] = [[] for _ in range(n_columns)] + for observation in train_indices.tolist(): + train_by_row[int(row_ids[observation])].append(observation) + train_by_column[int(column_ids[observation])].append(observation) + identity = np.eye(rank, dtype=np.float64) + for _ in range(iterations): + for row, observations in enumerate(train_by_row): + if not observations: + left[row, :] = 0.0 + continue + obs = np.asarray(observations, dtype=np.int64) + design = right[column_ids[obs]] + left[row, :] = np.linalg.solve( + design.T @ design + ridge * identity, + design.T @ targets[obs], + ) + for column, observations in enumerate(train_by_column): + if not observations: + right[column, :] = 0.0 + continue + obs = np.asarray(observations, dtype=np.int64) + design = left[row_ids[obs]] + right[column, :] = np.linalg.solve( + design.T @ design + ridge * identity, + design.T @ targets[obs], + ) + + def metrics(indices: np.ndarray) -> dict: + scores = np.sum(left[row_ids[indices]] * right[column_ids[indices]], axis=1) + return { + "observations": int(indices.size), + "sign_accuracy": float(np.mean((scores >= 0.0) == (targets[indices] >= 0.0))), + "rmse_pm1": float(np.sqrt(np.mean((scores - targets[indices]) ** 2))), + } + + seen_rows = np.zeros(n_rows, dtype=bool) + seen_columns = np.zeros(n_columns, dtype=bool) + seen_rows[row_ids[train_indices]] = True + seen_columns[column_ids[train_indices]] = True + covered_validation = validation_indices[ + seen_rows[row_ids[validation_indices]] + & seen_columns[column_ids[validation_indices]] + ] + return { + "rank": rank, + "train": metrics(train_indices), + "validation": metrics(validation_indices), + "covered_validation": ( + metrics(covered_validation) + if covered_validation.size + else {"observations": 0, "sign_accuracy": None, "rmse_pm1": None} + ), + "train_seen_rows": int(np.count_nonzero(seen_rows)), + "train_seen_columns": int(np.count_nonzero(seen_columns)), + "total_rows": n_rows, + "total_columns": n_columns, + } + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--instance", required=True, choices=sorted(INSTANCE_SPECS)) + parser.add_argument("--train-csv", required=True, type=Path) + parser.add_argument("--order", required=True, choices=ORDER_NAMES) + parser.add_argument("--ranks", default="1,2,4,8") + parser.add_argument("--ridge", type=float, default=1e-4) + parser.add_argument("--iterations", type=int, default=10) + parser.add_argument("--root-seed", type=int, default=42) + parser.add_argument("--report-out", required=True, type=Path) + args = parser.parse_args() + ranks = comma_ints(args.ranks) + if args.iterations < 1 or args.ridge <= 0.0: + parser.error("iterations and ridge must be positive") + + x_bits, y_bits = load_train_csv(args.train_csv, args.instance) + n = int(INSTANCE_SPECS[args.instance]["n"]) + order = variable_order(n, args.order) + x_ordered = x_bits[:, order] + train_indices, validation_indices = split_observations( + x_bits.shape[0], + stable_seed(args.root_seed, args.instance, args.order, "rank-split"), + ) + cuts: list[dict] = [] + for cut in range(1, x_ordered.shape[1]): + row_ids = binary_codes(x_ordered[:, :cut]) + column_ids = binary_codes(x_ordered[:, cut:]) + output_diagnostics = [] + for output_index in range(y_bits.shape[1]): + targets = 2.0 * y_bits[:, output_index].astype(np.float64) - 1.0 + rank_results = [] + for rank in ranks: + result = fit_matrix_completion( + row_ids, + column_ids, + targets, + train_indices, + validation_indices, + rank, + args.ridge, + args.iterations, + stable_seed( + args.root_seed, + args.instance, + args.order, + cut, + output_index, + rank, + "rank-completion", + ), + ) + rank_results.append(result) + output_diagnostics.append( + {"output_index": output_index, "rank_results": rank_results} + ) + cuts.append( + { + "cut_after_sites": cut, + "prefix_original_axes": order[:cut], + "suffix_original_axes": order[cut:], + "outputs": output_diagnostics, + } + ) + mean_validation = { + rank: float( + np.mean( + [ + output["rank_results"][rank_index]["validation"]["sign_accuracy"] + for output in output_diagnostics + ] + ) + ) + for rank_index, rank in enumerate(ranks) + } + print( + f"rank-diagnostic instance={args.instance} order={args.order} " + f"cut={cut}/{x_ordered.shape[1] - 1} mean_val={mean_validation}", + flush=True, + ) + + report = { + "schema": "occam71-train-unfolding-completion-v1", + "interpretation": ( + "Train-only low-rank completion proxy for each TT unfolding; " + "not an exact hidden-rank certificate." + ), + "instance": args.instance, + "order_name": args.order, + "order_original_axes": order, + "candidate_ranks": ranks, + "ridge": args.ridge, + "als_iterations": args.iterations, + "root_seed": args.root_seed, + "training_csv_sha256": sha256_file(args.train_csv), + "train_observations": int(train_indices.size), + "validation_observations": int(validation_indices.size), + "cuts": cuts, + } + atomic_json(args.report_out, report) + print(json.dumps({ + "instance": args.instance, + "order": args.order, + "cuts": len(cuts), + "report": str(args.report_out), + }, sort_keys=True), flush=True) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/slurm_tn_distill.sh b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/slurm_tn_distill.sh new file mode 100644 index 000000000..def7df238 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/slurm_tn_distill.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +#SBATCH --job-name=occ71-tn-bdd +#SBATCH --partition=home +#SBATCH --nodelist=n006 +#SBATCH --nodes=1 +#SBATCH --ntasks=1 +#SBATCH --cpus-per-task=1 +#SBATCH --mem=8G +#SBATCH --time=01:00:00 +#SBATCH --array=0-3%4 +#SBATCH --output=results/occam71/tensor-network/logs/distill-%A_%a.out + +set -euo pipefail + +export OMP_NUM_THREADS=1 +export OPENBLAS_NUM_THREADS=1 +export MKL_NUM_THREADS=1 +export NUMEXPR_NUM_THREADS=1 + +REPO=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +CODE="$REPO/tracks/qcs/solutions/Genshin_Impact-71/tensor_network" +FULL="$REPO/results/occam71/tensor-network/full-seed42" +SUMMARY="$FULL/summary-rank42681-mps42698/summary.json" +MPS_ROOT="$FULL/mps-job-42698" +ROOT="$FULL/distill-job-${SLURM_ARRAY_JOB_ID}" +PYTHON=/home/user_milksang/.conda/envs/crystalgpt/bin/python + +instances=(mystery-A mystery-B mystery-C mystery-D) +task=${SLURM_ARRAY_TASK_ID} +if (( task < 0 || task >= 4 )); then + echo "invalid task id: $task" >&2 + exit 2 +fi +instance=${instances[$task]} +cell=$(printf '%s/cells/%02d-%s' "$ROOT" "$task" "$instance") +mkdir -p "$cell" +start=$SECONDS +cd "$CODE" + +sha256sum \ + tn_common.py tn_truth.py distill_mps_bdd.py write_distill_manifest.py \ + slurm_tn_distill.sh TN_DISTILL_DESIGN.md \ + > "$cell/code.sha256" +sha256sum "$SUMMARY" > "$cell/source-summary.sha256" + +"$PYTHON" -u distill_mps_bdd.py \ + --instance "$instance" \ + --summary "$SUMMARY" \ + --mps-root "$MPS_ROOT" \ + --netlist-out "$cell/thresholded-mps-netlist.txt" \ + --report-out "$cell/report.json" + +"$PYTHON" -u write_distill_manifest.py \ + --cell-dir "$cell" \ + --job-id "$SLURM_ARRAY_JOB_ID" \ + --task-id "$task" \ + --instance "$instance" \ + --elapsed-seconds "$((SECONDS - start))" \ + --artifact "$cell/code.sha256" \ + --artifact "$cell/source-summary.sha256" \ + --artifact "$cell/thresholded-mps-netlist.txt" \ + --artifact "$cell/report.json" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/slurm_tn_mps_full.sh b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/slurm_tn_mps_full.sh new file mode 100644 index 000000000..4050fc7f4 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/slurm_tn_mps_full.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +#SBATCH --job-name=occ71-tn-mps +#SBATCH --partition=home +#SBATCH --nodelist=n006 +#SBATCH --nodes=1 +#SBATCH --ntasks=1 +#SBATCH --cpus-per-task=1 +#SBATCH --mem=6G +#SBATCH --time=02:00:00 +#SBATCH --array=0-63%16 +#SBATCH --output=results/occam71/tensor-network/logs/mps-%A_%a.out + +set -euo pipefail + +export OMP_NUM_THREADS=1 +export OPENBLAS_NUM_THREADS=1 +export MKL_NUM_THREADS=1 +export NUMEXPR_NUM_THREADS=1 + +REPO=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +CODE="$REPO/tracks/qcs/solutions/Genshin_Impact-71/tensor_network" +DATA="$REPO/results/occam71/occam-circuit/datasets" +ROOT="$REPO/results/occam71/tensor-network/full-seed42/mps-job-${SLURM_ARRAY_JOB_ID}" +PYTHON=/home/user_milksang/.conda/envs/crystalgpt/bin/python + +instances=(mystery-A mystery-B mystery-C mystery-D) +orders=(blocked_lsb blocked_msb interleaved_lsb interleaved_msb) +bonds=(2 4 8 16) + +task=${SLURM_ARRAY_TASK_ID} +if (( task < 0 || task >= 64 )); then + echo "invalid task id: $task" >&2 + exit 2 +fi +instance_index=$((task / 16)) +within=$((task % 16)) +order_index=$((within / 4)) +bond_index=$((within % 4)) +instance=${instances[$instance_index]} +order=${orders[$order_index]} +bond=${bonds[$bond_index]} +cell=$(printf '%s/cells/%02d-%s-%s-bond%d' \ + "$ROOT" "$task" "$instance" "$order" "$bond") +mkdir -p "$cell" +start=$SECONDS +cd "$CODE" + +sha256sum \ + tn_common.py tn_truth.py train_mps.py audit_mps.py \ + write_cell_manifest.py slurm_tn_mps_full.sh TN_FULL_DESIGN.md \ + > "$cell/code.sha256" +input_sha=$(sha256sum "$DATA/$instance/train.csv" | cut -d' ' -f1) +printf '%s %s\n' "$input_sha" "$DATA/$instance/train.csv" > "$cell/input.sha256" + +"$PYTHON" -u train_mps.py \ + --instance "$instance" \ + --train-csv "$DATA/$instance/train.csv" \ + --order "$order" \ + --bond "$bond" \ + --ridge 1e-5 \ + --sweeps 6 \ + --patience 2 \ + --validation-fraction 0.2 \ + --root-seed 42 \ + --model-out "$cell/model.npz" \ + --report-out "$cell/train.json" + +"$PYTHON" -u audit_mps.py \ + --model "$cell/model.npz" \ + --report-out "$cell/audit.json" + +"$PYTHON" -u write_cell_manifest.py \ + --cell-dir "$cell" \ + --kind mps \ + --job-id "$SLURM_ARRAY_JOB_ID" \ + --task-id "$task" \ + --instance "$instance" \ + --order "$order" \ + --bond "$bond" \ + --elapsed-seconds "$((SECONDS - start))" \ + --input-sha256 "$input_sha" \ + --artifact "$cell/code.sha256" \ + --artifact "$cell/input.sha256" \ + --artifact "$cell/model.npz" \ + --artifact "$cell/train.json" \ + --artifact "$cell/audit.json" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/slurm_tn_mps_full_v2.sh b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/slurm_tn_mps_full_v2.sh new file mode 100644 index 000000000..70abf6435 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/slurm_tn_mps_full_v2.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +#SBATCH --job-name=occ71-tn-mps +#SBATCH --partition=home +#SBATCH --nodelist=n006 +#SBATCH --nodes=1 +#SBATCH --ntasks=1 +#SBATCH --cpus-per-task=1 +#SBATCH --mem=6G +#SBATCH --time=02:00:00 +#SBATCH --array=0-63%16 +#SBATCH --output=results/occam71/tensor-network/logs/mps-%A_%a.out + +set -euo pipefail + +export OMP_NUM_THREADS=1 +export OPENBLAS_NUM_THREADS=1 +export MKL_NUM_THREADS=1 +export NUMEXPR_NUM_THREADS=1 + +REPO=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +CODE="$REPO/tracks/qcs/solutions/Genshin_Impact-71/tensor_network" +DATA="$REPO/results/occam71/occam-circuit/datasets" +ROOT="$REPO/results/occam71/tensor-network/full-seed42/mps-job-${SLURM_ARRAY_JOB_ID}" +PYTHON=/home/user_milksang/.conda/envs/crystalgpt/bin/python + +instances=(mystery-A mystery-B mystery-C mystery-D) +orders=(blocked_lsb blocked_msb interleaved_lsb interleaved_msb) +bonds=(2 4 8 16) + +task=${SLURM_ARRAY_TASK_ID} +if (( task < 0 || task >= 64 )); then + echo "invalid task id: $task" >&2 + exit 2 +fi +instance_index=$((task / 16)) +within=$((task % 16)) +order_index=$((within / 4)) +bond_index=$((within % 4)) +instance=${instances[$instance_index]} +order=${orders[$order_index]} +bond=${bonds[$bond_index]} +cell=$(printf '%s/cells/%02d-%s-%s-bond%d' \ + "$ROOT" "$task" "$instance" "$order" "$bond") +mkdir -p "$cell" +start=$SECONDS +cd "$CODE" + +sha256sum \ + tn_common.py tn_truth.py train_mps.py audit_mps.py \ + write_cell_manifest.py slurm_tn_mps_full_v2.sh TN_FULL_DESIGN_V2.md \ + > "$cell/code.sha256" +input_sha=$(sha256sum "$DATA/$instance/train.csv" | cut -d' ' -f1) +printf '%s %s\n' "$input_sha" "$DATA/$instance/train.csv" > "$cell/input.sha256" + +"$PYTHON" -u train_mps.py \ + --instance "$instance" \ + --train-csv "$DATA/$instance/train.csv" \ + --order "$order" \ + --bond "$bond" \ + --ridge 1e-5 \ + --sweeps 6 \ + --patience 2 \ + --validation-fraction 0.2 \ + --root-seed 42 \ + --model-out "$cell/model.npz" \ + --report-out "$cell/train.json" + +"$PYTHON" -u audit_mps.py \ + --model "$cell/model.npz" \ + --report-out "$cell/audit.json" + +"$PYTHON" -u write_cell_manifest.py \ + --cell-dir "$cell" \ + --kind mps \ + --job-id "$SLURM_ARRAY_JOB_ID" \ + --task-id "$task" \ + --instance "$instance" \ + --order "$order" \ + --bond "$bond" \ + --elapsed-seconds "$((SECONDS - start))" \ + --input-sha256 "$input_sha" \ + --artifact "$cell/code.sha256" \ + --artifact "$cell/input.sha256" \ + --artifact "$cell/model.npz" \ + --artifact "$cell/train.json" \ + --artifact "$cell/audit.json" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/slurm_tn_pilot.sh b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/slurm_tn_pilot.sh new file mode 100644 index 000000000..1e63ab5bd --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/slurm_tn_pilot.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash +#SBATCH --job-name=occam71-tn-pilot +#SBATCH --partition=home +#SBATCH --nodelist=n006 +#SBATCH --nodes=1 +#SBATCH --ntasks=1 +#SBATCH --cpus-per-task=4 +#SBATCH --mem=12G +#SBATCH --time=00:30:00 +#SBATCH --output=results/occam71/tensor-network/logs/pilot-%j.out + +set -euo pipefail + +REPO=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +CODE="$REPO/tracks/qcs/solutions/Genshin_Impact-71/tensor_network" +DATA="$REPO/results/occam71/occam-circuit/datasets" +RUN="$REPO/results/occam71/tensor-network/pilot-seed42/job-${SLURM_JOB_ID}" +PYTHON=/home/user_milksang/.conda/envs/crystalgpt/bin/python + +mkdir -p "$RUN/models" "$RUN/reports" "$RUN/logs" +cd "$CODE" + +"$PYTHON" -u -m unittest -v test_tn_arm.py + +sha256sum \ + tn_common.py tn_truth.py train_mps.py rank_diag.py audit_mps.py \ + test_tn_arm.py TN_DESIGN.md slurm_tn_pilot.sh \ + > "$RUN/code.sha256" + +for instance in practice-add-n4 practice-mul-n4; do + for order in blocked_lsb interleaved_lsb; do + "$PYTHON" -u rank_diag.py \ + --instance "$instance" \ + --train-csv "$DATA/$instance/train.csv" \ + --order "$order" \ + --ranks 1,2,4,8 \ + --iterations 8 \ + --root-seed 42 \ + --report-out "$RUN/reports/rank-${instance}-${order}.json" + for bond in 2 4; do + stem="${instance}-${order}-bond${bond}" + "$PYTHON" -u train_mps.py \ + --instance "$instance" \ + --train-csv "$DATA/$instance/train.csv" \ + --order "$order" \ + --bond "$bond" \ + --ridge 1e-5 \ + --sweeps 6 \ + --patience 2 \ + --validation-fraction 0.2 \ + --root-seed 42 \ + --model-out "$RUN/models/$stem.npz" \ + --report-out "$RUN/reports/train-$stem.json" + "$PYTHON" -u audit_mps.py \ + --model "$RUN/models/$stem.npz" \ + --oracle-ranks \ + --report-out "$RUN/reports/audit-$stem.json" + done + done +done + +"$PYTHON" - "$RUN" "$SLURM_JOB_ID" <<'PY' +import hashlib +import json +import pathlib +import sys + +run = pathlib.Path(sys.argv[1]) +job_id = sys.argv[2] +files = sorted( + path for path in run.rglob("*") + if path.is_file() and path.name not in {"manifest.json", "SUCCESS"} +) +manifest = { + "schema": "occam71-tn-pilot-manifest-v1", + "status": "success", + "job_id": job_id, + "root_seed": 42, + "files": [ + { + "path": str(path.relative_to(run)), + "bytes": path.stat().st_size, + "sha256": hashlib.sha256(path.read_bytes()).hexdigest(), + } + for path in files + ], +} +temporary = run / "manifest.json.tmp" +temporary.write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n") +temporary.replace(run / "manifest.json") +(run / "SUCCESS").write_text("success\n") +print(json.dumps(manifest, sort_keys=True), flush=True) +PY diff --git a/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/slurm_tn_rank_full.sh b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/slurm_tn_rank_full.sh new file mode 100644 index 000000000..f79011ab3 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/slurm_tn_rank_full.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +#SBATCH --job-name=occ71-tn-rank +#SBATCH --partition=home +#SBATCH --nodelist=n006 +#SBATCH --nodes=1 +#SBATCH --ntasks=1 +#SBATCH --cpus-per-task=1 +#SBATCH --mem=4G +#SBATCH --time=01:00:00 +#SBATCH --array=0-15%16 +#SBATCH --output=results/occam71/tensor-network/logs/rank-%A_%a.out + +set -euo pipefail + +export OMP_NUM_THREADS=1 +export OPENBLAS_NUM_THREADS=1 +export MKL_NUM_THREADS=1 +export NUMEXPR_NUM_THREADS=1 + +REPO=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +CODE="$REPO/tracks/qcs/solutions/Genshin_Impact-71/tensor_network" +DATA="$REPO/results/occam71/occam-circuit/datasets" +ROOT="$REPO/results/occam71/tensor-network/full-seed42/rank-job-${SLURM_ARRAY_JOB_ID}" +PYTHON=/home/user_milksang/.conda/envs/crystalgpt/bin/python + +instances=(mystery-A mystery-B mystery-C mystery-D) +orders=(blocked_lsb blocked_msb interleaved_lsb interleaved_msb) + +task=${SLURM_ARRAY_TASK_ID} +if (( task < 0 || task >= 16 )); then + echo "invalid task id: $task" >&2 + exit 2 +fi +instance=${instances[$((task / 4))]} +order=${orders[$((task % 4))]} +cell=$(printf '%s/cells/%02d-%s-%s' "$ROOT" "$task" "$instance" "$order") +mkdir -p "$cell" +start=$SECONDS +cd "$CODE" + +sha256sum \ + tn_common.py tn_truth.py rank_diag.py oracle_rank_audit.py \ + write_cell_manifest.py slurm_tn_rank_full.sh TN_FULL_DESIGN.md \ + > "$cell/code.sha256" +input_sha=$(sha256sum "$DATA/$instance/train.csv" | cut -d' ' -f1) +printf '%s %s\n' "$input_sha" "$DATA/$instance/train.csv" > "$cell/input.sha256" + +"$PYTHON" -u rank_diag.py \ + --instance "$instance" \ + --train-csv "$DATA/$instance/train.csv" \ + --order "$order" \ + --ranks 1,2,4,8,16 \ + --iterations 8 \ + --root-seed 42 \ + --report-out "$cell/rank.json" + +"$PYTHON" -u oracle_rank_audit.py \ + --instance "$instance" \ + --order "$order" \ + --report-out "$cell/oracle-ranks.json" + +"$PYTHON" -u write_cell_manifest.py \ + --cell-dir "$cell" \ + --kind rank \ + --job-id "$SLURM_ARRAY_JOB_ID" \ + --task-id "$task" \ + --instance "$instance" \ + --order "$order" \ + --elapsed-seconds "$((SECONDS - start))" \ + --input-sha256 "$input_sha" \ + --artifact "$cell/code.sha256" \ + --artifact "$cell/input.sha256" \ + --artifact "$cell/rank.json" \ + --artifact "$cell/oracle-ranks.json" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/slurm_tn_rank_full_v2.sh b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/slurm_tn_rank_full_v2.sh new file mode 100644 index 000000000..a7b87a85e --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/slurm_tn_rank_full_v2.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +#SBATCH --job-name=occ71-tn-rank +#SBATCH --partition=home +#SBATCH --nodelist=n006 +#SBATCH --nodes=1 +#SBATCH --ntasks=1 +#SBATCH --cpus-per-task=1 +#SBATCH --mem=4G +#SBATCH --time=01:00:00 +#SBATCH --array=0-15%16 +#SBATCH --output=results/occam71/tensor-network/logs/rank-%A_%a.out + +set -euo pipefail + +export OMP_NUM_THREADS=1 +export OPENBLAS_NUM_THREADS=1 +export MKL_NUM_THREADS=1 +export NUMEXPR_NUM_THREADS=1 + +REPO=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +CODE="$REPO/tracks/qcs/solutions/Genshin_Impact-71/tensor_network" +DATA="$REPO/results/occam71/occam-circuit/datasets" +ROOT="$REPO/results/occam71/tensor-network/full-seed42/rank-job-${SLURM_ARRAY_JOB_ID}" +PYTHON=/home/user_milksang/.conda/envs/crystalgpt/bin/python + +instances=(mystery-A mystery-B mystery-C mystery-D) +orders=(blocked_lsb blocked_msb interleaved_lsb interleaved_msb) + +task=${SLURM_ARRAY_TASK_ID} +if (( task < 0 || task >= 16 )); then + echo "invalid task id: $task" >&2 + exit 2 +fi +instance=${instances[$((task / 4))]} +order=${orders[$((task % 4))]} +cell=$(printf '%s/cells/%02d-%s-%s' "$ROOT" "$task" "$instance" "$order") +mkdir -p "$cell" +start=$SECONDS +cd "$CODE" + +sha256sum \ + tn_common.py tn_truth.py rank_diag.py oracle_rank_audit.py \ + write_cell_manifest.py slurm_tn_rank_full_v2.sh TN_FULL_DESIGN_V2.md \ + > "$cell/code.sha256" +input_sha=$(sha256sum "$DATA/$instance/train.csv" | cut -d' ' -f1) +printf '%s %s\n' "$input_sha" "$DATA/$instance/train.csv" > "$cell/input.sha256" + +"$PYTHON" -u rank_diag.py \ + --instance "$instance" \ + --train-csv "$DATA/$instance/train.csv" \ + --order "$order" \ + --ranks 1,2,4,8,16 \ + --iterations 8 \ + --root-seed 42 \ + --report-out "$cell/rank.json" + +"$PYTHON" -u oracle_rank_audit.py \ + --instance "$instance" \ + --order "$order" \ + --report-out "$cell/oracle-ranks.json" + +"$PYTHON" -u write_cell_manifest.py \ + --cell-dir "$cell" \ + --kind rank \ + --job-id "$SLURM_ARRAY_JOB_ID" \ + --task-id "$task" \ + --instance "$instance" \ + --order "$order" \ + --elapsed-seconds "$((SECONDS - start))" \ + --input-sha256 "$input_sha" \ + --artifact "$cell/code.sha256" \ + --artifact "$cell/input.sha256" \ + --artifact "$cell/rank.json" \ + --artifact "$cell/oracle-ranks.json" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/slurm_tn_summarize_full.sh b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/slurm_tn_summarize_full.sh new file mode 100644 index 000000000..c980c1309 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/slurm_tn_summarize_full.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +#SBATCH --job-name=occ71-tn-summary +#SBATCH --partition=home +#SBATCH --nodelist=n006 +#SBATCH --nodes=1 +#SBATCH --ntasks=1 +#SBATCH --cpus-per-task=1 +#SBATCH --mem=4G +#SBATCH --time=00:15:00 +#SBATCH --output=results/occam71/tensor-network/logs/summary-%j.out + +set -euo pipefail + +: "${RANK_JOB_ID:?RANK_JOB_ID is required}" +: "${MPS_JOB_ID:?MPS_JOB_ID is required}" + +export OMP_NUM_THREADS=1 +export OPENBLAS_NUM_THREADS=1 +export MKL_NUM_THREADS=1 + +REPO=/home/user_milksang/private/homefile/quantum_harness/issue71_occam +CODE="$REPO/tracks/qcs/solutions/Genshin_Impact-71/tensor_network" +ROOT="$REPO/results/occam71/tensor-network/full-seed42" +OUT="$ROOT/summary-rank${RANK_JOB_ID}-mps${MPS_JOB_ID}" +PYTHON=/home/user_milksang/.conda/envs/crystalgpt/bin/python + +mkdir -p "$OUT" +cd "$CODE" +sha256sum \ + tn_common.py summarize_pilot.py summarize_full.py \ + slurm_tn_summarize_full.sh \ + > "$OUT/code.sha256" + +"$PYTHON" -u summarize_full.py \ + --rank-root "$ROOT/rank-job-${RANK_JOB_ID}" \ + --mps-root "$ROOT/mps-job-${MPS_JOB_ID}" \ + --report-out "$OUT/summary.json" + +sha256sum "$OUT/summary.json" > "$OUT/summary.sha256" +printf 'success\n' > "$OUT/SUCCESS" diff --git a/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/summarize_full.py b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/summarize_full.py new file mode 100644 index 000000000..11f545197 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/summarize_full.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python3 +"""Validate and summarize all 80 promoted tensor-network cells.""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +import numpy as np + +from summarize_pilot import summarize_rank_report +from tn_common import atomic_json, sha256_file + + +INSTANCES = ("mystery-A", "mystery-B", "mystery-C", "mystery-D") +ORDERS = ( + "blocked_lsb", + "blocked_msb", + "interleaved_lsb", + "interleaved_msb", +) +BONDS = (2, 4, 8, 16) + + +def load(path: Path) -> dict: + return json.loads(path.read_text(encoding="utf-8")) + + +def validate_cell(cell: Path, kind: str) -> dict: + if not (cell / "SUCCESS").is_file(): + raise RuntimeError(f"missing SUCCESS: {cell}") + manifest = load(cell / "manifest.json") + if manifest.get("status") != "success" or manifest.get("kind") != kind: + raise RuntimeError(f"invalid manifest status/kind: {cell}") + for artifact in manifest["artifacts"]: + path = cell / artifact["path"] + if sha256_file(path) != artifact["sha256"]: + raise RuntimeError(f"artifact hash mismatch: {path}") + return manifest + + +def selection_key(configuration: dict) -> tuple[float, float, float, int]: + validation = configuration["validation"] + return ( + float(validation["exact_accuracy"]), + float(validation["bit_accuracy"]), + -float(validation["rmse_pm1"]), + -int(configuration["bond"]), + ) + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--rank-root", required=True, type=Path) + parser.add_argument("--mps-root", required=True, type=Path) + parser.add_argument("--report-out", required=True, type=Path) + args = parser.parse_args() + + rank_results = [] + rank_cells = sorted((args.rank_root / "cells").glob("*")) + if len(rank_cells) != 16: + raise RuntimeError(f"expected 16 rank cells, found {len(rank_cells)}") + for cell in rank_cells: + manifest = validate_cell(cell, "rank") + rank_report = load(cell / "rank.json") + oracle_report = load(cell / "oracle-ranks.json") + rank_results.append( + { + "instance": manifest["instance"], + "order": manifest["order"], + "elapsed_seconds": int(manifest["elapsed_seconds"]), + "train_completion_proxy": summarize_rank_report(rank_report), + "oracle_audit_only": { + "domain_rows": oracle_report["domain_rows"], + "boolean_max_rank_by_cut": oracle_report["boolean_01"][ + "max_across_outputs_by_cut" + ], + "signed_max_rank_by_cut": oracle_report["signed_pm1"][ + "max_across_outputs_by_cut" + ], + }, + "rank_report_sha256": sha256_file(cell / "rank.json"), + "oracle_report_sha256": sha256_file(cell / "oracle-ranks.json"), + } + ) + + configurations = [] + mps_cells = sorted((args.mps_root / "cells").glob("*")) + if len(mps_cells) != 64: + raise RuntimeError(f"expected 64 MPS cells, found {len(mps_cells)}") + for cell in mps_cells: + manifest = validate_cell(cell, "mps") + train = load(cell / "train.json") + audit = load(cell / "audit.json") + if audit["model_sha256"] != sha256_file(cell / "model.npz"): + raise RuntimeError(f"audit/model hash mismatch: {cell}") + configurations.append( + { + "instance": manifest["instance"], + "order": manifest["order"], + "bond": int(manifest["bond"]), + "elapsed_seconds": int(manifest["elapsed_seconds"]), + "validation": train["validation_metrics"], + "training": train["train_metrics"], + "full_domain_audit": audit["full_domain_metrics"], + "mismatching_rows": int(audit["mismatching_rows"]), + "model_sha256": audit["model_sha256"], + "train_report_sha256": sha256_file(cell / "train.json"), + "audit_report_sha256": sha256_file(cell / "audit.json"), + } + ) + + selected = [] + for instance in INSTANCES: + for order in ORDERS: + candidates = [ + item + for item in configurations + if item["instance"] == instance and item["order"] == order + ] + if {item["bond"] for item in candidates} != set(BONDS): + raise RuntimeError(f"incomplete bond sweep: {instance} {order}") + selected.append(max(candidates, key=selection_key)) + exact_models = [ + item for item in configurations if int(item["mismatching_rows"]) == 0 + ] + summary = { + "schema": "occam71-tn-full-summary-v1", + "root_seed": 42, + "rank_job_id": load(rank_cells[0] / "manifest.json")["job_id"], + "mps_job_id": load(mps_cells[0] / "manifest.json")["job_id"], + "selection_rule": ( + "Within each instance/order: maximize train-only validation exact " + "accuracy, then validation bit accuracy, then lower validation RMSE, " + "then smaller bond." + ), + "selection_firewall": ( + "Full-domain audits were executed only after each model was frozen " + "and hashed; no full-domain metric participated in selection." + ), + "interpretation": ( + "A continuous thresholded MPS with any mismatch is not exact Boolean " + "recovery and is not a legal candidate gate circuit." + ), + "rank_diagnostics": rank_results, + "all_mps_configurations": configurations, + "train_validation_selected": selected, + "exact_full_domain_models": exact_models, + "legal_candidate_circuits_produced": 0, + "runtime_seconds": { + "rank_cells_sum": int( + np.sum([item["elapsed_seconds"] for item in rank_results]) + ), + "rank_cell_max": int( + np.max([item["elapsed_seconds"] for item in rank_results]) + ), + "mps_cells_sum": int( + np.sum([item["elapsed_seconds"] for item in configurations]) + ), + "mps_cell_max": int( + np.max([item["elapsed_seconds"] for item in configurations]) + ), + }, + } + atomic_json(args.report_out, summary) + print(json.dumps({ + "rank_cells": len(rank_results), + "mps_cells": len(configurations), + "exact_models": len(exact_models), + "selected": selected, + "runtime_seconds": summary["runtime_seconds"], + "report": str(args.report_out), + "report_sha256": sha256_file(args.report_out), + }, indent=2, sort_keys=True), flush=True) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/summarize_pilot.py b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/summarize_pilot.py new file mode 100644 index 000000000..13fe05d61 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/summarize_pilot.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +"""Summarize a completed immutable issue-71 TN pilot directory.""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +import numpy as np + +from tn_common import atomic_json, sha256_file + + +def load_json(path: Path) -> dict: + return json.loads(path.read_text(encoding="utf-8")) + + +def summarize_rank_report(report: dict) -> dict: + ranks = [int(value) for value in report["candidate_ranks"]] + by_rank: dict[str, dict] = {} + for rank_index, rank in enumerate(ranks): + validation_values = [] + covered_values = [] + train_values = [] + for cut in report["cuts"]: + for output in cut["outputs"]: + item = output["rank_results"][rank_index] + train_values.append(float(item["train"]["sign_accuracy"])) + validation_values.append(float(item["validation"]["sign_accuracy"])) + covered = item["covered_validation"]["sign_accuracy"] + if covered is not None: + covered_values.append(float(covered)) + by_rank[str(rank)] = { + "mean_train_sign_accuracy": float(np.mean(train_values)), + "mean_validation_sign_accuracy": float(np.mean(validation_values)), + "mean_covered_validation_sign_accuracy": ( + float(np.mean(covered_values)) if covered_values else None + ), + } + return by_rank + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("run_directory", type=Path) + parser.add_argument("--output", type=Path) + args = parser.parse_args() + run = args.run_directory + manifest = load_json(run / "manifest.json") + if manifest.get("status") != "success" or not (run / "SUCCESS").is_file(): + raise RuntimeError("pilot is not marked successful") + + configurations = [] + for audit_path in sorted((run / "reports").glob("audit-*.json")): + audit = load_json(audit_path) + stem = audit_path.name[len("audit-") : -len(".json")] + train_path = run / "reports" / f"train-{stem}.json" + train = load_json(train_path) + configurations.append( + { + "instance": audit["instance"], + "order": audit["order_name"], + "bond": int(audit["max_bond"]), + "validation": train["validation_metrics"], + "full_domain": audit["full_domain_metrics"], + "mismatching_rows": int(audit["mismatching_rows"]), + "model_sha256": audit["model_sha256"], + "oracle_boolean_max_rank_by_cut": audit[ + "oracle_tt_ranks_audit_only" + ]["boolean_01"]["max_across_outputs_by_cut"], + "oracle_signed_max_rank_by_cut": audit[ + "oracle_tt_ranks_audit_only" + ]["signed_pm1"]["max_across_outputs_by_cut"], + } + ) + rank_proxies = [] + for rank_path in sorted((run / "reports").glob("rank-*.json")): + report = load_json(rank_path) + rank_proxies.append( + { + "instance": report["instance"], + "order": report["order_name"], + "aggregated_over_cuts_and_outputs": summarize_rank_report(report), + "report_sha256": sha256_file(rank_path), + } + ) + summary = { + "schema": "occam71-tn-pilot-summary-v1", + "source_manifest_sha256": sha256_file(run / "manifest.json"), + "job_id": manifest["job_id"], + "configurations": configurations, + "rank_completion_proxies": rank_proxies, + } + output = args.output or run / "summary.json" + atomic_json(output, summary) + print(json.dumps(summary, indent=2, sort_keys=True), flush=True) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/test_tn_arm.py b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/test_tn_arm.py new file mode 100644 index 000000000..164429cd5 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/test_tn_arm.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +"""Small deterministic unit tests for the self-written TN arm.""" + +from __future__ import annotations + +import tempfile +import unittest +from pathlib import Path + +import numpy as np + +from tn_common import ( + initialize_mps, + load_models, + load_train_csv, + predict_scores, + rank_profile, + save_models, + variable_order, +) +from tn_truth import enumerate_full_domain, tt_rank_vectors + + +class TensorArmTests(unittest.TestCase): + def test_orders_are_permutations(self) -> None: + for n in (1, 4, 8): + for name in ( + "blocked_lsb", + "blocked_msb", + "interleaved_lsb", + "interleaved_msb", + ): + self.assertEqual(sorted(variable_order(n, name)), list(range(2 * n))) + + def test_rank_profile_respects_binary_caps(self) -> None: + self.assertEqual(rank_profile(4, 99), [1, 2, 4, 2, 1]) + self.assertEqual(rank_profile(5, 2), [1, 2, 2, 2, 2, 1]) + + def test_strict_csv(self) -> None: + with tempfile.TemporaryDirectory() as directory: + path = Path(directory) / "train.csv" + path.write_text("input,output\n00000000,00000\n", encoding="ascii") + x_bits, y_bits = load_train_csv(path, "practice-add-n4") + self.assertEqual(x_bits.shape, (1, 8)) + self.assertEqual(y_bits.shape, (1, 5)) + path.write_text("input,output\n0000000X,00000\n", encoding="ascii") + with self.assertRaises(ValueError): + load_train_csv(path, "practice-add-n4") + + def test_model_round_trip_preserves_scores(self) -> None: + rng = np.random.default_rng(42) + cores = initialize_mps(4, 2, rng) + x_bits = np.asarray( + [[(row >> bit) & 1 for bit in range(4)] for row in range(16)], + dtype=np.int8, + ) + expected = predict_scores(cores, x_bits) + with tempfile.TemporaryDirectory() as directory: + path = Path(directory) / "model.npz" + metadata = {"n_outputs": 1, "n_sites": 4} + save_models(path, metadata, [cores]) + loaded = load_models(path) + np.testing.assert_allclose( + expected, predict_scores(loaded.models[0], x_bits) + ) + + def test_truth_encoding(self) -> None: + x_bits, outputs = enumerate_full_domain("practice-add-n4") + self.assertEqual(x_bits.shape, (256, 8)) + self.assertEqual(outputs.shape, (256, 5)) + # index = x + 16*y; x=13, y=4 + index = 13 + 16 * 4 + self.assertEqual( + sum(int(outputs[index, bit]) << bit for bit in range(5)), 17 + ) + + def test_add_interleaved_exact_rank_is_small(self) -> None: + _, outputs = enumerate_full_domain("practice-add-n4") + order = variable_order(4, "interleaved_lsb") + ranks = tt_rank_vectors(outputs.astype(np.float64), order) + self.assertLessEqual(max(max(vector) for vector in ranks), 4) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/test_tn_distill.py b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/test_tn_distill.py new file mode 100644 index 000000000..4ef418227 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/test_tn_distill.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 + +from __future__ import annotations + +import tempfile +import unittest +from pathlib import Path + +import numpy as np + +from distill_mps_bdd import ( + build_shared_robdd, + emit_mux_netlist, + verify_serialized_netlist, +) + + +class DistillationTests(unittest.TestCase): + def test_xor_and_or_multioutput_roundtrip(self) -> None: + bits = np.asarray( + [[(assignment >> bit) & 1 for bit in range(3)] + for assignment in range(8)], + dtype=np.int8, + ) + predictions = np.column_stack( + [bits[:, 0] ^ bits[:, 1], bits[:, 1] | bits[:, 2]] + ).astype(np.int8) + nodes, roots = build_shared_robdd(predictions, [0, 1, 2]) + self.assertGreater(len(nodes), 0) + with tempfile.TemporaryDirectory() as directory: + path = Path(directory) / "test.txt" + emit_mux_netlist(path, 3, [0, 1, 2], nodes, roots) + audit = verify_serialized_netlist(path, predictions) + self.assertTrue(audit["equivalent_to_thresholded_mps"]) + self.assertEqual(audit["mismatching_output_truth_tables"], 0) + + def test_variable_order_preserves_function(self) -> None: + bits = np.asarray( + [[(assignment >> bit) & 1 for bit in range(4)] + for assignment in range(16)], + dtype=np.int8, + ) + predictions = (bits[:, 0] ^ bits[:, 3])[:, None] + order = [3, 1, 0, 2] + nodes, roots = build_shared_robdd(predictions, order) + with tempfile.TemporaryDirectory() as directory: + path = Path(directory) / "test.txt" + emit_mux_netlist(path, 4, order, nodes, roots) + audit = verify_serialized_netlist(path, predictions) + self.assertTrue(audit["equivalent_to_thresholded_mps"]) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/tn_common.py b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/tn_common.py new file mode 100644 index 000000000..87ee3f9f8 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/tn_common.py @@ -0,0 +1,348 @@ +#!/usr/bin/env python3 +"""Shared, dependency-minimal utilities for the issue-71 tensor-network arm. + +Security boundary: +* Reads only strict two-column official CSV files and our own NPZ models. +* Does not execute input text, import competitor code, invoke subprocesses, or + access the network. +* Truth-function evaluation deliberately lives in tn_truth.py and is not + imported by the training program. +""" + +from __future__ import annotations + +import csv +import hashlib +import json +from dataclasses import dataclass +from pathlib import Path +from typing import Iterable + +import numpy as np + + +INSTANCE_SPECS = { + "practice-add-n4": {"n": 4, "m": 5}, + "practice-mul-n4": {"n": 4, "m": 8}, + "mystery-A": {"n": 8, "m": 9}, + "mystery-B": {"n": 7, "m": 7}, + "mystery-C": {"n": 6, "m": 12}, + "mystery-D": {"n": 5, "m": 11}, +} + +ORDER_NAMES = ( + "blocked_lsb", + "blocked_msb", + "interleaved_lsb", + "interleaved_msb", +) + + +def stable_seed(root_seed: int, *parts: object) -> int: + """Derive deterministic independent streams from root seed 42.""" + payload = ":".join([str(root_seed), *(str(p) for p in parts)]).encode("utf-8") + return int.from_bytes(hashlib.sha256(payload).digest()[:8], "little") % (2**32) + + +def variable_order(n: int, name: str) -> list[int]: + if name == "blocked_lsb": + return list(range(2 * n)) + if name == "blocked_msb": + return list(range(n - 1, -1, -1)) + list(range(2 * n - 1, n - 1, -1)) + if name == "interleaved_lsb": + return [j for i in range(n) for j in (i, n + i)] + if name == "interleaved_msb": + return [j for i in range(n - 1, -1, -1) for j in (i, n + i)] + raise ValueError(f"unsupported variable order: {name!r}") + + +def parse_bitstring(text: str, width: int, field: str, line_no: int) -> list[int]: + if len(text) != width or any(ch not in "01" for ch in text): + raise ValueError( + f"line {line_no}: {field} must be exactly {width} ASCII bits" + ) + return [ord(ch) - ord("0") for ch in text] + + +def load_train_csv(path: Path, instance: str) -> tuple[np.ndarray, np.ndarray]: + """Strictly parse an official train.csv, rejecting duplicate inputs.""" + if instance not in INSTANCE_SPECS: + raise ValueError(f"unknown instance {instance!r}") + n = int(INSTANCE_SPECS[instance]["n"]) + m = int(INSTANCE_SPECS[instance]["m"]) + xs: list[list[int]] = [] + ys: list[list[int]] = [] + seen: set[str] = set() + with path.open("r", encoding="ascii", newline="") as handle: + reader = csv.reader(handle) + header = next(reader, None) + if header != ["input", "output"]: + raise ValueError(f"{path}: expected header input,output") + for line_no, row in enumerate(reader, 2): + if len(row) != 2: + raise ValueError(f"line {line_no}: expected exactly two CSV fields") + x_text, y_text = row + if x_text in seen: + raise ValueError(f"line {line_no}: duplicate input") + seen.add(x_text) + xs.append(parse_bitstring(x_text, 2 * n, "input", line_no)) + ys.append(parse_bitstring(y_text, m, "output", line_no)) + if not xs: + raise ValueError(f"{path}: no training rows") + return np.asarray(xs, dtype=np.int8), np.asarray(ys, dtype=np.int8) + + +def rank_profile(n_sites: int, max_bond: int) -> list[int]: + if n_sites < 1 or max_bond < 1: + raise ValueError("n_sites and max_bond must be positive") + return [ + min(max_bond, 2**min(i, n_sites - i)) + for i in range(n_sites + 1) + ] + + +def initialize_mps( + n_sites: int, max_bond: int, rng: np.random.Generator +) -> list[np.ndarray]: + ranks = rank_profile(n_sites, max_bond) + cores = [] + for site in range(n_sites): + r_left, r_right = ranks[site], ranks[site + 1] + scale = 1.0 / np.sqrt(max(1, 2 * r_left)) + cores.append( + rng.normal(0.0, scale, size=(r_left, 2, r_right)).astype(np.float64) + ) + # Put the random model in a stable left-canonical gauge. + for site in range(n_sites - 1): + left_canonicalize(cores, site) + return cores + + +def predict_scores(cores: list[np.ndarray], x_ordered: np.ndarray) -> np.ndarray: + if x_ordered.ndim != 2 or x_ordered.shape[1] != len(cores): + raise ValueError("input/model site mismatch") + state = np.ones((x_ordered.shape[0], 1), dtype=np.float64) + for site, core in enumerate(cores): + selected = np.transpose(core[:, x_ordered[:, site], :], (1, 0, 2)) + state = np.einsum("ni,nij->nj", state, selected, optimize=True) + return state[:, 0] + + +def predict_bits(cores: list[np.ndarray], x_ordered: np.ndarray) -> np.ndarray: + return (predict_scores(cores, x_ordered) >= 0.0).astype(np.int8) + + +def left_canonicalize(cores: list[np.ndarray], site: int) -> None: + core = cores[site] + r_left, _, r_right = core.shape + q_mat, r_mat = np.linalg.qr(core.reshape(r_left * 2, r_right), mode="reduced") + if q_mat.shape[1] != r_right: + raise RuntimeError("invalid MPS rank profile during left QR") + cores[site] = q_mat.reshape(r_left, 2, r_right) + cores[site + 1] = np.einsum( + "ab,bic->aic", r_mat, cores[site + 1], optimize=True + ) + + +def right_canonicalize(cores: list[np.ndarray], site: int) -> None: + core = cores[site] + r_left, _, r_right = core.shape + q_mat, r_mat = np.linalg.qr( + core.reshape(r_left, 2 * r_right).T, mode="reduced" + ) + if q_mat.shape[1] != r_left: + raise RuntimeError("invalid MPS rank profile during right QR") + cores[site] = q_mat.T.reshape(r_left, 2, r_right) + cores[site - 1] = np.einsum( + "aib,bc->aic", cores[site - 1], r_mat.T, optimize=True + ) + + +def site_environments( + cores: list[np.ndarray], x_ordered: np.ndarray, site: int +) -> tuple[np.ndarray, np.ndarray]: + n_rows = x_ordered.shape[0] + left = np.ones((n_rows, 1), dtype=np.float64) + for j in range(site): + selected = np.transpose(cores[j][:, x_ordered[:, j], :], (1, 0, 2)) + left = np.einsum("ni,nij->nj", left, selected, optimize=True) + right = np.ones((n_rows, 1), dtype=np.float64) + for j in range(len(cores) - 1, site, -1): + selected = np.transpose(cores[j][:, x_ordered[:, j], :], (1, 0, 2)) + right = np.einsum("nij,nj->ni", selected, right, optimize=True) + return left, right + + +def ridge_cg( + features: np.ndarray, + targets: np.ndarray, + ridge: float, + initial: np.ndarray, + max_iterations: int, + tolerance: float, +) -> tuple[np.ndarray, dict[str, float | int]]: + """Solve ridge least squares by deterministic diagonally-preconditioned CG.""" + if features.shape[0] == 0: + return initial.copy(), {"iterations": 0, "relative_residual": 0.0} + rhs = features.T @ targets + diagonal = np.sum(features * features, axis=0) + ridge + + def matvec(vector: np.ndarray) -> np.ndarray: + return features.T @ (features @ vector) + ridge * vector + + solution = initial.copy() + residual = rhs - matvec(solution) + rhs_norm = max(float(np.linalg.norm(rhs)), 1.0) + relative = float(np.linalg.norm(residual)) / rhs_norm + if relative <= tolerance: + return solution, {"iterations": 0, "relative_residual": relative} + preconditioned = residual / np.maximum(diagonal, 1e-15) + direction = preconditioned.copy() + rz_old = float(residual @ preconditioned) + iterations = 0 + for iterations in range(1, max_iterations + 1): + image = matvec(direction) + denominator = float(direction @ image) + if denominator <= 0.0 or not np.isfinite(denominator): + break + alpha = rz_old / denominator + solution += alpha * direction + residual -= alpha * image + relative = float(np.linalg.norm(residual)) / rhs_norm + if relative <= tolerance: + break + preconditioned = residual / np.maximum(diagonal, 1e-15) + rz_new = float(residual @ preconditioned) + if rz_old == 0.0 or not np.isfinite(rz_new): + break + direction = preconditioned + (rz_new / rz_old) * direction + rz_old = rz_new + if not np.all(np.isfinite(solution)): + raise FloatingPointError("non-finite ridge solution") + return solution, {"iterations": iterations, "relative_residual": relative} + + +def update_one_site( + cores: list[np.ndarray], + x_ordered: np.ndarray, + targets_pm1: np.ndarray, + site: int, + ridge: float, + cg_iterations: int, + cg_tolerance: float, +) -> dict[str, float | int]: + left, right = site_environments(cores, x_ordered, site) + core = cores[site].copy() + total_iterations = 0 + worst_residual = 0.0 + for physical in (0, 1): + selected_rows = np.flatnonzero(x_ordered[:, site] == physical) + if selected_rows.size == 0: + continue + features = np.einsum( + "ni,nj->nij", + left[selected_rows], + right[selected_rows], + optimize=True, + ).reshape(selected_rows.size, -1) + solution, diagnostics = ridge_cg( + features, + targets_pm1[selected_rows], + ridge, + core[:, physical, :].reshape(-1), + cg_iterations, + cg_tolerance, + ) + core[:, physical, :] = solution.reshape(core.shape[0], core.shape[2]) + total_iterations += int(diagnostics["iterations"]) + worst_residual = max(worst_residual, float(diagnostics["relative_residual"])) + cores[site] = core + return { + "cg_iterations": total_iterations, + "worst_relative_residual": worst_residual, + } + + +def classification_metrics( + models: list[list[np.ndarray]], x_ordered: np.ndarray, y_bits: np.ndarray +) -> dict[str, float | int]: + scores = np.column_stack( + [predict_scores(output_model, x_ordered) for output_model in models] + ) + predictions = (scores >= 0.0).astype(np.int8) + targets_pm1 = 2.0 * y_bits.astype(np.float64) - 1.0 + return { + "rows": int(y_bits.shape[0]), + "bit_accuracy": float(np.mean(predictions == y_bits)), + "exact_accuracy": float(np.mean(np.all(predictions == y_bits, axis=1))), + "rmse_pm1": float(np.sqrt(np.mean((scores - targets_pm1) ** 2))), + } + + +@dataclass +class SavedMPS: + metadata: dict + models: list[list[np.ndarray]] + + +def save_models(path: Path, metadata: dict, models: list[list[np.ndarray]]) -> None: + arrays: dict[str, np.ndarray] = { + "metadata_utf8": np.frombuffer( + json.dumps(metadata, sort_keys=True, separators=(",", ":")).encode("utf-8"), + dtype=np.uint8, + ) + } + for output_index, output_model in enumerate(models): + for site, core in enumerate(output_model): + arrays[f"o{output_index:03d}_c{site:03d}"] = core + path.parent.mkdir(parents=True, exist_ok=True) + temporary = path.with_suffix(path.suffix + ".tmp.npz") + np.savez_compressed(temporary, **arrays) + temporary.replace(path) + + +def load_models(path: Path) -> SavedMPS: + with np.load(path, allow_pickle=False) as archive: + metadata = json.loads(bytes(archive["metadata_utf8"].tolist()).decode("utf-8")) + n_outputs = int(metadata["n_outputs"]) + n_sites = int(metadata["n_sites"]) + models = [ + [ + np.asarray(archive[f"o{output_index:03d}_c{site:03d}"]).copy() + for site in range(n_sites) + ] + for output_index in range(n_outputs) + ] + return SavedMPS(metadata=metadata, models=models) + + +def sha256_file(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as handle: + for block in iter(lambda: handle.read(1024 * 1024), b""): + digest.update(block) + return digest.hexdigest() + + +def atomic_json(path: Path, payload: dict | list) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + temporary = path.with_suffix(path.suffix + ".tmp") + temporary.write_text( + json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8" + ) + temporary.replace(path) + + +def comma_ints(text: str) -> list[int]: + values = [int(piece) for piece in text.split(",") if piece] + if not values or any(value < 1 for value in values): + raise ValueError("expected comma-separated positive integers") + return values + + +def comma_words(text: str, allowed: Iterable[str]) -> list[str]: + allowed_set = set(allowed) + values = [piece for piece in text.split(",") if piece] + if not values or any(value not in allowed_set for value in values): + raise ValueError(f"unsupported values in {text!r}") + return values diff --git a/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/tn_truth.py b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/tn_truth.py new file mode 100644 index 000000000..31edd1125 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/tn_truth.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +"""Independent full-domain truth evaluator for issue 71. + +This module is intentionally absent from train_mps.py and rank_diag.py. It is +used only after a model artifact has been frozen. +""" + +from __future__ import annotations + +import numpy as np + +from tn_common import INSTANCE_SPECS + + +FAMILIES = { + "practice-add-n4": "add", + "practice-mul-n4": "mul", + "mystery-A": "add", + "mystery-B": "absdiff", + "mystery-C": "mul", + "mystery-D": "sos", +} + + +def enumerate_full_domain(instance: str) -> tuple[np.ndarray, np.ndarray]: + if instance not in INSTANCE_SPECS or instance not in FAMILIES: + raise ValueError(f"unknown instance {instance!r}") + n = int(INSTANCE_SPECS[instance]["n"]) + m = int(INSTANCE_SPECS[instance]["m"]) + indices = np.arange(2 ** (2 * n), dtype=np.uint64) + mask = np.uint64((1 << n) - 1) + x_values = indices & mask + y_values = indices >> np.uint64(n) + family = FAMILIES[instance] + if family == "add": + outputs = x_values + y_values + elif family == "mul": + outputs = x_values * y_values + elif family == "absdiff": + outputs = np.where(x_values >= y_values, x_values - y_values, y_values - x_values) + elif family == "sos": + outputs = x_values * x_values + y_values * y_values + else: + raise AssertionError(family) + x_bits = np.column_stack( + [((indices >> np.uint64(bit)) & np.uint64(1)).astype(np.int8) + for bit in range(2 * n)] + ) + y_bits = np.column_stack( + [((outputs >> np.uint64(bit)) & np.uint64(1)).astype(np.int8) + for bit in range(m)] + ) + return x_bits, y_bits + + +def tt_rank_vectors( + values: np.ndarray, order: list[int], tolerance_scale: float = 1e-10 +) -> list[list[int]]: + """Exact numerical real TT ranks of complete scalar truth tensors. + + Returns one cut-rank vector per output channel. Values may be Boolean 0/1 + or signed -1/+1. The largest matrix here is only 256 by 256. + """ + if values.ndim != 2 or len(order) == 0: + raise ValueError("expected rows-by-output values and a nonempty order") + n_sites = len(order) + if values.shape[0] != 2**n_sites: + raise ValueError("full-domain row count mismatch") + rank_vectors: list[list[int]] = [] + for output_index in range(values.shape[1]): + original_axes = values[:, output_index].reshape((2,) * n_sites, order="F") + ordered_tensor = np.transpose(original_axes, axes=order) + cut_ranks: list[int] = [] + for cut in range(1, n_sites): + matrix = ordered_tensor.reshape(2**cut, 2 ** (n_sites - cut)) + singular_values = np.linalg.svd(matrix, compute_uv=False) + threshold = ( + tolerance_scale + * max(matrix.shape) + * (float(singular_values[0]) if singular_values.size else 0.0) + ) + cut_ranks.append(int(np.count_nonzero(singular_values > threshold))) + rank_vectors.append(cut_ranks) + return rank_vectors diff --git a/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/train_mps.py b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/train_mps.py new file mode 100644 index 000000000..2647fec9e --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/train_mps.py @@ -0,0 +1,241 @@ +#!/usr/bin/env python3 +"""Train-only continuous MPS baseline with one-site ridge ALS. + +The program never imports the truth evaluator. Hyperparameters and validation +split are fixed before any full-domain audit. +""" + +from __future__ import annotations + +import argparse +import copy +import json +from pathlib import Path + +import numpy as np + +from tn_common import ( + INSTANCE_SPECS, + ORDER_NAMES, + atomic_json, + classification_metrics, + initialize_mps, + load_train_csv, + predict_scores, + right_canonicalize, + save_models, + sha256_file, + stable_seed, + update_one_site, + variable_order, + left_canonicalize, +) + + +def split_indices(n_rows: int, seed: int, validation_fraction: float) -> tuple[np.ndarray, np.ndarray]: + if not (0.0 < validation_fraction < 0.5): + raise ValueError("validation_fraction must lie in (0, 0.5)") + rng = np.random.default_rng(seed) + permutation = rng.permutation(n_rows) + n_validation = max(1, min(n_rows - 1, int(round(n_rows * validation_fraction)))) + return permutation[n_validation:], permutation[:n_validation] + + +def scalar_metrics( + cores: list[np.ndarray], x_ordered: np.ndarray, y_bit: np.ndarray +) -> dict[str, float]: + scores = predict_scores(cores, x_ordered) + targets_pm1 = 2.0 * y_bit.astype(np.float64) - 1.0 + return { + "accuracy": float(np.mean((scores >= 0.0) == y_bit)), + "rmse_pm1": float(np.sqrt(np.mean((scores - targets_pm1) ** 2))), + } + + +def fit_output( + x_train: np.ndarray, + y_train: np.ndarray, + x_validation: np.ndarray, + y_validation: np.ndarray, + max_bond: int, + ridge: float, + sweeps: int, + patience: int, + seed: int, + cg_iterations: int, + cg_tolerance: float, + progress_prefix: str, +) -> tuple[list[np.ndarray], list[dict]]: + rng = np.random.default_rng(seed) + cores = initialize_mps(x_train.shape[1], max_bond, rng) + targets_pm1 = 2.0 * y_train.astype(np.float64) - 1.0 + best_cores = copy.deepcopy(cores) + best_key = (-1.0, float("-inf")) + history: list[dict] = [] + no_improvement = 0 + + for sweep in range(1, sweeps + 1): + cg_total = 0 + cg_worst = 0.0 + for site in range(len(cores)): + diagnostics = update_one_site( + cores, + x_train, + targets_pm1, + site, + ridge, + cg_iterations, + cg_tolerance, + ) + cg_total += int(diagnostics["cg_iterations"]) + cg_worst = max(cg_worst, float(diagnostics["worst_relative_residual"])) + if site + 1 < len(cores): + left_canonicalize(cores, site) + for site in range(len(cores) - 1, -1, -1): + diagnostics = update_one_site( + cores, + x_train, + targets_pm1, + site, + ridge, + cg_iterations, + cg_tolerance, + ) + cg_total += int(diagnostics["cg_iterations"]) + cg_worst = max(cg_worst, float(diagnostics["worst_relative_residual"])) + if site > 0: + right_canonicalize(cores, site) + + train_metrics = scalar_metrics(cores, x_train, y_train) + validation_metrics = scalar_metrics(cores, x_validation, y_validation) + record = { + "sweep": sweep, + "train": train_metrics, + "validation": validation_metrics, + "cg_iterations": cg_total, + "worst_cg_relative_residual": cg_worst, + } + history.append(record) + print( + f"{progress_prefix} sweep={sweep} " + f"train_acc={train_metrics['accuracy']:.6f} " + f"val_acc={validation_metrics['accuracy']:.6f} " + f"val_rmse={validation_metrics['rmse_pm1']:.6f}", + flush=True, + ) + selection_key = ( + validation_metrics["accuracy"], + -validation_metrics["rmse_pm1"], + ) + if selection_key > best_key: + best_key = selection_key + best_cores = copy.deepcopy(cores) + no_improvement = 0 + else: + no_improvement += 1 + if no_improvement >= patience: + break + return best_cores, history + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--instance", required=True, choices=sorted(INSTANCE_SPECS)) + parser.add_argument("--train-csv", required=True, type=Path) + parser.add_argument("--order", required=True, choices=ORDER_NAMES) + parser.add_argument("--bond", required=True, type=int) + parser.add_argument("--ridge", type=float, default=1e-5) + parser.add_argument("--sweeps", type=int, default=8) + parser.add_argument("--patience", type=int, default=3) + parser.add_argument("--validation-fraction", type=float, default=0.2) + parser.add_argument("--root-seed", type=int, default=42) + parser.add_argument("--cg-iterations", type=int, default=80) + parser.add_argument("--cg-tolerance", type=float, default=1e-7) + parser.add_argument("--model-out", required=True, type=Path) + parser.add_argument("--report-out", required=True, type=Path) + args = parser.parse_args() + + if args.bond < 1 or args.sweeps < 1 or args.patience < 1: + parser.error("bond, sweeps, and patience must be positive") + x_bits, y_bits = load_train_csv(args.train_csv, args.instance) + spec = INSTANCE_SPECS[args.instance] + order = variable_order(int(spec["n"]), args.order) + x_ordered = x_bits[:, order] + train_indices, validation_indices = split_indices( + x_bits.shape[0], + stable_seed(args.root_seed, args.instance, args.order, "split"), + args.validation_fraction, + ) + models: list[list[np.ndarray]] = [] + histories: list[list[dict]] = [] + for output_index in range(y_bits.shape[1]): + print( + f"fit instance={args.instance} order={args.order} bond={args.bond} " + f"output={output_index}/{y_bits.shape[1] - 1}", + flush=True, + ) + model, history = fit_output( + x_ordered[train_indices], + y_bits[train_indices, output_index], + x_ordered[validation_indices], + y_bits[validation_indices, output_index], + args.bond, + args.ridge, + args.sweeps, + args.patience, + stable_seed( + args.root_seed, + args.instance, + args.order, + args.bond, + output_index, + "mps", + ), + args.cg_iterations, + args.cg_tolerance, + f"output={output_index}", + ) + models.append(model) + histories.append(history) + + train_metrics = classification_metrics( + models, x_ordered[train_indices], y_bits[train_indices] + ) + validation_metrics = classification_metrics( + models, x_ordered[validation_indices], y_bits[validation_indices] + ) + metadata = { + "schema": "occam71-continuous-mps-v1", + "instance": args.instance, + "n_sites": int(x_bits.shape[1]), + "n_outputs": int(y_bits.shape[1]), + "order_name": args.order, + "order_original_axes": order, + "max_bond": args.bond, + "ridge": args.ridge, + "sweeps_budget": args.sweeps, + "patience": args.patience, + "validation_fraction": args.validation_fraction, + "root_seed": args.root_seed, + "training_csv_sha256": sha256_file(args.train_csv), + "train_rows": int(train_indices.size), + "validation_rows": int(validation_indices.size), + "train_metrics": train_metrics, + "validation_metrics": validation_metrics, + } + save_models(args.model_out, metadata, models) + report = { + **metadata, + "model_path": str(args.model_out), + "model_sha256": sha256_file(args.model_out), + "per_output_history": histories, + } + atomic_json(args.report_out, report) + print(json.dumps({k: report[k] for k in ( + "instance", "order_name", "max_bond", "train_metrics", + "validation_metrics", "model_sha256" + )}, sort_keys=True), flush=True) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/write_cell_manifest.py b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/write_cell_manifest.py new file mode 100644 index 000000000..9176303d1 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/write_cell_manifest.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""Atomically close one independent Slurm cell after all artifacts exist.""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +from tn_common import atomic_json, sha256_file + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--cell-dir", required=True, type=Path) + parser.add_argument("--kind", required=True, choices=("rank", "mps")) + parser.add_argument("--job-id", required=True) + parser.add_argument("--task-id", required=True, type=int) + parser.add_argument("--instance", required=True) + parser.add_argument("--order", required=True) + parser.add_argument("--bond", type=int) + parser.add_argument("--elapsed-seconds", required=True, type=int) + parser.add_argument("--input-sha256", required=True) + parser.add_argument("--artifact", action="append", default=[], type=Path) + args = parser.parse_args() + cell = args.cell_dir.resolve() + artifacts = [] + for supplied in args.artifact: + path = supplied.resolve() + if cell not in path.parents: + raise ValueError(f"artifact escapes cell directory: {path}") + if not path.is_file(): + raise FileNotFoundError(path) + artifacts.append( + { + "path": str(path.relative_to(cell)), + "bytes": path.stat().st_size, + "sha256": sha256_file(path), + } + ) + manifest = { + "schema": "occam71-tn-full-cell-v1", + "status": "success", + "kind": args.kind, + "job_id": args.job_id, + "task_id": args.task_id, + "instance": args.instance, + "order": args.order, + "bond": args.bond, + "root_seed": 42, + "elapsed_seconds": args.elapsed_seconds, + "input_sha256": args.input_sha256, + "artifacts": artifacts, + } + atomic_json(cell / "manifest.json", manifest) + (cell / "SUCCESS").write_text("success\n", encoding="ascii") + print(json.dumps(manifest, sort_keys=True), flush=True) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/write_distill_manifest.py b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/write_distill_manifest.py new file mode 100644 index 000000000..7a06fac83 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/tensor_network/write_distill_manifest.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 + +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +from tn_common import atomic_json, sha256_file + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--cell-dir", required=True, type=Path) + parser.add_argument("--job-id", required=True) + parser.add_argument("--task-id", required=True, type=int) + parser.add_argument("--instance", required=True) + parser.add_argument("--elapsed-seconds", required=True, type=int) + parser.add_argument("--artifact", action="append", default=[], type=Path) + args = parser.parse_args() + root = args.cell_dir.resolve() + artifacts = [] + for supplied in args.artifact: + path = supplied.resolve() + if root not in path.parents or not path.is_file(): + raise ValueError(f"invalid artifact: {path}") + artifacts.append({ + "path": str(path.relative_to(root)), + "bytes": path.stat().st_size, + "sha256": sha256_file(path), + }) + manifest = { + "schema": "occam71-tn-distill-cell-v1", + "status": "success", + "job_id": args.job_id, + "task_id": args.task_id, + "instance": args.instance, + "root_seed": 42, + "elapsed_seconds": args.elapsed_seconds, + "artifacts": artifacts, + } + atomic_json(root / "manifest.json", manifest) + (root / "SUCCESS").write_text("success\n", encoding="ascii") + print(json.dumps(manifest, sort_keys=True), flush=True) + + +if __name__ == "__main__": + main() diff --git a/tracks/qcs/solutions/Genshin_Impact-71/warmups/circuits/practice-add-n4.txt b/tracks/qcs/solutions/Genshin_Impact-71/warmups/circuits/practice-add-n4.txt new file mode 100644 index 000000000..a3f5450dd --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/warmups/circuits/practice-add-n4.txt @@ -0,0 +1,19 @@ +INPUTS 8 +w1 = XOR x1 x5 +w2 = AND x1 x5 +w3 = XOR x2 x6 +w4 = XOR w3 w2 +w5 = AND x2 x6 +w6 = AND w3 w2 +w7 = XOR w5 w6 +w8 = XOR x3 x7 +w9 = XOR w8 w7 +w10 = AND x3 x7 +w11 = AND w8 w7 +w12 = XOR w10 w11 +w13 = XOR x4 x8 +w14 = XOR w13 w12 +w15 = AND x4 x8 +w16 = AND w13 w12 +w17 = XOR w15 w16 +OUTPUTS w1 w4 w9 w14 w17 diff --git a/tracks/qcs/solutions/Genshin_Impact-71/warmups/circuits/practice-mul-n4.txt b/tracks/qcs/solutions/Genshin_Impact-71/warmups/circuits/practice-mul-n4.txt new file mode 100644 index 000000000..6d0459435 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/warmups/circuits/practice-mul-n4.txt @@ -0,0 +1,66 @@ +INPUTS 8 +w1 = AND x1 x5 +w2 = AND x1 x6 +w3 = AND x1 x7 +w4 = AND x1 x8 +w5 = AND x2 x5 +w6 = AND x2 x6 +w7 = AND x2 x7 +w8 = AND x2 x8 +w9 = AND x3 x5 +w10 = AND x3 x6 +w11 = AND x3 x7 +w12 = AND x3 x8 +w13 = AND x4 x5 +w14 = AND x4 x6 +w15 = AND x4 x7 +w16 = AND x4 x8 +w17 = XOR w2 w5 +w18 = AND w2 w5 +w19 = XOR w18 w9 +w20 = XOR w19 w6 +w21 = AND w18 w9 +w22 = AND w19 w6 +w23 = XOR w21 w22 +w24 = XOR w3 w20 +w25 = AND w3 w20 +w26 = XOR w25 w23 +w27 = XOR w26 w13 +w28 = AND w25 w23 +w29 = AND w26 w13 +w30 = XOR w28 w29 +w31 = XOR w27 w10 +w32 = XOR w31 w7 +w33 = AND w27 w10 +w34 = AND w31 w7 +w35 = XOR w33 w34 +w36 = XOR w4 w32 +w37 = AND w4 w32 +w38 = XOR w37 w35 +w39 = XOR w38 w30 +w40 = AND w37 w35 +w41 = AND w38 w30 +w42 = XOR w40 w41 +w43 = XOR w39 w14 +w44 = XOR w43 w11 +w45 = AND w39 w14 +w46 = AND w43 w11 +w47 = XOR w45 w46 +w48 = XOR w8 w44 +w49 = AND w8 w44 +w50 = XOR w49 w47 +w51 = XOR w50 w42 +w52 = AND w49 w47 +w53 = AND w50 w42 +w54 = XOR w52 w53 +w55 = XOR w51 w15 +w56 = XOR w55 w12 +w57 = AND w51 w15 +w58 = AND w55 w12 +w59 = XOR w57 w58 +w60 = XOR w59 w54 +w61 = XOR w60 w16 +w62 = AND w59 w54 +w63 = AND w60 w16 +w64 = XOR w62 w63 +OUTPUTS w1 w17 w24 w36 w48 w56 w61 w64 diff --git a/tracks/qcs/solutions/Genshin_Impact-71/warmups/predictions/practice-add-n4/test_outputs.csv b/tracks/qcs/solutions/Genshin_Impact-71/warmups/predictions/practice-add-n4/test_outputs.csv new file mode 100644 index 000000000..8bd1e1bd1 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/warmups/predictions/practice-add-n4/test_outputs.csv @@ -0,0 +1,137 @@ +input,output +01000111,00001 +11010100,10110 +00100001,00110 +00101010,10010 +01000000,01000 +11010000,11010 +10111101,00011 +01101111,10101 +10010011,10101 +00000010,00100 +10100010,10010 +11110111,10111 +10010100,11010 +01011010,11110 +01000100,00100 +00111101,11101 +11011110,01001 +01001001,11010 +10011111,00011 +11001111,01001 +10011010,01110 +01011011,11101 +00000011,00110 +11101111,01101 +10001011,01110 +00001000,10000 +10101011,01001 +01010101,00101 +01001101,10110 +11000000,11000 +11000011,11110 +10101100,00010 +00100100,01100 +01000011,01110 +01110101,00011 +00111011,10011 +01001111,10001 +00111111,11011 +00001011,10110 +01011000,11010 +11001101,01110 +01111100,10001 +10000010,10100 +11101011,00101 +10110101,11101 +10110010,10001 +11001110,01010 +01010000,01010 +10011101,00101 +00010110,01110 +01011110,10001 +11000110,10010 +10000110,11100 +01100001,01110 +00001101,11010 +00010011,00101 +11110110,10101 +11100100,10010 +10010101,11001 +11111111,01111 +11110000,11110 +10011110,00001 +10001000,01000 +10101110,00110 +10000100,11000 +11111110,01101 +10110111,11011 +10111011,01011 +01001011,11110 +11111011,00111 +00100110,01010 +00011101,11001 +10111000,01110 +11000111,10001 +01101000,11100 +00110101,01101 +00010000,00010 +11000100,10100 +11011111,01011 +11100111,10101 +10110001,10101 +10100110,11010 +10111110,00101 +00000100,01000 +01110100,00001 +00111110,11001 +01000101,00110 +01001110,10010 +10101000,01100 +01111011,11011 +01110000,01110 +00101000,10100 +11110010,11001 +11011100,01110 +11011011,00011 +11101000,00010 +01101010,11010 +10101101,00001 +10001111,00001 +11011000,00110 +10100111,11001 +00001100,11000 +10100101,11110 +11111001,00011 +01000110,00010 +00011001,10001 +10100011,10001 +10101111,00101 +01000001,01010 +00101001,10110 +11001011,00001 +01100111,00101 +11101101,01001 +01111000,11110 +01010001,01001 +00101110,11010 +01010010,01110 +11000101,10110 +10010110,11110 +01101110,10110 +10000001,10010 +10011011,01101 +11110101,10011 +11100101,10001 +00011010,10110 +01110111,00111 +11101001,00001 +01101101,10001 +11010110,10001 +00011100,11010 +00111001,10101 +00011111,11101 +00110011,00011 +11111100,01001 +11101100,01010 +10111001,01101 diff --git a/tracks/qcs/solutions/Genshin_Impact-71/warmups/predictions/practice-mul-n4/test_outputs.csv b/tracks/qcs/solutions/Genshin_Impact-71/warmups/predictions/practice-mul-n4/test_outputs.csv new file mode 100644 index 000000000..ace9ab004 --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/warmups/predictions/practice-mul-n4/test_outputs.csv @@ -0,0 +1,137 @@ +input,output +01000000,00000000 +10000011,00110000 +01011101,01110110 +00010110,00001100 +01011110,01100010 +00110010,00001100 +10111011,10010101 +11000001,00011000 +11101011,11011010 +01111111,01001011 +00011000,00010000 +01001001,01001000 +01111101,01011001 +11011000,11010000 +11110100,01111000 +10111111,11000011 +00000011,00000000 +10101011,10000010 +10110101,01000001 +11100101,01100010 +10110111,01101101 +00011110,00011100 +11111100,10110100 +00011001,00010010 +10101101,11101100 +00101101,00110100 +01100100,00110000 +10010101,01011010 +00100001,00000100 +00000100,00000000 +00010100,00001000 +11001000,11000000 +10110100,01011000 +11001110,10101000 +00100011,00001100 +01011011,01000001 +00001110,00000000 +01100001,00001100 +01101000,01100000 +11111110,10010110 +00111010,00111100 +10011110,11111100 +01101100,01001000 +10001100,11000000 +00010111,00001110 +00101111,00111100 +00011100,00011000 +00001100,00000000 +11001011,11100100 +00001001,00000000 +01001111,01111000 +01100011,00010010 +11111111,10000111 +00011011,00010110 +10011001,10001010 +10000101,01010000 +11010010,00110100 +11100110,01010100 +01100010,00011000 +10101001,10110100 +11000110,01001000 +01101110,01010100 +00110111,00010101 +10100111,01100010 +01001101,01101000 +10100101,01001100 +01000111,00111000 +10010111,01111110 +11011101,10011110 +11001111,10110100 +01000101,00101000 +01101111,01011010 +10001000,10000000 +00111101,00100001 +00100100,00010000 +11001100,10010000 +10111110,11011010 +01011010,01001100 +10010000,00000000 +01011000,01010000 +11101110,10001100 +00000001,00000000 +01001110,01110000 +10100011,00111100 +00000110,00000000 +01111001,01111110 +00111000,00110000 +10101111,11010010 +11000000,00000000 +01110000,00000000 +01011001,01011010 +00100110,00011000 +11110010,00111100 +11101100,10101000 +11000101,01111000 +11110000,00000000 +10000111,01110000 +01001010,01010000 +10110011,00111001 +00010000,00000000 +01010111,00110001 +11010000,00000000 +01111000,01110000 +11101010,11000100 +01100111,00101010 +00110110,00010010 +11101101,10110010 +11110011,00101101 +01010010,00010100 +11111101,10100101 +01111110,01000110 +01101101,01000010 +00110000,00000000 +00010011,00000110 +10100110,01111000 +10101110,11000100 +01101010,01111000 +11111010,11010010 +11100111,01000110 +01011111,01101001 +01001011,01011000 +10111001,10101110 +10101100,11110000 +00111100,00100100 +10110001,00010110 +10011010,10110100 +01101001,01101100 +10110110,01110010 +00001010,00000000 +00111110,00101010 +01000011,00011000 +01100110,00100100 +00000101,00000000 +11001001,11011000 +10001010,10100000 +01010000,00000000 diff --git a/tracks/qcs/solutions/Genshin_Impact-71/warmups/warmup_manifest.json b/tracks/qcs/solutions/Genshin_Impact-71/warmups/warmup_manifest.json new file mode 100644 index 000000000..26a11866c --- /dev/null +++ b/tracks/qcs/solutions/Genshin_Impact-71/warmups/warmup_manifest.json @@ -0,0 +1,51 @@ +{ + "note": "Deterministic construction; no random stream was required.", + "results": [ + { + "circuit": "/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/warmups/circuits/practice-add-n4.txt", + "commitment_match": true, + "commitment_sha256": "a02fa3f4aa915b97534fd5c70ba89a08522991030423f1eeb92260bc8539671a", + "exhaustive": { + "checked": 256, + "failures": 0 + }, + "family": "add", + "gates": 17, + "instance": "practice-add-n4", + "prediction_sha256": "a02fa3f4aa915b97534fd5c70ba89a08522991030423f1eeb92260bc8539671a", + "predictions": "/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/warmups/predictions/practice-add-n4/test_outputs.csv", + "training": { + "bit_accuracy": 1.0, + "correct_bits": 600, + "exact": 120, + "exact_accuracy": 1.0, + "rows": 120, + "total_bits": 600 + } + }, + { + "circuit": "/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/warmups/circuits/practice-mul-n4.txt", + "commitment_match": true, + "commitment_sha256": "f9e600cdd707f60639138e4e14656ea0c420fbce9b9fdde52d630d83076e88fa", + "exhaustive": { + "checked": 256, + "failures": 0 + }, + "family": "mul", + "gates": 64, + "instance": "practice-mul-n4", + "prediction_sha256": "f9e600cdd707f60639138e4e14656ea0c420fbce9b9fdde52d630d83076e88fa", + "predictions": "/home/user_milksang/private/homefile/quantum_harness/issue71_occam/results/occam71/warmups/predictions/practice-mul-n4/test_outputs.csv", + "training": { + "bit_accuracy": 1.0, + "correct_bits": 960, + "exact": 120, + "exact_accuracy": 1.0, + "rows": 120, + "total_bits": 960 + } + } + ], + "root_seed": 42, + "schema": "occam71-warmup-v1" +}