Skip to content
Merged
2 changes: 1 addition & 1 deletion .claude/board/STATUS_BOARD.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Plan: `.claude/plans/d-relalg-unified-v1.md`. Decisions recorded there: ADMISSIO
| **D-RU-0** | decisions: ADMISSION = B, D-RA-2 = A | DECIDED (operator, 2026-10-20) | — |
| **D-RU-1** | U1: JC statistical batch, distinct (selection, key, lane pair) triples over shared power-sum terminals | MEASURED (`crates/jc-batch-probe`; `entries/2026-10-20-u1-jc-batch.md`): 252 requests → 2 selection + 42 cross passes; at 1M rows 6.4× faster, 11.5× fewer rows traversed, 8.5× fewer values read, 126× fewer mask bytes; bit-identical to per-request; no R reference (R absent) | rows traversed and terminal passes below the per-request baseline; results bit-exact on integer lanes; overflow refused, not wrapped; R references |
| **D-RU-2** | U2: `let`-bound planes on the contract IR, immutable `Plane(i)`, versioned coordinates, no DAG inside a fold | MEASURED (`contract::set_program`, `relalg::program`; `entries/2026-10-21-u2-let-planes.md`): 16 queries sharing one conjunction at 1M rows: 3.1× faster, 14 vs 59 lane reads, 2 planes (250 kB) materialized, ≤ 2 scratch slots; answers equal recompute and a row oracle; other versions refused | rows traversed and planes materialized against recompute; width test ≤ 3 slots; answers and versions unchanged |
| **D-RU-3** | cross-rule CSE in crepe-plan-probe (256-rule benchmark, separate from the hop benchmark) | Queued (after D-RU-2) | answers == crepe and ascent; traversals and materializations measured |
| **D-RU-3** | cross-rule CSE in crepe-plan-probe (256-rule benchmark, separate from the hop benchmark) | MEASURED (`crepe-plan-probe/src/cse.rs`, `examples/rule_cse.rs`; `entries/2026-10-21-u3-cross-rule-cse.md`): 256 rules, 90 % shared core, 200k centers: rows laid out 15.3 M → 6.2 M, prepare 1.78 s → 1.22 s; lets cut ops 512 → 283 but folds are ≤ 2.4 ms and `share` costs 7 ms; one table for all rules is slower than per-rule when nothing is shared, so tables are per group of rules sharing a side; answers equal crepe, ascent and the reference | answers == crepe and ascent; traversals and materializations measured |
| **D-RU-4** | co-addressed join operator over `JoinDomain` | Queued (after D-RU-2) | parity with the star path; no position taken from another table's values |
| **D-RU-5** | Quack physical pins (dense vs sorted, materialize vs recompute) | Queued (after D-RU-3/4) | can-fire and can-stay-silent per pin |
| **D-RU-6** | recursion driver over BIND tiles (256-hop benchmark) | Queued/Lab (slot assignment must be argued as an attested frozen-codebook lookup under ADMISSION = B). Lab probe MEASURED (`crepe-plan-probe/examples/walk_tile.rs`, `entries/2026-10-21-d-ru-6-tile-walk-probe.md`): tuple sets equal ascent, crepe and walk_mask; execution gather-free; binding witness probe-local, so ADMISSION B is NOT demonstrated; count-only 1.0–9.3× slower than walk_mask, end to end 1.1–2.0× slower. Block-sparse arm C MEASURED (`examples/walk_sparse.rs`, `entries/2026-10-21-d-ru-6-sparse-compute.md`): faster than the dense tile path on every fixture (0.11–0.96× count-only); against walk_mask end to end it wins only on deep low-fan-out recursion (chain(230) 0.82×, chain(700) 0.59× UNCHECKED), is near parity on sparse graphs (1.03–1.07×) and loses on dense, cyclic, clustered and mixed-length graphs (1.20–2.43×) | least fixpoint == datafrog; ≥ 200 iterations asserted; cycles terminate. Benchmark requirement (operator, 2026-10-21; refs D-RMAP-2 §5b, #1642): tuple materialization, count-only evaluation, memory growth, address binding and value-derived reads by origin reported separately; tuple-set equality with crepe and ascent wherever they run |
Expand Down
65 changes: 65 additions & 0 deletions .claude/board/entries/2026-10-21-u3-cross-rule-cse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# 2026-10-21 — D-RU-3 (U3): cross-rule CSE in crepe-plan-probe

**Status:** MEASURED (lab; `crates/crepe-plan-probe/src/cse.rs`, `examples/rule_cse.rs`). Plan: `.claude/plans/d-relalg-unified-v1.md` §7, §12, stage table (U3). Builds on U2 (`contract::set_program`, `relalg::run_program`, #1649).

## What landed

- `cse::evaluate_shared(rules, demand, kind, opts, store, layout, sharing)`. It makes the same rewrites as `pipeline::evaluate` and gives the same answer. Per group of rules it does three things:
- lays out each distinct `SideKey` once, over one domain;
- writes every rule as `And(Sym(side))`;
- with `sharing` on, lifts shared parts into lets (`set_program::share`) and runs the group through `relalg::run_program`.
- `Layout::Components`: rules that share a side, directly or through other rules, share a table. A rule that shares nothing keeps a table of its own. `Layout::OneTable` puts every rule into one table.
- Tuples are read per rule by `native::tuples_over_mask`. This is the per-rule path's own read, moved into a helper. The 41 existing tests are unchanged.
- Refusals: a stale side (`JoinRefusal::Stale`), a generation vector of the wrong shape, and a program for another version.
- The shared path ignores `Options::semijoin`. A semijoin's reduced domain is per rule.

## Measured

Workload: 256 rules `Out(k,x,z) <- R_g(x,c), S_g(c,z), T(c,k)`. The first `p · 256` rules use core 0; every other rule has a core of its own. Release build, 4 cores, medians.
- Arms: A is the per-rule pipeline; U is one table with lets; D0 is components without lets; D is components with lets.
- `exists` runs only the folds. `bindings` reads the full bag.

200,000 centers, 20,000 rows per core relation (3 reps):

| core shared | arm | prepare | rows prepared | folds / ops | fold time | share (compile) | tuples |
|---|---|---|---|---|---|---|---|
| 0 % | A | 1851 ms | 15.3 M | 256 / 512 | — | — | 132 ms |
| 0 % | U | 2721 ms | 15.3 M | 256 / 512 | 2.38 ms | 1.1 ms | 259 ms |
| 0 % | D0 | 1794 ms | 15.3 M | 256 / 512 | 0.86 ms | — | 130 ms |
| 90 % | A | 1777 ms | 15.3 M | 256 / 512 | — | — | 142 ms |
| 90 % | D0 | 1284 ms | 6.2 M | 256 / 512 | 1.39 ms | — | 175 ms |
| 90 % | D | 1218 ms | 6.2 M | 257 / 283 | 1.18 ms | 7.1 ms | 169 ms |

At 4,000 centers (5 reps) the pattern is the same:
- 90 %: prepare A 197 ms, D0 35 ms, D 36 ms;
- `exists` folds: D0 0.44 ms, D 0.41 ms;
- `share` 7.7 ms.

**Findings.**
- **The win is the layout, not the fold.** At 90 % the shared core is laid out once instead of 230 times: 15.3 M rows become 6.2 M, and prepare falls 1.78 s → 1.22 s. That is −31 % end to end for `exists` and −27 % for `bindings`.
- **Lets cut ops 512 → 283, but fold time is negligible here** (≤ 2.4 ms against ≥ 1.2 s per run). `share` costs 7–8 ms of compile time at 90 %, more than the folds it saves. The pairwise subset search is quadratic in the number of rules.
- **One table for everything is wrong.** Every side is laid out densely over its table's domain. With nothing shared, U is slower than A: prepare 2.72 s vs 1.85 s, tuples 259 ms vs 132 ms. That is why `Layout::Components` exists. With it, D0 at 0 % lays out exactly A's rows, one table per rule.
- **A shared group pays width.** At 90 % the core-0 group spans almost all centers, so tuple reads are 175 ms vs 142 ms for A. The layout saving outweighs that.

**Correctness.**
- Every arm, under both layouts, equals:
- the reference evaluator (bags);
- `crepe!` and `ascent!` (sets; the 256 rules written as one parametrized rule);
- arm A.
- Checked at 0/50/90/100 % shared in the tests, and at 0/50/90 % at 4,000 centers in the benchmark.
- Bound demands go through the post filter, with and without propagation.
- **Disables:** seven, all red:
- sharing ignored;
- wrong table side read;
- generation check removed;
- keys not deduplicated;
- post filter dropped;
- components never joined;
- components always one table.
- The post-filter disable first did not compile. The suite had no bound demand, so a test was added first.

## Open

- `share`'s subset search costs more than it saves when planes are this small. Two possible fixes, neither measured: a cheaper share for star rules (group by identical core keys), or skipping lets when one plane is cheaper than the search.
- Factorization (plan arm E) and a shared BIND (arm F) are not built.
- The JC batch through the same lets (arm H) is not built.
3 changes: 2 additions & 1 deletion .claude/board/entries/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ index row, (3) no duplicate entry id. Checks 1 and 2 are deliberately
opposite directions; the stranding this convention prevents shows up in
exactly one of them, never both.

403 entries, 2026-08-06 .. 2026-10-21.
404 entries, 2026-08-06 .. 2026-10-21.

| date | entry id | finding | file |
|---|---|---|---|
| 2026-10-21 | `D-RU-3` | | [2026-10-21-u3-cross-rule-cse.md](2026-10-21-u3-cross-rule-cse.md) |
| 2026-10-21 | `D-RU-2` | | [2026-10-21-u2-let-planes.md](2026-10-21-u2-let-planes.md) |
| 2026-10-21 | `D-RU-6-LAB-1` | | [2026-10-21-d-ru-6-tile-walk-probe.md](2026-10-21-d-ru-6-tile-walk-probe.md) |
| 2026-10-21 | `D-RU-6-LAB-2` | | [2026-10-21-d-ru-6-sparse-compute.md](2026-10-21-d-ru-6-sparse-compute.md) |
Expand Down
2 changes: 1 addition & 1 deletion .claude/plans/d-relalg-unified-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -298,4 +298,4 @@ One workload, run under three toggles. Each toggle changes one mechanism only.
| U7 | prefetch and staging of Arm B ranges | U6 or a non-recursive multi-stage plan | critical-path overlap measured; bytes read unchanged |
| U8 | Wikidata and SAP-shaped fixtures; completeness refusals | U6, U7 | refusal tests; witness carried end to end |

U1 needs nothing new and comes first. **U1 MEASURED (2026-10-20, `crates/jc-batch-probe`, `entries/2026-10-20-u1-jc-batch.md`):** 252 requests reduce to 2 selection and 42 cross passes; at 1M rows the batch is 6.4× faster with 11.5× fewer rows traversed, 8.5× fewer values read and 126× fewer mask bytes, and its answers equal the per-request answers bit for bit. The R reference is replaced by JC's slice functions because R is not installed. **U2 MEASURED (2026-10-21, `contract::set_program` + `relalg::program`, `entries/2026-10-21-u2-let-planes.md`):** each shared junction subset is evaluated once into a scratch plane; for 16 queries sharing one conjunction at 1M rows the program is 3.1× faster than recompute and reads 14 lanes instead of 59, at the cost of 2 materialized planes (250 kB). Answers equal recompute and a row oracle, and a program for another version is refused. Both are accepted only on measured row traversals, materializations and total cost (§ Decisions), not on operator counts.
U1 needs nothing new and comes first. **U1 MEASURED (2026-10-20, `crates/jc-batch-probe`, `entries/2026-10-20-u1-jc-batch.md`):** 252 requests reduce to 2 selection and 42 cross passes; at 1M rows the batch is 6.4× faster with 11.5× fewer rows traversed, 8.5× fewer values read and 126× fewer mask bytes, and its answers equal the per-request answers bit for bit. The R reference is replaced by JC's slice functions because R is not installed. **U2 MEASURED (2026-10-21, `contract::set_program` + `relalg::program`, `entries/2026-10-21-u2-let-planes.md`):** each shared junction subset is evaluated once into a scratch plane; for 16 queries sharing one conjunction at 1M rows the program is 3.1× faster than recompute and reads 14 lanes instead of 59, at the cost of 2 materialized planes (250 kB). Answers equal recompute and a row oracle, and a program for another version is refused. **U3 MEASURED (2026-10-21, `crepe-plan-probe/src/cse.rs` + `examples/rule_cse.rs`, `entries/2026-10-21-u3-cross-rule-cse.md`):** the §7 claim "one core join plus 256 ANDs" holds for the layout, not for the folds. At 90 % shared core the core is laid out once (15.3 M → 6.2 M rows, prepare 1.78 s → 1.22 s); the lets cut ops 512 → 283, but the folds cost ≤ 2.4 ms and the subset search 7 ms. One table for all rules is slower than per-rule tables when nothing is shared, so tables are formed per group of rules that share a side. Both are accepted only on measured row traversals, materializations and total cost (§ Decisions), not on operator counts.
4 changes: 4 additions & 0 deletions .github/workflows/crepe-plan-probe.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ jobs:
# D-RU-6 lab probe: the same program over RMAP tiles, its oracle
# comparisons and refusals. Its benchmark is not run in CI either.
- run: cargo test --example walk_tile
# D-RU-3 (U3): cross-rule CSE; its oracle comparisons and refusals.
# The benchmark (`cargo run --release --example rule_cse`) is not run
# in CI.
- run: cargo test --example rule_cse
# D-RU-6 arm C: the block-sparse product, against arms A and B and the
# oracles, its refusals and the semi-naive bound.
- run: cargo test --example walk_sparse
Expand Down
Loading
Loading