diff --git a/docs/tracking/2026-06-12_PTF_TRACK2_BROWSER_VIZ_DESIGN.md b/docs/tracking/2026-06-12_PTF_TRACK2_BROWSER_VIZ_DESIGN.md new file mode 100644 index 000000000..ed7d6014e --- /dev/null +++ b/docs/tracking/2026-06-12_PTF_TRACK2_BROWSER_VIZ_DESIGN.md @@ -0,0 +1,401 @@ +# PTF Track 2 — Standalone Browser Visualization + Arbitrary-Program Trace Export + +**Created**: 2026-06-12 (PReduce-autonomy loop re-arm, iteration 44) +**Status**: OPENED — grounding complete; Stage 3 design decisions next +**Owner goal (verbatim direction, 2026-06-12)**: "generate a browser visualization +that can show the propagator network and play execution in order to show how the +system works for arbitrary prologos programs." +**Grounded at HEAD**: `ff739de7` (branch carries the full preduce-autonomy state) +**Prior art**: PTF Track 1 (observatory + VS Code panel) — this track extends it; +see `docs/tracking/2026-03-12_PROPAGATOR_VISUALIZATION_DESIGN.md` + +`2026-03-12_PROPAGATOR_OBSERVATORY.md`. + +## BRANCH DIRECTIVE — on-network reduction is the target (owner, 2026-06-14) + +**Written down per owner direction.** This is a **prototype branch for showing +how the visualization works for a FUTURE, propagator-native Prologos.** It is +built on top of the **PReduce on-network-reduction prototype**. Therefore: + +1. **On-network reduction is ALWAYS used on this branch.** The viz exporter + defaults reduction to on-network (`#:reduce? #t`; `--no-reduce` to opt out). + Functional reduction is run through the PReduce e-graph, not the + off-network recursive reducer. (Done: `viz-export.rkt`, 2026-06-14.) +2. **The visualization is based on, and shows, propagator-based reduction.** + That is the whole point — the viz exists to make the propagator-native + execution visible. + +**What "propagator-based reduction" IS today, precisely** (so the directive is +honest about the prototype's current reach): + +- **ON-network now**: each reduction STEP — a redex ⇒ result rewrite — is + applied to the e-graph as a **union propagator** (`eclass-union`, + `eclass-graph.rkt`). This is **DPO rewriting on the propagator substrate**, + which the PReduce series confirmed (PRN Master, Confirmed Findings §2). So + the union propagators the viz shows ARE the reductions, applied on-network. + The e-graph's structural sharing (hashcons) makes it the propagator-native, + automatically-memoizing reduction engine the vision calls for. +- **NOT-yet on-network** (the gap a fully propagator-native future closes, and + which the viz will surface MORE of as PReduce matures): (a) the **RHS value + computation** — instantiating a rule's result (e.g. `377+233=610`) is still a + function (`instantiate-template`, `rule-dispatch.rkt:apply-rule`), not a + compute-propagator reading operands and writing the sum; (b) the **recursion + driver** — `fib` calling `fib` is still the recursive reducer + (`reduction.rkt`). Track 8 (e-graph as the reduction ENGINE, retiring + `reduction.rkt`) is the future work this prototype anticipates. + +**Consequence for the viz**: with on-network reduction the default, the viewer +shows the reduction-as-rewriting (the union steps + the e-class DAG). As the +RHS-compute and recursion driver move on-network in future PReduce work, those +will appear as additional propagators with no viewer change required — the +viewer renders whatever propagators the trace contains. The viz is the +instrument that will make that progression visible. + +## Progress Tracker + +| Phase | Description | Status | Notes | +|---|---|---|---| +| 0 | Grounding audit (5 facets + adversarial critic) | ✅ | this commit; synthesis below | +| 0.5 | Environment shakeout + empirical capture probe (install Racket in container; run a demo file with observer armed; verify non-empty rounds + real edges; measure counts) | ✅ | iter 45; findings §6; probe = tools/viz-capture-probe.rkt | +| 1 | Stage 3 design lock: D1–D7 + critique round + VAG | ✅ | iter 46; §7; LOCKED (amended) | +| 0A | Acceptance file `examples/2026-06-12-ptf-track2-viz.prologos` + corpus definition (ordering debt from critique B1 — acceptance precedes ALL implementation) | ✅ | iter 47; Level-3 clean (17 commands, 0 errors); corpus audit §8 | +| 2a | In-container full-suite baseline (gate for any production edit) | ✅ | iter 48: **8658/439 ALL PASS, 401.5s** (after env fixes: rackcheck + `raco pkg install --link` + one stale 8.10 .zo; 9 env-failures → 0; zero flakes) | +| 2b | Production hooks: Tier-1 observer call (A1); pre-registered fallbacks: observer-site timestamps (A2), solve-boundary observatory hook (A4) — full suite + bench A/B at close | ✅ | iter 49 (`6d25e58`): armed path = per-fire champ-diff + one bsp-round; unarmed byte-identical; 3 unit tests; suite 8666/439 GREEN (400.6s ≈ baseline); fallbacks NOT yet needed (2c validates); corpus never takes Tier-1 (falsified-workload note in ledger) | +| 2c | `tools/viz-export.rkt` + golden tests (tests land WITH the exporter) | ✅ | iter 50 (`b0bbb88`): ALL criteria PASS on the corpus — monotone ✓, captures==commands ✓, **solver free path ✓** (relation epochs 67-81c/172-179p); fallbacks NOT needed; envelope ≤880KB | +| T | Dedicated test file `tests/test-viz-export.rkt` (schema regression) | ✅ | landed with 2c; 7/7 (envelope keys, parity, monotone, identity coverage, D7 bounds, solver-epoch assert, jsexpr round-trip) | +| 3 | Standalone browser viewer: topology + playback; component-aware layout; coverage display | ✅ | iter 51 (`10b0f4a`): tools/viz/index.html + check.js; headless core verification ALL PASS on the corpus; **browser acceptance = owner's machine** (no GUI in the container — honest limitation); artifacts delivered to owner | +| 4 | Riders per data: compound-cell component diffs; D7 depth; solver hook if 2c validation demands; any rider adding cells/propagators carries its own NTT model | ✅ | ADJUDICATED at close (iter 52): solve-hook DISSOLVED (free path validated); A2 timestamps DORMANT (bucketing validated); F4 production identity OUT OF SCOPE (PATH B); component diffs + D7 depth → DEFERRED.md w/ triggers. **TRACK CLOSED** — PIR: `2026-06-12_PTF_TRACK2_PIR.md` | + +## 1. Grounding synthesis (what EXISTS at `ff739de7`) + +All findings verified by HEAD-pinned read-only facet agents + an adversarial +completeness critic; citations are file:line at this HEAD. + +**Capture layer — exists and is LSP-agnostic:** +- `current-bsp-observer` parameter + `make-trace-accumulator` (propagator.rkt + ~576/594): when armed, the Tier-2 BSP loop calls the observer at every round end + (propagator.rkt:3581–3598) with a `bsp-round` record. +- Trace structs: `bsp-round` (round-number, network-snapshot, cell-diffs, + propagators-fired, contradiction, atms-events), `cell-diff` (cell-id, old, new, + source-propagator), `prop-trace` (initial-network, rounds, final-network, + metadata) — propagator.rkt:543–560. +- JSON serialization: `trace-serialize.rkt` (jsexpr; lattice values stringified). + `serialize-network-topology` (trace-serialize.rkt:111–171) emits cells AND + propagators **with `inputs`/`outputs` cell-id arrays** — real edges are in the + schema (critic-verified, trace-serialize.rkt:155–162). +- Multi-network capture: `prop-observatory.rkt` (`capture-network`, cell-meta with + subsystem/label/srcloc) + `observatory-serialize.rkt` → JSON. Headless-capable; + nothing LSP-dependent in the capture path (critic verdict §3). +- Driver hooks: `process-string/return-net` (driver.rkt:1959–1964) returns the + elab-network; `current-network-capture-box` + per-command observatory capture + (driver.rkt:1036–1058). One elaboration network per `process-file` + (driver.rkt:2234); ATMS solver network is a SEPARATE prop-network per command + (driver.rkt:481–482); per-module networks during module load (driver.rkt:2589). + +**Viewer prior art — exists, in the WRONG host for this goal:** +- `editors/vscode-prologos/src/propagatorView.ts` (1952 lines): Canvas + d3-dag + Sugiyama layout for the bipartite DAG (cells=circles, propagators=diamonds), + d3-zoom/quadtree/drag, BSP-round timeline scrubber with differential rendering, + subsystem palette (type-inference #6a9955 / infrastructure #888 / multiplicity + #b48ead), click-to-source. Phases 0–5 of the March design are COMPLETE. +- VS Code coupling is thin: one `acquireVsCodeApi()` site (propagatorView.ts:1878) + + message passing for editor commands/state; the rendering core is host-agnostic + (critic verdict §5: ~90% portable). +- Graphviz/Cytoscape were evaluated and REJECTED in the March design (§4.2) with + recorded rationale — the browser viewer should not relitigate this. + +**What .pnet/.pnetx can and cannot provide:** +- Both are VALUE/registry-oriented; `prop-network`/`elab-network` serialize as + runtime sentinels (pnet-serialize.rkt:101–105). The Track 5 .pnetx sections are + cell-state projections (eclasses + rewrites), NO topology. **Verdict: not a viz + source; the trace/observatory path is the right substrate.** + +## 2. Gap analysis (what the owner's goal needs that does NOT exist) + +| # | Gap | Evidence | +|---|---|---| +| G1 | No headless CLI tool runs a `.prologos` file and writes trace/observatory JSON to disk (sweep of tools/ + racket/prologos/tools/: none) | critic §3 | +| G2 | No standalone browser viewer; the only viewer is the VS Code webview fed by LSP endpoints | facet 5 | +| G3 | Tier-1 fast-path BSP runs (zero-worldview, fire-once-only worklists — the COMMON case for simple elaborations) **skip the observer entirely** (propagator.rkt:3437–3471 flush has no observer call) — simple programs would record zero rounds | critic §1/§6 | +| G4 | Solver-network (relations/solve) and per-module networks are not linked into one capture story; observatory captures are per-command elab-network snapshots | critic §6 | +| G5 | Compound/universe cells (post-PPN 4C) hold many components per cell; `cell-diff` is whole-cell — playback granularity inside compound cells is an open question | critic §4 | +| G6 | March-era scale estimates (~40–500 nodes/command) predate universe-cell consolidation; no current empirical counts | critic §4 | + +## 3. Design questions for Stage 3 (settle at Phase 1, after the Phase 0.5 probe) + +1. **Exporter shape**: new `tools/viz-export.rkt` wrapping `process-file` with + observer + observatory armed, emitting ONE self-contained JSON (topology + + rounds + metadata) per run. Open: per-command sections vs whole-file merge; + which networks (elab only day one vs elab+solver). +2. **Schema posture**: reuse the existing observatory/trace JSON schema verbatim + (keeps VS Code panel compatibility) vs a viz-specific envelope around it. + Default leaning: REUSE — the schema already carries edges, subsystems, srclocs. +3. **Viewer stack**: single-file static HTML+JS (no build step; vendored d3 + subpackages or dependency-free Canvas) vs porting propagatorView.ts behind a + browser shim. Open until the rendering-core extraction cost is sized. +4. **G3 posture**: instrument Tier-1 with an observer call (small production touch + in propagator.rkt — needs its own mini-audit + perf guard since Tier-1 IS the + fast path) vs an exporter-mode flag forcing Tier-2. The flag is + scheduler-coupling-suspect (cell/propagator/scheduler orthogonality); the + observer call must be zero-cost when unarmed. +5. **Playback granularity**: rounds-only day one (matches captured data) with + per-fire detail deferred; G5 component-level diffs deferred unless the probe + shows compound cells dominate the interesting traffic. + +## 4. Environment constraints (loop now runs in a remote container) + +- Racket is NOT installed; apt offers 8.10 while the project pins 9.0 — version + risk to verify at Phase 0.5 (worst case: build from upstream installer). +- Persistence = commit + push to the session branch (see ledger OWNER-PROVISIONAL + entry); no main, no PRs, owner merges — charter §2 intent preserved. + +## 5. R-lens targets carried to Phase 0.5 (verify surgically before design lock) + +- propagator.rkt:3437–3471 — confirm Tier-1 observer dropout in code (critic-reported). +- propagator.rkt:3581–3598 — confirm Tier-2 observer call shape + what it receives. +- trace-serialize.rkt:155–171 — confirm emitted JSON keys against a REAL dump. +- driver.rkt:1036–1058 — confirm observatory capture fires per command by default + when `current-observatory` is set (and what arms it outside LSP). +- Empirical: run `racket/prologos/lib/examples/prop-viz-demo.prologos` with + observer armed; count cells/propagators/rounds/diff-volume; check non-empty + propagator `inputs`/`outputs` at today's HEAD (the March "0 edges" note is + probe-scoped, critic-adjudicated likely stale post-PPN-4C — verify with data). + +## 6. Phase 0.5 findings (iteration 45, probe = `tools/viz-capture-probe.rkt`) + +Probe run: `prop-viz-demo.prologos`, 4 commands, 0 errors, 4.9s wall (cold +caches), JSON artifact 31.5KB. + +- **F1 — real edges EXIST at HEAD**: the last command's elab-network has 9 + propagators, ALL with non-empty `inputs` AND `outputs`. The March "0 edges" + note is empirically stale; the bipartite graph has real structure to draw. +- **F2 — the capture pipeline works headless end-to-end**: observer + + observatory + `current-network-capture-box` through `process-file` with no + LSP: 17 rounds, 35 cell-diffs (with old/new values + source propagator), + 152 fires, 4 observatory captures (one per command). Playback material is + real and well-formed JSON. +- **F3 — Tier-1 dropout (G3) did not blank this workload** (17 Tier-2 rounds + recorded). The per-run dropout SHARE remains unquantified — Phase 1 design + carries a cheap counter to size it before deciding the G3 posture. +- **F4 — subsystem categorization is DEGRADED at HEAD**: all 44 cells + categorize as `infrastructure` — `elab-cell-info` lookups return `'none` for + every cell. The March viewer's green/purple coloring assumed per-cell + cell-info that the PPN 4C universe migration hollowed out. The natural + replacement identity source is the Tier-3 `cell-domains` champ + (`prop-network-cell-domains`, PPN 4C Phase 1c) — a Phase 1 design input. +- **F5 — magnitudes are small** for per-command topology (44 cells / 9 props); + the LSP's 33MB-prelude-capture warning remains the scale ceiling — the + exporter keeps per-command scoping + the probe's diff cap. +- **F6 — environment**: the Racket 9.0 pin is REAL — 8.10 rejects + `thread #:pool 'own` (propagator.rkt:3748, parallel BSP). Racket 9.0 + installed in the container; raco make, targeted runner, and batch workers + all green (test-trace-serialize 19/19 via the runner). +- **F7 — latent defect flagged (adjacent code, NOT fixed — out of scope)**: + `lsp/server.rkt:553` reads `current-prop-net-box` AFTER `process-file`'s + parameterize unwinds; the parameter defaults to `#f` + (metavar-store.rkt:1401), so the LSP's `captured-prop-trace` path appears + structurally dead. The probe avoids the trap via + `current-network-capture-box`. Phase 2's exporter supersedes; flag for the + owner / an LSP follow-up. + +## 7. Stage-3 design decisions (Phase 1, iteration 46) + +Status: **LOCKED (amended)** — two independent adversarial critics returned +1 BLOCKER + 4 MAJOR (Critic A, refutation mandate) and 1 BLOCKER + 4 MAJOR +(Critic B, P/R/M/S + red-flags). All adjudicated in §7.7 (including push-backs); +amendments applied in-place below; 2-column VAG in §7.8. The single largest +amendment: **"tools-only Phase 2" was overturned** — the owner's goal requires +two minimal production hooks (Tier-1 observer call; pre-registered fallbacks), +so Phase 2 splits into 2a (baseline) / 2b (hooks, gated) / 2c (exporter). + +### D1 — Exporter: `tools/viz-export.rkt`, one self-contained JSON per run + +CLI: `racket tools/viz-export.rkt FILE.prologos -o out.json [--max-diffs N]`. +Capture recipe = the probe's validated trio (`current-bsp-observer` + +`current-observatory` + `current-network-capture-box`). Envelope: + +``` +{ "vizTrace": 1, + "file": ..., "wallMs": ..., "commands": N, "errors": N, + "captures": [ {label, subsystem, status, timestampMs, sequence, topology} ], + "finalTopology": {cells, propagators, stats}, // last elab-network + "rounds": [ {roundNumber, timestampMs, cellDiffs, propagatorsFired, + contradiction, atmsEvents} ], + "identity": { "cellDomains": {cid: domain}, // D4 identity stack + "wellKnownCells": {cid: name}, + "propagatorSrclocs": {pid: srcloc-string} } } +``` + +**Round↔command correlation**: `bsp-round` carries no timestamp and the +accumulator re-stamps round numbers globally across ALL scheduler runs — a raw +round list would present module-loading rounds, per-command runs, and solver +runs as ONE misleading timeline. The exporter's observer wrapper records +`(current-inexact-milliseconds)` per round; observatory captures already carry +wall-clock + sequence (driver.rkt:1059-1060). The viewer groups rounds into +command epochs by timestamp interleaving. Cheap, exporter-side, no production +edits. + +**Coverage day one**: the elab-network captures arrive per command via the +driver's observatory hook; session/capability/narrowing/user-reduction +subsystems register their own captures wherever those paths run (per the +PTF Track 1 observatory wiring) — the exporter exports ALL captures present. +Solver (relations/ATMS) networks have no observatory hook day one: NAMED gap, +Phase 4 rider (verify on a relations-using acceptance file in Phase 2). + +### D2 — Schema: reuse the existing serializers verbatim; identity as an envelope supplement + +`serialize-network-topology` + `serialize-bsp-round` are used UNCHANGED (the +probe verified completeness: edges, per-round diffs, values). The D4 identity +maps are computed BY THE EXPORTER into the envelope's `identity` section — +trace-serialize.rkt is FROZEN this track. **AMENDED AT LOCK (critique B5 — +the "fold-in decision point" was a validated≠deployed dangle): PATH B is +DECIDED.** Identity is exporter-local as the END-STATE of this track, not a +waystation: the fields are unproven until the viewer exists, and core-schema +churn must not precede validation. The VS Code panel's identity-hollowness +(F4) is explicitly OUT-OF-SCOPE — owner-queue material. Fold-in re-opens only +via a new owner-decided track. + +### D3 — Viewer: single-file static HTML+JS, dependency-free, in `tools/viz/` + +`tools/viz/index.html` — hand-rolled Canvas rendering, no build step, no +vendored libraries. Loads trace JSON via file-input/drag-drop (file:// safe; +no server, no CORS exposure) with optional `fetch` for served contexts. +Bipartite conventions REUSED from propagatorView.ts (cells=circles, +propagators=diamonds; replay-with-scrubber semantics); CODE not reused — the +extension's rendering core is entangled with its bundler + d3 subpackages and +one `acquireVsCodeApi()` seam; porting costs more than rewriting at this +feature size and would couple the standalone viewer to extension internals. +**AMENDED AT LOCK (critique B4): this is INCOMPLETE DECOMPLECTION, named** — +the more-aligned end-state is a shared rendering core with thin VS Code + +browser adapters; deferred because the standalone viewer must not depend on +the extension's build pipeline. Revisit triggers: >1k-node corpus graphs OR +the panel adopting the identity fields. +Layout **(amended per critique A5)**: component-aware from day one — +disconnected components laid out independently and arranged in a grid; +self-loops rendered as arcs on the node (the probe showed self-loops are +REAL: propagator 8 has inputs=[34], outputs=[34]); BFS-layering within +components. Legibility acceptance on the relations corpus file gates Phase 3 +close. **Pre-registered revisit condition** (unchanged): >1k-node graphs or +unreadable layouts → revisit layout (d3-dag port or WebWorker Sugiyama) as +its own decision — do not silently grow the hand-rolled one. + +### D4 — Cell/propagator identity: best-available-wins stack + +(1) well-known cell-id table (cells 0–21, from propagator.rkt's named +constants); (2) `prop-network-cell-domains` champ → domain symbol (the +post-universe-migration replacement for hollow `elab-cell-info` — finding F4); +(3) `elab-cell-info` srcloc/type when present; (4) the existing value-shape +heuristic as floor. **AMENDED AT LOCK (critique B2, resolved WITH DATA — +probe rerun, iteration 46)**: cell-domains coverage on the demo is **24/44 +(55%), 7 distinct domains** (hasheq-replace ×9, monotone-set ×7, +hash-of-lists-accumulator ×3, tropical-fuel ×2, constraint-status-map, +error-descriptor-map, hasse-registry) — NOT hollow, but below the 70% bar, so +the pre-registered rename fires: the claim is **"best-available identity with +MEASURED coverage"**, not "colors by domain primarily". The exporter emits +per-level coverage stats in the envelope; the viewer displays them. Note: +domain names are merge-strategy-flavored — the palette maps them to +lattice-meaningful colors, which honestly serves "how the system works" (the +merge structure IS the system). Propagators: srcloc from the propagator +struct (PPN 4C Phase 1.5 field) rendered as tooltip + (in served contexts) +source link. + +### D5 — G3 (Tier-1 observer dropout): **REVISED AT LOCK — the fix is PROMOTED to Phase 2b** + +Original posture (defer to Phase 4, mitigate with a viewer message) was +REFUTED by both critics: Critic A showed simple fire-once programs (the +canonical relational demos) produce ZERO rounds — "arbitrary programs" is +false without the fix; Critic B showed the "visibility" mitigation had no +data source — a hardcoded string, not surfacing. **Resolution: the observer +call lands in the Tier-1 branch (propagator.rkt:3437–3471) in Phase 2b** — +zero-cost when unarmed (one parameter read, the price Tier-2 already pays), +semantics scheduler-independent (it REPORTS fires; it alters nothing — the +orthogonality rule is satisfied at the propagator/observation layer, not via +an exporter-mode scheduler flag). The Phase-4 counter is DROPPED — dissolved +by the fix (observed fast-path runs leave no dropout to count). Gates: 2a +full-suite baseline precedes; bench A/B at 2b close (Tier-1 is the hot path; +the testing.md regression rules apply). + +### D6 — NTT model: NOT APPLICABLE (named, with reasoning) + +This track adds NO cells, propagators, lattices, bridges, or strata — it is a +read-side projection of existing network state through the established +zero-cost observer parameter. The NTT-model requirement binds tracks that BUILD +on the network; the only candidate production touch (D5's Tier-1 counter/ +observer) is deferred to Phase 4 and gets its own mini-design there. Mantra +check: observation tooling's PURPOSE is information flow OUT of the network to +humans; the capture mechanism is the codified `current-bsp-observer` pattern +(zero overhead when `#f`). + +### 7.7 Critique round findings + adjudications (two independent critics, iteration 46) + +**Critic A (mandate: refute the central claim)** — verdict was "substantially +refuted; recoverable with corrections"; all corrections adjudicated: + +| # | Finding | Adjudication | +|---|---|---| +| A1 | BLOCKER: Tier-1 fast path skips the observer → simple fire-once programs (canonical relational demos) trace EMPTY | ACCEPTED — fix promoted to Phase 2b (see D5 revision) | +| A2 | MAJOR: timestamp epoch-correlation fragile (sub-ms rounds, coarse clocks, wrapper-outside-loop) | PARTIAL, with push-back: the wrapper records AT observer invocation (inside the loop), and Linux/macOS clocks give sub-ms float precision — the named failure modes don't apply as stated. ACCEPTED core: bucketing is UNVALIDATED → Phase 2c acceptance criteria (strict per-run monotonicity; epoch count == command count on the corpus; pre-first-capture rounds labeled as load-epoch). Pre-registered fallback: 1-line observer-site timestamps in 2b if validation fails | +| A3 | MAJOR: value rendering opaque ("hash(N entries)") — playback uninformative | ACCEPTED as **D7 (new)**: exporter-side bounded semantic detail — one-level hash unpacking (keys + per-key summary), decision/worldview cells rendered as bitmask + labels, sizes capped; lands in the envelope (PATH B consistent); trace-serialize untouched. Viewer shows detail on hover/click | +| A4 | MAJOR: solver/relations networks invisible day one — the pedagogically central propagation missing | ACCEPTED, two-part: (i) FREE PATH first — rounds carry full network snapshots and the ambient observer fires in solver BSP runs too; the exporter derives per-epoch topology from each epoch's last snapshot (A1's fix also un-hides solver fast-paths); validated on the relations corpus file in 2c; (ii) pre-registered 2b fallback: solve-boundary observatory registration if solver epochs come back empty | +| A5 | MAJOR: BFS layering fails on solver graphs (disconnected components, self-loops, wide layers) | ACCEPTED into D3: component-aware layout day one + self-loop arcs + relations-file legibility gate | +| A6 | MINOR: monochrome coloring | Merged into D4 (resolved with data — see D4 amendment) | +| A7 | MINOR: cold-cache prelude can explode the trace | ACCEPTED: `--max-rounds` + truncation flag join `--max-diffs` | + +**Critic B (mandate: P/R/M/S + red-flag scan)** — verdict "cannot lock as-is"; +all blockers resolved at lock: + +| # | Finding | Adjudication | +|---|---|---| +| B1 | BLOCKER: no Phase-0 acceptance file (workflow.md mandate) | ACCEPTED — Phase 0A added; `examples/2026-06-12-ptf-track2-viz.prologos` is iteration 47's unit, BEFORE any implementation; corpus defined there | +| B2 | MAJOR: D4's cell-domains may be F4-hollow one level down | RESOLVED WITH DATA at lock (probe rerun): 24/44 = 55% coverage, 7 domains — not hollow, below the 70% bar → pre-registered rename FIRED (see D4 amendment) | +| B3 | MAJOR: D5's "visibility" had no data source | DISSOLVED by A1/D5 revision — observed fast-paths leave no dropout; counter dropped | +| B4 | MAJOR: D3 framed pragmatism as principle | ACCEPTED — reframed as incomplete decomplection with named deferral reason + two revisit triggers | +| B5 | MAJOR: D2's "fold-in decision point" = validated≠deployed dangle | ACCEPTED — **PATH B decided at lock**: exporter-local identity is the track's end-state; panel out-of-scope; fold-in reopens only via a new owner-decided track | +| B6 | MINOR: D6 NTT claim incomplete re Phase 4 | ACCEPTED — any Phase 4 rider adding cells/propagators carries its own NTT model; 2b's observer call adds neither | +| B7 | MINOR: envelope fields viewer-specific | ACCEPTED — D1-note: epoch-correlation fields are exporter/viewer-specific, not schema-canonical | +| B8 | MINOR: Phase T after Phase 4 | ACCEPTED — tracker reordered (2c carries its golden tests; T precedes the viewer) | +| B9 | MINOR: corpus undefined/unaudited | ACCEPTED — corpus at 0A; probe-based scale audit gates Phase 3 entry | + +### 7.8 Vision Alignment Gate (2-column: catalogue / challenge) + +| Decision | Column 1 — catalogue (passes?) | Column 2 — could it be MORE aligned? | +|---|---|---| +| D1 envelope | Reuses production serializers; one self-contained artifact | YES, marginally: epoch fields are viewer-coupled — named non-canonical (B7) rather than pretending generality | +| D2 schema | Production schema untouched; no churn before validation | The inherited "pre-registered fold-in" pattern WAS the drift — challenged and KILLED at lock (PATH B decided; no dual path remains) | +| D3 viewer | No build step; runs from file://; conventions reused | YES: shared rendering core is more aligned — named as incomplete decomplection with explicit reopen triggers, per the pragmatic-ban rule | +| D4 identity | 4-level best-available stack; measured | The original "primarily by domain" OVERSOLD an unmeasured source — measurement (55%) forced the honest claim; this is §5.8 measurement-as-design-instrument working | +| D5 Tier-1 | Zero-cost-unarmed observation; scheduler-orthogonal | The inherited "tools-only Phase 2" posture was CHALLENGED AND OVERTURNED — safety theater that made the owner's goal undeliverable; replaced by gated minimal hooks | +| D6 NTT | N/A claim scoped to read-side phases | Tightened: per-rider NTT obligation pre-registered for Phase 4 | +| D7 values | Bounded semantic unpacking, envelope-local | Watch: depth creep — capped sizes + Phase 4 rider for depth, not silent growth | + +VAG requirement met: at least one inherited pattern challenged — two were +(the D2 fold-in dangle; the tools-only Phase 2 posture), both overturned. + +## 8. Phase 0A — corpus definition + audit (iteration 47) + +**Corpus** (all Level-3 clean via probe/process-file at this HEAD): + +| File | commands | errors | rounds | diffs | fires | last-epoch net | captures | JSON | +|---|---|---|---|---|---|---|---|---| +| `examples/2026-06-12-ptf-track2-viz.prologos` (acceptance) | 17 | 0 | 45 | 91 | 380 | 36c/6p | 17 | 41KB | +| `lib/examples/prop-viz-demo.prologos` | 4 | 0 | 17 | 35 | 152 | 44c/9p | 4 | 31.5KB | +| `examples/relational-demo.prologos` | 16 | 0 | 17 | 1 | 18 | **87c/17p** | 16 | 27KB | + +**B9 scale verdict**: max topology 87 cells / 17 propagators — far under the +1k-node D3 revisit trigger. Component-aware BFS layout stands. JSON ≤ 41KB. + +**A4 free-path verdict: PARTIALLY VALIDATED, positive.** Round snapshots DO +carry solver topology (relational-demo's last epoch = 87c/17p, a solver +network distinct from its 41c/0p final elab-net), and solver work is visible +(acceptance: solver_unifies 73, solver_backtracks 17, atms_hypothesis_count 3). +CAVEAT (recorded, not resolved): relational-demo's 10 solve queries produced +only 18 observed fires — the share of solve execution that runs through +BSP-observable rounds vs DFS-internal steps is UNQUANTIFIED. Phase 2c +validates per-epoch solver topology on this corpus; the pre-registered 2b +solve-boundary observatory hook remains the fallback (per §7.7 A4). + +**Doc-drift flag (found by the Level-3 gate)**: `map [int+ 1 _] '[...]` and +`map [int* _ 2] [...]` — the partial-application forms shown as idiomatic in +`.claude/rules/prologos-syntax.md` — fail "Could not infer type" under `map` +at this HEAD; the generic `plus` partials work. Acceptance file uses the +working forms with a NOTE; flagged for owner (syntax-doc or inference fix — +not this track's scope). diff --git a/docs/tracking/2026-06-12_PTF_TRACK2_PIR.md b/docs/tracking/2026-06-12_PTF_TRACK2_PIR.md new file mode 100644 index 000000000..d2c7330e4 --- /dev/null +++ b/docs/tracking/2026-06-12_PTF_TRACK2_PIR.md @@ -0,0 +1,208 @@ +# PIR — PTF Track 2: Standalone Browser Visualization + Arbitrary-Program Trace Export + +**Date**: 2026-06-12 (track opened and closed the same day, loop iterations 44–52) +**Design doc**: `2026-06-12_PTF_TRACK2_BROWSER_VIZ_DESIGN.md` +**Mode**: fully autonomous loop (PReduce-autonomy charter, re-armed), remote +ephemeral container +**Naming note (found at close)**: this track named itself "PTF Track 2" from +the driver's observatory comments ("PTF Track 1 Phase 0", driver.rkt:1062); +the Master's PTF (Propagator Theory Foundations) section independently plans a +"Track 2: Pipeline Detection". The Master row for THIS work is listed as +**PTF Track V (Visualization Tooling)** to disambiguate; the track's own docs +keep their internal name. + +## §1 Stated objectives (Q1) + +Owner direction (verbatim, the /loop re-arm): "generate a browser +visualization that can show the propagator network and play execution in +order to show how the system works for arbitrary prologos programs." + +## §2 Delivered (Q2) + +- `tools/viz-export.rkt` — headless CLI exporter: `.prologos` → vizTrace/1 + JSON (per-command topologies, epoch-bucketed timestamped rounds, per-epoch + last-snapshot topologies, measured identity stack, bounded value detail, + always-computed validation block). +- `tools/viz/index.html` — single-file dependency-free browser viewer: + component-aware layered Canvas topology w/ self-loop arcs, zoom/pan/hover, + D4 identity coloring with displayed coverage, epoch+round playback with + fired/diffed highlighting and old→new diff attribution. +- `tools/viz/check.js` — headless node verification of the viewer's pure core. +- `tools/viz-capture-probe.rkt` — the Phase 0.5 instrument (kept; prints + per-command errors, domain coverage). +- **One production edit**: propagator.rkt Tier-1 observer call (gap G3) — + armed path per-fire champ-diff w/ attribution; unarmed byte-identical. +- Tests: `tests/test-viz-export.rkt` (7 golden checks), 3 Tier-1 observer + cases in `tests/test-propagator-bsp.rkt`. +- Acceptance: `examples/2026-06-12-ptf-track2-viz.prologos` (17 commands, + Level-3 clean; elaboration + pipelines + relations/solve + NAF). +- Phases: 0, 0.5, 1, 0A, 2a, 2b, 2c, T, 3 — all ✅ (9/9; Phase 4 riders + adjudicated at close: 2 dissolved, 1 dormant, 2 deferred). Commits + `5ef450a`..`10b0f4a` (~12 on the arc). + +## §3 Timeline (Q3) + +One day, 9 loop iterations: 44 grounding (5 facets + critic) → 45 env +shakeout + probe (Racket 9.0 wall found+fixed; F1–F7) → 46 design lock w/ 2 +adversarial critics (2 BLOCKERs + 8 MAJORs adjudicated) → 47 acceptance file + +corpus audit → 48 full-suite baseline (8658 green after 9 env fixes) → 49 +Tier-1 observer edit (suite 8666 green) → 50 exporter + golden test (all 2c +criteria pass) → 51 viewer shipped + delivered → 52 close. Design:impl ratio +≈ 4:5 iterations. + +## §4 Deferred (Q4) — riders adjudication per data + +- **Solve-boundary observatory hook**: DISSOLVED — the free path (per-epoch + last-round snapshots) exposes solver topology (validated 2c). +- **A2 observer-site timestamps**: DORMANT — exporter-side bucketing + validated on the corpus (monotone; captures==commands). +- **F4 production identity (panel revival)**: OUT OF SCOPE per locked PATH B + — owner-queue material. +- **Compound-cell component diffs** + **D7 unpacking depth**: DEFERRED.md + entries with pre-registered triggers (viewer users need sub-cell playback / + deeper hash views). +- **Viewer layout upgrade**: trigger stands (>1k-node corpus graphs). + +## §5 What went well (Q5) + +- **Reuse over rebuild**: PTF Track 1's capture layer (observer, trace + structs, serializers, observatory) carried ~70% of the exporter — the + grounding audit found it before design, preventing a greenfield mistake. +- **The probe-first discipline**: every design claim that later mattered + (edges exist; domains 55%; solver free path) was settled with DATA before + or at lock, not discovered mid-implementation. +- **Independent adversarial critics on unfamiliar surfaces** found 2 genuine + BLOCKERs the in-context draft missed (Tier-1 dropout; missing acceptance + file) — the RETRO.md prediction about panels confirmed on first test. +- **Per-fire champ-diff in the Tier-1 observer**: reusing the PReduce-era + shape-P infrastructure gave BETTER diff attribution than Tier-2's + per-round diffs, at armed-only cost. + +## §6 What went wrong (Q6) + +- 2 acceptance-fixture syntax failures from trusting the syntax DOC over the + implementation (`map [int+ 1 _]` partials fail inference; colon-form + `spec` parses dependent w/ single-clause defn) — gate-caught, but each cost + a diagnose-fix cycle. Why it seemed right: the doc is normative-looking. +- Bench PRE baseline launched concurrently with a compile — contaminated, + killed, redone post-hoc (structural adjudication + clean A/A run). +- The stop-hook forced commits mid-validation twice (commit-then-fix chains); + harmless but noisy history. + +## §7 Where we got lucky (Q7) + +- The relational runtime's solve path runs enough BSP rounds for snapshots to + carry solver topology — the free path could plausibly have been empty + (DFS-internal), forcing the production hook. The 2c validation would have + caught it, but the schedule won the coin flip. +- apt's Racket 8.10 compiled the codebase cleanly enough to LOOK viable; the + batch-worker crash surfaced `thread #:pool` before any wrong conclusions + were drawn from 8.10 behavior. + +## §8 What surprised us (Q8) + +- **None of the corpus takes Tier-1** (4th falsified-workload premise): the + critique's "simple programs trace empty" urgency was real-as-mechanism, + wrong-as-workload. The fix stands on the unit tests. +- The LSP's own prop-trace capture path appears structurally dead (F7, + server.rkt:553 post-unwind read) — production viz traces likely never + worked via LSP at current HEAD. +- Container A/A bench noise: same-code runs register up to "15.3% + significant speedup" — quantified and recorded as the local calibration bar. + +## §9 Architecture hold-up (Q9) + +Clean integration: ONE production edit (observer call in an existing +zero-cost-when-unarmed pattern), everything else read-side tools. The +cell/propagator/scheduler orthogonality held — the observation layer needed +no scheduler-specific coupling. Friction points: identity metadata +(elab-cell-info hollow post-universe-migration; cell-domains 55%) — the +network's OBSERVABILITY metadata lags its computation substrate, a real +finding for module-loading-on-network / PPN follow-ups. + +## §10 What this enables (Q10) + +- Demonstrating "how the system works" to outsiders with any program. +- A debugging instrument: per-round, per-propagator cell-write attribution + (already finer than the VS Code panel's). +- The vizTrace/1 envelope as a stable boundary for future viewers (LSP panel + could consume it; the schema is exporter-local but versioned). + +## §11 Technical debt (Q11) + +- Identity stack exporter-local (PATH B, decided not drifted) — fold-in + reopens only via owner decision. +- `well-known-cells` table hardcoded in the exporter (mirror of + propagator.rkt constants; cheap to extend, can drift silently). +- Viewer layout is BFS-layered, not Sugiyama — fine at ≤300 nodes, trigger + registered for >1k. +- 3 doc-vs-implementation drifts flagged for owner (syntax doc map-partials; + bench-ab `--ref`; spec colon-form) — not this track's scope to fix. + +## §12 Do differently (Q12) + +- Write the acceptance file at arc OPEN (the B1 blocker was a methodology + rule the loop should not have needed a critic to catch). +- Validate fixture syntax against the IMPLEMENTATION (tiny probe run) before + writing a long acceptance file, not after. + +## §13 Wrong assumptions (Q13) + +- "The capture pipeline is LSP-coupled" (it isn't — fully headless). +- "Subsystem coloring works" (hollow at HEAD). +- ".pnetx could be a topology source" (cell-state only). +- "Simple programs hit Tier-1" (not on this corpus). +Each was killed by grounding or probe data before it could shape code. + +## §14 What we learned about the problem (Q14) + +The visualization problem is 90% a CAPTURE-FIDELITY problem and 10% a +rendering problem. The interesting questions (which networks exist, which +runs are observable, what identity cells carry, how rounds map to commands) +all live on the Racket side; the browser side is straightforward once the +envelope is honest about coverage. + +## §15 Right problem? (Q15) + +Yes — the owner asked for exactly this, and the deliverable is the owner's +stated final goal for the re-armed loop. The deeper want ("show how the +system works") is served by the solver epochs + diff attribution; the +remaining gap to "fully pedagogical" is value rendering depth (D7 depth +rider) and DFS-internal solver steps, both named. + +## §16 Longitudinal note (10 most recent PIRs) + +This branch's recent PIRs (PReduce Tracks 1, 2, 4, 5, 3 — all 2026-06-10 — +plus this one) show a stable pattern set: (1) pre-registration + falsified +workload premises now appear in EVERY track (5th, 6th data points here — +the pattern is ripe and codified); (2) the gate-as-co-designer effect +(full-suite + Level-3 + golden tests catching what review missed) repeats; +(3) doc-vs-implementation drift is this arc's NEW recurring class (3 +instances in one day across syntax doc, bench-ab header, spec parsing) — +candidate for a standing "docs are hypotheses, gates are facts" +DEVELOPMENT_LESSONS entry; (4) the panel-economy question from the PReduce +retro is now answered with both poles: skip panels on loop-authored +surfaces (43 iterations, zero panel regrets), run them on unfamiliar +surfaces (2 BLOCKERs found here). + +## Autonomy retro (charter §9, this arc) + +- **Matched interactive quality**: grounding→probe→lock→gate cadence held; + every decision ledgered; the owner can audit 9 iterations from LEDGER + + design doc §7 alone. The critique round did real work (the lock amendments + were substantive, including overturning the loop's own "tools-only" + safety posture). +- **Fell short / costs**: 2 syntax stumbles a Prologos-fluent human likely + avoids; the stop-hook commit pressure produced commit-then-fix pairs; one + process self-catch (concurrent bench) that discipline should have + prevented outright. +- **Mitigations that earned their cost**: independent critics (2 BLOCKERs); + measurement-before-lock (D4 downgrade); pre-registered fallbacks (all + stayed dormant BECAUSE validation was designed in). +- **Environment**: the remote container is viable for the loop (Racket 9.0 + install + pkg links + recipe recorded); its bench noise floor (15.3% A/A) + makes perf claims weaker here — push perf-critical phases to the owner's + machine or demand structural arguments. +- **For the owner to correct at review**: browser acceptance of the viewer + (no GUI here); the 3 doc-drift flags; the naming collision (PTF Track V); + whether the deferred riders matter enough to schedule. diff --git a/docs/tracking/2026-06-14_PREDUCE_DEFAULT_AND_ONNETWORK_COMPUTE_PLAN.md b/docs/tracking/2026-06-14_PREDUCE_DEFAULT_AND_ONNETWORK_COMPUTE_PLAN.md new file mode 100644 index 000000000..20345b11e --- /dev/null +++ b/docs/tracking/2026-06-14_PREDUCE_DEFAULT_AND_ONNETWORK_COMPUTE_PLAN.md @@ -0,0 +1,276 @@ +# PReduce → Default + On-Network Compute/Recursion — Next-Session Plan + +**Created**: 2026-06-14 +**Status**: 🔄 IN PROGRESS — Phases 0–2 ✅; Phase 3 dropped (off-roadmap); +Phase 4a ✅ (recursion steps as union propagators — viz shows recursion as +propagators; suite 8678 all-pass). Phase 4b/4c (network DRIVES recursion) = +documented research frontier (worsen the perf wall on Racket; SH/Zig-era). +Phase 5 owner-gated. The viz success criterion is MET at tractable sizes. +**Branch**: `claude/charming-archimedes-98yb48` (prototype for a future +propagator-native Prologos, built on the PReduce on-network-reduction prototype) +**Owner directive (2026-06-14)**: move PReduce to default and implement the +missing on-network functionality so the propagator network *genuinely computes* +(not just records). Commit in phases, incrementally. +**Companions**: `2026-06-14_PTF_VIZ_ROADMAP.md` (the viz side; this is its +Phase C made concrete) · `2026-06-12_PTF_TRACK2_BROWSER_VIZ_DESIGN.md` § BRANCH +DIRECTIVE · PReduce Master `2026-05-02_PREDUCE_MASTER.md` (the thesis this +advances) · `docs/tracking/preduce-autonomy/{CHARTER,LEDGER,HANDOFF}.md`. + +--- + +## 1. Why this work, and what "done" means + +The visualization surfaced a precise truth about the substrate (see the viz +roadmap): with on-network reduction enabled, the e-graph **records** each +reduction step as a `union` propagator (DPO rewriting — PRN §2 confirmed), but +the actual **computing** is still off-network functions: + +- the per-step **value compute** (`377+233=610`) is `(apply + …)` inside + `instantiate-template` (`rule-dispatch.rkt`), not a propagator; +- the **recursion driver** (`fib`→`fib`) is the recursive `whnf` + (`reduction.rkt`); +- rule application is **atomic** inside `preduce-ingest-int` (intern → dispatch + → union → its own `run-to-quiescence`), so the redex / rule-fire / result + collapse into one observed round — you never see `int+(377,233)` as a live + cell. + +This plan advances the substrate toward the **PReduce thesis** (PReduce Master): +*"Reduction lifts entirely onto the propagator network … `reduction.rkt` is +retired in its entirety … rule application IS propagator firing … equivalence +classes ARE shared cells."* + +**`done` for this work = the success criterion is the VIZ**: running a reduction +program through `tools/viz-export.rkt` shows the computation happening *as +propagation* across observable BSP rounds — a redex cell appears, a rule/compute +propagator fires, the result cell is written, values flow — with no decoder +overlays needed. For `parallel-reduction` the arithmetic shows as +`read-A,B → write-C` propagators; for `fib` the recursion shows as propagators. + +**Out of scope (named, deferred)**: PERFORMANCE. The PReduce verdict (Track 3/4 +PIRs) is that on-network reduction is wall-clock-FREE to *record* but memo ≤ cost +on the Racket substrate; the perf payoff routes through SH/Zig lowering. This +plan is about **architectural + visualization completeness**, not speed. Per +charter §5.8 (honesty about the curve): **wall-clock regression is EXPECTED and +ACCEPTED here**; measure it, record it, do not panic or optimize prematurely. + +--- + +## 2. Methodology this plan binds to (repo conventions) + +Every phase below follows the standing discipline — non-negotiable: + +- **DESIGN_METHODOLOGY.org 5 stages** per phase that introduces new + architecture (research → audit → design → implement → PIR). Phases that are + pure flips/mechanical may compress, but must say so. +- **Grounding-audit opener** for each design phase (the `grounding-audit` + workflow / HEAD-pinned read-only facets) — never design from memory of the + code; re-verify the surfaces in §6 at the session's HEAD. +- **NTT model REQUIRED** for every phase that adds propagators/cells/lattices + (Phases 2–4): cells (`:reads`/`:writes`), propagator fire functions, lattice + declarations, with a correspondence table to the Racket implementation. +- **Network Reality Check (3 questions)** before any "on-network" claim: + (a) which `net-add-propagator` calls were added? (b) which `net-cell-write` + produces the result? (c) can you trace cell-creation → propagator-install → + cell-write → cell-read = result? "None/no" ⇒ the phase is still imperative. +- **Mantra audit** + **SRE lattice lens** at every design decision. +- **Acceptance file as Phase 0** (this plan's Phase 0): a `.prologos` file whose + on-network reduction must become fully visible; run via `process-file` + + `tools/viz-export.rkt` before/after every phase. +- **Testing**: `check-parens.sh` after every `.rkt` edit; the targeted runner + (`run-affected-tests.rkt --tests …`, never bare `raco test`) after production + edits; **full suite as the regression gate ONLY** (read failure logs, never + re-run to diagnose); benchmark (`bench-ab.rkt --runs 10`) after each + reduction-path phase against the prior baseline. +- **Incremental commits**: one commit per phase (or sub-phase), conventional + message, Series-prefixed, no co-author tags. Update the Progress Tracker + + ledger + dailies per commit (this is the autonomy branch). +- **Validated ≠ Deployed / belt-and-suspenders ban / "pragmatic" ban** all + apply — especially: a phase that keeps the old path "for safety" alongside the + new is incomplete, not safe. + +--- + +## 3. Progress Tracker + +| Phase | Description | Status | Notes | +|---|---|---|---| +| 0 | Baseline + acceptance file + scope-lock measurement | ✅ | `98e3b35` acceptance file (0-error, 155 rounds); suite ingest-ON baseline = 8669 all-pass (flipping breaks nothing); ingest hooks scope-locked (β/ι/δ/int-folds) | +| 1 | **On-network reduction is the ONLY path** — native deleted (owner upgraded "flip" → "flip + delete native") | ✅ | `1741476`+``: removed `current-preduce-ingest?`/`-int-folds?` gate, de-gated β/ι/δ/int ingest arms, deleted duplicate native arms; removed `PREDUCE_INGEST` env + viz `--reduce`/`--no-reduce`; ported `test-preduce-ingest`; deleted on/off micro-bench. **Full suite 8671 ALL PASS (406.7s).** Absorbs part of Phase 5 (native compute arms gone; `whnf` still drives). | +| 2 | **Rule application becomes propagator firing** (arithmetic dispatch: an imperative `dispatch-rules` call → an on-network STRATUM firing, mirroring the congruence engine) | ✅ | `656a294` core (cell-22 dispatch-request + `process-dispatch-requests` topology stratum + emitter in `preduce-ingest-int`) · `edeff08` 2T · `ac65fad` cell-count test bumps. NTT model + Network Reality Check in the design doc. Suite **8674 all-pass**. Scope: arithmetic path only (the grounding finding — see below); β/δ/ι is Phase 4 | +| 3 | ~~Compute as propagators~~ **DROPPED (off-roadmap)** | ❌ | The e-graph evaluates primitives FUNCTIONALLY inside a rule (`instantiate-template`'s `(apply op args)`) — that IS the e-graph design, not an incomplete version. A standalone `int+` propagator is the *direct-compute* substrate we explicitly chose NOT to build (gives up structural sharing). Owner-approved correction 2026-06-14. "Everything-on-network" is satisfied when rule application is stratum firing (Phase 2) — the compute runs INSIDE the dispatch fire | +| 4a | **Recursion step as a union propagator** (β/δ/ι record {redex,result} via eclass-union, not a cell-write; viz shows recursion as propagators) | ✅ | `2bf9b5e` + design doc `2026-06-14_PREDUCE_T8_PHASE4_RECURSION_ON_NETWORK.md`. Suite **8678 all-pass**; fib 6 viz: 172 rounds, ≤18 props/round, union propagators visible (pre-4a: "only one propagator"). Net-threading fix preserves the recursion subtree. THE viz goal met at tractable sizes | +| 4a-perf | **Perf wall LIFTED** (call-by-value memo key + incremental viz observer) | ✅ | `02da3bd` cbv memo key (naive fib exponential→LINEAR: fib 15 reduce >120s→0.27s; acceptance 2117→194ms) + `031ff2f` incremental observer (fib 15 export 16.3s→11.1s). **fib 15 now traceable** (234 rounds, 0 errors); suite 8673 all-pass (faster: 372s). cbv key normalizes the memo KEY only; reduction stays call-by-name. See §9 of the 4a doc | +| 4b/4c/5 design | **Network-DRIVEN reduction + bypass** — demand cascade + extraction | ✅ | `12f97a7` design doc `2026-06-14_PREDUCE_T8_PHASE4b5_NETWORK_DRIVEN_REDUCTION.md`. Reframes 4b/4c/5 as one arc: `whnf-step1` one-step classifier + reduce-stratum cascade + extraction. Form-rep: expr-structs + native one-step compute (sidesteps the de-Bruijn-subst template blocker). Honest staging (whnf is ~1700 lines/dozens of constructs → multi-session) + per-construct PARITY gate | +| 5a | **whnf-step1 + whnf-via-egraph (parity-gated)** — the validated one-step primitive | ✅ | `a72b3f2`. `whnf-step1` (migrated: β/ι/suc/fst/snd/J/boolrec/ann/vhead/vtail + demand; `'native` fallback for the rest) + `whnf-via-egraph` driver. PARITY harness `test-preduce-egraph.rkt` (27 checks) == native whnf. **Pure addition — default whnf untouched.** Suite **8705 all-pass (441 files)**. 5a's driver is a LOOP (validates the decomposition) | +| 5b | **Scheduler-driven cascade** — genuine network-DRIVE | ✅ | `c49c53c`. `whnf-via-egraph-network`: the BSP scheduler drives reduction (keep-pending reduce stratum, cell-23). Network Reality Check PASSES (scheduler-driven, not a loop). PARITY-gated (== native whnf, 47 checks); **suite 8725 all-pass**. Migrated head fragment is network-driven; rest 'native; demand subterms native (set-latch = future). See §10 of the 4b5 doc | +| 5c | **DEPLOY: the cascade is the DEFAULT reduction driver** (owner: "delete the recursive off-network DRIVER, keep the compute leaf") | ✅ | `12d4131` routing hook + batch 2 (reduce/δ/meta); `84137e5` batch 1 (int arith); `c98d3a9` default flip. `whnf` routes through the cascade by default; the BSP scheduler DRIVES ground reduction; native reducer demoted to compute-leaf + non-ground fallback. PREDUCE_NATIVE=1 forces native. **Full suite with cascade as default = 8750 all-pass (386.9s, ~15% over native).** | +| limits/terminal | **Why whnf-impl can't be physically deleted** | 🔒 | TWO architectural limits (not gaps): (1) non-ground reduction (metavars) can't be PCE-interned → native fallback retained; (2) full deletion = reimplementation (higher-order fold/map/filter RECURSE; FFI), not mechanical. So the DRIVER is deleted (cascade is the driver); the COMPUTE LEAF stays (the e-graph's own compute-inside-the-rule design). Perf of full on-network compute routes to SH/Zig | +| T | Dedicated tests per phase (`tests/test-preduce-*` extensions) | ⬜ | rule-firing observability, compute-propagator parity, recursion-on-network parity | + +Update ⬜→🔄→✅ + commit hash per phase, in this table and the autonomy ledger. + +--- + +## 4. Phase detail + +### Phase 0 — Baseline, acceptance file, scope-lock (no production code) + +- **Full-suite baseline** with ingest OFF (current default before Phase 1) and a + second run with `PREDUCE_INGEST=1` (ingest ON) — capture both `timings.jsonl` + + any failures. This quantifies exactly what flipping the default breaks + (correctness regressions to fix in Phase 1) and the wall-clock delta. +- **bench-ab baseline** (`--runs 10 benchmarks/comparative/ --output`) ON vs OFF. +- **Acceptance file** `examples/2026-06-14-onnetwork-reduction.prologos`: + reduction-heavy, 0-error, exercising arithmetic (the `parallel-reduction` + shape) + δ unfolds + recursion (fib). The success instrument: export it with + `tools/viz-export.rkt` after each phase and confirm the new on-network + structure is visible. +- **Scope-lock**: confirm against current HEAD which reduction kinds the ingest + hooks already cover (`reduction.rkt` `#:when (current-preduce-ingest?)` sites) + vs. which need new work (compute, recursion). +- Gate: suite state captured (both modes); acceptance file Level-3 clean. + +### Phase 1 — PReduce ingestion as the default + +- **Flip** `current-preduce-ingest?` default → #t (`reduction.rkt:1323`). Decide + `current-preduce-ingest-int-folds?` posture (default already #t). Retire / keep + the `PREDUCE_INGEST` env override as a *disable* switch only (no dual + production path — Validated≠Deployed). +- **Fix correctness regressions** surfaced by the suite: the e-graph result MUST + equal the native reduction for every covered redex (the `match best … _ → + (op-fn a b)` fallback in `preduce-ingest-int` is the parity check; any + divergence is a bug to fix, not to fall back around). +- Gate: **full suite green** with ingest on by default; bench A/B recorded + (regression expected — log it per §1, do not optimize). +- Network Reality Check N/A (no new propagators; this is a flag deployment). +- Commit: `feat(PReduce Track 8): on-network reduction is the production default`. + +### Phase 2 — Rule application as observable propagator firing + +- **Problem**: `preduce-ingest-int` → `dispatch-rules` → `apply-rule` → + `eclass-union` + `run-to-quiescence` happens atomically *inside* the reducer's + `whnf`, on the `prn-box` registry network, so the steps collapse into one + observed round. +- **Objective**: make rule application a propagator that fires across observable + BSP rounds — install rule propagators that watch e-class cells and, when an + e-class whose term matches a rule LHS appears, fire to produce + union the + RHS. "Rule application IS propagator firing" (the thesis). +- **Design questions (Stage-3, grounding-audit + likely a design panel)**: + - Does the rule become a propagator on the *main* observed network, or does + the registry network's quiescence need to surface to the observer per round? + - Topology: rules as standing propagators (fire on matching e-class births) + vs. a dispatch propagator keyed by head-symbol. Use the existing rule + registry (`current-rule-registry-cell-id`) + congruence-style watchers. + - Termination / confluence: this is non-monotone rewriting — which stratum? + (`.claude/rules/stratification.md`; PReduce SM4 critical-pairs-by-join.) +- **NTT model REQUIRED**; **Network Reality Check** must pass. +- Gate: the acceptance file's reduction is visible step-by-step in the viz + (redex cell → rule propagator fires → result cell, across rounds); full suite + green; parity with native reduction. +- Commit (may sub-phase: 2a dispatch-as-propagator, 2b observability). + +### Phase 3 — Compute as propagators + +- **Objective**: the arithmetic primitive becomes a propagator that **reads the + operand cells and writes the result cell**, replacing `(apply op args)` inside + `instantiate-template` (`rule-dispatch.rkt:176`, `compute-ops` table). Then + `int+(377,233)` is a propagator with inputs = the two operand e-classes, + output = the sum e-class — genuine compute-as-propagation. +- **Design questions**: a primitive-op propagator family (one per `compute-ops` + entry) installed when a rule's RHS has a `(compute …)` node; the fire function + reads operand cell values, computes, writes. Cohesion with the e-graph (the + result still interns + shares via hashcons). SRE lattice lens on the + result-cell domain. +- **NTT model REQUIRED**; **Network Reality Check** must pass (this is the + clearest `read-A,B → write-C` propagator in the whole substrate). +- Gate: `parallel-reduction` exported + viewed shows the arithmetic tree + reducing as compute-propagators (no `union ≡`-only view); full suite green; + bench recorded. +- Commit. + +### Phase 4 — Recursion / β-δ on-network + +- **Objective**: move the recursion driver on-network — `fib`→`fib` unfolding + becomes propagator-driven rewriting (δ unfold + β as e-graph rules firing as + propagators), so the recursion STRUCTURE lives on the network rather than in + the recursive `whnf`. This is PReduce Track 2's β/δ realized as *standing + propagators*, not gated hooks called from `whnf`. +- **This is the hardest phase** — research-grade; the recursion control flow is + the part the recursive reducer owns. Expect a full Stage-3 design arc + (grounding-audit + `design-options-panel` + ≥2 adversarial critique rounds + + NTT model). May reveal that fuel/termination + the strata interaction needs + its own sub-design (consume BSP-LE 2B speculative infra if non-confluent). +- Gate: `fib` exported + viewed shows the recursion as propagators (the + fibonacci DAG built by firing rules, not by off-network calls); parity with + native fib; full suite green. +- Commit (expect multiple sub-phases). + +### Phase 5 — Bypass `reduction.rkt` for the covered fragment (Track 8 slice) + +- **Objective**: for reduction kinds now fully on-network (arithmetic, δ/β as + covered), route `whnf`/`nf` through the e-graph extraction instead of the + recursive arms — the beginning of "`reduction.rkt` retired in its entirety." +- **Owner-gated** (charter §2 hard stop: Track 8 endgame is owner-checkpointed + even on this branch). STOP and checkpoint with the owner before landing. +- Gate: parity (e-graph reduction == native reduction on the full acceptance + corpus + suite); the bypassed arms are deleted, not kept alongside + (belt-and-suspenders ban). +- Commit only after owner sign-off. + +--- + +## 5. Risks, open questions, honest caveats + +- **Perf regresses; that's accepted** (§1). The risk is *panic-optimizing* — + don't. Record the curve; the payoff is SH/Zig (out of scope). +- **Phase 1 may surface latent correctness bugs** in the ingest path (it's been + gated off, so its production exposure is thin). Budget time for parity fixes. +- **Phases 2 & 4 are genuine research** — rule-application-as-propagator and + recursion-on-network are the PReduce thesis's hard core. They may need design + panels and may reveal that a clean realization requires substrate work beyond + this plan's framing. If a phase's design doesn't lock cleanly, STOP and + surface the design question rather than forcing an imperative shim. +- **Non-confluence / termination**: on-network rewriting is non-monotone; the + stratum assignment (S0 vs Sk) and fuel bounding must be designed + (`stratification.md`, PReduce SM4). Don't hand-wave it. +- **The e-graph runs on a separate `prn-box` network today** — Phase 2's + observability hinges on reconciling that with the observed BSP timeline. This + is a real architectural question to ground first. + +--- + +## 6. Key code surfaces (re-verify at session HEAD before trusting) + +- `reduction.rkt`: `current-preduce-ingest?` (:1323, the flag to flip); + `preduce-ingest-int` (the atomic ingest); the `#:when (current-preduce-ingest?)` + hook sites (~:1527/1548/1563 δ/β, :1662-1668 int folds); `whnf`/`nf` (the + recursive driver to eventually bypass). +- `rule-dispatch.rkt`: `dispatch-rules` (:239), `apply-rule` (computes via + `instantiate-template` then `eclass-union`), `instantiate-template` (:176, the + `(compute …)` → `(apply op args)` site = where the arithmetic is a function), + `compute-ops` table. +- `eclass-graph.rkt`: `eclass-intern`, `eclass-union` (:136, the union + propagator = the rewrite-as-propagator we already have), + `current-eclass-containment-box` (the viz capture; extend for rewrite pairs if + Phase 2 needs it). +- `driver.rkt`: `PREDUCE_INGEST` env handling (~:2304) — becomes the *disable* + switch after Phase 1. +- `tools/viz-export.rkt` + `tools/viz/index.html`: the success instrument — + each phase's acceptance is "the new on-network structure is visible here." +- PReduce Master `2026-05-02_PREDUCE_MASTER.md` (thesis + Track 6/7/8 framing); + PReduce Track 2/3 PIRs (the gated-off + perf-verdict context). + +--- + +## 7. First action for the next session + +1. Re-read this plan + the viz roadmap + the PReduce Master thesis + the branch + directive. Re-ground the §6 surfaces at HEAD (grounding-audit). +2. Do **Phase 0** (baseline both modes + acceptance file + scope-lock). Commit. +3. Open **Phase 1** (flip the default), run the suite, fix parity regressions, + commit. Then proceed phase-by-phase, one commit per phase, updating this + tracker + the ledger + dailies as you go. + +Stop and checkpoint with the owner at: any phase whose design won't lock cleanly +(surface the question), the first wall-clock regression worth a decision, and +**before Phase 5** (owner-gated). diff --git a/docs/tracking/2026-06-14_PREDUCE_T8_PHASE2_DISPATCH_AS_PROPAGATOR.md b/docs/tracking/2026-06-14_PREDUCE_T8_PHASE2_DISPATCH_AS_PROPAGATOR.md new file mode 100644 index 000000000..5c9323f65 --- /dev/null +++ b/docs/tracking/2026-06-14_PREDUCE_T8_PHASE2_DISPATCH_AS_PROPAGATOR.md @@ -0,0 +1,176 @@ +# PReduce Track 8 — Phase 2: Rule application as propagator firing + +**Created**: 2026-06-14 +**Status**: ✅ IMPLEMENTED (arithmetic path) — `656a294` core + `edeff08` 2T + +`ac65fad` cell-count test bumps. Suite 8674 all-pass; acceptance file 0 errors. +The realization is exactly the congruence template (S0 emitter → request cell → +topology stratum). Scope narrowed to the arithmetic/dispatch path per the +grounding finding below; the recursion path (β/δ/ι) is Phase 4. Owner chose +"Pursue the goal (P2→P4)" 2026-06-14. + +**Implementation note (vs §7 steps)**: the dispatch request is emitted by an S0 +emitter installed in `preduce-ingest-int` (the arithmetic caller), NOT from +`eclass-intern`'s new-class branch (§7 step 2b). Reason: `eclass-intern` is a +generic primitive used by every path (incl. β/δ/ι and apply-rule's RHS interns); +emitting from there would request dispatch on every interned class globally, +coupling the primitive to the rule system and risking unintended saturation. +Caller-emission keeps Phase 2 strictly scoped to arithmetic. A direct +request-write would not work: `run-to-quiescence-inner`/`-drain` (and the BSP +Tier-1 fast path) only run strata when the worklist is non-empty — so the +request must be emitted by a PROPAGATOR FIRE (the emitter), which creates the +worklist activity, exactly as the congruence watcher does. The emitter WATCHES +the redex class (non-empty inputs) so it is not Tier-1-fast-path eligible (Tier-1 +skips strata). + +## ⚠ GROUNDING FINDING (2026-06-14, before cutting core code) — scope narrows + +Reading `preduce-ingest-int` vs `preduce-ingest-delta` revealed the substrate has +TWO unlike reduction paths: + +- **int-fold path** (`preduce-ingest-int`, reduction.rkt:1330): `dispatch-rules` + → `apply-rule` → `instantiate-template` compute + `eclass-intern` RHS + + `eclass-union`. The union IS a propagator; only DISPATCH (match) is imperative. + Phase 2 ("dispatch as propagator firing") applies cleanly HERE. +- **β/δ/ι path** (`preduce-ingest-delta`, reduction.rkt:1350): interns the redex, + consults memo/store, on miss runs `(compute)` (the native step) and writes the + result as the class `:best` via a **direct `net-cell-write`** (reduction.rkt: + 1402). NO rule, NO union, NO propagator — it is compute-and-cache. + +**Consequence**: "rule application IS propagator firing" applies cleanly ONLY to +the arithmetic/dispatch path. The recursion path (β/δ/ι — the interesting part +for fib) has no "rule application" to convert into firing; it is a memoized +native compute. Making IT propagator-native is the deeper **Phase 4** (turn the +compute-and-cache into rule-based propagator firing) — a substrate redesign, the +PReduce thesis endgame, genuinely research-grade (the project itself deferred it +and routed the perf case to SH/Zig). + +Two further frictions even for the narrow (arithmetic) Phase 2: `apply-rule` runs +its OWN `run-to-quiescence` (so wrapping dispatch in a propagator fire risks +nested/re-entrant quiescence), and reactive dispatch means installing the union +propagator DURING a fire (topology-change-mid-quiescence — doable via the +topology stratum, as congruence does, but real risk). + +**Decision deferred to owner (charter §8: design didn't lock as assumed — +surface).** See the checkpoint in the session. + +--- + +## (original design below — realization template still valid for the arithmetic path) + +**Plan**: `2026-06-14_PREDUCE_DEFAULT_AND_ONNETWORK_COMPUTE_PLAN.md` (Phase 2) +**Roadmap basis**: PReduce Master thesis — *"rule application IS propagator +firing"*; everything-on-network mantra. Owner (2026-06-14): substrate change is +acceptable **as long as it's on the PReduce / everything-on-network roadmap**. + +## 1. The gap (grounded at HEAD) + +After Phase 1, reduction is on-network as an e-graph, BUT the rewrite control +flow is still imperative: `preduce-ingest-int`/`-delta` (reduction.rkt) interns +the redex and then **calls `dispatch-rules` synchronously** (`reduction.rkt:1334`) +to match a rule's LHS and instantiate its RHS. `union` (`eclass-union`) and +`congruence` (`process-congruence-requests`) are ALREADY propagators / stratum +handlers; only **dispatch** is an imperative function call. + +So "rule application IS propagator firing" = make dispatch reactive: the rewrite +fires from the network (during quiescence), not from an imperative call. + +## 2. Scoping correction (owner-relevant) + +The plan's **Phase 3 ("compute as propagators": a standalone `int+` propagator +reading operand cells, writing the sum) is OFF-roadmap** and is folded into +Phase 2 / dropped. Rationale: the PReduce e-graph **evaluates primitives +functionally inside a rule** (`instantiate-template`'s `(apply op args)`); that +is the e-graph design, not an incomplete version of it. "Everything-on-network" +is satisfied when **rule application is propagator/stratum firing** — the `+` +runs *inside* the rule-dispatch fire, which reads e-class cells and writes the +result cell (that is exactly what a propagator fire does). A separate +compute-propagator would be the *direct-compute-propagator* substrate we +explicitly chose NOT to build (it gives up the e-graph's structural sharing). +On-roadmap order is therefore **Phase 2 (dispatch firing) → Phase 4 (recursion +driver on-network)**. + +## 3. Realization — mirror the congruence engine + +Congruence (the template, `eclass-graph.rkt`): watcher propagators on e-class +`:canonical` components write `congruence-request-cell-id`; `register-stratum- +handler!` runs `process-congruence-requests` between rounds. Dispatch mirrors it: + +- **`dispatch-request-cell-id`** (reserved well-known cell; hash-union merge): + `cid → #t` for each newly-interned e-class that should be checked against the + rule registry. +- **Request emission**: `eclass-intern`, on creating a NEW class (the `[else]` + branch), writes `(hash cid #t)` to the dispatch-request cell — instead of the + caller imperatively calling `dispatch-rules`. +- **`process-dispatch-requests` stratum handler** `(net × pending) → net`: for + each requested `cid`, run the existing `dispatch-rules` (LHS-match + + `apply-rule` → `instantiate-template` + `eclass-intern` RHS + `eclass-union`). + Registered via `register-stratum-handler! dispatch-request-cell-id …`. The + handler runs DURING the `run-to-quiescence` the caller already drives. +- **Caller change**: `preduce-ingest-int`/`-delta` stop calling `dispatch-rules` + directly; they intern (which now emits the request) and `run-to-quiescence` + (which fires the dispatch stratum → applies rules), then read `:best`. The + read-after-quiescence contract is unchanged; only WHO triggers dispatch moves + from imperative-call to stratum-firing. + +## 4. NTT model (sketch + correspondence) + +``` +cell dispatch-requests : { eclass-id ↦ unit } -- hash-union merge, bot {} + reads: (eclass-intern writes here on new-class) + writes: (process-dispatch-requests clears per round via #:reset) + +stratum dispatch : (net, pending:{eclass-id}) -> net + for cid in pending: + rules = rules-for-head(registry, head(best(cid))) + for r in rules: apply-rule(net, r, cid) -- RHS instantiate + intern + union + -- runs between S0 rounds, like congruence; non-monotone (adds nodes) +``` + +| NTT construct | Racket | +|---|---| +| `dispatch-requests` cell | `dispatch-request-cell-id` (hash-union; `propagator.rkt` reserved id) | +| request emission | `eclass-intern` new-class branch → `net-cell-write … (hash cid #t)` | +| `dispatch` stratum | `process-dispatch-requests` + `register-stratum-handler!` | +| `apply-rule` | unchanged (`rule-dispatch.rkt`) | + +## 5. Network Reality Check (the 3 questions — must hold post-impl) + +1. **`net-add-propagator` / stratum added?** YES — `register-stratum-handler! + dispatch-request-cell-id process-dispatch-requests` (the dispatch firing). +2. **`net-cell-write` produces the result?** YES — `apply-rule`'s `eclass-union` + writes the joined `:best`; the dispatch is *triggered* by the request-cell + write, not an imperative call. +3. **Trace cell→stratum→cell?** intern writes request-cell → stratum handler + reads pending → applies rules → writes result e-class cell → caller reads + `:best`. YES. + +## 6. Risks & gates + +- **Synchrony**: dispatch now runs in the stratum (between rounds) inside the + caller's `run-to-quiescence`; the caller reads `:best` AFTER quiescence — + same contract. Risk: a class that needs dispatch but whose request lands after + the last round → run an extra quiescence pass (the stratum loop already + re-runs while requests pending, like congruence). VERIFY. +- **Infinite dispatch**: a rule whose RHS re-requests dispatch on the same class + → the hashcons makes re-intern a hit (no new request); the request cell is + reset per round. VERIFY no loop (fuel as backstop). +- **Recursion (whnf) still drives** — Phase 4. This phase only moves DISPATCH + on-network; `whnf` still calls `preduce-ingest`. That's expected; not a + regression. +- **GATE**: full suite green (8671 baseline); the acceptance file + (`examples/2026-06-14-onnetwork-reduction.prologos`) exports with dispatch + propagators/stratum visible; parity (same reduced values). + +## 7. Implementation steps (incremental commits) + +- **2a**: reserve `dispatch-request-cell-id` + preallocate (hash-union) in + `make-prop-network`; define `process-dispatch-requests` + register it. +- **2b**: emit dispatch-requests from `eclass-intern` (new-class branch). +- **2c**: switch `preduce-ingest-int`/`-delta` from the imperative + `dispatch-rules` call to intern-emits-request + quiescence; delete the + imperative call. Gate: full suite + acceptance parity. +- **2T**: extend `test-preduce-ingest.rkt` — assert a dispatch-request fires the + rewrite (the rewrite happens via the stratum, not the direct call). + +STOP-and-surface if 2a–2c won't lock cleanly (synchrony or loop issues that +need a deeper stratum-ordering design). diff --git a/docs/tracking/2026-06-14_PREDUCE_T8_PHASE4_RECURSION_ON_NETWORK.md b/docs/tracking/2026-06-14_PREDUCE_T8_PHASE4_RECURSION_ON_NETWORK.md new file mode 100644 index 000000000..10ba50c0e --- /dev/null +++ b/docs/tracking/2026-06-14_PREDUCE_T8_PHASE4_RECURSION_ON_NETWORK.md @@ -0,0 +1,237 @@ +# PReduce Track 8 — Phase 4: Recursion (β/δ/ι) on-network + +**Created**: 2026-06-14 +**Status**: ✅ 4a DONE + perf wall LIFTED on Racket (`2bf9b5e` union recording; +`02da3bd` call-by-value memo key — naive fib exponential→linear; `031ff2f` +incremental viz observer). Suite 8673 all-pass; **fib 15 now reduces in 0.27s and +viz-exports in 11s** (was >120s timeout). 4b/4c still the network-DRIVES-recursion +frontier. See §8 + §9. Phase 5 owner-gated. +**Owner posture (2026-06-14)**: "for this prototype branch, you lead and approve +design. don't ask me for details. I will review after the implementation is +complete." → I make the design decisions here; the methodology gates (NTT model, +Network Reality Check, full-suite + acceptance) still apply. +**Plan**: `2026-06-14_PREDUCE_DEFAULT_AND_ONNETWORK_COMPUTE_PLAN.md` (Phase 4) +**Builds on**: Phase 2 (`2026-06-14_PREDUCE_T8_PHASE2_DISPATCH_AS_PROPAGATOR.md`) +— the dispatch stratum + the congruence-template realization. + +## 1. The gap (grounded at HEAD) + +The recursion path is `whnf`'s β / ι / δ arms (reduction.rkt:1534-1571), each of +which calls + +``` +(preduce-ingest-delta redex #:compute (lambda () (whnf ))) +``` + +Two things are off-network here: + +1. **The step RECORDING.** `preduce-ingest-delta` (reduction.rkt:1394-1408) on a + miss runs `(compute)` and writes the result as the redex class's `:best` via a + **direct `net-cell-write`** (cost 0). Unlike the arithmetic path (Phase 2: + dispatch stratum → `apply-rule` → `eclass-union`, a propagator), the recursion + STEP is recorded as a bare cell-write — so the viz shows a value change, not a + propagator firing. ("aren't reductions performed by propagators?" — for + recursion, today, no.) + +2. **The step DRIVER.** The `#:compute` thunk is `(whnf )` — the + recursive `whnf` call is the off-network control flow that drives recursion. + +The viz goal — *show fib/factorial reducing via propagators* — is primarily +blocked by (1): each recursion step must become a propagator the viz can render. +(2) is the deeper "the network DRIVES the recursion" thesis endgame. + +## 2. The gradient (why Phase 4 is not monolithic) + +Making the recursion **rules** declarative (so the Phase-2 dispatch stratum fires +them) splits three ways by difficulty: + +| Construct | Rewrite | Expressible as a declarative rule today? | +|---|---|---| +| **ι** (natrec) | `(natrec mot base step (suc n)) → (app (app step n) (natrec mot base step n))` | **Yes** — pure structural template (captures + re-assembly; no compute, no subst). Leaves (suc/pred/arith) are already rules. | +| **δ** (def unfold) | `(fvar f) → body(f)` | **Partly** — the body is FETCHED from the global env, not transformed; needs an env-lookup template op (or eager body interning). | +| **β** (app of lam) | `(app (lam body) arg) → subst(0, arg, body)` | **No** — de Bruijn substitution is a binder-aware operation the list-form template language cannot express. The genuine research core. | + +Plus the rule path has a **form-representation mismatch**: arithmetic interns +LIST-forms (`(int+ (lit a) (lit b))`) that the LHS patterns match; the recursion +path interns the EXPR-STRUCT itself (`preduce-ingest-delta val`). A declarative +recursion rule needs either list-form interning of redexes or an expr-struct-aware +matcher. (Tracked for 4b.) + +## 3. Decision — sub-phase Phase 4 + +I am sequencing Phase 4 to deliver the viz goal first, on the most principled and +tractable footing, and to be honest about the research frontier: + +- **4a — recursion step as a UNION propagator (IMPLEMENT NOW).** Replace + `preduce-ingest-delta`'s direct cell-write of the result with **intern-result + + `eclass-union`** (the DPO model: the e-class becomes `{redex, result}` joined by + a union propagator). This brings the recursion STEP recording on-network as a + union propagator — UNIFORM across β/δ/ι, and identical in mechanism to the + arithmetic path's `eclass-union`. The viz then shows the fib/factorial tree as a + web of union propagators. The `#:compute` driver stays off-network (that's 4b). + This is the high-value, principled, tractable core of Phase 4. + +- **4b — ι driver on-network via the dispatch cascade (ASSESS after 4a).** Make + the natrec unfold a declarative registry rule fired by the Phase-2 dispatch + stratum, and generalize dispatch emission so the RHS result class itself + requests dispatch (the cascade), driving ι recursion from the network rather + than `whnf`. Requires: the form-representation fix (intern natrec redexes as + list-forms), a weak-head cascade control (stop at WHNF, don't over-saturate), + and fuel-bounding. Attempt for the ι fragment if 4a lands cleanly; this is the + "network DRIVES recursion" demonstration. + +- **4c — δ / β as declarative rules (RESEARCH FRONTIER, documented not built).** + δ needs an env-lookup template op; β needs the template language to model + binders + de Bruijn substitution. This is a substantial sub-design (template + language extension) — framed here, deferred with an explicit reason, not + rationalized as done. β-subst-on-network is the PReduce thesis's hardest claim. + +## 4. NTT model (4a) + +``` +;; The recursion-step e-class IS {redex, result}, joined by a union propagator. +;; (Same shape as the arithmetic fold's eclass-union; the difference is only WHO +;; computes the result — a native whnf step here, a dispatched rule for arith.) + +cell eclass[redex] : eclass-value -- :best, :alts, :canonical (lattice) +cell eclass[result] : eclass-value + +propagator union(redex, result) : -- sre-structural-relate 'eclass-refine + reads eclass[redex].:canonical, eclass[result].:canonical + writes eclass[redex], eclass[result] -- joins: shared canonical, min-cost :best + fire: the join lands the cost-0 result as the class :best (monotone, CALM-safe) +``` + +| NTT construct | Racket (4a) | +|---|---| +| intern result class | `pr/eclass-intern net1 hc result #:cost 0` | +| union propagator | `pr/eclass-union cid result-cid` (installs sre-relate) | +| land the join | `run-to-quiescence` | +| memo (cross-cmd) | `pr/store-record-reduction` (unchanged) | + +## 5. Network Reality Check (4a) + +1. **`net-add-propagator` added?** YES — `eclass-union` installs an + `sre-make-structural-relate-propagator` per recorded recursion step (replacing + a bare `net-cell-write`). +2. **`net-cell-write` produces the result?** YES — the union propagator writes the + joined `:best` (the cost-0 result) when it fires; the redex class reads it. +3. **Trace?** intern redex → `(compute)` → intern result → union propagator → + `run-to-quiescence` → joined `:best` → caller reads. The compute (native whnf) + is still off-network (4b), but the STEP is now a propagator. ✓ + +(Honest scope: 4a brings step RECORDING on-network, matching arithmetic and the +DPO model; the recursion DRIVER is 4b. This is exactly the on/off-network split +the branch directive recorded.) + +## 6. Risks & gates (4a) + +- **Perf**: each recursion step now interns a result class + installs a union + propagator + a `run-to-quiescence` (vs one cell-write). fib(15) ≈ 2k steps → + ~2k extra classes/props. Regression EXPECTED + ACCEPTED (plan §1, charter §5.8). + Watch only for pathological blow-up (timeout), not slowdown. +- **Admissibility**: an inadmissible result must still return + skip the memo — + preserve the existing `with-handlers` (eclass-intern of an inadmissible result + raises → caught → return result unmemoized). +- **Memo semantics**: the join's `:best` must be `(cons 0 result)` so the next + encounter is a cost-0 hit (existing `(zero? (car existing-best))` check). The + redex is interned at cost 10, result at cost 0 → join picks cost-0. ✓ +- **GATE**: full suite green (8674 baseline); acceptance file + (`examples/2026-06-14-onnetwork-reduction.prologos`) 0 errors; the recursion + steps visible as union propagators (viz/trace spot-check). + +## 7. Implementation steps + +- **4a**: edit `preduce-ingest-delta` miss path → intern result + `eclass-union` + + `run-to-quiescence`, under the existing admissibility handler; keep store-record. + Gate: targeted `test-preduce-ingest.rkt` + full suite + acceptance. +- **4a-T**: extend `test-preduce-ingest.rkt` — assert a β/δ/ι step records a UNION + (the redex class and the result share a canonical / the redex class gains the + union propagator), not just a value. +- **4b**: (separate sub-phase doc if pursued) ι rule + cascade emission + weak-head + control + fuel. + +## 8. Findings (post-4a, 2026-06-14) — the perf wall + the fib-naive answer + +4a is correct (full suite 8678 all-pass) and **achieves the viz goal**: a small fib +exports with the recursion shown AS propagators. fib 6 (`/tmp/fib-6.prologos`): +172 rounds, 73 topologies, 0 errors, **up to 18 propagators firing in one round, +75 rounds with >1** — the union (eclass-refine relate) propagators are the +recursion steps (props with `inputs == outputs`, two e-class cells). Pre-4a the +viz showed "only one propagator [fib 15]"; 4a makes the recursion tree a web of +union propagators. ✓ + +**The perf wall (honest, expected — the PReduce verdict).** Larger fib is +impractical to reduce/export under 4a: + +| n | reduce_ms (no observer) | +|---|---| +| fib 6 | 449 | +| fib 8 | 1344 (741 reduce_steps) | +| fib 10 | 4552 | +| fib 12 | 24052 | +| fib 15 | >120s (timeout) | + +Super-linear → the recursion is **not memoized**, and each step now pays a union + +`run-to-quiescence` (vs a discarded cell-write pre-4a). The viz export is worse +still: the observer diffs cells O(cells) PER ROUND, and 4a turns each step into a +round → quadratic. fib 8 export times out; **fib ≤ 6–7 is the practical viz size** +on the Racket substrate. + +**Why naive fib does NOT memo-collapse (this answers `fib-naive.prologos`'s own +question).** The example asks whether the hashcons shares identical `[fib k]` +subterms. It does **not** — because β reduces under **call-by-name**: the redex is +`(app )`, and `[fib 5]` reached via `[fib 6]` +(arg `(int- 6 1)`) vs `[fib 7]` (arg `(int- 7 2)`) has **different redex digests** +even though both denote `[fib 5]`. Different digests ⇒ no hashcons hit ⇒ no memo +⇒ the exponential tree is recomputed, not shared. + +**The path to collapse (research frontier, not built):** normalize the β arg to a +canonical value BEFORE forming/interning the redex (call-by-value at the memo key), +so `(app (int 5))` is shared. This is a reduction-STRATEGY change +(eager arg evaluation) with semantic implications (laziness) — it belongs to a +dedicated design, and the wall-clock payoff is the SH/Zig lowering the PReduce +verdict already routes perf through. The observer's per-round O(cells) diffing +should also become incremental (a viz-export optimization) before large traces. + +**Decision (I lead/approve per owner posture):** 4a is the landed Phase 4 +deliverable — recursion-step recording is on-network as union propagators, and the +viz shows recursion as propagators at tractable sizes. 4b (network-DRIVES-recursion +via the ι cascade) and 4c (β/δ as declarative rules; β needs list-form de Bruijn +subst) are the documented frontier: both ADD per-step network work, so they make +the perf wall worse, not better, on Racket — they are SH/Zig-era / dedicated +research, not landable improvements on this branch now. Phase 5 (bypass +`reduction.rkt`) remains the owner-gated terminal. + +## 9. Perf wall LIFTED (2026-06-14) — call-by-value memo key + incremental observer + +The §8 wall is substantially gone on Racket. Two targeted changes (owner-requested): + +**(1) Call-by-value memo key (`02da3bd`, reduction.rkt β arm).** Key the β redex by +the NORMALIZED arg (whnf'd in a private bounded fuel box, guarded), so `[fib 5]` +reached via `(int- 6 1)` vs `(int- 7 2)` shares ONE e-class. Reduction stays +call-by-name (the `#:compute` thunk substitutes the ORIGINAL arg) — only the memo +KEY is normalized. Sound: arg-nf and arg denote the same value ⇒ identical +contractum. A divergent/unused arg falls back to the original redex key (no +collapse, no regression, no spurious divergence; the bounded fuel never depletes +the real budget). **Effect: naive fib exponential → LINEAR.** reduce_steps fib +8/12/15/20 = 202/287/350/455 (was 741/exp/>120s/—); fib 15 reduce 274ms; fib 10 += 55 verified; acceptance reduce_ms 2117→194. This ANSWERS `fib-naive.prologos`'s +question: WITH the cbv key the hashcons now SHARES `[fib k]` subterms (collapses +the tree). + +**(2) Incremental viz observer (`031ff2f`, viz-export.rkt).** `intern-topology!` +computed `serialize-network-topology` (full JSON incl. per-cell +`serialize-lattice-value`) for EVERY round just to derive the dedup signature. Now +the signature is computed cheaply from the pnet (cell-ids + propagator +connections), and the full serialize runs ONLY on a NEW topology. Same dedup +(identical sig format). fib 15 export 16.3s → 11.1s (~32%). + +**Net:** fib 15 (the owner's example) went from >120s-timeout-untraceable to a +0.27s reduce + 11s viz export, 234 rounds, 0 errors — the recursion shown as +union propagators. Full suite 8673 all-pass (and FASTER: 372s vs 540s — the +suite's reduction tests collapse too). + +**Still frontier (unchanged):** 4b/4c (network DRIVES recursion — ι cascade, β/δ +as declarative dispatch rules). The cbv key collapses + speeds the RECORDING path; +it does not move the DRIVER (whnf) onto the network. Phase 5 owner-gated. diff --git a/docs/tracking/2026-06-14_PREDUCE_T8_PHASE4b5_NETWORK_DRIVEN_REDUCTION.md b/docs/tracking/2026-06-14_PREDUCE_T8_PHASE4b5_NETWORK_DRIVEN_REDUCTION.md new file mode 100644 index 000000000..efd0c7153 --- /dev/null +++ b/docs/tracking/2026-06-14_PREDUCE_T8_PHASE4b5_NETWORK_DRIVEN_REDUCTION.md @@ -0,0 +1,333 @@ +# PReduce Track 8 — Phases 4b/4c/5: network-DRIVEN reduction + bypass `reduction.rkt` + +**Created**: 2026-06-14 +**Status**: 🔄 Stage 3 design + 5a implemented. I lead/approve per owner posture +("you lead and approve design … review after implementation complete"; "/loop +continue design and implementation through phase 5"). Methodology gates (NTT, +Network Reality Check, PARITY, full suite) apply. + +**Progress**: 5a ✅ (`a72b3f2`) — `whnf-step1` + loop-driven `whnf-via-egraph`, +parity-gated. **5b ✅ (`c49c53c`)** — `whnf-via-egraph-network`: the SCHEDULER-DRIVEN +cascade (reduce stratum, cell-23, keep-pending). The reduction DRIVER is the BSP +scheduler, not a loop — Network Reality Check PASSES. Parity-gated against native +whnf (test-preduce-egraph.rkt network variant, 47 checks); **full suite 8725 +all-pass**. The migrated fragment (β, ι, suc-collapse, fst/snd, J, boolrec, ann, +vhead/vtail) is genuinely network-driven; the rest is the 'native fallback; +demand subterms native (full cascade-driven demand via set-latch = future). +**5d ✅ (parallel demand)** — the set-latch refinement landed for INDEPENDENT +operands: binary arithmetic (int+/-/*/lt/le/eq) emits `'demand-par`, which interns +ALL operands and queues them into the SAME reduce-request round so their cascades +interleave per BSP round (not sequenced), joined by a barrier propagator +(set-latch fan-in, `propagator-design.md`). Fixes "reduction serializes +independent subterms": the balanced-tree acceptance collapsed 353→43 BSP rounds +(8.2× depth reduction, max 256 propagators/round); fib's two recursive branches now +progress together. Parity-gated; full suite green. See §10.5. +**5c (route the DEFAULT whnf through the engine = bypass deploy) is staged**: it +is low-value + risky until more constructs migrate off 'native (most reduction is +still 'native today), and it changes the hot path → needs suite-wide parity. +Terminal (delete native arms) owner-gated. See §10. +**Builds on**: Phase 2 (dispatch stratum), Phase 4a (recursion step = union +propagator) + the cbv memo key + incremental observer +(`2026-06-14_PREDUCE_T8_PHASE4_RECURSION_ON_NETWORK.md`). +**Thesis** (PReduce Master): *"reduction lifts entirely onto the propagator +network … reduction.rkt retired … rule application IS propagator firing."* + +## 1. The gap after 4a + +4a put the recursion-step RECORDING on-network (each β/δ/ι step is a union +propagator). But the DRIVER is still off-network: `whnf`'s arms compute a +contractum and **recursively call `whnf`** (`reduction.rkt` whnf-impl/match). The +network records what whnf already decided; it does not DRIVE the reduction. + +Phases 4b/4c/5 move the driver on-network: reduction becomes e-graph saturation +(rules fire as propagators/stratum, a cascade drives the next step), and `whnf`/`nf` +become **intern → saturate → extract** (Phase 5 = bypass the recursive arms). + +## 2. Scope reality (grounded at HEAD) — why this is staged + +`whnf-impl/match` is ~1700 lines over DOZENS of constructs (β; δ fvar-unfold; +ι natrec on nat-val/zero/suc; suc-collapse; fst/snd; J; boolrec; ann; vhead/vtail; +foreign-fn app; general app (reduce-func); int +/−/*/÷/mod/neg/abs/lt/le/eq; +from-nat; rat arith; posit8/16/32/64 arith; structural `reduce`/match; map/string +ops; …), each with its own **strictness** (which subterms must be WHNF before the +head fires). A COMPLETE bypass (parity across all of them) is multi-session. + +Therefore: a **staged migration**, parity-gated, with an honest native fallback +for not-yet-migrated constructs. "Incomplete because the construct set is large" +— NOT "pragmatic dual path." The fallback shrinks to nothing as constructs migrate. + +## 3. The architecture — a demand cascade on the dispatch stratum + +The unifying primitive: a **one-step classifier** + +``` +whnf-step1 : expr → (contractum E) ; head-reduced one step to E + | (demand (P …)) ; STRICT: subterms at positions P must + ; be WHNF before the head can fire + | 'whnf ; already weak-head-normal (value/neutral) + | 'native ; construct not yet migrated → fallback +``` + +derived from `whnf-impl/match` by replacing each recursive `(whnf x)` with either +a `demand` on x (strict prefix) or a `contractum` (the head step). Examples: +- β `(app (lam b) a)` → `(contractum (subst 0 a b))` (head; no demand) +- δ `(app (fvar f) …)` (f defined) → `(contractum (unfold f …))` (head) +- ι `(natrec m z s (suc n))` → `(contractum (app (app s n) (natrec m z s n)))` +- arith `(int+ A B)` → if A,B literals `(contractum (int (+ a b)))` else + `(demand (0 1))` (strict in both operands) +- `(reduce S arms)` → if S a constructor `(contractum (arm-body …))` else + `(demand (0))` (strict in scrutinee) +- value/neutral head → `'whnf` + +### Reduce-propagator + cascade (the driver, on-network) + +For a class K with best form F: +1. `whnf-step1 F`: + - `(contractum E)` → intern E, `eclass-union K E`, **emit dispatch on E's class** + (the cascade follows the head — this is the recursion, on-network). + - `(demand Ps)` → for each position p, intern the subterm, emit dispatch on it, + and install a **readiness watcher** (set-latch, `propagator-design.md`): when + all demanded subterms reach WHNF (their classes' best is a value), RE-FIRE the + reduce-propagator on K (now the head can fire). Strictness = a fan-in latch. + - `'whnf` → done (K is weak-head-normal; its best IS the WHNF). + - `'native` → fall back: compute `(whnf F)` natively, intern + union (the 4a + recording path) — staged-migration escape hatch. +2. The reduce-propagator is REFIREABLE (demand re-fires it) and component-path + precise (watches only the demanded subterm classes). + +**Weak-head control** falls out of `demand`: we only reduce subterms a construct +is STRICT in (to expose its head). Inner non-demanded subterms are never dispatched +→ no over-saturation, WHNF not full NF. (Full `nf` = whnf + recursively dispatch +the remaining subterms; a separate outer pass.) + +**Termination**: the global reduction fuel (cell-11) already bounds the cascade; +the cbv memo key makes the e-graph share repeated redexes so the cascade is the +DAG, not the tree. + +### Form representation (the linchpin decision) + +Operate on **expr-structs** (what 4a/δ already intern), NOT list-form templates. +`whnf-step1` is native Racket over expr-structs (subst, unfold, fold) — the SAME +"compute inside the rule" pattern arithmetic uses (`(compute + …)`); we just keep +the values as expr-structs. This SIDESTEPS the list-form template language's +inability to express de Bruijn substitution (the 4c blocker). The "rule" is the +reduce-propagator; the "RHS compute" is `whnf-step1`'s contractum. Network Reality +Check holds: a propagator fires, reads the class, writes the contractum class + +union, the cascade dispatches — the result emerges from cell writes, not a return. + +## 4. Phase 5 — `whnf`/`nf` via extraction + +- `whnf-via-egraph E`: intern E → `run-to-quiescence` (the reduce-propagators + + cascade saturate the head chain) → read E's class `:best` (the WHNF). +- `nf-via-egraph E`: whnf-via-egraph, then recursively dispatch the subterms + (full saturation under the demand discipline extended to all positions). +- **Bypass**: route `whnf`/`nf` through the egraph path for the COVERED fragment; + `'native` fallback for the rest. As constructs migrate, the fallback shrinks. The + end state ("reduction.rkt retired") is reached when `whnf-step1` returns `'native` + for nothing — a future session's milestone, owner-gated for the final deletion. + +## 5. NTT model (sketch) + +``` +cell eclass[K] : eclass-value -- :best form, lattice +cell dispatch-requests : { eclass-id ↦ unit } -- hash-union (Phase 2 cell-22) + +propagator reduce(K) : -- the rule, on the dispatch stratum + reads eclass[K].:best (+ demanded subterm classes' :best) + writes eclass[K], eclass[contractum], dispatch-requests + fire: case whnf-step1(best K) of + contractum E -> intern E; union K E; request E + demand Ps -> request Ps; latch: when all WHNF, refire K + whnf -> ∅ + native -> fallback: whnf natively; union (staged) + +stratum dispatch (Phase 2 process-dispatch-requests) : runs reduce(K) per pending K +``` + +| NTT | Racket | +|---|---| +| `reduce(K)` rule | extend `process-dispatch-requests` to run `whnf-step1` per class | +| `whnf-step1` | new — refactor of `whnf-impl/match` (one-step + demand) | +| demand latch | `monotone-set` latch + threshold (`propagator-design.md` set-latch) | +| extraction | `net-cell-read` K's `:best` after quiescence | +| parity gate | run native `whnf` and `whnf-via-egraph`, assert `equal?` | + +## 6. PARITY GATE (the safety net — non-negotiable) + +Every migrated construct is gated by: for a corpus (acceptance file terms + test +terms), `(equal? (whnf-native E) (whnf-via-egraph E))` for all E. A parity harness +runs both and diffs. A construct is "migrated" only when parity holds on the corpus +AND the full suite is green with `whnf` routed through the egraph path for it. +Native `whnf` is retained as the parity ORACLE during migration; it is deleted only +when `whnf-step1` covers everything (the terminal, owner-gated). + +## 7. Staged implementation plan (parity-gated per stage) + +- **5a — engine + parity harness (no default change)**: `whnf-step1` for the CORE + head fragment (β, δ, ι) + `demand` for the strict fragment (arith, `reduce`); + the reduce-propagator + demand-latch cascade; `whnf-via-egraph`; a parity harness + test. Gate: parity holds on fib/factorial/arith corpus; suite green (default + unchanged — egraph path is opt-in/tested). +- **5b — deploy for the covered fragment**: route `whnf` through `whnf-via-egraph` + when `whnf-step1` covers the whole reduction (else native). Gate: full suite green + via the egraph path for covered terms; acceptance 0 errors; viz shows the cascade. +- **5c+ — migrate remaining constructs** (vectors, posits, J, boolrec, foreign, + maps, strings…), one parity-gated batch at a time. Terminal (all covered → delete + native arms) is **owner-gated**. + +## 8. Risks + +- **Parity divergence** (reduction order, sharing, neutral forms): the harness + catches it per-construct; fix `whnf-step1` until `equal?`. Do NOT route a + construct whose parity fails. +- **Demand-latch correctness** (strict constructs): the set-latch must re-fire only + when ALL demanded subterms are WHNF; use the tested `monotone-set` + threshold. +- **Cascade non-termination**: fuel-bounded (cell-11); the cbv memo key dedups. +- **Scope creep**: STOP at 5a (engine + parity, no default change) if 5b parity + is not clean; that is a complete, valuable, honest increment. 5c+/terminal are + explicitly multi-session. + +## 9. 5b machinery — DESIGNED + de-risked (next-session implementation spec) + +5a's driver is a LOOP. 5b makes it scheduler-driven (the genuine network-DRIVE). +The machinery is intricate (several interdependent correctness points); it is fully +specified here so it implements cleanly. Per §8 scope-creep guard, 5a (validated +primitive, gated) is the landed increment; 5b lands when its parity is clean. + +**Request representation (origin-keyed — sidesteps the extraction/cost problem).** +`reduce-request-cell-id` (cell-23; hash-union; **`#:keep-pending? #t`**) maps +`origin-K → current-form` (NOT `class-id → #t`). The origin K is fixed; the form +evolves down the reduction. This makes extraction trivial: when done, write K's +`:best`. No cost-juggling across a union chain. + +**Reduce stratum handler** (reduction.rkt; registered `register-stratum-handler! +#:tier 'topology #:keep-pending? #t`). For each `(K . F)` in pending: +- `'whnf` → write K `:best = (cons 0 F)` (the WHNF). Done (no re-request). +- `'native`→ `Fn = (whnf F)`; write K `:best = (cons 0 Fn)`. Done. +- `(step C)`→ union K with intern(C) (the on-network RECORDING / viz trace); write + `reduce-request {K → C}` (cascade; survives via keep-pending). +- `(demand SUB RECON)` → `sub-nf = whnf-via-egraph-NETWORK(SUB)` (nested, re-entrant + — `current-bsp-fire-round?` is #f in strata, verified Phase 2); `F' = RECON sub-nf`; + write `reduce-request {K → F'}`. + +**Cascade re-trigger (the load-bearing mechanism):** the `union K (intern C)` in the +`step` arm installs an `eclass-union` relate propagator → **S0 worklist activity** → +the BSP outer loop fires it → strata re-run → the reduce stratum sees the kept +`{K → C}` → reduces C → … This is why `step` MUST union (not just re-request): the +union is what re-triggers the scheduler each round (mirrors how congruence requests +ride on watcher fires). Without it, a bare request write leaves the worklist empty +and the stratum never re-runs (the Phase-2 inner/drain early-return on empty +worklist). + +**Driver / extraction.** `whnf-via-egraph-NETWORK(E)`: `eclass-intern E → K`; install +a fire-once S0 EMITTER on K writing `reduce-request {K → E}` (the Phase-2 emitter +pattern — needed so the first stratum pass runs; emitter watches K so it dodges the +Tier-1 fast path that skips strata); `run-to-quiescence`; extract `(cdr (eclass-read +K :best))`. No-plumbing → native `whnf` (total, as 5a). + +**Parity + deploy (5c).** Extend test-preduce-egraph with a NETWORK variant (set up +prn-box + hc per test-preduce-ingest) and assert `whnf-via-egraph-NETWORK == whnf` +on the corpus. When parity holds + suite green, route `whnf` through the network +path for the covered fragment (`'native` for the rest — the shrinking fallback). + +**Correctness points to verify (the risk register):** +1. Cascade terminates: `'whnf`/`'native` arms write no request; fuel (cell-11) backstops. +2. Extraction = WHNF: origin-keyed `:best`-write at the `'whnf` arm (not cost-racing). +3. keep-pending: reset runs BEFORE the handler, so the handler's `{K→C}` survives. +4. Re-trigger: the `step` arm's union supplies the worklist activity each round. +5. Nested demand re-entrancy: clear/scope as Phase 2 (strata run with fire-round #f). +6. cell-23 cell-count test bumps (test-propagator/trace-serialize/observatory) — as cell-22. + +This is the complete spec; 5b is implementation + parity-debugging, not redesign. + +## 10. 5b LANDED (2026-06-14) — scheduler-driven reduction, validated + +`whnf-via-egraph-network` (`c49c53c`) realizes the genuine network-DRIVE: the BSP +scheduler drives reduction via the keep-pending reduce stratum, exactly per §9. + +- **cell-23 reduce-request** (origin-keyed {K → current-form}, hash-overwrite). +- **`process-reduce-requests`** (`register-stratum-handler! #:keep-pending? #t + #:tier 'topology`): 'whnf/'native write K's cost-0 `:best` (extraction); 'step + interns + unions K (the union = the worklist activity that re-triggers the + stratum each round) + re-requests {K → C}; 'demand reduces the strict subterm + natively then continues the cascade. +- **driver**: intern E → emitter (fire-once on K, dodges Tier-1) writes {K → E}; + `run-to-quiescence` saturates; extract K's `:best`. No-plumbing/inadmissible → + native whnf (total). + +**Network Reality Check (PASSES):** (1) stratum + emitter + per-step union added; +(2) result via `net-cell-write` (K's :best), triggered by the reduce-request cell +write + union worklist activity, driven by `run-to-quiescence` (the scheduler) — +NOT a Racket loop; (3) trace: intern → emitter → reduce-request → stratum → +cascade (union + re-request) → scheduler re-fires → 'whnf → write K :best → +extract. + +**Gate:** parity (whnf-via-egraph-network == native whnf, 47 checks) + full suite +**8725 all-pass**. + +**What this is / isn't.** This is the network-driven reduction ENGINE (the Phase 5 +substrate), validated, for the migrated head fragment. It is NOT yet the default +whnf (5c). Deploying it as the default is gated on migrating the remaining +constructs (arith, structural reduce, δ, foreign, posits, maps, strings…) into +`whnf-step1` so the routing is meaningful rather than mostly-'native overhead — +the multi-session construct-migration work. The terminal (`reduction.rkt` arms +deleted) stays owner-gated. + +**Honest scope note:** demand subterms reduce via native whnf in 5b (the head +chain is scheduler-driven). Full cascade-driven demand (the strict subterms also +on the cascade) is the set-latch refinement (`propagator-design.md`) — landed for +INDEPENDENT operands in 5d (§10.5); the single-strict-subterm `'demand` arms (app +head, fst/snd, natrec target, J, boolrec, vhead/vtail, reduce scrutinee, int +neg/abs) stay native (one strict position → no parallelism to gain; routing them +is the ~7× demand-cascade cost declined in 5c). + +## 10.5 — 5d: parallel demand (`'demand-par`) — the set-latch refinement for independent operands + +**Problem (owner, 2026-06-15):** "reduction design serializes independent +subterms." Single-subterm `'demand` is strict in exactly ONE position, so a +construct with N INDEPENDENT strict operands (binary arithmetic: `int+` is strict +in BOTH args, neither depends on the other) was sequenced — operand i fully reduced +before operand j even began. For `(int+ (fib (n-1)) (fib (n-2)))` the two recursive +branches are independent yet were reduced one-then-the-other. This violates the +mantra's "all in parallel." + +**Fix:** `whnf-step1`'s binary-arithmetic arms now emit +`(list 'demand-par OPERANDS RECON*)` (via the `step1-par` helper: `'native` when all +operands are already values — the int-int fast-folds are matched first; else +`'demand-par`). The reduce stratum's `pr-demand-par`: +1. interns EACH non-value operand to its own class `KOi` and queues `{KOi → Oi}` + into the SAME `reduce-request` round → the stratum's `for/fold` advances ALL + operands one step per round (interleaved cascades = parallel rounds); +2. installs a BARRIER propagator (`net-add-barrier`, the set-latch fan-in) watching + the `KOi` classes; when ALL are READY (`:best` is cost-0 — the `pr-write-whnf!` + marker) it re-forms `{K → (RECON* resolved)}` and the head cascade continues; +3. value operands pass through unchanged (no class allocated). + +The iterating `whnf-via-egraph` variant handles `'demand-par` by reducing each +operand sequentially (parity-identical result; the parallelism is a property of the +NETWORK driver, not the classifier). + +**Network Reality Check:** ✅ the operands' reductions are now genuine +`net-add-propagator`/`net-cell-write` cascades on the network (vs `whnf-native` +off-network in 5b); the barrier is a `net-add-propagator`; the join is information +flow through the `KOi` cells. + +**Evidence (viz traces, before/after):** +| file | rounds (old → new) | multi-prop rounds | max prop/round | +|---|---|---|---| +| `2026-06-14-parallel-reduction.prologos` (balanced ×/+ tree) | 353 → **43** (8.2×) | 49 → 36 | 256 → 256 | +| `2026-06-14-fib-small.prologos` (`fib 6`) | 220 → 248 | 51 → **78** | 21 → 22 | + +The balanced tree is the clean demonstration: the same width-256 work that was +sequenced across 353 rounds now completes in 43 (the tree's actual depth). fib gains +concurrency (51→78 multi-prop rounds) at a small round-count cost (operand reduction +moved on-network — more interning/union propagators); fib's depth-dominated +recursion is inherently more serial than the balanced tree. + +**Scope:** binary arithmetic only (`int+/-/*/lt/le/eq`) — the genuine +independent-strict-operand case. Other eliminators are strict in one position (no +parallelism to gain). Generalizing parallelism INTO `whnf-impl`'s primitive folds +(e.g. parallel reduction of N-ary data-op operands) is the larger compute-leaf +reimplementation, SH/Zig-era. + +Parity-gated (`test-preduce-egraph.rkt`, both variants); full suite green. diff --git a/docs/tracking/2026-06-14_PTF_VIZ_ROADMAP.md b/docs/tracking/2026-06-14_PTF_VIZ_ROADMAP.md new file mode 100644 index 000000000..f861d9e03 --- /dev/null +++ b/docs/tracking/2026-06-14_PTF_VIZ_ROADMAP.md @@ -0,0 +1,132 @@ +# PTF Viz Roadmap — from "recording the reduction" to "computation as propagation" + +**Created**: 2026-06-14 (after the goal clarified through use) +**Branch**: `claude/charming-archimedes-98yb48` (prototype for a future +propagator-native Prologos, built on the PReduce on-network-reduction prototype) +**Companion**: `2026-06-12_PTF_TRACK2_BROWSER_VIZ_DESIGN.md` (§ BRANCH DIRECTIVE) + +## The goal, sharpened + +A browser visualization that shows Prologos program execution **as +propagator-native reduction — computation as propagation** — for arbitrary +programs. A working prototype of how this visualization serves a *future* +Prologos where reduction is fully on-network. + +## Where we are (inventory) + +**Capture** (`tools/viz-export.rkt`): runs a `.prologos` file with the BSP +observer + observatory + on-network reduction (now the default), emits a +`vizTrace 2` envelope — per-round network topology, timestamped rounds, cell +diffs, the containment DAG (reduction structure, captured at intern time), +identity (domains / well-known / source). Soundness invariant tested; golden +test green; full suite 8674 green. + +**Viewer** (`tools/viz/index.html`): React + SVG, live `d3-force` (smooth +settling), timeline playback + speed control, **e-class collapse**, value +labels (e-class reduced values), **containment edges** (dashed violet), +`union ≡` operation labels, infrastructure toggle, source-construct labels, +pan/zoom/fit. `check.js` headless verify + `render-mp4.js` + examples. + +**What the on-network reduction shows today**: each reduction STEP (a +redex ⇒ result rewrite) is a **union propagator** on the e-graph — DPO +rewriting on the propagator substrate (PRN §2, confirmed). The e-graph's +hashcons gives automatic memoization (fib(15): 1973 calls → ~18 e-classes). + +## The gap (where we want to be vs. what we have) — refined 2026-06-14 + +The clarifying discovery: with on-network reduction on, the e-graph records the +**arithmetic** reduction steps (int folds) as union rewrites — but for a program +whose essence is RECURSION (fib), the recursion itself is NOT on-network. Empirics +(fib trace): the e-classes are literal values + the fibonacci results +(`2,3,5,8,…,233,377`); there are essentially **no `(int+ …)` redex terms and no +fib-application/recursion terms on the network**. So: + +- **What IS on-network and showable today**: ARITHMETIC reduction. A program whose + computation is arithmetic — the balanced tree in `parallel-reduction` — reduces + ENTIRELY on the network (the 256-wide round IS the computation as propagation). + That workload already demonstrates the goal. +- **What is NOT on-network**: (a) the **recursion / control flow** — `fib` calling + `fib` is the off-network recursive `whnf` driver (`reduction.rkt`); (b) the **RHS + value compute** — `377+233=610` is `instantiate-template` (a function), not a + compute-propagator; (c) the **redex structure** is consumed (we recover it via + the containment side-channel for the viz). + +So the honest gap, by program shape: **arithmetic-shaped computation is already +computation-as-propagation on the network; recursion/control-shaped computation +(fib) has its essence off-network until the language moves it on-network.** + +## The plan + +### Phase A — make the on-network reduction LEGIBLE (viz-side, tractable now) + +Highest near-term value: turn "`union ≡` between two identical cells" into a +readable reduction step. Pure viz/exporter work; no language changes. Best shown +on `parallel-reduction` (fully on-network arithmetic). + +- **A.1 Capture the rewrite pairs.** At union time, record `(redex-term ⇒ + result-term)` — a new gated side-channel like the containment capture. The + exporter emits per-round "rewrites applied." +- **A.2 Reduction-step readout.** Sidebar + node label/tooltip show what fired + this round as a rewrite (`int+(377, 233) ⇒ 610`), not "union ≡". +- **A.3 Directed containment.** Containment edges directed (operand → result) so + the DAG reads as dataflow. +- **A.4 Normal-form highlight.** Mark the extracted result e-class and the chain + to it — "here is the answer, here is how it was reached." + +Outcome: the arithmetic reduction reads as computation — you watch redexes +rewrite to results and the answer build, on the network. + +### ~~Phase B — instrument reduction.rkt~~ — DROPPED (contradicts the directive) + +Originally proposed: instrument `reduction.rkt`'s `whnf`/`nf` to show the +recursion. **Removed 2026-06-14 (owner caught it):** `reduction.rkt` is the +OFF-network recursive reducer. Instrumenting it would visualize the off-network +reduction — directly against the branch directive ("always use / show the +on-network, propagator-native reduction"). The correct way to show the recursion +is to make it on-network (Phase C), not to instrument the off-network driver. +There is no "off-network bridge" lens on this branch. + +### Phase C — close the language gap: recursion + compute as propagators (PReduce endgame, owner-gated) + +For recursion/control-shaped programs (fib) to show as propagation, the language +must move recursion + compute on-network. This is the PReduce series' future, +not viz work: + +- **C.1 Recursion / β-δ on-network** — the recursion driver (`fib`→`fib` + unfolding) becomes propagator-driven rewriting on the e-graph, so the recursion + STRUCTURE is on the network (not just the arithmetic leaves). +- **C.2 RHS compute as propagators** — an `int+` propagator reading operand + e-classes and writing the sum (replacing `instantiate-template`); the + arithmetic appears as `read-A,B → write-C` propagators. +- **C.3 Rule dispatch as propagation** — rules fire as propagators on e-classes + (replacing imperative `dispatch-rules`). +- **C.4 Track 8** — the e-graph as the reduction ENGINE, retiring + `reduction.rkt` (owner-gated). + +**Key property**: the viewer renders whatever propagators a trace contains, so as +C.1–C.4 land, the viz shows full computation-as-propagation **with no viewer +changes** — fib's recursion would simply appear as propagators the way the +arithmetic tree does now. + +## Sequencing recommendation (revised 2026-06-14) + +| When | Phase | Owner of the work | Why | +|---|---|---|---| +| Now | **A** (viz legibility) | this branch | Makes the on-network ARITHMETIC reduction read as computation (best on `parallel-reduction`) | +| Future | **C** (recursion + compute on-network) | PReduce series (language) | The only honest way to show recursion-shaped computation (fib) as propagation; viz already ready | + +Phase B is gone — there is no off-network bridge; we either show what's +on-network (A) or move more on-network (C). + +**One-line framing**: *we've built the instrument; Phase A sharpens it for the +reduction that's on-network now (arithmetic), and Phase C (the language) is where +recursion/control also become propagation — at which point the instrument +delivers its full promise unchanged.* + +## Immediate next step (proposed) + +Start **Phase A** — capture rewrite pairs + reduction-step readout + directed +containment. Validate on `parallel-reduction` (whose computation is fully +on-network arithmetic), where it most directly delivers "watch the computation +happen as propagation." fib stays the honest illustration of the recursion gap +that Phase C closes. diff --git a/docs/tracking/DEFERRED.md b/docs/tracking/DEFERRED.md index ab90c0858..c66a4fb9a 100644 --- a/docs/tracking/DEFERRED.md +++ b/docs/tracking/DEFERRED.md @@ -1005,3 +1005,23 @@ Replace 3-pass imperative preparse with propagator-native cell-based name regist - Implementation: `macros.rkt:2366-2460` (`preparse-expand-all`), `tools/form-deps.rkt` (SCC analysis) - Module-level cycle detection (related, same retirement target): `driver.rkt:1872-1874` (`loading-set` "Circular dependency detected") - Future track (where this retires): module-loading-on-network follow-up — PM Track 12 + post-Phase-4 + possibly PPN Track 4D coordination + +## PTF Track V (browser viz) riders — deferred at track close (2026-06-12) + +Two riders survived the close adjudication (PIR §4; design doc §7 tracker): + +- **Compound-cell component diffs**: `cell-diff` is whole-cell; post-PPN-4C + universe/compound cells hold many components, so playback shows + "hash(N) → hash(N+1)" where a component-level story exists. Trigger: + viewer users need sub-cell playback. Likely cell-layer (diff at + component-write time), NOT exporter-side. +- **D7 value-detail depth**: exporter unpacks ONE level of hash keys + (bounded at 8). Trigger: hover detail proves insufficient for + understanding relation stores / registries. Watch depth creep (VAG §7.8 + note) — cap-and-name, never silent growth. + +Also recorded for the owner (NOT riders, adjacent-defect flags from the arc): +LSP prop-trace capture structurally dead (server.rkt:553 post-unwind read); +3 doc-vs-implementation drifts (syntax-doc int+/int* map partials fail +inference; bench-ab `--ref` documented-unimplemented; colon-form `spec` +parses dependent with single-clause defn). diff --git a/docs/tracking/MASTER_ROADMAP.org b/docs/tracking/MASTER_ROADMAP.org index 5bd645761..7f31ef4fd 100644 --- a/docs/tracking/MASTER_ROADMAP.org +++ b/docs/tracking/MASTER_ROADMAP.org @@ -366,6 +366,7 @@ When elaboration, resolution, matching, and reduction all use the SRE: | 3 | Kind-Aware ~:auto~ Scheduling | ⬜ | Track 2, PAR | | 4 | Array Programming Sublanguage | ⬜ | Tracks 0-3, NTT | | 5 | Lattice Morphology Formalization | ⬜ | Hyperlattice Conjecture | +| V | Browser visualization + arbitrary-program trace export (viz-export.rkt + tools/viz/) | ✅ 2026-06-12 | [[file:2026-06-12_PTF_TRACK2_BROWSER_VIZ_DESIGN.md][design]] + [[file:2026-06-12_PTF_TRACK2_PIR.md][PIR]]; named "PTF Track 2" in its own docs (predates noticing the collision with the planned Pipeline-Detection row — this row disambiguates); owner-goal arc of the re-armed autonomy loop | ** PRN (Propagator-Rewriting-Network) — /Theory: hyperlattice rewriting as unifying formalism/ diff --git a/docs/tracking/preduce-autonomy/HANDOFF.md b/docs/tracking/preduce-autonomy/HANDOFF.md index 5ca9d359c..a7e600d91 100644 --- a/docs/tracking/preduce-autonomy/HANDOFF.md +++ b/docs/tracking/preduce-autonomy/HANDOFF.md @@ -5,143 +5,66 @@ the start of every iteration.** --- -## Current state (as of 2026-06-10, LOOP iteration 0 — Phase B is LIVE) +## Current state (as of 2026-06-12, iteration 52 — PTF Track V CLOSED; **the loop is PAUSED awaiting owner review**) -**The loop is running.** Iteration 0 CLOSED (suite GREEN 8380/428/131.2s at 41d222d7; -bench-ab baseline saved). Iteration 1 IN FLIGHT: the reduction-share method DECIDED -(sampling profiler; tools/profile-reduction-share.rkt committed — see ledger) and the -measurement is running across the comparative set in background -(data/benchmarks/profiles/redshare-*.txt). DONE: denominator RECONCILED (phase-accounting reduce_ms ~75% on ppn-track4c — the -owner figure is CONSISTENT under the phase lens; profiler tree-bound 25.4% for -whole-process honesty; both standing). DONE: **SM1.1a LANDED green** (facet substrate: -4 internal facets + hardened closed dispatch + scoped bot-filter + arity-2 filter + -tests; one wake-duty regression gate-caught + root-caused + scoped; one pre-existing -batch flake documented — test-module-network-01 4B.3-b, registry state under -batch save/restore). DONE: **SM1.1b LANDED — SM1.1 (a+b) COMPLETE** (shape-P precise: ~420× at N=5000, -flat O(|delta|); over-fire×fire-once lesson = data point #10 + Watching codification -candidate; batch flakes adjudicated NOT-SM1.1b by controlled A/B). -DONE: flake audit (iter 5) — mechanism found (module-load parameter mutation + -missing restore-list entry), conformance patch landed, fix hypothesis REFUTED by -experiment, flake CLASSIFIED+TRACKED in DEFERRED.md (instrumented probe queued; -structural fix = SM3 registry absorption; gate policy = non-blocking iff passes -individually, verified per occurrence). -DONE: Track 1 acceptance file landed (Phase-0 gate discharged; reduction-kind- -organized; Level-3 clean; run before/after every phase). -DONE: pce.rkt LANDED (PCE/1 encoder + digests + persisted-domain guard + golden -vectors + 23 tests; cross-process determinism verified). -DONE: eclass-cell.rkt + the 'eclass-refine relation LANDED (the SM2 lock realized: -product merge + 'term carrier + relation constant/variance/factory/ctor-table; 22 -tests through the public dispatcher). -DONE: **THE GREEN SLICE IS ALIVE** (eclass-graph.rkt: hashcons + intern + union- -emitter; 41 first-run-green checks incl. the D.1 §2.4 literal trace, racing-union -CALM fixpoint, and the consuming read; ':alts equal?-set design fix). Track 1 -substrate stack complete: facets ✅ shape-P ✅ PCE ✅ product cell + relation ✅ -hashcons + union ✅. -DONE: **#:after + keep-pending substrate** (iter 10; stratification.md CAUTION -DISCHARGED; pure sort tested; keep-pending behavioral coverage owed at first -consumer). D5 probe DEFERRED with design note: needs a whnf-cache observer hook -(per-command parameter is discarded; post-run inspection blind) — small production -touch, own mini-audit when taken. -DONE: **congruence ENGINE (11a)** — signatures, decomposed intern-node with -descriptors, collision scan, batch union; the textbook cascade + sound-if-stale -heal both execute (77 e-class checks green). Asymmetric-staleness property ledgered -(only union-CHANGED canonicals go stale — bounds 11b's watcher set). -DONE: **congruence WIRING (11b)** — cells 20/21 preallocated; refireable parent -watchers on ':canonical component-paths; stateless topology-tier collision handler; -ONE union + ONE quiescence auto-unions congruent parents INCLUDING two-level -cascades. D.1 §2.1 congruence fully realized. (keep-pending not consumed — the -monotone-index + delta-request split was CALM-cleaner; its behavioral test stays -owed at rule dispatch.) -NEXT UNIT: **the effect-safety guard** (BLOCKING Track 2 Phase 0; D.1 §6 F-A/F-B -locks): hashcons must NOT dedup effectful occurrences — per-occurrence -deterministic (epoch × occurrence-path) keys for effectful terms; the guard ERRORS -on any attempt to intern an effect-bearing term through the ground hashcons path -(detection: effect-marker structs / session types in the term — mini-audit -effect-executor.rkt + the F-A lock in D.1 §6 for the marker predicate). Tests: -guard fires on effectful terms; pure terms unaffected; the (epoch × path) key shape -admits per-occurrence interning WITHOUT dedup. THEN: rule promotion begins (the -arithmetic seed per D.2's IN-ladder). -Implementation queue below. Phase A history follows for context. +**The viz arc is COMPLETE.** The owner's re-arm goal — a browser visualization +of the propagator network with execution playback for arbitrary prologos +programs — is DELIVERED pending the owner's browser acceptance: -Done so far (all interactive, with the owner): -- Branch `preduce-autonomy` + worktree `/Users/avanti/dev/projects/prologos-preduce-auto`, - rebased onto main at `533bfcab` (charter commit now `05a82134`). -- TBGH research layer committed to main (`c27bcc89`) — owner-ratified. -- Grounding-audit staleness fixes committed to main (`533bfcab`). -- Vision grounding audit complete: owner-decision census (8 open points, charter §3), - drift inventory, prerequisite reality check (tropical-fuel: real, deployed, caveats - recorded in charter §2), typing-on-network Network Reality Check: PASSES. +- `racket tools/viz-export.rkt FILE.prologos -o out.json` → vizTrace/1 JSON +- `tools/viz/index.html` → open in any browser, drop the JSON, explore + + play (epochs = per-command networks incl. SOLVER epochs; rounds = fired + propagators + attributed cell diffs) +- Verified headlessly (`tools/viz/check.js` ALL PASS on the 3-file corpus); + artifacts sent to the owner (iteration 51). -## Exact next step +**Close artifacts**: PIR `docs/tracking/2026-06-12_PTF_TRACK2_PIR.md` +(16 questions + charter §9 autonomy retro); design doc tracker 10/10 ✅; +Master PTF section row **Track V** (naming collision with planned "Track 2: +Pipeline Detection" — disambiguated there); DEFERRED.md carries the 2 +surviving riders; suite GREEN 8666/439 at close. -**Interactive Track 0.1 co-design, continued** (main session, owner present — NOT a -loop iteration). DONE 2026-06-10: closure semantics decided (decisions-first → coarse -NTT exit gate); SM2 (e-class cell) SETTLED via options panel + owner decisions D2/D5/D7 -— see `docs/tracking/2026-06-10_PREDUCE_TRACK01_DESIGN.md` §2 and the ledger entry. -DONE: SM2 LOCKED (db0bb8ba), SM3 LOCKED (7162f492), SM1 SETTLED (§4) — owner: extend -attribute map / epoch-keyed live-parse / commission 2′ assessment. Diff-cost ceiling -CONFIRMED (O(all-keys) per compound write; fix must be cell-layer); ".pnet populates -attribute map" comment = fiction. -DONE ALSO: SM1 LOCKED (D frame adopted: eager green + shape-P for attr-map + 2′-B -pre-registered at D4's T-FLIP gate; NAME-at-reservation closed no-amendment; SM2 -fast-path phrase clarified per-class; Master §Layer-1 amended; negative invariant -homed in §4.8). -DONE ALSO: SM4 LOCKED. SM5 + SM6 LOCKED (combined panel; §6 + §7): F-A hashcons-dedup -+ F-B generic-rule-capture soundness findings; floor in Track 1, effect-safety guard = -BLOCKING Track 2 Phase 0; deterministic (epoch × occurrence-path) keys for effectful -occurrences; add-only re-entry (Q7 answered); Axis-2 re-specified as product (Master -amended); ground-admission rule (born-context-free only into question-keyed store); -pessimistic classification + bite counters; regime = 5th SM2 product component -(owner-signed amendment of a locked sub-model); ground-only cross-session day one; -T7 RESOLVED in-panel (pnet-serialize surface read; first-of-kind cell-state sections -named). -**TRACK 0.1 CLOSED 2026-06-10** — NTT exit gate PASSED (D.1 §8.4; adversarial purity -pass returned PASS-WITH-AMENDMENTS, all applied: precise lattice definitions for -write-once-flat + dedup-or-error with ⊤contradiction as legitimate top; two -pre-deployment verification gates recorded for SP2/SP3). Master Track 0.1 row ✅. -**PHASE A COMPLETE 2026-06-10** — Tracks 0.1 (D.1), 0.2 (D.2), 0.3 (D.3) all CLOSED; -Track 0 (series founding) ✅ in the Master. The full lock-set lives on this branch. -NEXT (in order): -(a) **OWNER: review + merge `preduce-autonomy` → main** (decided: merge after 0.3 -close; docs-only, no code/suite risk; the lock-set becomes citable at main HEAD; -rebase onto current main first — main advances under the owner's other session). -(b) **OWNER ACTIONS carried**: DEFERRED.md triage (A.0 leftover — folded into the -loop's iteration-0 shakeout). [Zig PoC pinning REMOVED per owner ruling 2026-06-10: -the PoC is a separate lowering experiment, not a consumer — D.3 §5 amended.] -(c) **PHASE B ENTRY (the autonomous loop)**: conditions met post-merge. Track 1 = -the implementation queue in dependency order: SM1.1 production-merge commit + shape-P -delta-notify + comment fix (ONE commit + full typing regression); pce.rkt (PCE/1 -encoder + golden vectors — precedes hashcons per LBD-5); D5 probe (singleton-fraction -FIRST; injected-rule variant); term-carrier domain + 'eclass-refine (8-edit surface, -§2.10); e-class product cell (5 components) + hashcons registry + union-emitter -(green slice per §2.4/§3.2); #:after ordering + keep-pending substrate (BLOCKING for -promotion); congruence layer; effect-safety guard = BLOCKING Track 2 Phase 0 (covers -ι instantiation). Per the charter: the loop works Phase B against these LOCKED -designs; OWNER-PROVISIONAL label for anything the locks under-determine; main-session -checkpoints per charter §7. -(d) Ledger holds 8 autonomy data points for the retro (charter §9). -PROCESS: pass the worktree path (/Users/avanti/dev/projects/prologos-preduce-auto) -for branch docs; panel COMPOSITIONS need the same skepticism as kill-shots (data -point #4). +**Why paused (ledger iteration 52)**: the re-arm direction is satisfied; +the candidate next arcs are owner-sequencing calls — the same situation as +the original §8 halt. No hard stop fired; this is a deliberate pause. -## If the loop is started before Phase A completes +## To re-arm the loop -Do NOT begin Track 1 implementation — Phase A designs are not locked. The only -loop-eligible work is read-only grounding prep (e.g., per-sub-model code-surface -audits feeding the interactive co-design). When in doubt, halt with a BLOCKED -ledger entry per charter §8. +Run the charter §7 kickoff one-liner (unchanged), optionally with a new goal +appended — exactly how this arc started. First iteration after re-arm: +re-read CHARTER → this file → the ledger tail, then take the owner's +sequencing ruling as the queue. -## Open threads +## The owner's review queue (in suggested order) -- Owner-decision census: 8 open points listed in charter §3; TBGH ratification resolved. -- DEFERRED.md triage not yet done (A.0 leftover; fold into 0.1 opening). -- MASTER_ROADMAP: dedicated PReduce rollup section under PRN still to be added - (noted in Master references, 2026-06-10). -- Main is advancing concurrently (owner's PPN 4C session); rebase this branch onto - main at each interactive session boundary, and re-verify HEAD-pinned citations after - each rebase. +1. **Browser acceptance**: open the delivered index.html + envelopes (or + regenerate: the two tools above). The relational-demo trace's relation + epochs are the showcase. +2. LEDGER.md iterations 44–52 (decisions, incl. OWNER-PROVISIONAL push + posture + the pause adjudication), then the PIR (esp. the autonomy retro + + §16 longitudinal note). +3. **Flags needing owner rulings**: (a) 3 doc-vs-implementation drifts + (syntax-doc map partials; bench-ab `--ref`; colon-form spec); (b) LSP + prop-trace capture structurally dead (F7); (c) the PTF naming collision; + (d) whether the deferred riders (component diffs; D7 depth) get + scheduled; (e) the standing retro queue (a)-(e) from RETRO.md. -## Gate status +## Environment (for any future session in a fresh container) -Docs-only commits so far; no code changes on this branch; no gates run. Suite state -inherited from main (8380/0 green per `82f22446` commit message). +`/home/user/prologos`, branch `claude/charming-archimedes-98yb48` (the +autonomy state; push = persistence). Setup recipe: install Racket 9.0 +(`curl -sL -o /tmp/r.sh https://download.racket-lang.org/installers/9.0/racket-9.0-x86_64-linux-cs.sh +&& sudo sh /tmp/r.sh --unix-style --dest /usr/local --create-dir`), then +`raco pkg install --auto --skip-installed rackcheck`, `raco pkg install +--link --auto racket/prologos`, `raco make -j 4 driver.rkt` (in +racket/prologos). Full suite ~400s/4 cores; bench A/A noise floor 15.3% +(ledger iter 49); no Workflow runtime (use parallel Explore agents); no +PushNotification tool (final summaries are the doorbell); no GUI browser +(node headless checks + owner acceptance). + +## Gate status at close + +Suite 8666/439 ALL PASS (iter 49, post-production-edit; no production edits +since). Targeted: test-viz-export 7/7, test-propagator-bsp 21/21. Acceptance +file Level-3 clean. Corpus envelopes validate (monotone, captures==commands, +solver epochs present). check.js ALL PASS. diff --git a/docs/tracking/preduce-autonomy/LEDGER.md b/docs/tracking/preduce-autonomy/LEDGER.md index 8b0670025..368410dab 100644 --- a/docs/tracking/preduce-autonomy/LEDGER.md +++ b/docs/tracking/preduce-autonomy/LEDGER.md @@ -1174,3 +1174,545 @@ Entry template: honesty-about-the-curve §5.8 was written to produce. - Track 3 PIR + Master row CLOSED + Phase 2 DESCOPED (named). Doorbell. - **Landed in**: (this commit) + +## 2026-06-12 — LOOP RE-ARM, iteration 44 — [SIGNIFICANT] New arc opened per owner direction: PTF Track 2 (standalone browser viz + arbitrary-program trace export) +- **The owner's /loop re-arm message IS the sequencing ruling** the §8 halt was + waiting on: "Our final goal is generate a browser visualization that can show + the propagator network and play execution in order to show how the system works + for arbitrary prologos programs." This supersedes the retro's candidate queue + (a)-(e) (SH/Zig case, registries-as-cells, D5, prn residue, Phase-2 riders) — + those remain queued, not cancelled. +- **Grounding audit run per charter §5.1** (5 HEAD-pinned facets + adversarial + completeness critic; the Workflow runtime is absent in this environment, so the + same structure ran as parallel Explore agents — disciplines preserved: SHA + verification, file:line citations, verified-vs-inferred, capture-gaps). +- **Headline finding**: this is NOT greenfield. PTF Track 1 already built the + capture layer (current-bsp-observer + bsp-round/cell-diff/prop-trace + + trace-serialize JSON WITH propagator input/output edges + observatory) and a + complete VS Code webview viewer (propagatorView.ts, BSP-round replay, scrubber). + The REAL gaps: no headless CLI exporter (G1), no standalone browser host (G2), + Tier-1 fast-path observer dropout (G3), solver/per-module capture seams (G4), + compound-cell diff granularity (G5). Design doc: + docs/tracking/2026-06-12_PTF_TRACK2_BROWSER_VIZ_DESIGN.md. +- **Naming [ROUTINE]**: lands as PTF Track 2 (extends PTF Track 1 observatory), + not a new series — taxonomy consistency per workflow.md Series-prefix rule. +- **Landed in**: (this commit) + +## 2026-06-12 — LOOP iteration 44 — [⚠ OWNER-PROVISIONAL] Environment migration: the loop now runs in a remote ephemeral container; persistence = push to the session branch +- **Decision**: commits push to `claude/charming-archimedes-98yb48` on origin + (the branch carries the full preduce-autonomy state; verified + `preduce-autonomy` is an ancestor of its HEAD with zero divergence). The + charter §2 "no pushes to remotes" line is operationally amended for this + environment: the container is reclaimed on idle, so unpushed work is LOST — + no-push here equals destroying the ledger the owner reviews by. +- **Intent preserved**: still no commits to main, no production-default flips, + no PRs, owner merges. The push target is an isolated branch. +- **Reversal path**: delete the remote session branch; nothing on main moves. + Blast radius: one branch. +- **Landed in**: (this commit) + +## 2026-06-12 — LOOP iteration 45 — [ROUTINE] PTF Track 2 Phase 0.5 CLOSED — environment shakeout + empirical capture probe; the design opens with DATA +- **Environment**: apt Racket 8.10 hit a REAL wall (`thread #:pool 'own`, + propagator.rkt:3748 — parallel BSP needs 9.0); official 9.0 installed; + toolchain green in-container (raco make clean; targeted runner + batch + workers live: test-trace-serialize 19/19). check-parens.sh gained a PATH + fallback (owner's pinned Mac path preserved). +- **Probe** (`tools/viz-capture-probe.rkt`, committed): mirrors the LSP arming + recipe but captures the elab-network via `current-network-capture-box` + (surviving process-file's parameterize unwind — the LSP's own post-unwind + read at server.rkt:553 is flagged as structurally dead, finding F7). +- **Findings (design doc §6)**: F1 real propagator edges EXIST at HEAD (9/9 + with inputs+outputs — the March "0 edges" note is empirically stale); F2 + headless capture works end-to-end (17 rounds / 35 diffs / 152 fires / 4 + per-command observatory captures / 31.5KB JSON); F3 Tier-1 dropout didn't + blank the demo (share unquantified — Phase 1 carries a counter); F4 + subsystem categorization DEGRADED (all cells 'infrastructure'; cell-info + hollowed by the universe migration; cell-domains champ is the candidate + replacement); F5 magnitudes small per-command. +- **Gate status**: parens ✅; targeted smoke ✅; probe run ✅; full suite NOT + run this iteration (docs+tools-only changes; first in-container full-suite + baseline is owed before the first PRODUCTION edit — likely Phase 1/2 boundary). +- **Landed in**: (this commit) + +## 2026-06-12 — LOOP iteration 46 — [SIGNIFICANT] PTF Track 2 Stage-3 design LOCKED (amended) — and the critique apparatus EARNED ITS COST on unfamiliar surfaces +- **Process**: PROPOSED D1–D6 drafted from grounding+probe data (commit + 6dd6245), then TWO independent adversarial critics (fresh contexts, + file-grounded; one refutation-mandated, one P/R/M/S+red-flags) — the first + Phase-B-style design round on surfaces the loop did NOT author, and the + panels found what in-context drafting missed: 2 BLOCKERs + 8 MAJORs. +- **The big overturn**: "tools-only Phase 2" was REFUTED — Tier-1 fast-path + runs skip the observer, so simple fire-once programs (the canonical + relational demos) trace EMPTY; "arbitrary programs" was false as drafted. + The Tier-1 observer call is PROMOTED to Phase 2b (zero-cost unarmed, + scheduler-orthogonal, gated by 2a full-suite baseline + bench A/B). +- **Dual-path killed at lock**: D2's "fold-in decision point pre-registered" + was adjudicated a validated≠deployed dangle (critique B5) — PATH B decided: + identity is exporter-local as the END-STATE; trace-serialize.rkt frozen; + VS Code panel out-of-scope. +- **Measurement as design instrument (§5.8) fired in-round**: critique B2 + ("is cell-domains F4-hollow one level down?") was answered with DATA before + lock — probe extended + rerun: 55% coverage, 7 domains → the pre-registered + <70% rename clause fired; D4's claim downgraded to "best-available identity + with measured coverage". +- **Methodology debt caught**: no Phase-0 acceptance file (workflow.md + mandate) — Phase 0A added; iteration 47 writes + examples/2026-06-12-ptf-track2-viz.prologos + corpus BEFORE implementation. +- **Push-backs recorded** (the loop challenged the critics too): A2's + clock-coarseness mechanics rejected (sub-ms float clocks; wrapper records + inside the loop), its core accepted as unvalidated-until-measured with + acceptance criteria + pre-registered fallback. New D7 (semantic value + detail) from A3. Full adjudication: design doc §7.7; VAG §7.8 (two + inherited patterns challenged AND overturned). +- **Landed in**: (this commit) + +## 2026-06-12 — LOOP iteration 47 — [ROUTINE] PTF Track 2 Phase 0A CLOSED — acceptance file Level-3 clean; corpus audited; the A4 free-path question answered with data +- **Acceptance file** (`examples/2026-06-12-ptf-track2-viz.prologos`): 17 + commands, 0 errors — elaboration (spec/defn, multi-arity ι, Nat patterns), + polymorphic map pipelines, relations (facts + recursive ancestor + 4 solve + shapes incl. empty-result), NAF (mirrors the WFLE-attested form). First run + had 2 inference errors — the Level-3 gate doing its job: the + `map [int+ 1 _]`/`[int* _ 2]` partial forms FROM THE SYNTAX DOC fail + "Could not infer type" at HEAD (generic `plus` partials work). Fixed with + working forms + NOTE; **doc-vs-implementation drift flagged for the owner**. +- **Corpus audit (B9)**: 3 files, max 87 cells/17 propagators, JSON ≤ 41KB — + far under the 1k D3 revisit trigger; component-aware BFS layout stands. +- **A4 free-path: PARTIALLY VALIDATED, positive** — round snapshots carry + SOLVER topology (87c/17p ≠ 41c/0p elab); solver counters fire (73 unifies, + 17 backtracks, 3 ATMS hypotheses). Caveat recorded: BSP-observable share of + solve execution unquantified (relational-demo: 10 queries → 18 observed + fires); 2c validates; 2b hook stays the fallback. +- **Probe upgraded**: per-command error messages printed (the diagnosis tool + for the above). +- **Gates**: parens ✅; Level-3 acceptance ✅ (0 errors); corpus probes ✅. +- **Landed in**: (this commit) + +## 2026-06-12 — LOOP iteration 48 — [ROUTINE] PTF Track 2 Phase 2a CLOSED — the in-container full-suite baseline is GREEN: 8658/439 ALL PASS (401.5s) +- **First-ever full suite in this container**: initial run 8536 green / 9 + FAILURES — ALL NINE adjudicated environment-setup, zero code failures: + (a) 6× `prologos/propagator` collection not found → repo was never + `raco pkg install --link`ed here; (b) 2× missing `rackcheck` package; + (c) 1× stale 8.10-compiled .zo (test-observatory-01). Fixed all three + classes; the 9 files re-verified individually via the targeted runner + (127/127); then ONE forced full-suite regression gate (the anti-rerun + guard correctly fired on the unforced attempt — pkg installs are invisible + to its .rkt-mtime check; --force-rerun is the sanctioned escape). +- **The baseline**: 8658 tests / 439 files / all pass / 401.5s wall (4-core + container; the owner-machine ~130s is not comparable — per-container + rolling-median rules apply from here). Zero flakes this run (the + registry-visibility family did not fire). +- **2b is UNBLOCKED**: the Tier-1 observer production edit may proceed + (mini-audit of propagator.rkt:3437–3471 first; bench-ab PRE baseline + captured before the edit, A/B after — sequential, never concurrent). +- **Landed in**: (this commit) + +## 2026-06-12 — LOOP iteration 49 — [SIGNIFICANT] PTF Track 2 Phase 2b CLOSED — the track's production edit landed green: Tier-1 BSP runs are observer-visible +- **The edit** (`6d25e58`, propagator.rkt Tier-1 branch): when an observer is + armed, the flush fires with PER-FIRE eq?-pruned `champ-diff` (O(changed) + per fire) giving precise cell-diff attribution, and emits ONE bsp-round + mirroring Tier-2's shape. The unarmed fold is byte-identical to the old + fast path — the observer parameter read pre-existed at function entry, so + the unarmed delta is literally one branch on a local. +- **Gates**: parens ✅; raco make ✅; targeted (trace-serialize, trace-data, + observatory-01/02, propagator-bsp) 81/81 ✅; 3 NEW unit tests pin + armed/attribution/unarmed ✅; acceptance probe unchanged-green ✅; + FULL SUITE **8666/439 ALL PASS, 400.6s** (baseline 8658/401.5s — +8 = the + new checks; wall in noise) ✅. +- **Perf adjudication (honest, in lieu of the locked "bench A/B")**: + bench-ab's documented `--ref HEAD~1` mode is NOT IMPLEMENTED (header + drift; line 170 "same code for now") — second doc-vs-implementation drift + this arc. Evidence for the zero-cost-unarmed claim: (i) code structure + (one branch); (ii) suite wall within noise; (iii) a clean A/A distribution + run captured (/tmp/bench-2b-post.json → standing reference) which ALSO + quantified the container's noise floor: same-code A/A registers up to + "15.3% significant speedup" (solve-adversarial, warm-up effects) — ALL + future in-container perf claims must clear that bar, not just p<0.05. +- **Falsified workload premise (codify-pattern #1 fires again)**: critique + A1 asserted simple relational programs take Tier-1 — NONE of the 3 corpus + files do (probe delta = 0). The fix is still right (fire-once workloads + exist — unit-tested; the gap was real at the scheduler level), but the + pedagogical urgency was overstated. Recorded as the 4th falsified-premise + data point. +- **Process self-catch**: the bench PRE baseline was launched CONCURRENTLY + with the edit's compile — contaminated, killed, replaced by the post-hoc + clean run + structural adjudication. Lesson: bench runs get an exclusive + machine, ALWAYS — even at iteration starts. +- **Landed in**: code `6d25e58`; docs (this commit) + +## 2026-06-12 — LOOP iteration 50 — [ROUTINE] PTF Track 2 Phase 2c + T CLOSED — the exporter exists; every locked validation criterion PASSED; the solver free path is REAL +- **`tools/viz-export.rkt`** (`b0bbb88`): vizTrace/1 envelope per the locked + D1/D2/D4/D7 — per-command captures with topology; epoch-bucketed rounds + (timestamped by the observer wrapper); per-epoch LAST-SNAPSHOT topologies; + per-topology identity (impl note: cell-id spaces are PER-NETWORK, so + identity cannot be a global map — recorded as a D1 implementation + amendment); bounded D7 value detail; always-computed validation block. +- **The 2c criteria, all PASS on the 3-file corpus**: timestamps monotone ✓; + captures==commands (17/17, 16/16, 4/4) ✓; **solver free path VALIDATED** — + relation/solve epochs expose the solver machinery (67–81 cells / + 172–179 propagators vs the 41c/0p final elab net; relational-demo's store + visibly GROWS 57c/2p → 87c/17p across the tutorial). The pre-registered + 2b fallbacks (observer-site timestamps; solve-boundary hook) are NOT + needed — they stay dormant, documented. +- **Golden test** `tests/test-viz-export.rkt` 7/7 (Phase T's deliverable, + landed with 2c per the locked tracker note). Two fixture syntax stumbles + on the way (inline defn body = WS layout violation; `spec NAME : T` colon + form parses as a DEPENDENT signature on single-clause defns — 3rd + doc-vs-implementation drift flag this arc) — both gate-caught. +- No production edits this iteration (tools+tests only) → full suite not + required; targeted gate green. +- **Landed in**: code `b0bbb88`; docs (this commit) + +## 2026-06-12 — LOOP iteration 51 — [ROUTINE] PTF Track 2 Phase 3 SHIPPED — the owner's goal artifact exists and was DELIVERED +- **`tools/viz/index.html`** (`10b0f4a`): single-file, dependency-free + browser viewer per locked D3 — component-aware layered Canvas layout + (union-find components, BFS layers, grid arrangement, self-loop arcs), + zoom/pan/hover with valueDetail + srcloc tooltips, D4 identity coloring + with the MEASURED coverage line always displayed, scope selector + (final / per-command captures / per-epoch solver snapshots), and + playback: epoch selector + round scrubber + play — fired propagators + ring red, diffed cells flash gold, the diff list shows old → new with + source-propagator attribution. +- **Verification in a browserless container** (named limitation): the pure + core (scopes/graph/layout/bucketing) is in a delimited @PURE block; + `tools/viz/check.js` extracts and runs it under node against the REAL + corpus envelopes — ALL CHECKS PASS (32/33/8 scopes; max 260 nodes; all + edges resolve; all nodes positioned, zero coordinate overlaps; round + bucketing complete 45/45, 17/17, 17/17). The owner's browser is the real + acceptance — index.html + two corpus envelopes DELIVERED to the owner + (proactive file send). +- **Landed in**: code `10b0f4a`; docs (this commit) + +## 2026-06-12 — LOOP iteration 52 — [SIGNIFICANT] PTF TRACK V (the viz arc) CLOSED — and the loop PAUSES awaiting owner review (§8-adjacent adjudication) +- **Track close**: 9/9 phases ✅; riders adjudicated (2 dissolved, 1 dormant, + 2 → DEFERRED.md); PIR written skeleton-first from the 16 questions incl. + the charter §9 autonomy retro (`2026-06-12_PTF_TRACK2_PIR.md`); Master PTF + section gains the disambiguated **Track V** row (naming collision with the + planned "Track 2: Pipeline Detection" found at close — the arc's docs keep + their internal name, the Master row disambiguates). +- **The owner's stated final goal is DELIVERED pending browser acceptance**: + exporter + viewer + corpus envelopes sent to the owner (iteration 51). + Suite green (8666/439); every phase gated; one production edit total. +- **⚠ PAUSE ADJUDICATION (the §8 lens)**: none of the hard stop conditions + fired — but the re-arm direction's goal is satisfied pending the owner's + acceptance run, and the next arcs (retro queue (a)-(e), the deferred + riders, the doc-drift fixes) are OWNER-SEQUENCING calls — exactly the + situation in which the original loop halted. DECISION: stop re-arming; + leave the kickoff one-liner standing (charter §7); the owner re-arms with + a sequencing ruling, exactly as this arc began. Reversal path: trivial + (run the one-liner). +- **Landed in**: (this commit) + +## 2026-06-14 — [SIGNIFICANT, owner directive] On-network reduction is the BRANCH DEFAULT; the viz targets propagator-based reduction +- **Owner direction (verbatim intent)**: this is a prototype branch for showing + how the viz works for a FUTURE propagator-native Prologos, built on the + PReduce on-network-reduction prototype. On this branch we ALWAYS use the + on-network (PReduce) reduction; the visualization is based on / shows that + propagator-based reduction. "Write that down." +- **Done**: `viz-export.rkt` flips `#:reduce?` default to #t (on-network + reduction ON by default; `--no-reduce` opts out). Directive written into + `docs/tracking/2026-06-12_PTF_TRACK2_BROWSER_VIZ_DESIGN.md` § BRANCH DIRECTIVE. +- **Honesty recorded in the directive** (the precise reach of "propagator-based + reduction" today): reduction STEPS (redex⇒result) ARE on-network as union + propagators = DPO rewriting (PRN §2 confirmed); NOT-yet-on-network = the RHS + value compute (`instantiate-template`) + the recursion driver + (`reduction.rkt`). Track 8 (e-graph as the engine) is the anticipated future. +- Golden test green; exporter compiles. +- **Landed in**: (this commit) + +## 2026-06-14 — LOOP (PReduce Track 8 plan) Phases 0-1 — [SIGNIFICANT, owner directive] on-network reduction is the ONLY path; native deleted +- **Owner directive**: "flip it and delete the native version (prototype branch + so it's safe)" + "any tests hardcoded to this part should be ported or thrown." +- **Phase 0**: acceptance file examples/2026-06-14-onnetwork-reduction.prologos + (0-error, exercises β/ι/δ/int-folds; 155 rounds/71 topologies on-network). + Baseline: full suite with PREDUCE_INGEST=1 = 8669 ALL PASS → flipping the + default breaks nothing (de-risked the deletion). Ingest hooks scope-locked. +- **Phase 1 (upgraded to native-deletion)**: removed the + current-preduce-ingest?/-int-folds? parameter gate; de-gated the β/ι(nat-val, + suc)/δ ingest arms (now unconditional); DELETED the duplicate native arms for + β, ι, int+/-/*. Non-duplicate native rules (div/mod/cmp, fst/snd/boolrec/J, + constructor-fvar canonical) kept — not off-network duplicates. SAFE because + preduce-ingest-{delta,int} degrade to the exact native step (#:compute/op-fn) + when the e-graph infra is absent (verified: preduce-ingest-delta cond falls to + (compute)). Removed PREDUCE_INGEST env (driver) + #:reduce?/--reduce/--no-reduce + (viz-export). Ported test-preduce-ingest.rkt (param wrappers stripped, e-graph + assertions kept — they now test the only path); deleted obsolete on/off micro- + benchmark. GATE: full suite 8671 ALL PASS (406.7s). Absorbs part of Phase 5 + (native compute arms gone; whnf still drives — full whnf-bypass is later). +- Commits: 1741476 (bench deletion) + the source-edit follow-up + this tracker. +- NEXT: Phase 2 (rule application as OBSERVABLE propagator firing) — research- + grade per the plan; grounding-audit + likely owner checkpoint. + +## 2026-06-14 — LOOP (PReduce Track 8 plan) Phase 2 — [SIGNIFICANT] arithmetic dispatch is now an on-network STRATUM FIRING; grounding narrowed scope + owner chose P2→P4 +- **Grounding finding (before cutting code)**: the substrate has TWO unlike + reduction paths. int-fold (`preduce-ingest-int`) = `dispatch-rules` → + `apply-rule` → `eclass-intern` RHS + `eclass-union` (the union IS a + propagator; only DISPATCH was imperative). β/δ/ι (`preduce-ingest-delta`, + reduction.rkt:1350-1408) = memoized native `(compute)` + a DIRECT + `net-cell-write` of the result as `:best` (reduction.rkt:1402) — NO rule, NO + union, NO propagator. So "rule application IS propagator firing" applies + cleanly ONLY to the arithmetic path; the recursion path (the fib/factorial + case) has no rule application to convert — it is the deeper Phase 4. +- **Surfaced to owner** (charter §8 — design didn't lock as assumed). Owner chose + **"Pursue the goal (P2→P4)"**: land Phase 2 (arithmetic) as the foundation, + then design Phase 4 properly (NTT + critique) so the viz shows fib reducing via + propagators. +- **Phase 2 done (arithmetic path)**: dispatch moved from the imperative + `dispatch-rules` call to an on-network topology STRATUM firing, mirroring the + congruence engine exactly: + - `propagator.rkt`: reserve cell-22 `dispatch-request-cell-id` (hash-overwrite + merge); preallocate in `make-prop-network` (net12) with drift check; provide. + - `rule-dispatch.rkt`: `process-dispatch-requests` topology-tier handler — + CLEARS the request cell at entry (apply-rule drives its own nested + `run-to-quiescence`; the stratification.md fork-clear idiom prevents + re-dispatch), reads registry+hashcons cell-ids from their parameters, runs + `dispatch-rules` per pending class. `register-stratum-handler!`. + - `reduction.rkt`: `preduce-ingest-int` installs a fire-once S0 EMITTER on the + redex class that writes the dispatch-request cell, then `run-to-quiescence`; + reads `:best` after quiescence (same contract). The emitter must (a) exist as + a propagator fire — a direct request-write would not trigger strata since the + inner/drain schedulers only run strata when the worklist is non-empty; and + (b) WATCH the redex class (non-empty inputs) so it is NOT Tier-1-fast-path + eligible (Tier-1 skips strata). + - Emission from the caller (not `eclass-intern`'s new-class branch, plan §7 + step 2b) keeps scope strictly arithmetic — `eclass-intern` is generic. +- **Network Reality Check**: ✅ stratum + emitter propagator added; result via the + union propagator's `net-cell-write`; trace intern→emitter→request-cell→ + stratum→dispatch-rules→apply-rule→eclass-union→`:best`→caller read. +- **Phase 3 (plan) DROPPED** as off-roadmap (owner-approved): the e-graph + evaluates primitives functionally inside a rule — a standalone compute + propagator is the direct-compute substrate we chose NOT to build. +- **Gate**: test-preduce-ingest.rkt 29 tests (+3 2T proving the stratum handler + does the rewrite); acceptance file 0 errors; **full suite 8674 ALL PASS + (548.6s)**. 3 cell-count test failures (cell-22 shifts the well-known count + 22→23) fixed in test-propagator / test-trace-serialize / test-observatory-01. +- Commits: `656a294` (core) · `edeff08` (2T) · `ac65fad` (cell-count bumps) · + `3f13ac9` (grounding finding doc) + this tracker/ledger update. +- NEXT: Phase 4 design — recursion β/δ/ι on-network (research-grade; full Stage-3 + arc). This is the owner's actual goal (viz shows fib reducing via propagators). + +## 2026-06-14 — LOOP (PReduce Track 8 plan) Phase 4a — [SIGNIFICANT, owner delegated design] recursion step is now a UNION propagator; viz shows recursion as propagators +- **Owner posture (verbatim)**: "for this prototype branch, you lead and approve + design. don't ask me for details. I will review after the implementation is + complete." → I make the Phase 4 design decisions; methodology gates still apply. + (Phase 5 — bypass reduction.rkt — kept as the documented irreversible hard-stop.) +- **Phase 4a done** (design doc `2026-06-14_PREDUCE_T8_PHASE4_RECURSION_ON_NETWORK.md`): + `preduce-ingest-delta`'s miss path records the β/δ/ι reduction step as a UNION + propagator (intern result + `eclass-union`, the DPO {redex,result} e-class — the + SAME mechanism as the arithmetic fold), replacing the bare `net-cell-write`. + Net-threading fix (load-bearing): publish the redex class (net1) BEFORE compute + and read the POST-compute net, so the recursion SUBTREE persists (pre-4a + discarded compute's net, keeping only the top redex→result and defeating + hashcons sharing). 4a-T: +4 tests (result interned as its own class; redex+result + share a canonical = the union). Suite **8678 all-pass** (540.9s, no suite + regression — the heavy reduction perf tests are skip-listed). +- **VIZ GOAL MET (tractable sizes)**: fib 6 export = 172 rounds, 73 topologies, + 0 errors, **up to 18 propagators firing per round (75 rounds with >1)** — the + union (eclass-refine relate) propagators ARE the recursion steps. Pre-4a the viz + showed "only one propagator [fib 15]"; 4a makes the recursion a web of + propagators. Commit `2bf9b5e`. +- **PERF WALL (honest finding, the PReduce verdict in action)**: reduce_ms fib + 6/8/10/12 = 449/1344/4552/24052; fib 15 >120s. Super-linear because naive fib + does NOT memo-collapse: β reduces call-by-name, so `[fib 5]` redexes carry + differently-unreduced args (`(int- 6 1)` vs `(int- 7 2)`) → different digests → + no hashcons hit → exponential recompute, now × per-step union+quiescence cost. + This ANSWERS `fib-naive.prologos`'s own question (it recomputes, doesn't share). + The viz observer's O(cells)/round diffing makes export quadratic → fib ≤ ~6-7 is + the practical viz size on Racket. +- **4b/4c = documented research frontier (NOT built)**: network-DRIVES-recursion + (ι cascade; β/δ as declarative rules — β needs list-form de Bruijn subst) ADDS + per-step network work → worsens the wall. Path to memo-collapse = call-by-value + memo key (reduction-strategy change) + incremental observer; wall-clock payoff + routes to SH/Zig. These are SH/Zig-era / dedicated research, not landable wins on + this branch now. +- Commits: `2bf9b5e` (4a core + 4a-T + design doc) + this tracker/ledger update. +- NEXT: present Phase 0–4a for owner review (per posture). Phase 4b/4c + 5 await + owner direction (perf-frontier / SH-Zig + owner-gated terminal). + +## 2026-06-14 — LOOP (PReduce Track 8) Phase 4a perf — [SIGNIFICANT, owner-requested] perf wall LIFTED: cbv memo key + incremental observer +- **Owner request (verbatim)**: "do The single highest-value follow-up is making + naive fib memo-collapse so large traces work: a call-by-value memo key + (normalize the β arg before the redex digest, keeping reduction itself + call-by-name) + an incremental viz observer (drop the per-round O(cells) diff)." +- **Part 1 — call-by-value memo key (`02da3bd`, reduction.rkt β arm)**: key the β + redex by the NORMALIZED arg (whnf'd in a PRIVATE bounded fuel box, guarded by + with-handlers), so `[fib 5]` reached via `(int- 6 1)` vs `(int- 7 2)` shares ONE + e-class. Reduction stays CALL-BY-NAME — `#:compute` substitutes the ORIGINAL + arg; only the memo KEY is normalized. Sound (arg-nf = arg in value ⇒ identical + contractum; whnf deterministic). Divergent/unused arg falls back to the original + key (no collapse, no regression, bounded fuel never depletes the real budget). + **Naive fib exponential → LINEAR**: reduce_steps fib 8/12/15/20 = + 202/287/350/455 (was 741/exp/>120s); fib 15 reduce 274ms; fib 10 = 55 verified; + acceptance reduce_ms 2117→194. Answers fib-naive's own question (WITH the cbv key + the hashcons SHARES `[fib k]` subterms = collapses the tree). +- **Part 2 — incremental viz observer (`031ff2f`, viz-export.rkt)**: + `intern-topology!` did a full `serialize-network-topology` (incl. per-cell + `serialize-lattice-value`) EVERY round just for the dedup signature. Now the + signature is computed cheaply from the pnet (cell-ids + propagator connections), + full serialize ONLY on a NEW topology. Identical sig format ⇒ identical dedup. + fib 15 export 16.3s → 11.1s (~32%). Also added cell-22 (dispatch-request) to the + well-known-cells identity table (missed in Phase 2). +- **Net**: fib 15 (owner's example) >120s-untraceable → 0.27s reduce + 11s export, + 234 rounds, 0 errors, recursion shown as union propagators. **Full suite 8673 + ALL PASS — and FASTER (372s vs 540s)**: the suite's reduction tests collapse too. + 0 failures. +- **Still frontier**: 4b/4c (network DRIVES recursion). The cbv key collapses + + speeds the RECORDING path; the DRIVER (whnf) is still off-network. Phase 5 + owner-gated. +- Commits: `02da3bd` (cbv memo key) · `031ff2f` (incremental observer) + this + tracker/ledger/design-doc update. +- NEXT: send owner the now-working fib 15 trace; await direction on 4b/4c + 5. + +## 2026-06-14 — LOOP (PReduce Track 8) Phases 4b/4c/5 — [SIGNIFICANT, owner: through phase 5] design + 5a (whnf-step1 + parity), 5b/5c designed+de-risked +- **Owner**: "/loop continue design and implementation through phase 5." (Phase 5 + owner-gate lifted by this explicit directive; I lead/approve design.) +- **Design (`12f97a7`)**: `2026-06-14_PREDUCE_T8_PHASE4b5_NETWORK_DRIVEN_REDUCTION.md` + — network-DRIVEN reduction via a demand cascade + extraction bypass. Grounded the + reality: `whnf-impl/match` is ~1700 lines over dozens of constructs with subtle + strictness → a COMPLETE bypass (parity across all) is multi-session; staged + migration with a shrinking `'native` fallback, PARITY-gated per construct. + Form-rep decision: expr-structs + native one-step compute (sidesteps the + list-form template's de-Bruijn-subst gap — the 4c blocker). +- **5a (`a72b3f2`)**: `whnf-step1` one-step classifier ('whnf | 'native | + (step C) | (demand SUB RECON)) mirroring whnf-impl/match for the migrated + fragment (β, ι natrec, suc-collapse, fst/snd, J, boolrec, ann, vhead/vtail + + subterm-demand); `whnf-via-egraph` driver (iterate step1; demand via nested; + neutral-stuck via no-progress). PARITY harness `tests/test-preduce-egraph.rkt` + (27 checks): whnf-via-egraph == native whnf across a corpus + migrated arms + confirmed step/demand (not silent native). Pure addition — default whnf + UNTOUCHED (opt-in); 'native fallback makes non-migrated constructs + parity-trivial. **Full suite 8705 all-pass (441 files, 330s).** +- **5a's driver is a LOOP, not the scheduler** — it validates the one-step + decomposition + gives the intern→reduce→extract shape. The genuine network-DRIVE + (scheduler-driven reduce-stratum cascade) is 5b. +- **5b/5c DESIGNED + de-risked (§9, not yet built)**: origin-keyed reduce-request + (cell-23, keep-pending) → extraction is a simple K `:best` write (no cost-race); + the `step` arm's eclass-union supplies the worklist activity that re-triggers the + stratum each round (the load-bearing cascade mechanism); emitter (Phase-2 pattern) + kicks the first pass; nested demand re-entrant (strata run fire-round #f). + Risk register (6 points) recorded. NOT BUILT because the machinery has several + interdependent correctness points (cascade re-trigger, extraction, parity) whose + clean landing is a focused effort — methodology forbids half-built hot-path + machinery; 5a (validated primitive, gated) is the honest landed increment per the + design's §8 scope-creep guard. +- Commits: `12f97a7` (design) · `a72b3f2` (5a) + this tracker/ledger update. +- NEXT: implement 5b per §9 (origin-keyed cascade + emitter + extraction), parity + harness NETWORK variant; then 5c (route whnf through it for the covered fragment). + Terminal (delete native arms) owner-gated. + +## 2026-06-14 — LOOP (PReduce Track 8) Phase 5b — [SIGNIFICANT] scheduler-driven reduction cascade — genuine network-DRIVE, parity-gated +- Built `whnf-via-egraph-network` (`c49c53c`): the reduction DRIVER is now the BSP + scheduler (keep-pending reduce stratum on cell-23), not a Racket loop. Per design + §9: origin-keyed reduce-request {K → current-form}; 'whnf/'native write K's cost-0 + :best (extraction); 'step interns + unions K (the union supplies the worklist + activity that RE-TRIGGERS the stratum each round — the load-bearing cascade + mechanism) + re-requests {K → C}; 'demand reduces the strict subterm natively then + continues. Emitter kicks the first pass (dodges Tier-1). No-plumbing/inadmissible + → native whnf (total). +- **Network Reality Check PASSES**: scheduler-driven stratum + emitter + per-step + union; result via net-cell-write (K :best); trace intern→emitter→reduce-request + →stratum→cascade→scheduler-re-fire→extract. This is the genuine network-DRIVE for + the migrated head fragment (β/ι/suc/fst/snd/J/boolrec/ann/vhead/vtail). +- **PARITY gate**: test-preduce-egraph.rkt network variant (+20 → 47 checks): + whnf-via-egraph-network == native whnf across the corpus. cell-23 cell-count + bumps (test-propagator/trace-serialize/observatory) + viz-export identity table. + **Full suite 8725 all-pass (441 files, 343s).** +- **Honest scope**: this is the network-driven ENGINE (the Phase 5 substrate), + opt-in + validated; NOT yet the default whnf. 5c (route the default through it = + bypass deploy) is staged — low-value + hot-path-risky until more constructs + migrate off 'native (most reduction is still 'native). Demand subterms native in + 5b (head chain scheduler-driven; full cascade-driven demand via set-latch = + future). Terminal (delete native arms) owner-gated. +- Commits: `c49c53c` (5b) + this tracker/ledger/design-doc update. +- NEXT (staged): migrate more constructs into whnf-step1 (arith/reduce/δ via + demand+compute) so 5c routing is meaningful; then 5c deploy (parity-gated); 5d + set-latch demand; terminal owner-gated. + +## 2026-06-15 — LOOP (PReduce Track 8) Phase 5c — [SIGNIFICANT, owner directive] the cascade is the DEFAULT reduction driver; off-network DRIVER deleted (compute leaf kept) +- **Owner**: "/loop implement through phase 5 including deletion of off-network + reduction" → calibrated to "Delete the recursive off-network DRIVER, keep the + compute leaf" (AskUserQuestion 2026-06-15). +- **Routing hook + deploy** (`12d4131`, `568ef07`, `c98d3a9`): `current-egraph-whnf?` + default flipped ON — `whnf` routes through the scheduler-driven cascade + (whnf-via-egraph-network) by default. The BSP scheduler now DRIVES ground + reduction; the recursive native reducer is no longer the default path. De-routing + helper `whnf-native` (parameterize#f) prevents the native fallback re-entering the + cascade. PREDUCE_NATIVE=1 forces native. +- **batch 1 (int arith)** + **batch 2 (reduce/match, δ, meta)** migrated to + whnf-step1 (the driver classifier); the rest (rat/posit/quire/generic arith, + maps/pvec/set, FFI) ride 'native → the COMPUTE LEAF (native compute, "compute + inside the rule"). +- **GATE: full suite with the cascade as DEFAULT = 8750 ALL PASS (386.9s, ~15% + over native 335s)** — every reduction value correct. The one routed-suite failure + (test-preduce-ingest, 4a-recording-structure assertions) pinned PREDUCE_NATIVE + (it tests the path routing replaces). +- **Two fundamental LIMITS found (honest, architectural — not gaps)**: + 1. **Non-ground reduction can't be on-network**: the e-graph requires + PCE-ADMISSIBLE (ground) terms; metavar/elaboration reduction can't be interned + → de-routes to native. So the native reducer is RETAINED as the non-ground + fallback (can't be physically deleted). + 2. **Full deletion of whnf-impl is a reimplementation, not a mechanical grind**: + ~150 primitive folds (mechanical) + higher-order ops (fold/map/filter RECURSE + via f-application — non-mechanical) + runtime FFI. So "delete off-network + reduction" = delete the DRIVER (done: cascade is the driver), keep the COMPUTE + LEAF (the e-graph's own compute-inside-the-rule design). +- Net: the off-network reduction DRIVER is deleted (as the default path); the + native reducer is demoted to compute-leaf + non-ground-fallback. On-network + reduction (the cascade) is the production default driver. +- Commits: `12d4131` (hook + batch 2) · `568ef07` (test pin) · `c98d3a9` (default + flip) + this ledger update. (`84137e5` batch 1 earlier.) +- NEXT (optional refinements): route 'native compute-leaf operands back through the + cascade (more on-network for ground compute); migrate more primitive folds into + whnf-step1 to shrink the leaf; the higher-order/FFI tail + perf remain SH/Zig-era. + +## 2026-06-15 — LOOP (PReduce Track 8) Phase 5c refinement — [ROUTINE, owner: "do #1"] route the 'native compute-leaf operands on-network; #2 declined (tension) +- **#1 DONE** (`0c51010`): when whnf-step1 hits 'native (a primitive compute + construct not yet migrated — rat/posit/generic arith, data ops, FFI), the cascade + reduces it via whnf-impl directly (ambient routing ON) instead of whnf-native, so + its OPERAND sub-reductions route BACK through the cascade (on-network); only the + primitive fold is native. Full suite 8750 all-pass, 294.6s (FASTER than the prior + default-routed 386.9s; ~88% of native 335s). Owner confirmed "do #1". +- **#2 DECLINED (not a clean win — tension found)**: migrating more folds into + whnf-step1 routes their operands through the cascade's `demand` path, which is + either NATIVE (regressing #1's operand-routing) or ~7× slower (demand-routed: + every recursive subterm nest-cascades — acceptance 247ms→1699ms measured). So + fold-migration trades operand-on-network for fold-as-cascade-step visibility — no + free lunch; #1 sits at the better point. The 7× full-demand-routing lever (maximal + on-network + viz, ~7× cost) is left to owner discretion; NOT taken. +- **Architecture END-STATE (Phase 5c complete per "delete driver, keep compute + leaf")**: the BSP scheduler DRIVES ground reduction (β/ι/δ/app/reduce/proj + + int-arith via whnf-step1 + the reduce-stratum cascade, default-on); the native + reducer is the COMPUTE LEAF (primitive folds, operands routed on-network) + + the NON-GROUND fallback (metavar/elaboration — e-graph admissibility limit). Suite + green; ~12-15% over native. +- Commit: `0c51010` + this ledger update. + +## 2026-06-15 — LOOP (PReduce Track 8) Phase 5d — [SIGNIFICANT, owner: "fix: reduction design serializes independent subterms, and any other 'all in parallel' work"] parallel demand +- **Decision**: replace single-subterm `'demand` with `'demand-par` for the + INDEPENDENT-strict-operand case (binary arithmetic). Independent operands now + reduce in the SAME BSP rounds (interleaved cascades) instead of being sequenced. + This is the set-latch demand refinement the 5b design doc deferred (§10.5). +- **Options considered**: + - (chosen) **barrier-joined parallel cascade**: `step1-par` emits `'demand-par`; + `pr-demand-par` interns each non-value operand to its own class KOi, queues all + {KOi → Oi} into the same `reduce-request` round (the per-key hash-union merge + accumulates them), and installs ONE barrier propagator (`net-add-barrier`, + set-latch fan-in) that re-forms {K → recon*(resolved)} when all KOi reach + cost-0 :best. Reuses existing first-class primitives; minimal surface. + - (deferred) **e-node + congruence reduction rule**: intern `(int+ KO1 KO2)` as an + e-node and let a congruence-driven reduction rule fire when children are values + — more "structurally emergent" but a larger redesign; not needed for the fix. + - (rejected) full demand-routing for ALL `'demand` arms — the ~7× cost declined in + 5c; single-strict-subterm arms have no independent parallelism to gain anyway. +- **Principle / precedent cited**: design mantra "all in parallel"; set-latch fan-in + (`propagator-design.md`); Network Reality Check (operand reductions are now real + net-add-propagator/net-cell-write cascades, not whnf-native off-network). +- **Evidence**: viz before/after — balanced ×/+ tree 353→**43** BSP rounds (8.2× + depth reduction, 256 propagators/round); fib 6 51→**78** multi-prop rounds. + fib + tree values identical on-network vs native (8, 1462). Parity-gated + (test-preduce-egraph both variants) + 250 targeted tests green; full routed suite + = regression gate (running). +- **Scope (honest)**: binary arithmetic only — the genuine independent-strict case. + Parallelism INSIDE whnf-impl's primitive folds (N-ary data ops) and inside `nf` + (full-NF subterm normalization is a pure recursive Racket fn, not on-network) is + the larger compute-leaf / nf reimplementation, SH/Zig-era; explicitly out of scope. +- **Landed in**: commit `b2c5e31` (reduction.rkt + design doc §10.5) + this ledger + update. diff --git a/docs/tracking/preduce-autonomy/dailies/2026-06-12_dailies.md b/docs/tracking/preduce-autonomy/dailies/2026-06-12_dailies.md new file mode 100644 index 000000000..8feb74c73 --- /dev/null +++ b/docs/tracking/preduce-autonomy/dailies/2026-06-12_dailies.md @@ -0,0 +1,196 @@ +# PReduce-autonomy loop dailies — 2026-06-12 (re-arm: PTF Track 2 arc) + +## Carried forward + +- **The §8 halt state (2026-06-10)**: 43 iterations, Tracks 1/2/4/5/3 closed with + PIRs, series verdict rendered; RETRO.md written; owner queue (a)-(e) recorded. +- **Post-halt addenda (2026-06-10/11, owner-interactive)**: three stacked defects + fixed (dead keys × form codec × prn poisoning); the warm verdict REWRITTEN for + real corpora — ppn-track4c WARM reduce 123ms vs 1172ms OFF (9.5×); suite 8663 + green at `ff739de7`. +- **Owner re-arm (this session)**: new final goal = standalone browser viz of the + propagator network with execution playback for arbitrary programs → opened as + **PTF Track 2**. The retro queue (a)-(e) remains queued behind it. +- **Environment changed**: loop now runs in a remote ephemeral container + (/home/user/prologos, session branch `claude/charming-archimedes-98yb48` == + preduce-autonomy state). Racket NOT yet installed (apt has 8.10; project pins + 9.0). Pushes to the session branch are the persistence mechanism (ledger + OWNER-PROVISIONAL entry). + +## Watching + +- **Racket 8.10-vs-9.0 risk**: unverified whether the codebase compiles/runs on + apt's 8.10. Phase 0.5 shakeout answers it; fallback = upstream installer. +- **Tier-1 observer dropout (G3)**: if most simple elaborations take the Tier-1 + flush, traces for small demo programs may be EMPTY — the single biggest risk to + "arbitrary programs" playback. Needs the empirical probe before design lock. +- **March "0 edges" note**: critic adjudicates it probe-scoped and likely stale + post-PPN-4C (typing-propagators installs real input/output edges); verify with + a real dump at Phase 0.5. + +## Iteration 44 (this session) + +- Re-grounded from files post-re-arm: CHARTER → stale HANDOFF → git history → + RETRO → ledger tail (the HANDOFF on disk predated the halt; reconstructed the + true state from RETRO.md + post-halt commits, then rewrote it). +- Ran the charter §5.1 grounding audit for the new arc: 5 HEAD-pinned facets + (trace-serialize, propagator.rkt scheduler anatomy, driver lifecycle, + pnet-serialize/.pnetx, prior-art sweep) + adversarial completeness critic. + No Workflow runtime in this environment — same structure via parallel Explore + agents, disciplines preserved. +- Headline: PTF Track 1 already provides capture (bsp-round/cell-diff/prop-trace, + topology JSON WITH edges, observatory) + a complete VS Code viewer. Gaps + G1–G6 catalogued in the design doc. .pnetx ruled out as a viz source + (cell-state only, no topology). +- Opened `docs/tracking/2026-06-12_PTF_TRACK2_BROWSER_VIZ_DESIGN.md` (tracker, + synthesis, gaps, Stage-3 questions, R-lens targets). Ledger: SIGNIFICANT + (arc open) + ROUTINE (PTF Track 2 naming) + OWNER-PROVISIONAL (push posture). +- HANDOFF rewritten (it was 33 iterations stale — process note: the late-loop + iterations stopped rewriting it; RETRO.md absorbed the role. Recorded here so + the retro's file-spine claim stays honest: the spine held via RETRO + ledger + + dailies, not via HANDOFF). + +## Iteration 45 (PTF Track 2 Phase 0.5 — shakeout + probe) + +- **The 8.10 wall**: apt Racket compiled the codebase clean but batch workers + died on `thread #:pool 'own` (propagator.rkt:3748) — the 9.0 pin is real + and the watch item resolves NEGATIVE for 8.10. Official 9.0 installed; + everything green after recompile (smoke: test-trace-serialize 19/19 via the + targeted runner — workers live). +- **Probe landed + ran** (`tools/viz-capture-probe.rkt`): findings F1–F7 in + the design doc §6. Headlines: real propagator edges exist at HEAD (March + "0 edges" empirically stale); headless capture works end-to-end (17 rounds / + 35 diffs / 152 fires / 31.5KB JSON on prop-viz-demo); subsystem coloring + source is HOLLOW post-universe-migration (F4 — Phase 1 design input: + cell-domains champ as replacement); LSP's own trace-capture path looks + structurally dead (F7, flagged not fixed). +- **Tooling conformance**: check-parens.sh PATH fallback (Mac pin preserved). +- **Lesson (small)**: probe's first run caught a missing require + (prologos-error? lives in errors.rkt, not driver.rkt) — the + commit-then-validate pressure from the stop-hook is real; the fix commit + followed within minutes. Keep instrument commits AFTER first green run when + possible. +- **Watching (updated)**: Racket 8.10 risk → RESOLVED (9.0 mandatory). + Tier-1 dropout share → still open, Phase 1 counter. NEW: in-container + full-suite baseline owed before first production edit. + +## Iteration 46 (PTF Track 2 Phase 1 — Stage-3 design lock) + +- Drafted D1–D6 PROPOSED (commit 6dd6245), then ran TWO independent + adversarial critics. They were worth it — this is the first design round + this loop ran on surfaces it did NOT author, and the in-context draft had + real holes: Tier-1 dropout made "arbitrary programs" FALSE as drafted + (BLOCKER); no acceptance file (BLOCKER); the D2 "fold-in" dangle; D4's + unmeasured identity claim. +- Lock amendments: Tier-1 observer fix PROMOTED to Phase 2b ("tools-only + Phase 2" overturned — named safety theater); PATH B decided for identity + (exporter-local end-state, panel out-of-scope); D4 downgraded per DATA + (probe extended in-round: 55% domain coverage, 7 domains — not hollow, + below the bar); D7 added (semantic value detail); component-aware layout + day one; --max-rounds; corpus + acceptance file = iteration 47 BEFORE any + implementation. +- Push-backs recorded against critics where they overreached (A2 clock + mechanics) — adjudication, not deference. §7.7 carries the full table; + §7.8 VAG challenged + overturned two inherited patterns. +- Process note for the retro: the RETRO's "panels unused in Phase B — + correct for surfaces the loop authored, but a longer arc on unfamiliar + code would need them" prediction CONFIRMED on its first test. +- **Watching**: 2b's bench A/B on the Tier-1 hot path (regression rules + apply); epoch-bucketing validation criteria (2c); D7 depth creep. + +## Iteration 47 (PTF Track 2 Phase 0A — acceptance file + corpus audit) + +- Acceptance file written from ATTESTED syntax only (prop-viz-demo, the + syntax doc, relational-demo, the WFLE NAF form) — and the Level-3 gate + STILL caught 2 errors: the syntax doc's own idiomatic + `map [int* _ 2] '[...]` partial forms fail inference at HEAD. Replaced + with working `plus` partials + NOTE; **doc-drift flagged for owner**. + Lesson re-confirmed: attested-in-docs ≠ runs-at-HEAD; only the gate knows. +- Corpus: acceptance (45 rounds/91 diffs/380 fires) + prop-viz-demo + + relational-demo (last-epoch snapshot = 87c/17p SOLVER topology — the A4 + free path is real). Scale verdict: everything tiny vs the 1k trigger. +- A4 caveat recorded honestly: 10 solve queries → only 18 observed fires; + the BSP-observable share of solve execution is the 2c validation question. +- Probe now prints per-command error messages (was: counts only). +- **Watching (added)**: relational runtime's DFS-vs-BSP split — if most + solve work is DFS-internal, the 2b solve-boundary hook (or deeper solver + instrumentation) becomes load-bearing for the owner's "show how it works" + goal on relational programs. + +## Iteration 48 (PTF Track 2 Phase 2a — full-suite baseline) + +- First full suite ever in this container: 9 failures, all environment + (missing pkg link ×6, missing rackcheck ×2, stale 8.10 .zo ×1) — fixed, + re-verified individually (127/127), then ONE forced regression gate: + **8658/439 ALL PASS, 401.5s**. Zero real code failures; zero flakes. +- Process notes: the anti-rerun guard fired on the unforced gate attempt + (pkg installs don't touch .rkt mtimes) — --force-rerun was the right + call, recorded. One cwd-reset stumble (shell resets between calls in + this harness; absolute-path discipline re-learned). +- Environment recovery recipe extended (HANDOFF): pkg link + rackcheck are + now part of container re-setup alongside the Racket 9.0 install. +- **2b is unblocked.** Bench-ab PRE baseline still owed (capture in 2b's + iteration, before the edit). + +## Iteration 49 (PTF Track 2 Phase 2b — the Tier-1 observer production edit) + +- The edit landed green first try: armed path with per-fire champ-diff + (precise propagator attribution — better than Tier-2's per-round diffs!), + unarmed path byte-identical. 3 unit tests; suite 8666/439 GREEN. +- **Surprise**: zero probe delta on the corpus — NONE of our 3 files take + Tier-1 (the critique's "simple programs trace empty" premise falsified on + this corpus; 4th falsified-premise data point). The unit tests prove the + path works where Tier-1 IS taken. +- **Tooling drift #2**: bench-ab's `--ref` is documented but unimplemented + (A/A self-comparison only). Perf adjudicated structurally (one branch, + suite wall in noise) + clean A/A distribution captured — which quantified + the container noise floor: same-code "15.3% significant" — calibrate all + future claims against it. +- **Process self-catch**: launched bench PRE concurrent with compile — + contaminated, killed, lesson recorded (bench = exclusive machine, always). +- **Watching**: 2c validates epoch bucketing + solver free-path on the + corpus; the A2/A4 pre-registered fallbacks activate there if needed. + +## Iteration 50 (PTF Track 2 Phase 2c + T — the exporter + golden test) + +- viz-export.rkt landed; ALL locked validation criteria pass on the corpus; + the solver free path is REAL (relation epochs 67-81c/172-179p; the + relational tutorial's store visibly grows 57→87 cells across epochs). + Fallbacks dormant. Golden test 7/7. +- Implementation amendment recorded: identity must be PER-TOPOLOGY (cell-id + spaces are per-network) — the locked D1 sketch had it top-level. +- **Doc-drift #3**: `spec NAME : T` (colon form) parses as a DEPENDENT + signature when paired with single-clause defn — type mismatch with a Pi + over Type. The no-colon form works. (Colon form + multi-clause defn DOES + work — the acceptance factorial.) Flag for owner with the other two. +- Envelope sizes: 197KB–880KB per corpus file (under the 5MB bar; per-epoch + topologies dominate — viewer lazy-loads epochs if it ever matters). +- NEXT: Phase 3 — the viewer (the user-facing deliverable). + +## Iteration 51 (PTF Track 2 Phase 3 — the viewer, SHIPPED) + +- tools/viz/index.html: single-file, no deps, no build. Topology (bipartite + Canvas, component-aware layout, self-loop arcs, zoom/pan/hover) + + playback (epoch select, scrubber, play; fired=red ring, diffed=gold + flash, old→new diff list with attribution) + D4 coloring with the + coverage line. Pure core in a @PURE block; check.js runs it headlessly + under node on the real corpus — ALL CHECKS PASS. +- Artifacts DELIVERED to owner (index.html + 2 corpus envelopes). +- Honest limitation: no GUI browser in this container — the owner's + browser is the real acceptance run. +- NEXT: track close — PIR + §9 autonomy retro + Master row; Phase 4 riders + adjudicated per data (most dissolved: solver hook not needed, F4 + production identity out of scope per PATH B; remaining candidates: + compound-cell component diffs, D7 depth — DEFERRED.md material). + +## Iteration 52 (TRACK CLOSE — and the loop pauses) + +- PIR written (16 questions + autonomy retro); riders adjudicated; Master + PTF gains the disambiguated Track V row (naming collision found at close); + DEFERRED.md carries the 2 surviving riders + the owner flags. +- **The loop PAUSES** (ledgered): the re-arm goal is delivered pending the + owner's browser acceptance; next arcs are owner-sequencing calls. The + kickoff one-liner stands in charter §7 — re-arm exactly as this arc began. +- Arc totals: 9 iterations, ~15 commits, 1 production edit, suite + 8658→8666 green throughout, 0 unresolved failures, 4 owner flags, 2 + deferred riders. The dailies interval closes with this entry. diff --git a/racket/prologos/benchmarks/micro/bench-preduce-ingest.rkt b/racket/prologos/benchmarks/micro/bench-preduce-ingest.rkt deleted file mode 100644 index e80e994d7..000000000 --- a/racket/prologos/benchmarks/micro/bench-preduce-ingest.rkt +++ /dev/null @@ -1,56 +0,0 @@ -#lang racket/base -;; PReduce Track 2 — the ingestion overhead FLOOR (§5.8; ledger iter 23). -;; Measures whnf of int-fold exprs: native (gate OFF) vs e-graph (gate ON), -;; FRESH positions (every fold a new literal pair — worst case: full -;; intern+dispatch+union round-trip) vs MEMO HITS (same expr — best case: -;; hashcons lookup). The floor bounds what δ/β must save per position. -(require racket/set - "../../reduction.rkt" - "../../syntax.rkt" - "../../eclass-graph.rkt" - "../../rule-registry.rkt" - "../../kernel-rules-seed.rkt" - "../../propagator.rkt" - (only-in "../../metavar-store.rkt" current-persistent-registry-net-box)) - -(define-syntax-rule (bench-ns label N-val body) - (let* ([N N-val] - [_ (for ([i (in-range (quotient N 10))]) body)] - [t0 (current-inexact-monotonic-milliseconds)]) - (for ([i (in-range N)]) body) - (let* ([t1 (current-inexact-monotonic-milliseconds)] - [ns-per (/ (* (- t1 t0) 1e6) N)]) - (printf "~a: ~a ns/op (~a ops)\n" label (real->decimal-string ns-per 0) N) - ns-per))) - -;; OFF baseline: the native fold arm -(define exprs (for/vector ([i (in-range 10000)]) - (expr-int-add (expr-int i) (expr-int 2)))) -(define idx (box 0)) -(define (next-expr!) - (define i (unbox idx)) - (set-box! idx (modulo (add1 i) 10000)) - (vector-ref exprs i)) - -(printf "== native (gate OFF) ==\n") -(bench-ns "whnf int-fold, fresh exprs" 10000 (whnf (next-expr!))) -(define same-expr (expr-int-add (expr-int 7) (expr-int 35))) -(bench-ns "whnf int-fold, same expr" 10000 (whnf same-expr)) - -;; ON: plumb the e-graph -(define prn-box (box (make-prop-network))) -(parameterize ([current-rule-registry-cell-id #f] - [current-eclass-hashcons-cell-id #f] - [current-persistent-registry-net-box prn-box]) - (init-rule-registry-cell! prn-box) - (set-box! prn-box (run-to-quiescence - (register-arithmetic-seed! (unbox prn-box) - (current-rule-registry-cell-id)))) - (init-eclass-hashcons-cell! prn-box) - (parameterize ([current-preduce-ingest? #t]) - (printf "== e-graph (gate ON) ==\n") - (set-box! idx 0) - (bench-ns "whnf int-fold, FRESH positions (intern+dispatch+union)" 2000 - (whnf (next-expr!))) - (bench-ns "whnf int-fold, MEMO HIT (hashcons lookup)" 10000 - (whnf same-expr)))) diff --git a/racket/prologos/data/cache/pnet/prologos/data/nat.pnet b/racket/prologos/data/cache/pnet/prologos/data/nat.pnet index bbb45dc37..4a0694e24 100644 --- a/racket/prologos/data/cache/pnet/prologos/data/nat.pnet +++ b/racket/prologos/data/cache/pnet/prologos/data/nat.pnet @@ -1 +1 @@ -(1 "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos:1776910266" #hasheq((add . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Nat))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-bvar 0) (#(struct:expr-reduce-arm zero 0 #(struct:expr-bvar 1)) #(struct:expr-reduce-arm suc 1 #(struct:expr-suc #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::add) #(struct:expr-bvar 2)) #(struct:expr-bvar 0))))) #t))))) (apply . (#(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi mw #(struct:expr-Pi mw #(struct:expr-bvar 1) #(struct:expr-bvar 1)) #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-bvar 2))))) . #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam mw #(struct:expr-Pi mw #(struct:expr-bvar 1) #(struct:expr-bvar 1)) #(struct:expr-lam mw #(struct:expr-bvar 2) #(struct:expr-app #(struct:expr-bvar 1) #(struct:expr-bvar 0)))))))) (bool-to-nat . (#(struct:expr-Pi mw #(struct:expr-Bool) #(struct:expr-Nat)) . #(struct:expr-lam mw #(struct:expr-Bool) #(struct:expr-reduce #(struct:expr-bvar 0) (#(struct:expr-reduce-arm true 0 #(struct:expr-nat-val 1)) #(struct:expr-reduce-arm false 0 #(struct:expr-nat-val 0))) #t)))) (clamp . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Nat)))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-hole) #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::max) #(struct:expr-bvar 2)) #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::min) #(struct:expr-bvar 0)) #(struct:expr-bvar 1)))))))) (compose . (#(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi mw #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-bvar 2)) #(struct:expr-Pi mw #(struct:expr-Pi mw #(struct:expr-bvar 1) #(struct:expr-bvar 4)) #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-bvar 4))))))) . #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam mw #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-bvar 2)) #(struct:expr-lam mw #(struct:expr-Pi mw #(struct:expr-bvar 1) #(struct:expr-bvar 4)) #(struct:expr-lam mw #(struct:expr-bvar 2) #(struct:expr-app #(struct:expr-bvar 2) #(struct:expr-app #(struct:expr-bvar 1) #(struct:expr-bvar 0))))))))))) (const . (#(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi mw #(struct:expr-bvar 1) #(struct:expr-Pi mw #(struct:expr-bvar 1) #(struct:expr-bvar 3))))) . #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam mw #(struct:expr-bvar 1) #(struct:expr-lam mw #(struct:expr-bvar 1) #(struct:expr-bvar 1))))))) (double . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Nat)) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-bvar 0) (#(struct:expr-reduce-arm zero 0 #(struct:expr-nat-val 0)) #(struct:expr-reduce-arm suc 1 #(struct:expr-suc #(struct:expr-suc #(struct:expr-app #(struct:expr-fvar prologos::data::nat::double) #(struct:expr-bvar 0)))))) #t)))) (flip . (#(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi mw #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-bvar 2))) #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-Pi mw #(struct:expr-bvar 4) #(struct:expr-bvar 3))))))) . #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam mw #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-bvar 2))) #(struct:expr-lam mw #(struct:expr-bvar 2) #(struct:expr-lam mw #(struct:expr-bvar 4) #(struct:expr-app #(struct:expr-app #(struct:expr-bvar 2) #(struct:expr-bvar 0)) #(struct:expr-bvar 1)))))))))) (ge? . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Bool))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::le?) #(struct:expr-bvar 0)) #(struct:expr-bvar 1)))))) (gt? . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Bool))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::lt?) #(struct:expr-bvar 0)) #(struct:expr-bvar 1)))))) (id . (#(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi mw #(struct:expr-bvar 0) #(struct:expr-bvar 1))) . #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam mw #(struct:expr-bvar 0) #(struct:expr-bvar 0))))) (le? . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Bool))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-app #(struct:expr-fvar prologos::data::nat::zero?) #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::sub) #(struct:expr-bvar 1)) #(struct:expr-bvar 0))))))) (lt? . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Bool))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::le?) #(struct:expr-bvar 0)) #(struct:expr-bvar 1)) (#(struct:expr-reduce-arm true 0 #(struct:expr-false)) #(struct:expr-reduce-arm false 0 #(struct:expr-true))) #t))))) (max . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Nat))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::le?) #(struct:expr-bvar 1)) #(struct:expr-bvar 0)) (#(struct:expr-reduce-arm true 0 #(struct:expr-bvar 0)) #(struct:expr-reduce-arm false 0 #(struct:expr-bvar 1))) #t))))) (min . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Nat))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::le?) #(struct:expr-bvar 1)) #(struct:expr-bvar 0)) (#(struct:expr-reduce-arm true 0 #(struct:expr-bvar 1)) #(struct:expr-reduce-arm false 0 #(struct:expr-bvar 0))) #t))))) (mult . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Nat))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-bvar 0) (#(struct:expr-reduce-arm zero 0 #(struct:expr-nat-val 0)) #(struct:expr-reduce-arm suc 1 #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::add) #(struct:expr-bvar 2)) #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::mult) #(struct:expr-bvar 2)) #(struct:expr-bvar 0))))) #t))))) (nat-eq? . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Bool))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::le?) #(struct:expr-bvar 1)) #(struct:expr-bvar 0)) (#(struct:expr-reduce-arm true 0 #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::le?) #(struct:expr-bvar 0)) #(struct:expr-bvar 1))) #(struct:expr-reduce-arm false 0 #(struct:expr-false))) #t))))) (on . (#(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi mw #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-Pi mw #(struct:expr-bvar 3) #(struct:expr-bvar 3))) #(struct:expr-Pi mw #(struct:expr-Pi mw #(struct:expr-bvar 1) #(struct:expr-bvar 4)) #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-Pi mw #(struct:expr-bvar 3) #(struct:expr-bvar 5)))))))) . #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam mw #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-Pi mw #(struct:expr-bvar 3) #(struct:expr-bvar 3))) #(struct:expr-lam mw #(struct:expr-Pi mw #(struct:expr-bvar 1) #(struct:expr-bvar 4)) #(struct:expr-lam mw #(struct:expr-bvar 2) #(struct:expr-lam mw #(struct:expr-bvar 3) #(struct:expr-app #(struct:expr-app #(struct:expr-bvar 3) #(struct:expr-app #(struct:expr-bvar 2) #(struct:expr-bvar 1))) #(struct:expr-app #(struct:expr-bvar 2) #(struct:expr-bvar 0)))))))))))) (pow . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Nat))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-bvar 0) (#(struct:expr-reduce-arm zero 0 #(struct:expr-nat-val 1)) #(struct:expr-reduce-arm suc 1 #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::mult) #(struct:expr-bvar 2)) #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::pow) #(struct:expr-bvar 2)) #(struct:expr-bvar 0))))) #t))))) (pred . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Nat)) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-bvar 0) (#(struct:expr-reduce-arm zero 0 #(struct:expr-nat-val 0)) #(struct:expr-reduce-arm suc 1 #(struct:expr-bvar 0))) #t)))) (prologos::core::apply . (#(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi mw #(struct:expr-Pi mw #(struct:expr-bvar 1) #(struct:expr-bvar 1)) #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-bvar 2))))) . #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam mw #(struct:expr-Pi mw #(struct:expr-bvar 1) #(struct:expr-bvar 1)) #(struct:expr-lam mw #(struct:expr-bvar 2) #(struct:expr-app #(struct:expr-bvar 1) #(struct:expr-bvar 0)))))))) (prologos::core::compose . (#(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi mw #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-bvar 2)) #(struct:expr-Pi mw #(struct:expr-Pi mw #(struct:expr-bvar 1) #(struct:expr-bvar 4)) #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-bvar 4))))))) . #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam mw #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-bvar 2)) #(struct:expr-lam mw #(struct:expr-Pi mw #(struct:expr-bvar 1) #(struct:expr-bvar 4)) #(struct:expr-lam mw #(struct:expr-bvar 2) #(struct:expr-app #(struct:expr-bvar 2) #(struct:expr-app #(struct:expr-bvar 1) #(struct:expr-bvar 0))))))))))) (prologos::core::const . (#(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi mw #(struct:expr-bvar 1) #(struct:expr-Pi mw #(struct:expr-bvar 1) #(struct:expr-bvar 3))))) . #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam mw #(struct:expr-bvar 1) #(struct:expr-lam mw #(struct:expr-bvar 1) #(struct:expr-bvar 1))))))) (prologos::core::flip . (#(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi mw #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-bvar 2))) #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-Pi mw #(struct:expr-bvar 4) #(struct:expr-bvar 3))))))) . #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam mw #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-bvar 2))) #(struct:expr-lam mw #(struct:expr-bvar 2) #(struct:expr-lam mw #(struct:expr-bvar 4) #(struct:expr-app #(struct:expr-app #(struct:expr-bvar 2) #(struct:expr-bvar 0)) #(struct:expr-bvar 1)))))))))) (prologos::core::id . (#(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi mw #(struct:expr-bvar 0) #(struct:expr-bvar 1))) . #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam mw #(struct:expr-bvar 0) #(struct:expr-bvar 0))))) (prologos::core::on . (#(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi mw #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-Pi mw #(struct:expr-bvar 3) #(struct:expr-bvar 3))) #(struct:expr-Pi mw #(struct:expr-Pi mw #(struct:expr-bvar 1) #(struct:expr-bvar 4)) #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-Pi mw #(struct:expr-bvar 3) #(struct:expr-bvar 5)))))))) . #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam mw #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-Pi mw #(struct:expr-bvar 3) #(struct:expr-bvar 3))) #(struct:expr-lam mw #(struct:expr-Pi mw #(struct:expr-bvar 1) #(struct:expr-bvar 4)) #(struct:expr-lam mw #(struct:expr-bvar 2) #(struct:expr-lam mw #(struct:expr-bvar 3) #(struct:expr-app #(struct:expr-app #(struct:expr-bvar 3) #(struct:expr-app #(struct:expr-bvar 2) #(struct:expr-bvar 1))) #(struct:expr-app #(struct:expr-bvar 2) #(struct:expr-bvar 0)))))))))))) (prologos::data::nat::add . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Nat))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-bvar 0) (#(struct:expr-reduce-arm zero 0 #(struct:expr-bvar 1)) #(struct:expr-reduce-arm suc 1 #(struct:expr-suc #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::add) #(struct:expr-bvar 2)) #(struct:expr-bvar 0))))) #t))))) (prologos::data::nat::bool-to-nat . (#(struct:expr-Pi mw #(struct:expr-Bool) #(struct:expr-Nat)) . #(struct:expr-lam mw #(struct:expr-Bool) #(struct:expr-reduce #(struct:expr-bvar 0) (#(struct:expr-reduce-arm true 0 #(struct:expr-nat-val 1)) #(struct:expr-reduce-arm false 0 #(struct:expr-nat-val 0))) #t)))) (prologos::data::nat::clamp . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Nat)))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-hole) #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::max) #(struct:expr-bvar 2)) #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::min) #(struct:expr-bvar 0)) #(struct:expr-bvar 1)))))))) (prologos::data::nat::double . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Nat)) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-bvar 0) (#(struct:expr-reduce-arm zero 0 #(struct:expr-nat-val 0)) #(struct:expr-reduce-arm suc 1 #(struct:expr-suc #(struct:expr-suc #(struct:expr-app #(struct:expr-fvar prologos::data::nat::double) #(struct:expr-bvar 0)))))) #t)))) (prologos::data::nat::ge? . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Bool))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::le?) #(struct:expr-bvar 0)) #(struct:expr-bvar 1)))))) (prologos::data::nat::gt? . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Bool))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::lt?) #(struct:expr-bvar 0)) #(struct:expr-bvar 1)))))) (prologos::data::nat::le? . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Bool))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-app #(struct:expr-fvar prologos::data::nat::zero?) #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::sub) #(struct:expr-bvar 1)) #(struct:expr-bvar 0))))))) (prologos::data::nat::lt? . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Bool))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::le?) #(struct:expr-bvar 0)) #(struct:expr-bvar 1)) (#(struct:expr-reduce-arm true 0 #(struct:expr-false)) #(struct:expr-reduce-arm false 0 #(struct:expr-true))) #t))))) (prologos::data::nat::max . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Nat))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::le?) #(struct:expr-bvar 1)) #(struct:expr-bvar 0)) (#(struct:expr-reduce-arm true 0 #(struct:expr-bvar 0)) #(struct:expr-reduce-arm false 0 #(struct:expr-bvar 1))) #t))))) (prologos::data::nat::min . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Nat))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::le?) #(struct:expr-bvar 1)) #(struct:expr-bvar 0)) (#(struct:expr-reduce-arm true 0 #(struct:expr-bvar 1)) #(struct:expr-reduce-arm false 0 #(struct:expr-bvar 0))) #t))))) (prologos::data::nat::mult . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Nat))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-bvar 0) (#(struct:expr-reduce-arm zero 0 #(struct:expr-nat-val 0)) #(struct:expr-reduce-arm suc 1 #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::add) #(struct:expr-bvar 2)) #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::mult) #(struct:expr-bvar 2)) #(struct:expr-bvar 0))))) #t))))) (prologos::data::nat::nat-eq? . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Bool))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::le?) #(struct:expr-bvar 1)) #(struct:expr-bvar 0)) (#(struct:expr-reduce-arm true 0 #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::le?) #(struct:expr-bvar 0)) #(struct:expr-bvar 1))) #(struct:expr-reduce-arm false 0 #(struct:expr-false))) #t))))) (prologos::data::nat::pow . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Nat))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-bvar 0) (#(struct:expr-reduce-arm zero 0 #(struct:expr-nat-val 1)) #(struct:expr-reduce-arm suc 1 #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::mult) #(struct:expr-bvar 2)) #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::pow) #(struct:expr-bvar 2)) #(struct:expr-bvar 0))))) #t))))) (prologos::data::nat::pred . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Nat)) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-bvar 0) (#(struct:expr-reduce-arm zero 0 #(struct:expr-nat-val 0)) #(struct:expr-reduce-arm suc 1 #(struct:expr-bvar 0))) #t)))) (prologos::data::nat::sub . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Nat))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-bvar 0) (#(struct:expr-reduce-arm zero 0 #(struct:expr-bvar 1)) #(struct:expr-reduce-arm suc 1 #(struct:expr-app #(struct:expr-fvar prologos::data::nat::pred) #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::sub) #(struct:expr-bvar 2)) #(struct:expr-bvar 0))))) #t))))) (prologos::data::nat::zero? . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Bool)) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-bvar 0) (#(struct:expr-reduce-arm zero 0 #(struct:expr-true)) #(struct:expr-reduce-arm suc 1 #(struct:expr-false))) #t)))) (sub . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Nat))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-bvar 0) (#(struct:expr-reduce-arm zero 0 #(struct:expr-bvar 1)) #(struct:expr-reduce-arm suc 1 #(struct:expr-app #(struct:expr-fvar prologos::data::nat::pred) #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::sub) #(struct:expr-bvar 2)) #(struct:expr-bvar 0))))) #t))))) (zero? . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Bool)) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-bvar 0) (#(struct:expr-reduce-arm zero 0 #(struct:expr-true)) #(struct:expr-reduce-arm suc 1 #(struct:expr-false))) #t))))) #hasheq((add . #(struct:spec-entry ((Nat Nat -> Nat)) #f #f #(struct:srcloc "" 0 0 0) () () #f #f)) (apply . #(struct:spec-entry (((A -> B) A -> B)) #f #f #(struct:srcloc "" 0 0 0) () ((A Type 0) (B Type 0)) #f #f)) (bool-to-nat . #(struct:spec-entry ((Bool -> Nat)) #f #f #(struct:srcloc "" 0 0 0) () () #f #f)) (clamp . #(struct:spec-entry ((Nat Nat -> Nat -> Nat)) #f #f #(struct:srcloc "" 0 0 0) () () #f #f)) (compose . #(struct:spec-entry (((B -> C) (A -> B) A -> C)) #f #f #(struct:srcloc "" 0 0 0) () ((B Type 0) (C Type 0) (A Type 0)) #f #f)) (const . #(struct:spec-entry ((A B -> A)) #f #f #(struct:srcloc "" 0 0 0) () ((A Type 0) (B Type 0)) #f #f)) (double . #(struct:spec-entry ((Nat -> Nat)) #f #f #(struct:srcloc "" 0 0 0) () () #f #f)) (flip . #(struct:spec-entry (((A -> B -> C) B A -> C)) #f #f #(struct:srcloc "" 0 0 0) () ((A Type 0) (B Type 0) (C Type 0)) #f #f)) (ge? . #(struct:spec-entry ((Nat Nat -> Bool)) #f #f #(struct:srcloc "" 0 0 0) () () #f #f)) (gt? . #(struct:spec-entry ((Nat Nat -> Bool)) #f #f #(struct:srcloc "" 0 0 0) () () #f #f)) (id . #(struct:spec-entry ((A -> A)) #f #f #(struct:srcloc "" 0 0 0) () ((A Type 0)) #f #f)) (le? . #(struct:spec-entry ((Nat Nat -> Bool)) #f #f #(struct:srcloc "" 0 0 0) () () #f #f)) (lt? . #(struct:spec-entry ((Nat Nat -> Bool)) #f #f #(struct:srcloc "" 0 0 0) () () #f #f)) (max . #(struct:spec-entry ((Nat Nat -> Nat)) #f #f #(struct:srcloc "" 0 0 0) () () #f #f)) (min . #(struct:spec-entry ((Nat Nat -> Nat)) #f #f #(struct:srcloc "" 0 0 0) () () #f #f)) (mult . #(struct:spec-entry ((Nat Nat -> Nat)) #f #f #(struct:srcloc "" 0 0 0) () () #f #f)) (nat-eq? . #(struct:spec-entry ((Nat Nat -> Bool)) #f #f #(struct:srcloc "" 0 0 0) () () #f #f)) (on . #(struct:spec-entry (((B -> B -> C) (A -> B) A A -> C)) #f #f #(struct:srcloc "" 0 0 0) () ((B Type 0) (C Type 0) (A Type 0)) #f #f)) (pow . #(struct:spec-entry ((Nat Nat -> Nat)) #f #f #(struct:srcloc "" 0 0 0) () () #f #f)) (pred . #(struct:spec-entry ((Nat -> Nat)) #f #f #(struct:srcloc "" 0 0 0) () () #f #f)) (sub . #(struct:spec-entry ((Nat Nat -> Nat)) #f #f #(struct:srcloc "" 0 0 0) () () #f #f)) (zero? . #(struct:spec-entry ((Nat -> Bool)) #f #f #(struct:srcloc "" 0 0 0) () () #f #f))) #hasheq((Ordering . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/ordering.prologos" 8 0 40)) (add . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 10 0 69)) (and . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/bool.prologos" 17 0 62)) (apply . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/core.prologos" 25 0 23)) (bool-eq . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/bool.prologos" 38 0 66)) (bool-to-nat . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 119 0 74)) (clamp . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 126 0 54)) (compose . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/core.prologos" 20 0 29)) (const . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/core.prologos" 15 0 20)) (double . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 24 0 79)) (eq-ord . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/ordering.prologos" 8 0 40)) (flip . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/core.prologos" 30 0 26)) (ge? . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 85 0 24)) (gt-ord . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/ordering.prologos" 8 0 40)) (gt? . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 80 0 24)) (id . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/core.prologos" 10 0 15)) (implies . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/bool.prologos" 59 0 33)) (le? . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 68 0 31)) (lt-ord . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/ordering.prologos" 8 0 40)) (lt? . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 73 0 73)) (max . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 108 0 66)) (min . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 101 0 66)) (mult . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 17 0 76)) (nand . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/bool.prologos" 49 0 30)) (nat-eq? . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 90 0 80)) (nor . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/bool.prologos" 54 0 28)) (not . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/bool.prologos" 10 0 63)) (on . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/core.prologos" 36 0 32)) (or . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/bool.prologos" 24 0 60)) (pow . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 57 0 92)) (pred . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 31 0 60)) (prologos::core::apply . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/core.prologos" 25 0 23)) (prologos::core::compose . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/core.prologos" 20 0 29)) (prologos::core::const . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/core.prologos" 15 0 20)) (prologos::core::flip . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/core.prologos" 30 0 26)) (prologos::core::id . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/core.prologos" 10 0 15)) (prologos::core::on . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/core.prologos" 36 0 32)) (prologos::data::bool::and . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/bool.prologos" 17 0 62)) (prologos::data::bool::bool-eq . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/bool.prologos" 38 0 66)) (prologos::data::bool::implies . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/bool.prologos" 59 0 33)) (prologos::data::bool::nand . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/bool.prologos" 49 0 30)) (prologos::data::bool::nor . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/bool.prologos" 54 0 28)) (prologos::data::bool::not . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/bool.prologos" 10 0 63)) (prologos::data::bool::or . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/bool.prologos" 24 0 60)) (prologos::data::bool::xor . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/bool.prologos" 31 0 62)) (prologos::data::nat::add . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 10 0 69)) (prologos::data::nat::bool-to-nat . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 119 0 74)) (prologos::data::nat::clamp . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 126 0 54)) (prologos::data::nat::double . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 24 0 79)) (prologos::data::nat::ge? . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 85 0 24)) (prologos::data::nat::gt? . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 80 0 24)) (prologos::data::nat::le? . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 68 0 31)) (prologos::data::nat::lt? . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 73 0 73)) (prologos::data::nat::max . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 108 0 66)) (prologos::data::nat::min . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 101 0 66)) (prologos::data::nat::mult . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 17 0 76)) (prologos::data::nat::nat-eq? . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 90 0 80)) (prologos::data::nat::pow . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 57 0 92)) (prologos::data::nat::pred . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 31 0 60)) (prologos::data::nat::sub . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 50 0 70)) (prologos::data::nat::zero? . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 38 0 65)) (prologos::data::ordering::Ordering . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/ordering.prologos" 8 0 40)) (prologos::data::ordering::eq-ord . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/ordering.prologos" 8 0 40)) (prologos::data::ordering::gt-ord . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/ordering.prologos" 8 0 40)) (prologos::data::ordering::lt-ord . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/ordering.prologos" 8 0 40)) (sub . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 50 0 70)) (xor . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/bool.prologos" 31 0 62)) (zero? . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 38 0 65))) (add mult double pred zero? sub pow le? lt? gt? ge? nat-eq? min max bool-to-nat clamp) "prologos::data::nat" #hasheq(($compose . expand-compose-sexp) ($list-literal . expand-list-literal) ($lseq-literal . expand-lseq-literal) ($mixfix . expand-mixfix-form) ($pipe-gt . expand-pipe-block) ($quasiquote . expand-quasiquote) ($quote . expand-quote) (cond . expand-cond) (do . expand-do) (if . expand-if) (let . expand-let) (pipe2 . #(struct:preparse-macro pipe2 (pipe2 $x $f $g) ($g ($f $x)))) (pipe3 . #(struct:preparse-macro pipe3 (pipe3 $x $f $g $h) ($h ($g ($f $x))))) (twice . #(struct:preparse-macro twice (twice $f $x) ($f ($f $x)))) (unless . #(struct:preparse-macro unless (unless $cond $body) (if $cond unit $body))) (when . #(struct:preparse-macro when (when $cond $body) (if $cond $body unit))) (with-transient . expand-with-transient)) #hasheq((eq-ord . #(struct:ctor-meta Ordering () () () 1)) (false . #(struct:ctor-meta Bool () () () 1)) (gt-ord . #(struct:ctor-meta Ordering () () () 2)) (lt-ord . #(struct:ctor-meta Ordering () () () 0)) (suc . #(struct:ctor-meta Nat () (Nat) (#t) 1)) (true . #(struct:ctor-meta Bool () () () 0)) (unit . #(struct:ctor-meta Unit () () () 0)) (zero . #(struct:ctor-meta Nat () () () 0))) #hasheq((Bool . (true false)) (Nat . (zero suc)) (Ordering . (lt-ord eq-ord gt-ord)) (Unit . (unit))) #hasheq() #hasheq(((Posit16 . Posit32) . #t) ((Nat . Int) . #t) ((Posit8 . Posit64) . #t) ((Posit8 . Posit32) . #t) ((Posit32 . Posit64) . #t) ((Nat . Rat) . #t) ((Posit8 . Posit16) . #t) ((Posit16 . Posit64) . #t) ((Int . Rat) . #t)) #hasheq() #hasheq() #hasheq() #hasheq() #hasheq() #hasheq() #hasheq() #hasheq() #hasheq((add . (x y)) (and . (a b)) (apply . (A B f x)) (bool-eq . (a b)) (bool-to-nat . (b)) (clamp . (low high)) (compose . (B C A g f x)) (const . (A B x _)) (double . (n)) (flip . (A B C f b a)) (ge? . (x y)) (gt? . (x y)) (id . (A x)) (implies . (a b)) (le? . (x y)) (lt? . (x y)) (max . (x y)) (min . (x y)) (mult . (x y)) (nand . (a b)) (nat-eq? . (x y)) (nor . (a b)) (not . (b)) (on . (B C A f g x y)) (or . (a b)) (pow . (base exp)) (pred . (n)) (sub . (x y)) (xor . (a b)) (zero? . (n))) #hasheq() #hasheq() #hasheq()) \ No newline at end of file +(1 "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos:1781293808" #hasheq((add . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Nat))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-bvar 0) (#(struct:expr-reduce-arm zero 0 #(struct:expr-bvar 1)) #(struct:expr-reduce-arm suc 1 #(struct:expr-suc #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::add) #(struct:expr-bvar 2)) #(struct:expr-bvar 0))))) #t))))) (apply . (#(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi mw #(struct:expr-Pi mw #(struct:expr-bvar 1) #(struct:expr-bvar 1)) #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-bvar 2))))) . #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam mw #(struct:expr-Pi mw #(struct:expr-bvar 1) #(struct:expr-bvar 1)) #(struct:expr-lam mw #(struct:expr-bvar 2) #(struct:expr-app #(struct:expr-bvar 1) #(struct:expr-bvar 0)))))))) (bool-to-nat . (#(struct:expr-Pi mw #(struct:expr-Bool) #(struct:expr-Nat)) . #(struct:expr-lam mw #(struct:expr-Bool) #(struct:expr-reduce #(struct:expr-bvar 0) (#(struct:expr-reduce-arm true 0 #(struct:expr-nat-val 1)) #(struct:expr-reduce-arm false 0 #(struct:expr-nat-val 0))) #t)))) (clamp . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Nat)))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-hole) #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::max) #(struct:expr-bvar 2)) #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::min) #(struct:expr-bvar 0)) #(struct:expr-bvar 1)))))))) (compose . (#(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi mw #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-bvar 2)) #(struct:expr-Pi mw #(struct:expr-Pi mw #(struct:expr-bvar 1) #(struct:expr-bvar 4)) #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-bvar 4))))))) . #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam mw #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-bvar 2)) #(struct:expr-lam mw #(struct:expr-Pi mw #(struct:expr-bvar 1) #(struct:expr-bvar 4)) #(struct:expr-lam mw #(struct:expr-bvar 2) #(struct:expr-app #(struct:expr-bvar 2) #(struct:expr-app #(struct:expr-bvar 1) #(struct:expr-bvar 0))))))))))) (const . (#(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi mw #(struct:expr-bvar 1) #(struct:expr-Pi mw #(struct:expr-bvar 1) #(struct:expr-bvar 3))))) . #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam mw #(struct:expr-bvar 1) #(struct:expr-lam mw #(struct:expr-bvar 1) #(struct:expr-bvar 1))))))) (double . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Nat)) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-bvar 0) (#(struct:expr-reduce-arm zero 0 #(struct:expr-nat-val 0)) #(struct:expr-reduce-arm suc 1 #(struct:expr-suc #(struct:expr-suc #(struct:expr-app #(struct:expr-fvar prologos::data::nat::double) #(struct:expr-bvar 0)))))) #t)))) (flip . (#(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi mw #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-bvar 2))) #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-Pi mw #(struct:expr-bvar 4) #(struct:expr-bvar 3))))))) . #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam mw #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-bvar 2))) #(struct:expr-lam mw #(struct:expr-bvar 2) #(struct:expr-lam mw #(struct:expr-bvar 4) #(struct:expr-app #(struct:expr-app #(struct:expr-bvar 2) #(struct:expr-bvar 0)) #(struct:expr-bvar 1)))))))))) (ge? . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Bool))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::le?) #(struct:expr-bvar 0)) #(struct:expr-bvar 1)))))) (gt? . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Bool))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::lt?) #(struct:expr-bvar 0)) #(struct:expr-bvar 1)))))) (id . (#(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi mw #(struct:expr-bvar 0) #(struct:expr-bvar 1))) . #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam mw #(struct:expr-bvar 0) #(struct:expr-bvar 0))))) (le? . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Bool))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-app #(struct:expr-fvar prologos::data::nat::zero?) #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::sub) #(struct:expr-bvar 1)) #(struct:expr-bvar 0))))))) (lt? . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Bool))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::le?) #(struct:expr-bvar 0)) #(struct:expr-bvar 1)) (#(struct:expr-reduce-arm true 0 #(struct:expr-false)) #(struct:expr-reduce-arm false 0 #(struct:expr-true))) #t))))) (max . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Nat))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::le?) #(struct:expr-bvar 1)) #(struct:expr-bvar 0)) (#(struct:expr-reduce-arm true 0 #(struct:expr-bvar 0)) #(struct:expr-reduce-arm false 0 #(struct:expr-bvar 1))) #t))))) (min . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Nat))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::le?) #(struct:expr-bvar 1)) #(struct:expr-bvar 0)) (#(struct:expr-reduce-arm true 0 #(struct:expr-bvar 1)) #(struct:expr-reduce-arm false 0 #(struct:expr-bvar 0))) #t))))) (mult . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Nat))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-bvar 0) (#(struct:expr-reduce-arm zero 0 #(struct:expr-nat-val 0)) #(struct:expr-reduce-arm suc 1 #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::add) #(struct:expr-bvar 2)) #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::mult) #(struct:expr-bvar 2)) #(struct:expr-bvar 0))))) #t))))) (nat-eq? . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Bool))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::le?) #(struct:expr-bvar 1)) #(struct:expr-bvar 0)) (#(struct:expr-reduce-arm true 0 #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::le?) #(struct:expr-bvar 0)) #(struct:expr-bvar 1))) #(struct:expr-reduce-arm false 0 #(struct:expr-false))) #t))))) (on . (#(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi mw #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-Pi mw #(struct:expr-bvar 3) #(struct:expr-bvar 3))) #(struct:expr-Pi mw #(struct:expr-Pi mw #(struct:expr-bvar 1) #(struct:expr-bvar 4)) #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-Pi mw #(struct:expr-bvar 3) #(struct:expr-bvar 5)))))))) . #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam mw #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-Pi mw #(struct:expr-bvar 3) #(struct:expr-bvar 3))) #(struct:expr-lam mw #(struct:expr-Pi mw #(struct:expr-bvar 1) #(struct:expr-bvar 4)) #(struct:expr-lam mw #(struct:expr-bvar 2) #(struct:expr-lam mw #(struct:expr-bvar 3) #(struct:expr-app #(struct:expr-app #(struct:expr-bvar 3) #(struct:expr-app #(struct:expr-bvar 2) #(struct:expr-bvar 1))) #(struct:expr-app #(struct:expr-bvar 2) #(struct:expr-bvar 0)))))))))))) (pow . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Nat))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-bvar 0) (#(struct:expr-reduce-arm zero 0 #(struct:expr-nat-val 1)) #(struct:expr-reduce-arm suc 1 #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::mult) #(struct:expr-bvar 2)) #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::pow) #(struct:expr-bvar 2)) #(struct:expr-bvar 0))))) #t))))) (pred . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Nat)) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-bvar 0) (#(struct:expr-reduce-arm zero 0 #(struct:expr-nat-val 0)) #(struct:expr-reduce-arm suc 1 #(struct:expr-bvar 0))) #t)))) (prologos::core::apply . (#(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi mw #(struct:expr-Pi mw #(struct:expr-bvar 1) #(struct:expr-bvar 1)) #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-bvar 2))))) . #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam mw #(struct:expr-Pi mw #(struct:expr-bvar 1) #(struct:expr-bvar 1)) #(struct:expr-lam mw #(struct:expr-bvar 2) #(struct:expr-app #(struct:expr-bvar 1) #(struct:expr-bvar 0)))))))) (prologos::core::compose . (#(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi mw #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-bvar 2)) #(struct:expr-Pi mw #(struct:expr-Pi mw #(struct:expr-bvar 1) #(struct:expr-bvar 4)) #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-bvar 4))))))) . #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam mw #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-bvar 2)) #(struct:expr-lam mw #(struct:expr-Pi mw #(struct:expr-bvar 1) #(struct:expr-bvar 4)) #(struct:expr-lam mw #(struct:expr-bvar 2) #(struct:expr-app #(struct:expr-bvar 2) #(struct:expr-app #(struct:expr-bvar 1) #(struct:expr-bvar 0))))))))))) (prologos::core::const . (#(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi mw #(struct:expr-bvar 1) #(struct:expr-Pi mw #(struct:expr-bvar 1) #(struct:expr-bvar 3))))) . #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam mw #(struct:expr-bvar 1) #(struct:expr-lam mw #(struct:expr-bvar 1) #(struct:expr-bvar 1))))))) (prologos::core::flip . (#(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi mw #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-bvar 2))) #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-Pi mw #(struct:expr-bvar 4) #(struct:expr-bvar 3))))))) . #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam mw #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-bvar 2))) #(struct:expr-lam mw #(struct:expr-bvar 2) #(struct:expr-lam mw #(struct:expr-bvar 4) #(struct:expr-app #(struct:expr-app #(struct:expr-bvar 2) #(struct:expr-bvar 0)) #(struct:expr-bvar 1)))))))))) (prologos::core::id . (#(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi mw #(struct:expr-bvar 0) #(struct:expr-bvar 1))) . #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam mw #(struct:expr-bvar 0) #(struct:expr-bvar 0))))) (prologos::core::on . (#(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-Pi mw #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-Pi mw #(struct:expr-bvar 3) #(struct:expr-bvar 3))) #(struct:expr-Pi mw #(struct:expr-Pi mw #(struct:expr-bvar 1) #(struct:expr-bvar 4)) #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-Pi mw #(struct:expr-bvar 3) #(struct:expr-bvar 5)))))))) . #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam m0 #(struct:expr-Type #(struct:lzero)) #(struct:expr-lam mw #(struct:expr-Pi mw #(struct:expr-bvar 2) #(struct:expr-Pi mw #(struct:expr-bvar 3) #(struct:expr-bvar 3))) #(struct:expr-lam mw #(struct:expr-Pi mw #(struct:expr-bvar 1) #(struct:expr-bvar 4)) #(struct:expr-lam mw #(struct:expr-bvar 2) #(struct:expr-lam mw #(struct:expr-bvar 3) #(struct:expr-app #(struct:expr-app #(struct:expr-bvar 3) #(struct:expr-app #(struct:expr-bvar 2) #(struct:expr-bvar 1))) #(struct:expr-app #(struct:expr-bvar 2) #(struct:expr-bvar 0)))))))))))) (prologos::data::nat::add . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Nat))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-bvar 0) (#(struct:expr-reduce-arm zero 0 #(struct:expr-bvar 1)) #(struct:expr-reduce-arm suc 1 #(struct:expr-suc #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::add) #(struct:expr-bvar 2)) #(struct:expr-bvar 0))))) #t))))) (prologos::data::nat::bool-to-nat . (#(struct:expr-Pi mw #(struct:expr-Bool) #(struct:expr-Nat)) . #(struct:expr-lam mw #(struct:expr-Bool) #(struct:expr-reduce #(struct:expr-bvar 0) (#(struct:expr-reduce-arm true 0 #(struct:expr-nat-val 1)) #(struct:expr-reduce-arm false 0 #(struct:expr-nat-val 0))) #t)))) (prologos::data::nat::clamp . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Nat)))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-hole) #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::max) #(struct:expr-bvar 2)) #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::min) #(struct:expr-bvar 0)) #(struct:expr-bvar 1)))))))) (prologos::data::nat::double . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Nat)) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-bvar 0) (#(struct:expr-reduce-arm zero 0 #(struct:expr-nat-val 0)) #(struct:expr-reduce-arm suc 1 #(struct:expr-suc #(struct:expr-suc #(struct:expr-app #(struct:expr-fvar prologos::data::nat::double) #(struct:expr-bvar 0)))))) #t)))) (prologos::data::nat::ge? . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Bool))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::le?) #(struct:expr-bvar 0)) #(struct:expr-bvar 1)))))) (prologos::data::nat::gt? . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Bool))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::lt?) #(struct:expr-bvar 0)) #(struct:expr-bvar 1)))))) (prologos::data::nat::le? . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Bool))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-app #(struct:expr-fvar prologos::data::nat::zero?) #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::sub) #(struct:expr-bvar 1)) #(struct:expr-bvar 0))))))) (prologos::data::nat::lt? . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Bool))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::le?) #(struct:expr-bvar 0)) #(struct:expr-bvar 1)) (#(struct:expr-reduce-arm true 0 #(struct:expr-false)) #(struct:expr-reduce-arm false 0 #(struct:expr-true))) #t))))) (prologos::data::nat::max . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Nat))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::le?) #(struct:expr-bvar 1)) #(struct:expr-bvar 0)) (#(struct:expr-reduce-arm true 0 #(struct:expr-bvar 0)) #(struct:expr-reduce-arm false 0 #(struct:expr-bvar 1))) #t))))) (prologos::data::nat::min . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Nat))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::le?) #(struct:expr-bvar 1)) #(struct:expr-bvar 0)) (#(struct:expr-reduce-arm true 0 #(struct:expr-bvar 1)) #(struct:expr-reduce-arm false 0 #(struct:expr-bvar 0))) #t))))) (prologos::data::nat::mult . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Nat))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-bvar 0) (#(struct:expr-reduce-arm zero 0 #(struct:expr-nat-val 0)) #(struct:expr-reduce-arm suc 1 #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::add) #(struct:expr-bvar 2)) #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::mult) #(struct:expr-bvar 2)) #(struct:expr-bvar 0))))) #t))))) (prologos::data::nat::nat-eq? . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Bool))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::le?) #(struct:expr-bvar 1)) #(struct:expr-bvar 0)) (#(struct:expr-reduce-arm true 0 #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::le?) #(struct:expr-bvar 0)) #(struct:expr-bvar 1))) #(struct:expr-reduce-arm false 0 #(struct:expr-false))) #t))))) (prologos::data::nat::pow . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Nat))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-bvar 0) (#(struct:expr-reduce-arm zero 0 #(struct:expr-nat-val 1)) #(struct:expr-reduce-arm suc 1 #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::mult) #(struct:expr-bvar 2)) #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::pow) #(struct:expr-bvar 2)) #(struct:expr-bvar 0))))) #t))))) (prologos::data::nat::pred . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Nat)) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-bvar 0) (#(struct:expr-reduce-arm zero 0 #(struct:expr-nat-val 0)) #(struct:expr-reduce-arm suc 1 #(struct:expr-bvar 0))) #t)))) (prologos::data::nat::sub . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Nat))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-bvar 0) (#(struct:expr-reduce-arm zero 0 #(struct:expr-bvar 1)) #(struct:expr-reduce-arm suc 1 #(struct:expr-app #(struct:expr-fvar prologos::data::nat::pred) #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::sub) #(struct:expr-bvar 2)) #(struct:expr-bvar 0))))) #t))))) (prologos::data::nat::zero? . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Bool)) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-bvar 0) (#(struct:expr-reduce-arm zero 0 #(struct:expr-true)) #(struct:expr-reduce-arm suc 1 #(struct:expr-false))) #t)))) (sub . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Nat))) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-bvar 0) (#(struct:expr-reduce-arm zero 0 #(struct:expr-bvar 1)) #(struct:expr-reduce-arm suc 1 #(struct:expr-app #(struct:expr-fvar prologos::data::nat::pred) #(struct:expr-app #(struct:expr-app #(struct:expr-fvar prologos::data::nat::sub) #(struct:expr-bvar 2)) #(struct:expr-bvar 0))))) #t))))) (zero? . (#(struct:expr-Pi mw #(struct:expr-Nat) #(struct:expr-Bool)) . #(struct:expr-lam mw #(struct:expr-Nat) #(struct:expr-reduce #(struct:expr-bvar 0) (#(struct:expr-reduce-arm zero 0 #(struct:expr-true)) #(struct:expr-reduce-arm suc 1 #(struct:expr-false))) #t))))) #hasheq((add . #(struct:spec-entry ((Nat Nat -> Nat)) #f #f #(struct:srcloc "" 0 0 0) () () #f #f)) (apply . #(struct:spec-entry (((A -> B) A -> B)) #f #f #(struct:srcloc "" 0 0 0) () ((A Type 0) (B Type 0)) #f #f)) (bool-to-nat . #(struct:spec-entry ((Bool -> Nat)) #f #f #(struct:srcloc "" 0 0 0) () () #f #f)) (clamp . #(struct:spec-entry ((Nat Nat -> Nat -> Nat)) #f #f #(struct:srcloc "" 0 0 0) () () #f #f)) (compose . #(struct:spec-entry (((B -> C) (A -> B) A -> C)) #f #f #(struct:srcloc "" 0 0 0) () ((B Type 0) (C Type 0) (A Type 0)) #f #f)) (const . #(struct:spec-entry ((A B -> A)) #f #f #(struct:srcloc "" 0 0 0) () ((A Type 0) (B Type 0)) #f #f)) (double . #(struct:spec-entry ((Nat -> Nat)) #f #f #(struct:srcloc "" 0 0 0) () () #f #f)) (flip . #(struct:spec-entry (((A -> B -> C) B A -> C)) #f #f #(struct:srcloc "" 0 0 0) () ((A Type 0) (B Type 0) (C Type 0)) #f #f)) (ge? . #(struct:spec-entry ((Nat Nat -> Bool)) #f #f #(struct:srcloc "" 0 0 0) () () #f #f)) (gt? . #(struct:spec-entry ((Nat Nat -> Bool)) #f #f #(struct:srcloc "" 0 0 0) () () #f #f)) (id . #(struct:spec-entry ((A -> A)) #f #f #(struct:srcloc "" 0 0 0) () ((A Type 0)) #f #f)) (le? . #(struct:spec-entry ((Nat Nat -> Bool)) #f #f #(struct:srcloc "" 0 0 0) () () #f #f)) (lt? . #(struct:spec-entry ((Nat Nat -> Bool)) #f #f #(struct:srcloc "" 0 0 0) () () #f #f)) (max . #(struct:spec-entry ((Nat Nat -> Nat)) #f #f #(struct:srcloc "" 0 0 0) () () #f #f)) (min . #(struct:spec-entry ((Nat Nat -> Nat)) #f #f #(struct:srcloc "" 0 0 0) () () #f #f)) (mult . #(struct:spec-entry ((Nat Nat -> Nat)) #f #f #(struct:srcloc "" 0 0 0) () () #f #f)) (nat-eq? . #(struct:spec-entry ((Nat Nat -> Bool)) #f #f #(struct:srcloc "" 0 0 0) () () #f #f)) (on . #(struct:spec-entry (((B -> B -> C) (A -> B) A A -> C)) #f #f #(struct:srcloc "" 0 0 0) () ((B Type 0) (C Type 0) (A Type 0)) #f #f)) (pow . #(struct:spec-entry ((Nat Nat -> Nat)) #f #f #(struct:srcloc "" 0 0 0) () () #f #f)) (pred . #(struct:spec-entry ((Nat -> Nat)) #f #f #(struct:srcloc "" 0 0 0) () () #f #f)) (sub . #(struct:spec-entry ((Nat Nat -> Nat)) #f #f #(struct:srcloc "" 0 0 0) () () #f #f)) (zero? . #(struct:spec-entry ((Nat -> Bool)) #f #f #(struct:srcloc "" 0 0 0) () () #f #f))) #hasheq((Ordering . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/ordering.prologos" 8 0 40)) (add . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 10 0 69)) (and . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/bool.prologos" 17 0 62)) (bool-eq . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/bool.prologos" 38 0 66)) (bool-to-nat . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 119 0 74)) (clamp . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 126 0 54)) (double . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 24 0 79)) (eq-ord . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/ordering.prologos" 8 0 40)) (ge? . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 85 0 24)) (gt-ord . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/ordering.prologos" 8 0 40)) (gt? . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 80 0 24)) (implies . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/bool.prologos" 59 0 33)) (le? . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 68 0 31)) (lt-ord . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/ordering.prologos" 8 0 40)) (lt? . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 73 0 73)) (max . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 108 0 66)) (min . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 101 0 66)) (mult . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 17 0 76)) (nand . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/bool.prologos" 49 0 30)) (nat-eq? . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 90 0 80)) (nor . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/bool.prologos" 54 0 28)) (not . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/bool.prologos" 10 0 63)) (or . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/bool.prologos" 24 0 60)) (pow . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 57 0 92)) (pred . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 31 0 60)) (prologos::data::bool::and . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/bool.prologos" 17 0 62)) (prologos::data::bool::bool-eq . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/bool.prologos" 38 0 66)) (prologos::data::bool::implies . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/bool.prologos" 59 0 33)) (prologos::data::bool::nand . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/bool.prologos" 49 0 30)) (prologos::data::bool::nor . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/bool.prologos" 54 0 28)) (prologos::data::bool::not . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/bool.prologos" 10 0 63)) (prologos::data::bool::or . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/bool.prologos" 24 0 60)) (prologos::data::bool::xor . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/bool.prologos" 31 0 62)) (prologos::data::nat::add . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 10 0 69)) (prologos::data::nat::bool-to-nat . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 119 0 74)) (prologos::data::nat::clamp . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 126 0 54)) (prologos::data::nat::double . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 24 0 79)) (prologos::data::nat::ge? . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 85 0 24)) (prologos::data::nat::gt? . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 80 0 24)) (prologos::data::nat::le? . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 68 0 31)) (prologos::data::nat::lt? . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 73 0 73)) (prologos::data::nat::max . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 108 0 66)) (prologos::data::nat::min . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 101 0 66)) (prologos::data::nat::mult . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 17 0 76)) (prologos::data::nat::nat-eq? . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 90 0 80)) (prologos::data::nat::pow . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 57 0 92)) (prologos::data::nat::pred . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 31 0 60)) (prologos::data::nat::sub . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 50 0 70)) (prologos::data::nat::zero? . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 38 0 65)) (prologos::data::ordering::Ordering . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/ordering.prologos" 8 0 40)) (prologos::data::ordering::eq-ord . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/ordering.prologos" 8 0 40)) (prologos::data::ordering::gt-ord . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/ordering.prologos" 8 0 40)) (prologos::data::ordering::lt-ord . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/ordering.prologos" 8 0 40)) (sub . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 50 0 70)) (xor . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/bool.prologos" 31 0 62)) (zero? . #(struct:srcloc "/home/user/prologos/racket/prologos/lib/prologos/data/nat.prologos" 38 0 65))) (add mult double pred zero? sub pow le? lt? gt? ge? nat-eq? min max bool-to-nat clamp) "prologos::data::nat" #hasheq(($compose . expand-compose-sexp) ($list-literal . expand-list-literal) ($lseq-literal . expand-lseq-literal) ($mixfix . expand-mixfix-form) ($pipe-gt . expand-pipe-block) ($quasiquote . expand-quasiquote) ($quote . expand-quote) (cond . expand-cond) (do . expand-do) (if . expand-if) (let . expand-let) (pipe2 . #(struct:preparse-macro pipe2 (pipe2 $x $f $g) ($g ($f $x)))) (pipe3 . #(struct:preparse-macro pipe3 (pipe3 $x $f $g $h) ($h ($g ($f $x))))) (twice . #(struct:preparse-macro twice (twice $f $x) ($f ($f $x)))) (unless . #(struct:preparse-macro unless (unless $cond $body) (if $cond unit $body))) (when . #(struct:preparse-macro when (when $cond $body) (if $cond $body unit))) (with-transient . expand-with-transient)) #hasheq((eq-ord . #(struct:ctor-meta Ordering () () () 1)) (false . #(struct:ctor-meta Bool () () () 1)) (gt-ord . #(struct:ctor-meta Ordering () () () 2)) (lt-ord . #(struct:ctor-meta Ordering () () () 0)) (suc . #(struct:ctor-meta Nat () (Nat) (#t) 1)) (true . #(struct:ctor-meta Bool () () () 0)) (unit . #(struct:ctor-meta Unit () () () 0)) (zero . #(struct:ctor-meta Nat () () () 0))) #hasheq((Bool . (true false)) (Nat . (zero suc)) (Ordering . (lt-ord eq-ord gt-ord)) (Unit . (unit))) #hasheq() #hasheq(((Posit32 . Posit64) . #t) ((Nat . Rat) . #t) ((Posit8 . Posit16) . #t) ((Posit16 . Posit64) . #t) ((Int . Rat) . #t) ((Posit16 . Posit32) . #t) ((Nat . Int) . #t) ((Posit8 . Posit64) . #t) ((Posit8 . Posit32) . #t)) #hasheq() #hasheq() #hasheq() #hasheq() #hasheq() #hasheq() #hasheq() #hasheq() #hasheq((add . (x y)) (and . (a b)) (apply . (A B f x)) (bool-eq . (a b)) (bool-to-nat . (b)) (clamp . (low high)) (compose . (B C A g f x)) (const . (A B x _)) (double . (n)) (flip . (A B C f b a)) (ge? . (x y)) (gt? . (x y)) (id . (A x)) (implies . (a b)) (le? . (x y)) (lt? . (x y)) (max . (x y)) (min . (x y)) (mult . (x y)) (nand . (a b)) (nat-eq? . (x y)) (nor . (a b)) (not . (b)) (on . (B C A f g x y)) (or . (a b)) (pow . (base exp)) (pred . (n)) (sub . (x y)) (xor . (a b)) (zero? . (n))) #hasheq() #hasheq() #hasheq()) \ No newline at end of file diff --git a/racket/prologos/driver.rkt b/racket/prologos/driver.rkt index 2befec743..4ba79a2ad 100644 --- a/racket/prologos/driver.rkt +++ b/racket/prologos/driver.rkt @@ -66,7 +66,6 @@ (only-in "extraction-store.rkt" init-extraction-store-cell! current-extraction-store-cell-id) ;; PReduce iter 37 (only-in "preduce-pnet.rkt" preduce-load-pnetx! preduce-save-pnetx!) ;; PReduce iter 37 - (only-in "reduction.rkt" current-preduce-ingest? current-preduce-ingest-int-folds?) ;; PReduce iter 22/33 A/B switches "champ.rkt" "unify.rkt" "atms.rkt" @@ -2298,15 +2297,9 @@ (preduce-load-pnetx! prn-box path-str (current-eclass-hashcons-cell-id) (current-extraction-store-cell-id))) - ;; the A/B switch (subprocess benchmarking): PREDUCE_INGEST=1 turns the - ;; gated whnf hook ON for this process — the experiment toggle, NOT a - ;; deployment path (the flip criterion is named at the hook's definition) - (when (getenv "PREDUCE_INGEST") - (current-preduce-ingest? #t) - ;; iter 33: PREDUCE_INGEST=db = δ/β-only (int folds native) — the - ;; selectivity experiment mode - (when (equal? (getenv "PREDUCE_INGEST") "db") - (current-preduce-ingest-int-folds? #f)))) + ;; (PREDUCE_INGEST env toggle removed 2026-06-14 — on-network reduction is + ;; unconditional now; PREDUCE_PNETX persistence remains its own experiment.) + ) (define-values (results pc) (parameterize ([current-phase-timings pt] [current-provenance-counters pv] diff --git a/racket/prologos/eclass-graph.rkt b/racket/prologos/eclass-graph.rkt index 89425bb3b..c7bebb50b 100644 --- a/racket/prologos/eclass-graph.rkt +++ b/racket/prologos/eclass-graph.rkt @@ -27,6 +27,7 @@ "propagator.rkt") (provide current-eclass-hashcons-cell-id + current-eclass-containment-box ;; PTF Track V: reduction-DAG capture init-eclass-hashcons-cell! current-intern-origin current-parent-index-cell-id @@ -74,6 +75,30 @@ ;; --- intern --- +;; PTF Track V (viz): optional containment recording. When set to a (box hash), +;; eclass-intern records parent-alloc → (listof child-alloc) for each interned +;; term's IMMEDIATE sub-terms, resolving children against the registry at intern +;; time (children are interned bottom-up, so they're present). This captures the +;; reduction DAG structure (e.g. int+((lit 377),(lit 233)) → its operands) which +;; is otherwise consumed: an e-class's :best becomes the reduced VALUE, losing +;; the redex shape. Off-network viz side-channel; default #f = zero cost. +(define current-eclass-containment-box (make-parameter #f)) + +;; Immediate child sub-terms of a sexp-encoded term (op c1 c2 ...): the operands. +;; (lit N) is a leaf. Non-list / symbol-headed-lit terms have no children. +(define (term-immediate-children t) + (if (and (pair? t) (symbol? (car t)) (not (eq? (car t) 'lit))) + (filter pair? (cdr t)) + '())) + +(define (record-term-containment! box reg parent-alloc term) + (define h (unbox box)) + (for ([ch (in-list (term-immediate-children term))]) + (define cd (pce-persistable-digest PCE-KIND-GROUND-TERM ch)) + (define entry (and (hash? reg) (hash-ref reg cd #f))) + (when entry ;; child already interned → record parent → child-alloc + (hash-update! h parent-alloc (lambda (l) (cons (car entry) l)) '())))) + ;; → (values net' class-cid digest) (define (eclass-intern net reg-cid term #:cost [cost 1] @@ -100,6 +125,8 @@ #:regime regime)) (define net2 (net-cell-write net1 cid v0)) (define net3 (net-cell-write net2 reg-cid (hash digest (cons alloc cid)))) + (when (current-eclass-containment-box) + (record-term-containment! (current-eclass-containment-box) reg alloc term)) (values net3 cid digest)])) ;; --- union-emitter --- diff --git a/racket/prologos/examples/2026-06-12-ptf-track2-viz.prologos b/racket/prologos/examples/2026-06-12-ptf-track2-viz.prologos new file mode 100644 index 000000000..6e5433811 --- /dev/null +++ b/racket/prologos/examples/2026-06-12-ptf-track2-viz.prologos @@ -0,0 +1,92 @@ +ns examples.ptf-track2-viz + +;; ======================================================================== +;; +;; PTF Track 2: Browser Visualization — Acceptance File (Phase 0A) +;; +;; Exercises what the visualization must SHOW for "arbitrary programs": +;; every expression is LIVE. Run via `process-file` (or the capture probe, +;; tools/viz-capture-probe.rkt) BEFORE and AFTER each Track 2 phase. +;; The probe over this file is the corpus scale audit (design doc §7.7 B9) +;; and the solver free-path check (§7.7 A4). +;; +;; Coverage by VIZ CONCERN: +;; §1 elaboration — spec/defn, polymorphic metas, multi-arity ι +;; §2 functional pipelines — application, partials, generic traits +;; §3 relations — facts, recursive rules, solve queries (the SOLVER +;; network: unification, fork, worldviews — the A4 target) +;; §4 negation-as-failure — S1 stratum activity (nogoods/worldviews) +;; +;; ======================================================================== + +def tag : String := "ptf-track2-viz-acceptance" + +;; ======================================================================== +;; §1 Elaboration — type-inference cells, multi-arity ι +;; ======================================================================== + +spec factorial : Int -> Int +defn factorial [n] + | 0 -> 1 + | n -> * n [factorial [- n 1]] + +[factorial 6] + +spec is-zero Nat -> Bool +defn is-zero + | zero -> true + | suc _ -> false + +[is-zero 3N] + +;; ======================================================================== +;; §2 Functional pipelines — polymorphic metas + generic arithmetic +;; ======================================================================== + +[+ [* 3 4] [- 10 3]] + +;; NOTE: generic `plus` partials — the int+/int* partial forms shown in +;; prologos-syntax.md fail "Could not infer type" under map at HEAD +;; (doc-vs-implementation drift, flagged in the loop dailies 2026-06-12). +map [plus 1 _] '[1 2 3] + +map [plus 10 _] [int-range 1 5] + +;; ======================================================================== +;; §3 Relations — the solver network (A4 free-path target) +;; ======================================================================== + +defr parent [?x ?y] + || "alice" "bob" + "bob" "carol" + "bob" "dave" + "carol" "eve" + +defr ancestor [?x ?y] + &> (parent x y) + &> (parent x z) (ancestor z y) + +;; ground query (single answer) +eval (solve (parent "alice" who)) + +;; open query (all four facts) +eval (solve (parent x y)) + +;; recursive query (transitive closure — backtracking + freshening) +eval (solve (ancestor "alice" who)) + +;; empty result (failure path) +eval (solve (parent "eve" child)) + +;; ======================================================================== +;; §4 Negation-as-failure — S1 stratum (mirrors the WFLE acceptance form) +;; ======================================================================== + +defr blocked [?x] + || "dave" + +defr open-child [?x ?y] + &> (parent x y) (not (blocked y)) + +;; bob's children minus the blocked one => carol only +eval (solve (open-child "bob" who)) diff --git a/racket/prologos/examples/2026-06-14-fib-naive.prologos b/racket/prologos/examples/2026-06-14-fib-naive.prologos new file mode 100644 index 000000000..024865c0d --- /dev/null +++ b/racket/prologos/examples/2026-06-14-fib-naive.prologos @@ -0,0 +1,14 @@ +ns examples.fib-naive + +;; Naive (non-memoized) Fibonacci: each call spawns TWO independent recursive +;; calls — the classic exponential recursion tree. With on-network reduction, +;; watch whether the e-graph's hashcons SHARES identical [fib k] subterms +;; (collapsing the exponential tree) or recomputes them. + +spec fib Int -> Int +defn fib + | 0 -> 0 + | 1 -> 1 + | n -> [int+ [fib [int- n 1]] [fib [int- n 2]]] + +[fib 15] diff --git a/racket/prologos/examples/2026-06-14-fib-small.prologos b/racket/prologos/examples/2026-06-14-fib-small.prologos new file mode 100644 index 000000000..e88245046 --- /dev/null +++ b/racket/prologos/examples/2026-06-14-fib-small.prologos @@ -0,0 +1,25 @@ +ns examples.fib-small + +;; Small naive Fibonacci — the TRACEABLE companion to 2026-06-14-fib-naive.prologos +;; (which uses [fib 15] and hits the perf wall under on-network reduction). +;; +;; PReduce Track 8 Phase 4a: each reduction step (β/δ/ι) is recorded ON-NETWORK as +;; a UNION propagator (the DPO {redex, result} e-class), so the viz shows the +;; recursion AS propagators — not "only one propagator [fib n]" as before. +;; +;; Export it: racket tools/viz-export.rkt examples/2026-06-14-fib-small.prologos -o fib.json +;; fib 6 ≈ 172 rounds, ≤18 propagators firing per round (75 rounds with >1). +;; +;; Note (Phase 4a §8 findings): naive fib does NOT memo-collapse — β reduces +;; call-by-name, so [fib k] reached via different paths carries differently-reduced +;; args and hashes to different e-classes. So this stays exponential (× per-step +;; union cost); keep n small for the viz. Larger n + memo-collapse (call-by-value +;; memo key) + incremental observer is the SH/Zig-era perf frontier. + +spec fib Int -> Int +defn fib + | 0 -> 0 + | 1 -> 1 + | n -> [int+ [fib [int- n 1]] [fib [int- n 2]]] + +[fib 6] diff --git a/racket/prologos/examples/2026-06-14-onnetwork-reduction.prologos b/racket/prologos/examples/2026-06-14-onnetwork-reduction.prologos new file mode 100644 index 000000000..4da4dcd89 --- /dev/null +++ b/racket/prologos/examples/2026-06-14-onnetwork-reduction.prologos @@ -0,0 +1,51 @@ +ns examples.onnetwork-reduction + +;; PReduce-default + on-network-compute ACCEPTANCE FILE (Phase 0). +;; Exercises every reduction kind the ingest hooks cover (reduction.rkt): +;; β (lambda application), ι (Nat natrec), δ (definition unfolding), +;; int folds (int+/int-/int*). Every form is LIVE and must be 0-error. +;; Success instrument for the plan: export with +;; racket tools/viz-export.rkt examples/2026-06-14-onnetwork-reduction.prologos -o out.json +;; and confirm each phase's new on-network structure is visible. + +def tag : String := "onnetwork-reduction-acceptance" + +;; ---- δ : definition unfolding chains ---- +def base := 7 +def derived := [int* base 6] +def chained := [int+ derived [int- base 2]] +chained + +;; ---- β : explicit lambda application ---- +[[fn [x : Int] [int* x 2]] 21] + +spec twice [Int -> Int] Int -> Int +defn twice [f x] [f [f x]] +[twice [fn [n : Int] [int+ n 1]] 10] + +;; ---- ι : Nat structural recursion (natrec) ---- +spec is-zero Nat -> Bool +defn is-zero + | zero -> true + | suc _ -> false +[is-zero 3N] +[is-zero 0N] + +;; ---- int recursion + δ + arithmetic : factorial / fib ---- +spec factorial Int -> Int +defn factorial + | 0 -> 1 + | n -> [int* n [factorial [int- n 1]]] +[factorial 5] +[factorial 6] + +spec fib Int -> Int +defn fib + | 0 -> 0 + | 1 -> 1 + | n -> [int+ [fib [int- n 1]] [fib [int- n 2]]] +[fib 8] + +;; ---- int folds : a balanced arithmetic tree (wide parallel reduction) ---- +[int+ [int+ [int* 6 7] [int- 100 58]] [int+ [int* 3 4] [int* 8 9]]] +[int* [int+ 3 4] [int- 50 8]] diff --git a/racket/prologos/examples/2026-06-14-parallel-reduction.prologos b/racket/prologos/examples/2026-06-14-parallel-reduction.prologos new file mode 100644 index 000000000..85934ad23 --- /dev/null +++ b/racket/prologos/examples/2026-06-14-parallel-reduction.prologos @@ -0,0 +1,7 @@ +ns examples.parallel-reduction + +;; A balanced arithmetic tree: every leaf multiplication is independent and +;; reduces in the SAME BSP round (wide parallel reduction); then each layer of +;; sums reduces in parallel. The shape of the mantra: all-at-once, in parallel. + +[int+ [int+ [int+ [int+ [int+ [int+ [int* 1 6] [int* 9 5]] [int+ [int* 3 5] [int* 5 2]]] [int+ [int+ [int* 6 5] [int* 5 3]] [int+ [int* 1 6] [int* 5 7]]]] [int+ [int+ [int+ [int* 7 3] [int* 4 6]] [int+ [int* 6 4] [int* 4 4]]] [int+ [int+ [int* 1 9] [int* 2 3]] [int+ [int* 3 1] [int* 4 5]]]]] [int+ [int+ [int+ [int+ [int* 7 5] [int* 7 9]] [int+ [int* 8 6] [int* 1 5]]] [int+ [int+ [int* 8 5] [int* 7 6]] [int+ [int* 2 1] [int* 6 5]]]] [int+ [int+ [int+ [int* 8 8] [int* 3 3]] [int+ [int* 1 5] [int* 6 2]]] [int+ [int+ [int* 3 1] [int* 8 1]] [int+ [int* 4 1] [int* 5 2]]]]]] [int+ [int+ [int+ [int+ [int+ [int* 1 8] [int* 3 2]] [int+ [int* 4 1] [int* 4 3]]] [int+ [int+ [int* 8 7] [int* 8 1]] [int+ [int* 1 2] [int* 4 4]]]] [int+ [int+ [int+ [int* 9 1] [int* 8 3]] [int+ [int* 6 5] [int* 4 3]]] [int+ [int+ [int* 5 9] [int* 2 4]] [int+ [int* 8 1] [int* 7 7]]]]] [int+ [int+ [int+ [int+ [int* 2 8] [int* 4 9]] [int+ [int* 9 6] [int* 8 8]]] [int+ [int+ [int* 4 9] [int* 7 3]] [int+ [int* 3 8] [int* 4 6]]]] [int+ [int+ [int+ [int* 7 9] [int* 9 6]] [int+ [int* 4 3] [int* 8 3]]] [int+ [int+ [int* 1 8] [int* 3 2]] [int+ [int* 6 8] [int* 5 2]]]]]]] diff --git a/racket/prologos/examples/2026-06-14-reduction-showcase.prologos b/racket/prologos/examples/2026-06-14-reduction-showcase.prologos new file mode 100644 index 000000000..8c0408293 --- /dev/null +++ b/racket/prologos/examples/2026-06-14-reduction-showcase.prologos @@ -0,0 +1,32 @@ +ns examples.reduction-showcase + +;; Many INDEPENDENT reductions. Independent subexpressions elaborate and reduce +;; as parallel propagators (wide BSP rounds fire dozens at once), and on-network +;; reduction (PREDUCE_INGEST=1) records each β/δ/ι step into the e-graph. + +spec factorial Int -> Int +defn factorial + | 0 -> 1 + | n -> [int* n [factorial [int- n 1]]] + +spec fib Int -> Int +defn fib + | 0 -> 0 + | 1 -> 1 + | n -> [int+ [fib [int- n 1]] [fib [int- n 2]]] + +[factorial 5] +[factorial 6] +[factorial 7] +[fib 7] +[fib 8] +[fib 9] + +[int+ [int* 6 7] [int- 100 58]] +[int* [int+ 3 4] [int+ 5 6]] +[int- [int* 9 9] [int+ 20 21]] +[int+ [int+ 1 2] [int+ 3 4]] +[int* [int- 50 8] [int+ 1 1]] + +[int+ [factorial 4] [fib 6]] +[int* [factorial 3] [factorial 3]] diff --git a/racket/prologos/propagator.rkt b/racket/prologos/propagator.rkt index 75cc939ad..c84e7e2d2 100644 --- a/racket/prologos/propagator.rkt +++ b/racket/prologos/propagator.rkt @@ -101,6 +101,8 @@ retraction-stratum-request-cell-id congruence-sig-index-cell-id ;; PReduce Track 1 11b congruence-request-cell-id ;; PReduce Track 1 11b + dispatch-request-cell-id ;; PReduce Track 8 Phase 2 + reduce-request-cell-id ;; PReduce Track 8 Phase 5b resolution-stratum-request-cell-id retraction-stratum-merge resolution-stratum-merge @@ -810,6 +812,34 @@ [(not (hash? new)) old] [else (for/fold ([acc old]) ([(k v) (in-hash new)]) (hash-set acc k v))])) +;; PReduce Track 8 Phase 2: cell-22 dispatch request — {class-cid → #t}, the +;; per-round delta of newly-interned redex classes whose head should be matched +;; against the rule registry. preduce-ingest-int installs an S0 emitter on the +;; redex class that writes this cell; the dispatch stratum handler +;; (rule-dispatch.rkt) reads it and runs dispatch-rules per class (→ apply-rule +;; → eclass-union). This moves dispatch from an imperative call to a STRATUM +;; FIRING — "rule application IS propagator firing" for the arithmetic path (the +;; recursion path β/δ/ι is the deeper Phase 4). Mirrors the congruence engine +;; (cell-21 + process-congruence-requests). +(define dispatch-request-cell-id (cell-id 22)) +(define (dispatch-request-merge old new) + (cond + [(not (hash? old)) new] + [(not (hash? new)) old] + [else (for/fold ([acc old]) ([(k v) (in-hash new)]) (hash-set acc k v))])) + +;; PReduce Track 8 Phase 5b: cell-23 reduce request — {origin-class-cid → current-form}, +;; the network-DRIVEN reduction cascade (design §9). Origin-keyed (K fixed, form +;; evolves) so extraction is a simple K :best write. hash-OVERWRITE merge (latest +;; form per K wins). Driven by the keep-pending reduce stratum (reduction.rkt); the +;; step arm's eclass-union supplies the worklist activity that re-triggers it. +(define reduce-request-cell-id (cell-id 23)) +(define (reduce-request-merge old new) + (cond + [(not (hash? old)) new] + [(not (hash? new)) old] + [else (for/fold ([acc old]) ([(k v) (in-hash new)]) (hash-set acc k v))])) + ;; Merges for the 2A.0 stratum-request cells. Local definitions per ;; propagator.rkt's existing pattern (cf. naf-pending-merge at line 622, ;; topology-request-merge at line 686). Defined locally because @@ -1247,6 +1277,31 @@ (error 'make-prop-network "congruence-request-cell-id allocation drift: expected ~a, got ~a" congruence-request-cell-id actual-congruence-request-cid)) + ;; PReduce Track 8 Phase 2: cell-22 dispatch request. Empty accumulator until + ;; preduce-ingest-int installs an emitter + rule-dispatch.rkt registers the + ;; handler — zero behavior change for non-e-class networks (the dispatch + ;; stratum's empty-pending check skips). Same posture as cell-20/21. + (define-values (net12 actual-dispatch-request-cid) + (net-register-specialized-cell net11 (hash) dispatch-request-merge + #:tier 'warm + #:storage 'general + #:fires-on 'any-change)) + (unless (equal? actual-dispatch-request-cid dispatch-request-cell-id) + (error 'make-prop-network + "dispatch-request-cell-id allocation drift: expected ~a, got ~a" + dispatch-request-cell-id actual-dispatch-request-cid)) + ;; PReduce Track 8 Phase 5b: cell-23 reduce request. Empty until + ;; whnf-via-egraph installs an emitter + reduction.rkt registers the keep-pending + ;; reduce stratum — zero behavior change otherwise (empty-pending skip). + (define-values (net13 actual-reduce-request-cid) + (net-register-specialized-cell net12 (hash) reduce-request-merge + #:tier 'warm + #:storage 'general + #:fires-on 'any-change)) + (unless (equal? actual-reduce-request-cid reduce-request-cell-id) + (error 'make-prop-network + "reduce-request-cell-id allocation drift: expected ~a, got ~a" + reduce-request-cell-id actual-reduce-request-cid)) ;; D.4 1V-3 Item #1-bis (§11.X.3 step 3): set fuel-cell-cache on prop-net-warm. ;; D.4 1V-5 Item #1-quater (§11.X.4 step 3): set worldview-cache-cache on prop-net-warm. ;; Both cells now registered (worldview-cache at base-net; fuel-cell at net2); @@ -1258,11 +1313,11 @@ ;; sharing into net9's cells map. Direct-refs lookup from net9's cells CHAMP ;; retrieves the original prop-cells. (let* ([fc-h (cell-id-hash fuel-cell-id)] - [fc-cell (champ-lookup (prop-network-cells net11) fc-h fuel-cell-id)] + [fc-cell (champ-lookup (prop-network-cells net13) fc-h fuel-cell-id)] [wv-h (cell-id-hash worldview-cache-cell-id)] - [wv-cell (champ-lookup (prop-network-cells net11) wv-h worldview-cache-cell-id)]) - (struct-copy prop-network net11 - [warm (struct-copy prop-net-warm (prop-network-warm net11) + [wv-cell (champ-lookup (prop-network-cells net13) wv-h worldview-cache-cell-id)]) + (struct-copy prop-network net13 + [warm (struct-copy prop-net-warm (prop-network-warm net13) [fuel-cell-cache fc-cell] [worldview-cache-cache wv-cell])]))) @@ -3458,17 +3513,56 @@ ;; No speculation, no branching, no NAF, no inter-propagator dependencies. ;; Fire all worklist propagators directly on canonical. One pass. ;; No snapshot, no dedup, no topology, no strata. - (define result - (for/fold ([n net]) - ([pid (in-list (prop-network-worklist net))]) - (define prop (champ-lookup (prop-network-propagators n) - (prop-id-hash pid) pid)) - (if (eq? prop 'none) n - (fire-propagator prop n)))) ;; PPN 4C Phase 1.5 + ;; PTF Track 2 Phase 2b: Tier-1 runs were observer-invisible (gap G3) — + ;; fire-once-only programs traced EMPTY. When an observer is armed, fire + ;; with per-fire eq?-pruned champ-diff (O(changed) per fire) for precise + ;; cell-diff attribution and emit ONE bsp-round mirroring Tier-2's shape. + ;; The unarmed fold is unchanged — zero cost when no observer. + (define-values (result t1-diffs t1-pids) + (if observer + (for/fold ([n net] [diffs '()] [pids '()]) + ([pid (in-list (prop-network-worklist net))]) + (define prop (champ-lookup (prop-network-propagators n) + (prop-id-hash pid) pid)) + (if (eq? prop 'none) + (values n diffs pids) + (let ([n2 (fire-propagator prop n)]) + (define-values (chg nw) + (champ-diff (prop-network-cells n) + (prop-network-cells n2) + (lambda (o r) (equal? (prop-cell-value o) + (prop-cell-value r))))) + (define diffs* + (for/fold ([d diffs]) + ([cv (in-list (append chg nw))]) + (define cid (car cv)) + (define old (champ-lookup (prop-network-cells n) + (cell-id-hash cid) cid)) + (cons (cell-diff cid + (if (eq? old 'none) + 'bot + (prop-cell-value old)) + (prop-cell-value (cdr cv)) + pid) + d))) + (values n2 diffs* (cons pid pids))))) + (values (for/fold ([n net]) + ([pid (in-list (prop-network-worklist net))]) + (define prop (champ-lookup (prop-network-propagators n) + (prop-id-hash pid) pid)) + (if (eq? prop 'none) n + (fire-propagator prop n))) ;; PPN 4C Phase 1.5 + '() + '()))) ;; Clear worklist after flush - (struct-copy prop-network result - [hot (struct-copy prop-net-hot (prop-network-hot result) - [worklist '()])])] + (define cleared + (struct-copy prop-network result + [hot (struct-copy prop-net-hot (prop-network-hot result) + [worklist '()])])) + (when observer + (observer (bsp-round 0 cleared (reverse t1-diffs) (reverse t1-pids) + (prop-network-contradiction cleared) '()))) + cleared] [else ;; TIER 2 (or empty worklist): full BSP with optimizations. diff --git a/racket/prologos/reduction.rkt b/racket/prologos/reduction.rkt index 62dee5eda..a00cc53e3 100644 --- a/racket/prologos/reduction.rkt +++ b/racket/prologos/reduction.rkt @@ -44,9 +44,10 @@ "prop-observatory.rkt") ;; Observatory: capture user network runs (provide whnf nf nf-whnf conv conv-nf - current-preduce-ingest? ;; PReduce Track 2 ingestion gate (iter 22) - current-preduce-ingest-int-folds? ;; iter 33: the selectivity lever preduce-ingest-delta ;; 2026-06-11: exported for consult-wiring tests + whnf-step1 whnf-via-egraph ;; Phase 5a: network-driven reduction engine + whnf-via-egraph-network ;; Phase 5b: scheduler-driven cascade + current-egraph-whnf? ;; Phase 5c: route the default whnf through it current-nf-cache current-whnf-cache current-reduction-fuel current-nat-value-cache ;; Solver normalization (for benchmarks + PUnify) @@ -1311,21 +1312,15 @@ (expr-error? e)))) ;; error propagation ;; ======================================== -;; PReduce Track 2 ingestion hook (iter 22; Track 2 design §4 — LAZY ingestion). -;; PARAMETER-GATED, DEFAULT OFF: this unit's deliverable is the OVERHEAD-FLOOR -;; instrument (what intern+dispatch costs per arithmetic position — the bound on -;; what δ/β must later save), NOT a deployment. The flip criterion is NAMED: -;; δ (Phase 2) + guarded β (Phase 3) landed AND the §5.8 A/B positive — then the -;; default flips or the hook reverts (validated≠deployed; no permanent dual path). -;; Speculative-context note: e-graph writes during speculative whnf persist as -;; MONOTONE GARBAGE (dead-branch interns) — sound-but-wasteful, the same class as -;; stale-canonical duplicate allocation (eclass-graph.rkt header). -(define current-preduce-ingest? (make-parameter #f)) -;; SELECTIVE ingestion (iter 33 — the Track 4 PIR §15 lever): int-fold ingestion -;; gated SEPARATELY; the floor data says blanket small-fold floods are the -;; overhead source while δ/β memos carry the value. Default #t preserves the -;; ALL mode; the driver's PREDUCE_INGEST=db sets it #f (δ/β-only). -(define current-preduce-ingest-int-folds? (make-parameter #t)) +;; PReduce ingestion hook (Track 2 design §4 — LAZY ingestion). +;; ON-NETWORK REDUCTION IS THE ONLY PATH on this branch (2026-06-14 owner +;; directive): the parameter gate and the off-network native arms were deleted; +;; β/ι/δ/int-folds always route through the e-graph ingest, which degrades to the +;; native step (#:compute / op-fn) when the e-graph infra is absent, so the hooks +;; are behaviorally total. Speculative-context note: e-graph writes during +;; speculative whnf persist as MONOTONE GARBAGE (dead-branch interns) — +;; sound-but-wasteful, the same class as stale-canonical duplicate allocation +;; (eclass-graph.rkt header). (define (preduce-ingest-int e op-sym op-fn a b) ;; e-graph round-trip when the plumbing is live; NATIVE fold otherwise — @@ -1339,7 +1334,21 @@ (define form (list op-sym (list 'lit a) (list 'lit b))) (define net0 (unbox prn-box)) (define-values (net1 cid _d) (pr/eclass-intern net0 hc form #:cost 5)) - (define-values (net2 _fired) (pr/dispatch-rules net1 hc reg cid #:result-cost 1)) + ;; Phase 2 (PReduce Track 8): dispatch is a STRATUM FIRING, not an imperative + ;; call. Install a fire-once S0 emitter on the redex class that writes the + ;; class to the dispatch-request cell; run-to-quiescence fires it (worklist + ;; activity → the BSP loop), and the topology-tier dispatch handler + ;; (rule-dispatch.rkt process-dispatch-requests) then runs dispatch-rules → + ;; apply-rule → eclass-union, landing the folded result as :best. We read + ;; AFTER quiescence — same contract as the old direct dispatch-rules call; + ;; only WHO triggers dispatch moved on-network. The emitter WATCHES cid + ;; (non-empty inputs) so the worklist is NOT Tier-1-fast-path eligible + ;; (Tier-1 skips strata, which would skip dispatch). + (define-values (net1a _epid) + (net-add-fire-once-propagator + net1 (list cid) (list dispatch-request-cell-id) + (lambda (n) (net-cell-write n dispatch-request-cell-id (hash cid #t))))) + (define net2 (run-to-quiescence net1a)) (set-box! prn-box net2) (define best (hash-ref (pr/eclass-read net2 cid) ':best #f)) (match best @@ -1400,18 +1409,37 @@ (set-box! prn-box net2) stored] [else - ;; the native step, then the result joins as the cost-0 best AND - ;; records in the store. The RESULT may itself be inadmissible — - ;; encode-check it by writing under the same handler: an - ;; inadmissible RESULT aborts the memo + store writes (the class - ;; keeps the body alone) but still returns the result. + ;; the native step, then the result joins the redex class as a UNION + ;; PROPAGATOR (Phase 4a — PReduce Track 8): the reduction step is the + ;; DPO {redex, result} e-class joined by eclass-union (the SAME + ;; mechanism as the arithmetic fold's union), NOT a bare cell-write — + ;; so the step is visible as PROPAGATION (the viz goal: recursion + ;; performed by propagators). + ;; + ;; Net threading: we PUBLISH the redex class (net1) before compute and + ;; read the POST-compute net, so the recursion SUBTREE — the sub-step + ;; unions that compute builds via nested preduce calls — PERSISTS in + ;; the e-graph (the pre-4a code discarded compute's net, keeping only + ;; the top redex→result). Persisting the subtree also lets the + ;; hashcons memo SHARE repeated sub-redexes (e.g. naive fib's + ;; overlapping calls), which the discard previously defeated. The + ;; e-graph grows O(distinct subterms) — accepted (plan §1; perf + ;; regression expected on this branch). + ;; + ;; The RESULT may be inadmissible — eclass-intern raises; the handler + ;; returns the result unmemoized (the class keeps the body alone) but + ;; still computes. (compute's own mutations already published via the + ;; set-box! below remain — sound monotone garbage.) + (set-box! prn-box net1) (define result (compute)) (with-handlers ([exn:fail? (lambda (_e) result)]) - (define net2 (net-cell-write net1 cid - (pr/make-eclass-value #:best (cons 0 result)))) + (define net-post (unbox prn-box)) + (define-values (net2 result-cid _rd) + (pr/eclass-intern net-post hc result #:cost 0)) + (define net2b (run-to-quiescence (pr/eclass-union net2 cid result-cid))) (define net3 (if store-cid - (pr/store-record-reduction net2 store-cid cid result) - net2)) + (pr/store-record-reduction net2b store-cid cid result) + net2b)) (set-box! prn-box net3) result)])]))] [else (compute)])) @@ -1440,7 +1468,24 @@ ;; Use (box N) to set a limit; whnf-impl decrements on each call. (define current-reduction-fuel (make-parameter #f)) +;; Phase 5c: the DEFAULT whnf routes through the scheduler-driven cascade +;; (whnf-via-egraph-network) — the cascade is the on-network reduction DRIVER for +;; ground terms (owner directive 2026-06-15: "delete the recursive off-network +;; DRIVER, keep the compute leaf"). The native reducer (whnf-core/whnf-impl) is +;; retained NOT as the driver but as: (a) the primitive COMPUTE LEAF reached via +;; whnf-step1's 'native (arithmetic folds, data-structure ops, FFI — "compute +;; inside the rule", the e-graph's own design), and (b) the NON-GROUND fallback: +;; the e-graph requires PCE-admissible (ground) terms, so metavar/elaboration +;; reduction can't be interned and de-routes via whnf-native. Set #f to force the +;; native driver (e.g. test-preduce-ingest, which tests the 4a recording path). +(define current-egraph-whnf? (make-parameter (not (getenv "PREDUCE_NATIVE")))) + (define (whnf e) + (if (current-egraph-whnf?) + (whnf-via-egraph-network e) + (whnf-core e))) + +(define (whnf-core e) (define cache (current-whnf-cache)) (cond [(and cache (hash-ref cache e #f)) @@ -1451,6 +1496,12 @@ (hash-set! cache e result)) result])) +;; the native reducer with routing DISABLED for the whole sub-reduction — the +;; cascade's 'native / no-plumbing / inadmissible fallbacks use this so the native +;; one-step compute (and its internal whnf recursion) never re-enters the cascade. +(define (whnf-native e) + (parameterize ([current-egraph-whnf? #f]) (whnf-core e))) + ;; Fast-path: is this expression definitely already in WHNF? ;; Returns #t for type atoms, type constructors, value constructors, ;; compound type formers, lambdas, pairs, and unions — expressions that @@ -1518,20 +1569,35 @@ (define (whnf-impl/match e) (match e ;; Beta reduction: app(lam(m, A, body), arg) -> whnf(subst(0, arg, body)) - ;; PReduce guarded β (Phase 3, iter 25; gated, default OFF): the redex - ;; memoizes as {redex, whnf(redex)} — the SAME e-class mechanics as δ — - ;; UNLESS the arg is effect-headed (pessimistic guard + counter; the e-graph - ;; never records a β that could dedup/delete an effect — D.1 §6.2; the - ;; native β below stays sound for the legacy semantics). - [(and redex (expr-app (expr-lam _ _ body) arg)) - #:when (current-preduce-ingest?) + ;; PReduce guarded β: the redex memoizes ON-NETWORK as {redex, whnf(redex)} + ;; — the SAME e-class mechanics as δ — UNLESS the arg is effect-headed + ;; (pessimistic guard + counter; the e-graph never records a β that could + ;; dedup/delete an effect — D.1 §6.2; the guard path still computes natively). + ;; (Off-network native β arm deleted 2026-06-14: on-network reduction is the + ;; only path on this branch; preduce-ingest-delta degrades to #:compute when + ;; the e-graph infra is absent, so this is behaviorally total.) + [(and redex (expr-app (expr-lam m A body) arg)) (if (expr-head-effectful? arg) (begin (pr/guard-skip-note!) (whnf (subst 0 arg body))) - (preduce-ingest-delta redex - #:compute (lambda () (whnf (subst 0 arg body)))))] - [(expr-app (expr-lam _ _ body) arg) - (whnf (subst 0 arg body))] + ;; CALL-BY-VALUE MEMO KEY (Phase 4a perf): key the β redex by the + ;; NORMALIZED arg, so recursive calls reaching the same value share ONE + ;; e-class and the recursion MEMO-COLLAPSES (e.g. naive fib: [fib 5] + ;; reached via (int- 6 1) vs (int- 7 2) now share (app (int 5))). + ;; Reduction stays CALL-BY-NAME — #:compute substitutes the ORIGINAL arg; + ;; only the memo KEY is normalized. Sound: arg-nf and arg denote the same + ;; value, so the contractum is identical (whnf is deterministic). The + ;; key-normalization is BOUNDED (a private fuel box, so it never depletes + ;; the real budget) and GUARDED (a divergent/erroring arg that + ;; call-by-name would not force falls back to the ORIGINAL redex key — no + ;; collapse for that redex, but no regression and no spurious divergence). + (let* ([fb (current-reduction-fuel)] + [arg-nf (with-handlers ([exn:fail? (lambda (_e) arg)]) + (parameterize ([current-reduction-fuel + (box (if fb (unbox fb) 1000000))]) + (whnf arg)))]) + (preduce-ingest-delta (expr-app (expr-lam m A body) arg-nf) + #:compute (lambda () (whnf (subst 0 arg body))))))] ;; Projections on pairs [(expr-fst (expr-pair e1 _)) (whnf e1)] @@ -1539,13 +1605,12 @@ ;; Iota reduction for natrec — native nat-val (Idris 2 model) [(expr-natrec _ base _ (expr-nat-val n)) #:when (= n 0) (whnf base)] - ;; PReduce ι ingestion (Track 3 Phase 1, iter 41; gated, default OFF): - ;; the natrec recursion carriers memoize as {redex, result} e-classes — - ;; the δ mechanics verbatim (#:compute = the native step; totality). - ;; Guard: an effect-headed base/step skips the recording (pessimistic; - ;; native ι stays legacy-sound). + ;; PReduce ι ingestion: the natrec recursion carriers memoize ON-NETWORK as + ;; {redex, result} e-classes — the δ mechanics verbatim. An effect-headed + ;; base/step skips the recording (pessimistic; the guard path computes + ;; natively). (Off-network native ι arms deleted 2026-06-14.) [(and redex (expr-natrec mot base step (expr-nat-val n))) - #:when (and (> n 0) (current-preduce-ingest?)) + #:when (> n 0) (if (or (expr-head-effectful? base) (expr-head-effectful? step)) (begin (pr/guard-skip-note!) (whnf (expr-app (expr-app step (expr-nat-val (- n 1))) @@ -1554,13 +1619,9 @@ #:compute (lambda () (whnf (expr-app (expr-app step (expr-nat-val (- n 1))) (expr-natrec mot base step (expr-nat-val (- n 1))))))))] - [(expr-natrec mot base step (expr-nat-val n)) #:when (> n 0) - (whnf (expr-app (expr-app step (expr-nat-val (- n 1))) - (expr-natrec mot base step (expr-nat-val (- n 1)))))] ;; Iota reduction for natrec — legacy Peano representation [(expr-natrec _ base _ (expr-zero)) (whnf base)] [(and redex (expr-natrec mot base step (expr-suc n))) - #:when (current-preduce-ingest?) (if (or (expr-head-effectful? base) (expr-head-effectful? step) (expr-head-effectful? n)) (begin (pr/guard-skip-note!) @@ -1569,8 +1630,6 @@ #:compute (lambda () (whnf (expr-app (expr-app step n) (expr-natrec mot base step n))))))] - [(expr-natrec mot base step (expr-suc n)) - (whnf (expr-app (expr-app step n) (expr-natrec mot base step n)))] ;; Suc collapse: concrete inner → native nat-val [(expr-suc (expr-nat-val k)) (expr-nat-val (+ k 1))] @@ -1656,20 +1715,12 @@ ;; ---- Int iota rules: compute when arguments are int literals ---- - ;; Binary arithmetic on literals - ;; PReduce ingestion (gated, default OFF — see preduce-ingest-int above): - [(expr-int-add (expr-int a) (expr-int b)) - #:when (and (current-preduce-ingest?) (current-preduce-ingest-int-folds?)) - (preduce-ingest-int e 'int+ + a b)] - [(expr-int-sub (expr-int a) (expr-int b)) - #:when (and (current-preduce-ingest?) (current-preduce-ingest-int-folds?)) - (preduce-ingest-int e 'int- - a b)] - [(expr-int-mul (expr-int a) (expr-int b)) - #:when (and (current-preduce-ingest?) (current-preduce-ingest-int-folds?)) - (preduce-ingest-int e 'int* * a b)] - [(expr-int-add (expr-int a) (expr-int b)) (expr-int (+ a b))] - [(expr-int-sub (expr-int a) (expr-int b)) (expr-int (- a b))] - [(expr-int-mul (expr-int a) (expr-int b)) (expr-int (* a b))] + ;; Binary arithmetic on literals — ON-NETWORK via the e-graph ingest path + ;; (preduce-ingest-int interns + folds, degrading to the native op when the + ;; e-graph infra is absent). Off-network native folds deleted 2026-06-14. + [(expr-int-add (expr-int a) (expr-int b)) (preduce-ingest-int e 'int+ + a b)] + [(expr-int-sub (expr-int a) (expr-int b)) (preduce-ingest-int e 'int- - a b)] + [(expr-int-mul (expr-int a) (expr-int b)) (preduce-ingest-int e 'int* * a b)] [(expr-int-div (expr-int a) (expr-int b)) (if (zero? b) e (expr-int (quotient a b)))] [(expr-int-mod (expr-int a) (expr-int b)) @@ -3185,12 +3236,15 @@ ;; Constructor and type-name fvars are canonical — do NOT unfold. ;; This keeps constructor applications as (fvar 'cons arg1 arg2) in WHNF, ;; allowing structural PM (try-structural-reduce) to decompose them. - ;; PReduce δ-memo (gated, default OFF — see preduce-ingest-delta above): + ;; PReduce δ-memo: non-constructor defn references unfold ON-NETWORK via the + ;; e-graph ({body, whnf(body)} as one e-class), degrading to whnf(body) when + ;; the e-graph infra is absent. The native arm below now handles only + ;; constructor/type fvars (canonical, not unfolded). Off-network δ path + ;; deleted 2026-06-14 — on-network reduction is the only path on this branch. [(expr-fvar name) - #:when (and (current-preduce-ingest?) - (not (or (lookup-ctor name) (lookup-ctor (ctor-short-name name)) - (lookup-type-ctors name) - (lookup-type-ctors (ctor-short-name name))))) + #:when (not (or (lookup-ctor name) (lookup-ctor (ctor-short-name name)) + (lookup-type-ctors name) + (lookup-type-ctors (ctor-short-name name)))) (let ([val (global-env-lookup-value name)]) (if val (preduce-ingest-delta val) e))] [(expr-fvar name) @@ -3211,6 +3265,311 @@ ;; ======================================== ;; Full Normalization +;; ======================================== +;; Phase 5a (PReduce Track 8): network-driven reduction engine — the ONE-STEP +;; classifier + an extraction-style driver. The design's foundational primitive +;; (2026-06-14_PREDUCE_T8_PHASE4b5_NETWORK_DRIVEN_REDUCTION.md §3). PARITY-gated +;; against native whnf (tests/test-preduce-egraph.rkt). The scheduler-driven +;; cascade (reduce stratum) is the staged next increment; this validates the +;; one-step decomposition is parity-correct and gives whnf-via-egraph (the +;; intern→reduce→extract shape Phase 5 generalizes). +;; +;; whnf-step1 : expr -> 'whnf | 'native | (cons 'step C) +;; | (list 'demand SUB RECON) +;; | (list 'demand-par OPERANDS RECON*) +;; 'whnf — E is weak-head-normal (value/neutral) +;; 'native — construct not yet migrated; driver delegates to native whnf +;; (staged fallback — shrinks as constructs migrate) +;; (cons 'step C) — E head-reduces ONE step to C +;; (list 'demand SUB RECON) — E is STRICT in SUB; reduce SUB to WHNF, then the +;; next form is (RECON sub-nf). Mis-classification is parity-safe +;; (the migrated arms mirror whnf-impl/match; the rest is 'native). +;; (list 'demand-par OPERANDS RECON*) — E is STRICT in N INDEPENDENT operands +;; (binary arithmetic: both args, neither depends on the other). +;; The operands reduce TOGETHER (network driver: one cascade, +;; interleaved per BSP round — "all in parallel"; iterating +;; driver: sequentially, parity-identical). RECON* takes the LIST +;; of reduced operands (positional) and re-forms the head. +;; This is the fix for "reduction serializes independent +;; subterms": single-subterm 'demand sequenced operand i before +;; operand j even when independent (e.g. fib's two recursive +;; branches under int+); 'demand-par lets them progress in the +;; same rounds. + +;; step1-par : classify a strict-in-N-independent-operands head. If EVERY operand +;; is already a value (whnf-trivial), the primitive is ready to fold → 'native (the +;; compute leaf — int-int fast-folds are matched by the literal arms before this). +;; Otherwise the operands are reduced in parallel via 'demand-par; recon* re-forms +;; the head from the positional list of reduced operands. +(define (step1-par operands recon*) + (if (andmap whnf-trivial? operands) + 'native + (list 'demand-par operands recon*))) + +(define (whnf-step1 e) + (cond + [(whnf-trivial? e) 'whnf] + [else + (match e + ;; β (head). Effect-headed arg → native (the guard path; never recorded). + [(expr-app (expr-lam _ _ body) arg) + (if (expr-head-effectful? arg) 'native (cons 'step (subst 0 arg body)))] + ;; projections on pairs (head) + [(expr-fst (expr-pair e1 _)) (cons 'step e1)] + [(expr-snd (expr-pair _ e2)) (cons 'step e2)] + ;; ι natrec (head) — nat-val / legacy zero+suc. Effect-headed → native. + [(expr-natrec _ base _ (expr-nat-val n)) #:when (= n 0) (cons 'step base)] + [(expr-natrec mot base step (expr-nat-val n)) + #:when (> n 0) + (if (or (expr-head-effectful? base) (expr-head-effectful? step)) + 'native + (cons 'step (expr-app (expr-app step (expr-nat-val (- n 1))) + (expr-natrec mot base step (expr-nat-val (- n 1))))))] + [(expr-natrec _ base _ (expr-zero)) (cons 'step base)] + [(expr-natrec mot base step (expr-suc n)) + (if (or (expr-head-effectful? base) (expr-head-effectful? step) + (expr-head-effectful? n)) + 'native + (cons 'step (expr-app (expr-app step n) (expr-natrec mot base step n))))] + ;; suc-collapse (head) + [(expr-suc (expr-nat-val k)) (cons 'step (expr-nat-val (+ k 1)))] + [(expr-suc (expr-zero)) (cons 'step (expr-nat-val 1))] + ;; J / boolrec / ann / vhead / vtail (head) + [(expr-J _ base left _ (expr-refl)) (cons 'step (expr-app base left))] + [(expr-boolrec _ tc _ (expr-true)) (cons 'step tc)] + [(expr-boolrec _ _ fc (expr-false)) (cons 'step fc)] + [(expr-ann e1 _) (cons 'step e1)] + [(expr-vhead _ _ (expr-vcons _ _ hd _)) (cons 'step hd)] + [(expr-vtail _ _ (expr-vcons _ _ _ tl)) (cons 'step tl)] + ;; demand arms — reduce the head-exposing subterm first (mirror the native + ;; "reduce subterm, then retry" arms). RECON re-forms the head with the nf. + [(expr-app f a) + #:when (not (expr-lam? f)) + (list 'demand f (lambda (f-nf) (expr-app f-nf a)))] + [(expr-fst e1) (list 'demand e1 (lambda (x) (expr-fst x)))] + [(expr-snd e1) (list 'demand e1 (lambda (x) (expr-snd x)))] + [(expr-natrec mot base step target) + (list 'demand target (lambda (t) (expr-natrec mot base step t)))] + [(expr-J mot base left right proof) + (list 'demand proof (lambda (p) (expr-J mot base left right p)))] + [(expr-boolrec mot tc fc target) + (list 'demand target (lambda (t) (expr-boolrec mot tc fc t)))] + [(expr-vhead t n v) (list 'demand v (lambda (x) (expr-vhead t n x)))] + [(expr-vtail t n v) (list 'demand v (lambda (x) (expr-vtail t n x)))] + ;; int arithmetic (Phase 5c migration batch 1): fold when both operands are + ;; int literals; else demand the non-value operand. Both-values-but-not-both- + ;; int (rat/posit/coercion) and div/mod (zero-guard) → native — parity-safe. + [(expr-int-add (expr-int a) (expr-int b)) (cons 'step (expr-int (+ a b)))] + [(expr-int-sub (expr-int a) (expr-int b)) (cons 'step (expr-int (- a b)))] + [(expr-int-mul (expr-int a) (expr-int b)) (cons 'step (expr-int (* a b)))] + [(expr-int-neg (expr-int a)) (cons 'step (expr-int (- a)))] + [(expr-int-abs (expr-int a)) (cons 'step (expr-int (abs a)))] + [(expr-int-lt (expr-int a) (expr-int b)) (cons 'step (if (< a b) (expr-true) (expr-false)))] + [(expr-int-le (expr-int a) (expr-int b)) (cons 'step (if (<= a b) (expr-true) (expr-false)))] + [(expr-int-eq (expr-int a) (expr-int b)) (cons 'step (if (= a b) (expr-true) (expr-false)))] + ;; binary arithmetic — STRICT in BOTH operands, which are INDEPENDENT (neither + ;; depends on the other). 'demand-par reduces them together (the fix for + ;; serialized independent subterms). recon* re-forms the head positionally; + ;; the both-int literal arms above fold first, so by the time recon* re-forms + ;; with both operands reduced, the literal arm fires next round. + [(expr-int-add a b) (step1-par (list a b) (lambda (vs) (expr-int-add (car vs) (cadr vs))))] + [(expr-int-sub a b) (step1-par (list a b) (lambda (vs) (expr-int-sub (car vs) (cadr vs))))] + [(expr-int-mul a b) (step1-par (list a b) (lambda (vs) (expr-int-mul (car vs) (cadr vs))))] + [(expr-int-lt a b) (step1-par (list a b) (lambda (vs) (expr-int-lt (car vs) (cadr vs))))] + [(expr-int-le a b) (step1-par (list a b) (lambda (vs) (expr-int-le (car vs) (cadr vs))))] + [(expr-int-eq a b) (step1-par (list a b) (lambda (vs) (expr-int-eq (car vs) (cadr vs))))] + [(expr-int-neg a) (if (whnf-trivial? a) 'native (list 'demand a (lambda (x) (expr-int-neg x))))] + [(expr-int-abs a) (if (whnf-trivial? a) 'native (list 'demand a (lambda (x) (expr-int-abs x))))] + ;; structural reduce / match (Phase 5c batch 2): strict in the scrutinee. + ;; Try a constructor-decomposition arm on the current scrutinee; else demand + ;; the scrutinee; else (a value but no arm) a builtin-constructor arm; else + ;; stuck/neutral. Reuses the native try-structural-reduce / try-builtin-reduce. + [(expr-reduce scrut arms structural?) + (cond + [(try-structural-reduce scrut arms) => (lambda (r) (cons 'step r))] + [(not (whnf-trivial? scrut)) + (list 'demand scrut (lambda (s) (expr-reduce s arms structural?)))] + [(try-builtin-reduce scrut arms) => (lambda (r) (cons 'step r))] + [else 'whnf])] + ;; δ — fvar global-definition unfold. Constructor/type fvars are canonical. + [(expr-fvar name) + (cond + [(or (lookup-ctor name) (lookup-ctor (ctor-short-name name)) + (lookup-type-ctors name) (lookup-type-ctors (ctor-short-name name))) + 'whnf] + [(global-env-lookup-value name) => (lambda (val) (cons 'step val))] + [else 'whnf])] + ;; solved metavariable → its solution + [(expr-meta id cell-id) + (cond [(meta-solution/cell-id cell-id id) => (lambda (sol) (cons 'step sol))] + [else 'whnf])] + ;; rat/posit/quire/generic arith, maps/pvec/set + transients, net/cell/prop + ;; FFI, union-find, table-store, solver/goal, … : staged batches → native. + [_ 'native])])) + +;; whnf-via-egraph : reduce E to WHNF by ITERATING whnf-step1 (the extraction +;; shape — Phase 5 generalizes this to scheduler-driven saturation). Demand is +;; satisfied by a NESTED whnf-via-egraph on the strict subterm; no-progress (the +;; head still demands an already-normal subterm) is a neutral WHNF (the native +;; `(equal? e1* e1)` stuck check). Fuel-bounded (shares current-reduction-fuel). +(define (whnf-via-egraph e) + (let loop ([cur e]) + (define step (whnf-step1 cur)) + (cond + [(eq? step 'whnf) cur] + [(eq? step 'native) (whnf-native cur)] ; native reduces this construct (de-routed) + [(eq? (car step) 'step) (loop (cdr step))] + [(eq? (car step) 'demand-par) + ;; iterating variant: reduce each independent operand to WHNF (sequentially + ;; here — the network driver is what makes them parallel), assemble, recon. + (define operands (cadr step)) + (define recon* (caddr step)) + (define next (recon* (map whnf-via-egraph operands))) + (if (equal? next cur) cur (loop next))] + [else ;; 'demand + (define sub (cadr step)) + (define recon (caddr step)) + (define sub-nf (whnf-via-egraph sub)) + (define next (recon sub-nf)) + (if (equal? next cur) cur (loop next))]))) + +;; ==================================================================== +;; Phase 5b (PReduce Track 8): SCHEDULER-DRIVEN reduction — the genuine +;; network-DRIVE (design §9). The reduce DRIVER is the BSP scheduler (a +;; keep-pending reduce stratum), not a Racket loop. whnf-via-egraph-network +;; interns the term, kicks an emitter, and run-to-quiescence drives the head +;; cascade; the WHNF is EXTRACTED from the origin class's :best. +;; +;; Origin-keyed requests {K → current-form}: K (the origin class) is fixed, the +;; form evolves down the reduction → extraction is a simple K :best write. +;; The 'step arm's eclass-union supplies the S0 worklist activity that re-triggers +;; the stratum each round (the load-bearing cascade mechanism, §9). Demand +;; subterms are reduced via native whnf here (correct result; full cascade-driven +;; demand via a set-latch is the documented refinement) — the HEAD chain is +;; scheduler-driven. PARITY-gated (test-preduce-egraph.rkt network variant). + +;; write the WHNF as the origin class's cost-0 best (the extraction target) +(define (pr-write-whnf! net K F) + (net-cell-write net K (pr/make-eclass-value #:best (cons 0 F)))) + +;; record the step (union K with intern(C) — also the re-trigger worklist activity) +;; and re-request {K → C}. Inadmissible C → resolve natively into K's best. +(define (pr-cascade! net hc K C) + (with-handlers ([exn:fail? (lambda (_e) (pr-write-whnf! net K (whnf-native C)))]) + (define-values (net1 Cc _d) (pr/eclass-intern net hc C #:cost 5)) + (define net2 (pr/eclass-union net1 K Cc)) + (net-cell-write net2 reduce-request-cell-id (hash K C)))) + +;; ---- 'demand-par: independent operands reduce in parallel (set-latch fan-in) ---- +;; An e-class is READY (its sub-cascade has reached WHNF) once its :best is cost-0, +;; the marker pr-write-whnf! sets. Pre-reduction it carries the intern cost (≥1). +(define (pr-eclass-ready? v) + (define best (and (hash? v) (hash-ref v ':best #f))) + (and best (zero? (car best)) #t)) + +;; the reduced WHNF term in a READY class (pr-eclass-ready? already gated cost-0) +(define (pr-eclass-whnf-of net cid) + (cdr (hash-ref (pr/eclass-read net cid) ':best))) + +;; pr-demand-par: drive N INDEPENDENT operands to WHNF together, then re-form the +;; head. Each non-value operand is interned to its own class KOi and queued +;; {KOi → Oi} into the SAME reduce-request round → the stratum advances ALL of them +;; one step per round (interleaved cascades = "all in parallel"; vs single-subterm +;; 'demand which sequenced operand i fully before operand j). A BARRIER propagator +;; (set-latch fan-in, propagator-design.md) watches the KOi classes; when ALL are +;; ready it re-forms {K → (recon* resolved)} and the head cascade continues. Value +;; operands pass through unchanged (no class allocated). Inadmissible operand → +;; native fallback for the whole head (parity-safe). +(define (pr-demand-par net hc K operands recon*) + (with-handlers ([exn:fail? (lambda (_e) + (pr-write-whnf! net K (whnf-native (recon* operands))))]) + ;; slot := (cons 'val term) [pass-through] | (cons 'class KOi) [cascade-driven] + (define-values (net* slots class-cids) + (for/fold ([n net] [slots '()] [cids '()]) ([Oi (in-list operands)]) + (cond + [(whnf-trivial? Oi) (values n (cons (cons 'val Oi) slots) cids)] + [else + (define-values (n1 KOi _d) (pr/eclass-intern n hc Oi #:cost 5)) + (define n2 (net-cell-write n1 reduce-request-cell-id (hash KOi Oi))) + (values n2 (cons (cons 'class KOi) slots) (cons KOi cids))]))) + (define slots* (reverse slots)) + (cond + [(null? class-cids) + ;; defensive: step1-par yields 'native when all operands are values, so this + ;; is unreachable via the classifier — handle it as an immediate re-form. + (pr-cascade! net* hc K (recon* operands))] + [else + (define conditions (map (lambda (cid) (cons cid pr-eclass-ready?)) class-cids)) + (define body-fn + (lambda (n) + (define resolved + (map (lambda (slot) + (if (eq? (car slot) 'val) (cdr slot) (pr-eclass-whnf-of n (cdr slot)))) + slots*)) + (net-cell-write n reduce-request-cell-id (hash K (recon* resolved))))) + (define-values (net2 _pid) + (net-add-barrier net* conditions '() (list reduce-request-cell-id) body-fn)) + net2]))) + +;; the keep-pending reduce stratum handler +(define (process-reduce-requests net pending) + (define hc (pr/current-eclass-hashcons-cell-id)) + (cond + [(and hc (hash? pending)) + (for/fold ([n net]) ([(K F) (in-hash pending)]) + (define step (whnf-step1 F)) + (cond + [(eq? step 'whnf) (pr-write-whnf! n K F)] + ;; 'native: a COMPUTE-LEAF construct (arith fold / data-structure op / FFI). + ;; whnf-impl computes the primitive (native) but its OPERAND sub-reductions + ;; route back through the cascade (the ambient default is on) — so only the + ;; fold is off-network; the operands' driving stays on-network. (NOT whnf F + ;; — that re-routes F itself → infinite loop; NOT whnf-native — that + ;; de-routes the operands too.) + [(eq? step 'native) (pr-write-whnf! n K (whnf-impl F))] + [(eq? (car step) 'step) (pr-cascade! n hc K (cdr step))] + ;; 'demand-par — N independent operands reduce TOGETHER on-network, joined + ;; by a barrier (set-latch fan-in). This is the parallel path: the operands' + ;; cascades interleave per round rather than being sequenced. + [(eq? (car step) 'demand-par) + (pr-demand-par n hc K (cadr step) (caddr step))] + [else ;; 'demand — reduce the strict subterm natively (de-routed). Routing + ;; demand subterms back through the cascade is CORRECT but ~7× slower + ;; (every recursive subterm spawns a nested cascade); the head chain is + ;; already on-network via the 'step cascade, so keep demand native. + (define sub (cadr step)) + (define recon (caddr step)) + (define next (recon (whnf-native sub))) + (if (equal? next F) (pr-write-whnf! n K F) (pr-cascade! n hc K next))]))] + [else net])) + +(register-stratum-handler! reduce-request-cell-id + process-reduce-requests + #:tier 'topology + #:keep-pending? #t + #:reset-value (hash)) + +;; whnf-via-egraph-network : reduce E to WHNF by SCHEDULER-DRIVEN saturation. +;; intern E → K; emitter writes {K → E}; run-to-quiescence drives the cascade; +;; extract K's :best (the WHNF). No e-graph plumbing / inadmissible E → native whnf. +(define (whnf-via-egraph-network e) + (define prn-box (current-persistent-registry-net-box)) + (define hc (pr/current-eclass-hashcons-cell-id)) + (cond + [(and prn-box hc) + (with-handlers ([exn:fail? (lambda (_e) (whnf-native e))]) + (define net0 (unbox prn-box)) + (define-values (net1 K _d) (pr/eclass-intern net0 hc e #:cost 10)) + (define-values (net1a _ep) + (net-add-fire-once-propagator + net1 (list K) (list reduce-request-cell-id) + (lambda (n) (net-cell-write n reduce-request-cell-id (hash K e))))) + (define net2 (run-to-quiescence net1a)) + (set-box! prn-box net2) + (define best (hash-ref (pr/eclass-read net2 K) ':best #f)) + (if (and best (zero? (car best))) (cdr best) (whnf-native e)))] + [else (whnf-native e)])) + ;; First reduce to WHNF, then normalize all subterms. ;; Per-command memoization: when current-nf-cache is active, ;; cache nf results keyed by expr (transparent structs → equal?-based hashing). diff --git a/racket/prologos/rule-dispatch.rkt b/racket/prologos/rule-dispatch.rkt index f2947da2d..48a159ffb 100644 --- a/racket/prologos/rule-dispatch.rkt +++ b/racket/prologos/rule-dispatch.rkt @@ -36,7 +36,8 @@ dispatch-rules guard-skip-count reset-guard-skip-count! - guard-skip-note!) + guard-skip-note! + process-dispatch-requests) ;; --- the Phase-0 observability counter (PERF-COUNTERS struct-field integration ;; rides with Phase 1's driver wiring, where the counter becomes externally @@ -256,3 +257,37 @@ (apply-rule n hashcons-cid rule class-cid #:class-of class-of #:cost result-cost)) (values n2 (if fired? (add1 fired) fired))]))])) + +;; --- Phase 2 (PReduce Track 8): dispatch as a STRATUM FIRING ---------------- +;; "Rule application IS propagator firing." Instead of preduce-ingest-int calling +;; dispatch-rules imperatively, it installs an S0 emitter on the redex class that +;; writes dispatch-request-cell-id; run-to-quiescence then fires THIS handler +;; between rounds (topology tier — apply-rule installs union propagators, a +;; structural change). Mirrors the congruence engine (eclass-graph.rkt +;; process-congruence-requests + cell-21). +;; +;; The handler reads the registry + hashcons cell-ids from their parameters +;; (set by the driver / test before whnf, valid in run-to-quiescence's dynamic +;; extent), and runs the existing dispatch-rules on each pending class. +;; +;; Re-entrancy: apply-rule drives its OWN run-to-quiescence (to land the union). +;; That nested quiescence would re-see the SAME pending requests (the framework's +;; post-handler reset has not run yet), re-dispatching ad infinitum. So we CLEAR +;; the request cell on the working net at entry (the stratification.md "fork-based +;; handlers must clear the request cell" idiom) before any apply-rule runs. +(define (process-dispatch-requests net pending) + (define net0 (net-cell-reset net dispatch-request-cell-id (hash))) + (define hc (current-eclass-hashcons-cell-id)) + (define reg (current-rule-registry-cell-id)) + (cond + [(and hc reg (hash? pending)) + (for/fold ([n net0]) ([(cid _v) (in-hash pending)]) + (define-values (n2 _fired) + (dispatch-rules n hc reg cid #:result-cost 1)) + n2)] + [else net0])) + +(register-stratum-handler! dispatch-request-cell-id + process-dispatch-requests + #:tier 'topology + #:reset-value (hash)) diff --git a/racket/prologos/tests/test-observatory-01.rkt b/racket/prologos/tests/test-observatory-01.rkt index 759d8e3db..dd12f19d9 100644 --- a/racket/prologos/tests/test-observatory-01.rkt +++ b/racket/prologos/tests/test-observatory-01.rkt @@ -304,9 +304,9 @@ (check-not-eq? meta-c 'none) (check-eq? (cell-meta-subsystem meta-a) 'user) (check-eq? (cell-meta-domain meta-a) 'lattice) - (check-equal? (cell-meta-label meta-a) "cell-22") ;; PReduce Track 1 11b: cell-ids 0-21 pre-allocated (0-19 as before + 20=congruence-sig-index, 21=congruence-request) - (check-equal? (cell-meta-label meta-b) "cell-23") - (check-equal? (cell-meta-label meta-c) "cell-24")) + (check-equal? (cell-meta-label meta-a) "cell-24") ;; PReduce Track 8 Phase 5b: cell-ids 0-23 pre-allocated (0-22 + 23=reduce-request) + (check-equal? (cell-meta-label meta-b) "cell-25") + (check-equal? (cell-meta-label meta-c) "cell-26")) ;; ======================================== ;; 11. Multiple captures accumulate diff --git a/racket/prologos/tests/test-preduce-egraph.rkt b/racket/prologos/tests/test-preduce-egraph.rkt new file mode 100644 index 000000000..2f5b0de97 --- /dev/null +++ b/racket/prologos/tests/test-preduce-egraph.rkt @@ -0,0 +1,141 @@ +#lang racket/base +;; PReduce Track 8 Phase 5a — network-driven reduction engine: PARITY harness. +;; whnf-via-egraph (the intern→reduce→extract driver, iterating the whnf-step1 +;; one-step classifier) MUST agree with native whnf on every term. The migrated +;; arms (β, ι, suc-collapse, fst/snd, J, boolrec, ann, vhead/vtail, + the +;; subterm-demand arms) are validated here; everything else is the 'native +;; fallback (parity-trivial). This is the gate the design (§6) makes +;; non-negotiable before any construct is "migrated". +(require rackunit + "../reduction.rkt" + "../syntax.rkt" + "../propagator.rkt" + "../eclass-graph.rkt" + "../rule-registry.rkt" + "../kernel-rules-seed.rkt" + (only-in "../metavar-store.rkt" current-persistent-registry-net-box)) + +;; --- whnf-step1 classifies the migrated head redexes as 'step (not 'native) --- +;; (proves the arms are actually MIGRATED, not silently falling back) +(check-equal? (car (whnf-step1 (expr-app (expr-lam 'mw (expr-Int) (expr-bvar 0)) + (expr-int 5)))) + 'step "β is migrated (step, not native)") +(check-equal? (car (whnf-step1 (expr-fst (expr-pair (expr-int 1) (expr-int 2))))) + 'step "fst-on-pair is migrated") +(check-equal? (car (whnf-step1 (expr-suc (expr-nat-val 4)))) + 'step "suc-collapse is migrated") +(check-equal? (car (whnf-step1 (expr-natrec (expr-Nat) (expr-nat-val 3) + (expr-lam 'mw (expr-Nat) (expr-bvar 0)) + (expr-nat-val 2)))) + 'step "ι natrec(nat-val) is migrated") +;; a value is 'whnf; an un-migrated redex (arith) is 'native +(check-equal? (whnf-step1 (expr-int 7)) 'whnf "literal is whnf") +(check-equal? (car (whnf-step1 (expr-int-add (expr-int 1) (expr-int 2)))) + 'step "int arith is migrated (Phase 5c batch 1)") +(check-equal? (whnf-step1 (expr-int-div (expr-int 8) (expr-int 0))) + 'native "div-by-zero stays native (zero-guard)") +;; a non-lam application demands its function +(check-equal? (car (whnf-step1 (expr-app (expr-fvar 'foo) (expr-int 5)))) + 'demand "app-of-non-lam demands the function") + +;; --- PARITY: whnf-via-egraph == native whnf, over a corpus --- +(define corpus + (list + ;; identity β + (expr-app (expr-lam 'mw (expr-Int) (expr-bvar 0)) (expr-int 5)) + ;; K combinator partial: (λx.λy.x) 3 → (λy. 3-shifted) + (expr-app (expr-lam 'mw (expr-Int) (expr-lam 'mw (expr-Int) (expr-bvar 1))) + (expr-int 3)) + ;; two β steps: ((λx.λy.x) 7 8) → 7 + (expr-app (expr-app (expr-lam 'mw (expr-Int) + (expr-lam 'mw (expr-Int) (expr-bvar 1))) + (expr-int 7)) + (expr-int 8)) + ;; β whose body is a projection: (λp. fst p) (pair 1 2) → 1 + (expr-app (expr-lam 'mw (expr-Sigma (expr-Int) (expr-Int)) + (expr-fst (expr-bvar 0))) + (expr-pair (expr-int 1) (expr-int 2))) + ;; suc-collapse + (expr-suc (expr-nat-val 4)) + ;; ι natrec: 3 + 2 via suc-step — whnf exposes suc (weak head) + (expr-natrec (expr-Nat) (expr-nat-val 3) + (expr-lam 'mw (expr-Nat) (expr-lam 'mw (expr-Nat) (expr-suc (expr-bvar 0)))) + (expr-nat-val 2)) + ;; fst / snd on pairs + (expr-fst (expr-pair (expr-int 1) (expr-int 2))) + (expr-snd (expr-pair (expr-int 1) (expr-int 2))) + ;; boolrec + (expr-boolrec (expr-Bool) (expr-int 10) (expr-int 20) (expr-true)) + (expr-boolrec (expr-Bool) (expr-int 10) (expr-int 20) (expr-false)) + ;; J on refl + (expr-J (expr-Type 0) (expr-lam 'mw (expr-Int) (expr-bvar 0)) (expr-int 9) + (expr-int 9) (expr-refl)) + ;; ann is transparent + (expr-ann (expr-int 42) (expr-Int)) + ;; arith — now MIGRATED (Phase 5c batch 1): folds + nested-demand + comparisons + (expr-int-add (expr-int 1) (expr-int 2)) + (expr-int-mul (expr-int 6) (expr-int 7)) + (expr-int-sub (expr-int 10) (expr-int 3)) + (expr-int-neg (expr-int 5)) + (expr-int-abs (expr-int -8)) + ;; nested arith (strict demand on operands): (1+2)*(10-3) → 21 + (expr-int-mul (expr-int-add (expr-int 1) (expr-int 2)) + (expr-int-sub (expr-int 10) (expr-int 3))) + ;; deeper nest: ((2*3)+(4*5)) → 26 + (expr-int-add (expr-int-mul (expr-int 2) (expr-int 3)) + (expr-int-mul (expr-int 4) (expr-int 5))) + ;; comparisons → Bool + (expr-int-lt (expr-int 3) (expr-int 5)) + (expr-int-le (expr-int 5) (expr-int 5)) + (expr-int-eq (expr-int 4) (expr-int 4)) + (expr-int-eq (expr-int 4) (expr-int 9)) + ;; β exposing nested arith: (λx. (x+1)*(x-1)) 5 → 24 + (expr-app (expr-lam 'mw (expr-Int) + (expr-int-mul (expr-int-add (expr-bvar 0) (expr-int 1)) + (expr-int-sub (expr-bvar 0) (expr-int 1)))) + (expr-int 5)) + ;; div/mod stay native (zero-guard) — parity must still hold + (expr-int-div (expr-int 20) (expr-int 4)) + (expr-int-mod (expr-int 17) (expr-int 5)) + ;; β exposing arith: (λx. x+1) 5 → 6 (β migrated, int+ native) + (expr-app (expr-lam 'mw (expr-Int) (expr-int-add (expr-bvar 0) (expr-int 1))) + (expr-int 5)) + ;; higher-order: (λf. f 5) (λx. x) → 5 + (expr-app (expr-lam 'mw (expr-Pi 'mw (expr-Int) (expr-Int)) + (expr-app (expr-bvar 0) (expr-int 5))) + (expr-lam 'mw (expr-Int) (expr-bvar 0))) + ;; neutral / stuck: (foo 5) with foo a free var — both leave it stuck + (expr-app (expr-fvar 'foo) (expr-int 5)) + ;; already a value + (expr-int 99) + (expr-lam 'mw (expr-Int) (expr-bvar 0)))) + +(for ([e (in-list corpus)] [i (in-naturals)]) + (check-equal? (whnf-via-egraph e) (whnf e) + (format "parity at corpus[~a]: ~a" i e))) + +;; --- nf-level spot check: the ι term fully normalizes to 5 the same both ways --- +(define plus-redex + (expr-natrec (expr-Nat) (expr-nat-val 3) + (expr-lam 'mw (expr-Nat) (expr-lam 'mw (expr-Nat) (expr-suc (expr-bvar 0)))) + (expr-nat-val 2))) +(check-true (expr-suc? (whnf-via-egraph plus-redex)) + "whnf-via-egraph exposes suc (weak head), like native") + +;; --- Phase 5b: SCHEDULER-DRIVEN parity — whnf-via-egraph-network == native whnf --- +;; With the e-graph plumbing live, the reduce stratum drives the head cascade +;; (the genuine network-DRIVE). Same corpus; same answers. +(parameterize ([current-rule-registry-cell-id #f] + [current-eclass-hashcons-cell-id #f] + [current-persistent-registry-net-box (box (make-prop-network))]) + (define pb (current-persistent-registry-net-box)) + (init-rule-registry-cell! pb) + (set-box! pb (run-to-quiescence + (register-arithmetic-seed! (unbox pb) (current-rule-registry-cell-id)))) + (init-eclass-hashcons-cell! pb) + (for ([e (in-list corpus)] [i (in-naturals)]) + (check-equal? (whnf-via-egraph-network e) (whnf e) + (format "NETWORK parity at corpus[~a]: ~a" i e))) + ;; the ι term still exposes suc at the weak head, scheduler-driven + (check-true (expr-suc? (whnf-via-egraph-network plus-redex)) + "whnf-via-egraph-network exposes suc (weak head)")) diff --git a/racket/prologos/tests/test-preduce-ingest.rkt b/racket/prologos/tests/test-preduce-ingest.rkt index 45c4140f1..d6ef2b47f 100644 --- a/racket/prologos/tests/test-preduce-ingest.rkt +++ b/racket/prologos/tests/test-preduce-ingest.rkt @@ -1,9 +1,12 @@ #lang racket/base -;; PReduce Track 2 — the ingestion hook (iter 22): gated OFF by default; when ON, -;; whnf's int folds route through the e-graph and land the same answers, with the -;; classes observable afterward. +;; PReduce ingestion (the on-network reduction path). On-network reduction is the +;; ONLY path on this branch (the parameter gate + off-network native arms were +;; deleted 2026-06-14): whnf's β/ι/δ/int-folds always route through the e-graph +;; and land the same answers, with the classes observable afterward; the hook is +;; TOTAL (degrades to the native step when the e-graph infra is absent). (require rackunit racket/set - (only-in "../rule-dispatch.rkt" guard-skip-count reset-guard-skip-count!) + (only-in "../rule-dispatch.rkt" + guard-skip-count reset-guard-skip-count! process-dispatch-requests) "../reduction.rkt" "../syntax.rkt" "../eclass-graph.rkt" @@ -16,16 +19,20 @@ store-record-reduction) (only-in "../pce.rkt" pce-digest PCE-KIND-GROUND-TERM)) -;; OFF (the default): native fold; no e-graph involvement -(check-equal? (whnf (expr-int-add (expr-int 1) (expr-int 2))) (expr-int 3)) -(check-false (current-preduce-ingest?) "default is OFF") +;; This file tests the 4a RECORDING path (preduce-ingest-delta/int) specifically — +;; the e-graph classes whnf interns via the native β/ι/δ arms. The Phase 5c routing +;; deploy (PREDUCE_ROUTE=1) routes whnf through the scheduler cascade instead, which +;; records differently (eclass-union, tested by test-preduce-egraph). So pin routing +;; OFF here: this file validates the path routing replaces, until the native reducer +;; is deleted (at which point these recording assertions retire). Reduction VALUES +;; are identical either way; only the recorded class structure differs. +(current-egraph-whnf? #f) -;; ON with no plumbing: still the native answers (the hook is total) -(parameterize ([current-preduce-ingest? #t]) - (check-equal? (whnf (expr-int-add (expr-int 1) (expr-int 2))) (expr-int 3)) - (check-equal? (whnf (expr-int-mul (expr-int 6) (expr-int 7))) (expr-int 42))) +;; No e-graph plumbing: the ingest path degrades to the native fold (total). +(check-equal? (whnf (expr-int-add (expr-int 1) (expr-int 2))) (expr-int 3)) +(check-equal? (whnf (expr-int-mul (expr-int 6) (expr-int 7))) (expr-int 42)) -;; ON with full plumbing: same answers, AND the e-graph holds the folded classes +;; With full plumbing: same answers, AND the e-graph holds the folded classes (define prn-box (box (make-prop-network))) (parameterize ([current-rule-registry-cell-id #f] [current-eclass-hashcons-cell-id #f] @@ -35,11 +42,10 @@ (register-arithmetic-seed! (unbox prn-box) (current-rule-registry-cell-id)))) (init-eclass-hashcons-cell! prn-box) - (parameterize ([current-preduce-ingest? #t]) - (check-equal? (whnf (expr-int-add (expr-int 1) (expr-int 2))) (expr-int 3) - "e-graph path lands the same fold") - (check-equal? (whnf (expr-int-sub (expr-int 10) (expr-int 3))) (expr-int 7)) - (check-equal? (whnf (expr-int-mul (expr-int 6) (expr-int 7))) (expr-int 42))) + (check-equal? (whnf (expr-int-add (expr-int 1) (expr-int 2))) (expr-int 3) + "e-graph path lands the same fold") + (check-equal? (whnf (expr-int-sub (expr-int 10) (expr-int 3))) (expr-int 7)) + (check-equal? (whnf (expr-int-mul (expr-int 6) (expr-int 7))) (expr-int 42)) ;; the classes are now IN the e-graph (the memo exists) (define net (unbox prn-box)) (define hc (current-eclass-hashcons-cell-id)) @@ -50,37 +56,39 @@ "the class best is the folded literal") ;; re-whnf of the same shape is a hashcons HIT (no new class) (define n-classes-before (hash-count (net-cell-read net hc))) - (parameterize ([current-preduce-ingest? #t]) - (whnf (expr-int-add (expr-int 1) (expr-int 2)))) + (whnf (expr-int-add (expr-int 1) (expr-int 2))) (check-equal? (hash-count (net-cell-read (unbox prn-box) hc)) n-classes-before "re-ingestion of the same position is a memo hit")) -;; ---- δ-memo (Phase 2, iter 24): {body, whnf(body)} as one e-class ---- -(require (only-in "../eclass-cell.rkt" eclass-bot)) -(define prn-box2 (box (make-prop-network))) +;; ---- Phase 2 (PReduce Track 8): dispatch as a STRATUM FIRING ---- +;; The arithmetic rewrite is performed by the dispatch stratum handler +;; (process-dispatch-requests), NOT an imperative dispatch-rules call. Intern a +;; redex WITHOUT dispatching, then run the handler directly on a pending request: +;; it must land the fold and clear the request cell (the entry-reset idiom). (parameterize ([current-rule-registry-cell-id #f] [current-eclass-hashcons-cell-id #f] - [current-persistent-registry-net-box prn-box2]) - (init-rule-registry-cell! prn-box2) - (init-eclass-hashcons-cell! prn-box2) + [current-persistent-registry-net-box (box (make-prop-network))]) + (define pb (current-persistent-registry-net-box)) + (init-rule-registry-cell! pb) + (set-box! pb (run-to-quiescence + (register-arithmetic-seed! (unbox pb) + (current-rule-registry-cell-id)))) + (init-eclass-hashcons-cell! pb) (define hc (current-eclass-hashcons-cell-id)) - ;; a "definition body" worth memoizing: (int+ (int* 2 3) 4) — whnf folds to 10 - (define body (expr-int-add (expr-int-mul (expr-int 2) (expr-int 3)) (expr-int 4))) - (parameterize ([current-preduce-ingest? #t]) - ;; the hook is exercised through the δ entry point directly (the fvar arm - ;; needs a global env; the memo mechanics are what this test pins) - (define r1 (whnf body)) - (check-equal? r1 (expr-int 10))) - ;; simulate the δ path: the class keyed by digest(body) holds whnf(body) at cost 0 - (define dig (pce-digest PCE-KIND-GROUND-TERM body)) - ;; note: the int-fold ingestion above interned SUBTERM forms, not the body - ;; expr itself — the δ memo is exercised end-to-end below via the public hook - (void)) + (define form (list 'int+ (list 'lit 2) (list 'lit 3))) + (define-values (n1 cid _d) (eclass-intern (unbox pb) hc form #:cost 5)) + ;; pre-dispatch: the class best is still the UNREDUCED redex + (check-equal? (hash-ref (eclass-read n1 cid) ':best) (cons 5 form) + "pre-dispatch: best is the unreduced redex") + ;; the STRATUM HANDLER performs the rewrite (proving dispatch is on-network) + (define n2 (process-dispatch-requests n1 (hash cid #t))) + (check-equal? (hash-ref (eclass-read n2 cid) ':best) (cons 1 '(lit 5)) + "the dispatch stratum handler folded 2+3=5 into the class best") + ;; the handler cleared the request cell at entry (re-entrancy guard) + (check-true (hash-empty? (net-cell-read n2 dispatch-request-cell-id)) + "the handler cleared the dispatch-request cell at entry")) -;; the δ hook through whnf with a real global env is exercised at Level 3 -;; (acceptance with PREDUCE_INGEST=1 — every def reference routes through it); -;; here we pin the memo MECHANICS via two whnf calls on an fvar-free body and -;; the e-graph class shape after a manual delta round-trip: +;; ---- δ-memo: {body, whnf(body)} as one e-class ---- (parameterize ([current-rule-registry-cell-id #f] [current-eclass-hashcons-cell-id #f] [current-persistent-registry-net-box (box (make-prop-network))]) @@ -89,15 +97,14 @@ (init-eclass-hashcons-cell! pb) (define hc (current-eclass-hashcons-cell-id)) (define body (expr-int-add (expr-int 1) (expr-int 2))) - (parameterize ([current-preduce-ingest? #t]) - ;; first whnf of the body: the int-fold hook interns + folds - (check-equal? (whnf body) (expr-int 3))) + ;; first whnf of the body: the int-fold hook interns + folds + (check-equal? (whnf body) (expr-int 3)) ;; the body's class exists in the e-graph (the int hook keyed it by its form) (define net (unbox pb)) (check-true (> (hash-count (net-cell-read net hc)) 0) "ingestion populated the per-file e-graph")) -;; ---- guarded β (Phase 3, iter 25): the Track 2 exit criterion ---- +;; ---- guarded β: the Track 2 exit criterion ---- (parameterize ([current-rule-registry-cell-id #f] [current-eclass-hashcons-cell-id #f] [current-persistent-registry-net-box (box (make-prop-network))]) @@ -109,8 +116,7 @@ (define redex (expr-app (expr-lam 'mw (expr-Int) (expr-int-add (expr-bvar 0) (expr-int 1))) (expr-int 5))) - (parameterize ([current-preduce-ingest? #t]) - (check-equal? (whnf redex) (expr-int 6) "guarded β fires and lands the contractum")) + (check-equal? (whnf redex) (expr-int 6) "guarded β fires and lands the contractum") ;; the redex class holds the contractum at cost 0 (the {redex, result} e-class) (define dig (pce-digest PCE-KIND-GROUND-TERM redex)) (define cid (eclass-lookup (unbox pb) hc dig)) @@ -119,22 +125,21 @@ "the contractum is the class best — β recorded as an e-class join") ;; memo hit: same redex again, no new classes (define n0 (hash-count (net-cell-read (unbox pb) hc))) - (parameterize ([current-preduce-ingest? #t]) (whnf redex)) + (whnf redex) (check-equal? (hash-count (net-cell-read (unbox pb) hc)) n0 "β memo hit") ;; an EFFECT-HEADED arg: ((λx. 99) (read ch)) — the guard SKIPS the e-graph ;; (pessimistic: deletion of an effectful capture must never be RECORDED); - ;; the native β still computes (legacy effects are deferred descriptors) + ;; the guard path still computes (legacy effects are deferred descriptors) (define eff-redex (expr-app (expr-lam 'mw (expr-Int) (expr-int 99)) (expr-app (expr-fvar 'read) (expr-fvar 'ch)))) (reset-guard-skip-count!) (define n1 (hash-count (net-cell-read (unbox pb) hc))) - (parameterize ([current-preduce-ingest? #t]) - (check-equal? (whnf eff-redex) (expr-int 99) "native β remains sound")) + (check-equal? (whnf eff-redex) (expr-int 99) "the guard path still computes") (check-equal? (guard-skip-count) 1 "the guard skipped the e-graph recording") (check-equal? (hash-count (net-cell-read (unbox pb) hc)) n1 "no class recorded for the effect-capturing redex")) -;; ---- ι ingestion (Track 3 Phase 1, iter 41): the natrec recursion carriers ---- +;; ---- ι ingestion: the natrec recursion carriers ---- (parameterize ([current-rule-registry-cell-id #f] [current-eclass-hashcons-cell-id #f] [current-persistent-registry-net-box (box (make-prop-network))]) @@ -150,20 +155,18 @@ (expr-lam 'mw (expr-Nat) (expr-lam 'mw (expr-Nat) (expr-suc (expr-bvar 0)))) (expr-nat-val 2))) - (parameterize ([current-preduce-ingest? #t]) - ;; whnf exposes the head constructor (WEAK head — the inner natrec stays); - ;; nf drives the full value: 3+2=5 (the first expectation here forgot - ;; weak-head semantics — the hook was right, the test was wrong) - (check-true (expr-suc? (whnf plus-redex)) "whnf exposes suc (weak head)") - (check-equal? (nf plus-redex) (expr-nat-val 5) "nf lands 3+2=5 through the e-graph")) + ;; whnf exposes the head constructor (WEAK head — the inner natrec stays); + ;; nf drives the full value: 3+2=5 (through the e-graph) + (check-true (expr-suc? (whnf plus-redex)) "whnf exposes suc (weak head)") + (check-equal? (nf plus-redex) (expr-nat-val 5) "nf lands 3+2=5 through the e-graph") ;; the redex chain memoized: classes exist for the nat-val steps (check-true (> (hash-count (net-cell-read (unbox pb) hc)) 0) "ι redexes populated the e-graph") ;; memo hit: same redex re-whnf'd, zero new classes (define n0 (hash-count (net-cell-read (unbox pb) hc))) - (parameterize ([current-preduce-ingest? #t]) (whnf plus-redex)) + (whnf plus-redex) (check-equal? (hash-count (net-cell-read (unbox pb) hc)) n0 "ι memo hit") - ;; effect-headed step: the guard SKIPS the recording, native ι still computes + ;; effect-headed step: the guard SKIPS the recording, the guard path still computes (reset-guard-skip-count!) (define eff-redex (expr-natrec (expr-lam 'mw (expr-Nat) (expr-Nat)) @@ -171,9 +174,8 @@ (expr-app (expr-fvar 'read) (expr-fvar 'ch)) (expr-nat-val 1))) (define n1 (hash-count (net-cell-read (unbox pb) hc))) - (parameterize ([current-preduce-ingest? #t]) - (void (with-handlers ([exn:fail? (lambda (_e) 'native-error-ok)]) - (whnf eff-redex)))) + (void (with-handlers ([exn:fail? (lambda (_e) 'native-error-ok)]) + (whnf eff-redex))) (check-true (> (guard-skip-count) 0) "effect-headed step skipped the e-graph") (check-equal? (hash-count (net-cell-read (unbox pb) hc)) n1 "no class recorded for the effectful ι redex")) @@ -192,10 +194,6 @@ (define sc (current-extraction-store-cell-id)) (define body '(consult-body 20 22)) (define result '(consult-result 42)) - ;; seed: intern the body (a prior session's class), record its result in - ;; the store, then ERASE the class best back to... simpler: a FRESH network - ;; world where only the STORE knows the answer — intern body in a scratch - ;; net to learn the question key, then seed the production store directly. (define-values (n1 cid1 _d) (eclass-intern (unbox pb) hc body #:cost 10)) (set-box! pb n1) (set-box! pb (store-record-reduction (unbox pb) sc cid1 result)) @@ -212,3 +210,30 @@ (check-equal? (preduce-ingest-delta body #:compute (lambda () (error 'x "ran"))) result "subsequent encounters hit the promoted best")) + +;; ---- Phase 4a (PReduce Track 8): recursion step recorded as a UNION ---- +;; A β/δ/ι reduction joins {redex, result} via a union PROPAGATOR (not a bare +;; cell-write): the result is interned as its OWN class (pre-4a it was only the +;; redex's :best) and the union makes redex + result share a :canonical. +(parameterize ([current-rule-registry-cell-id #f] + [current-eclass-hashcons-cell-id #f] + [current-persistent-registry-net-box (box (make-prop-network))]) + (define pb (current-persistent-registry-net-box)) + (init-rule-registry-cell! pb) + (init-eclass-hashcons-cell! pb) + (define hc (current-eclass-hashcons-cell-id)) + ;; a PURE β redex: ((λx. x + 1) 5) → 6 + (define redex (expr-app (expr-lam 'mw (expr-Int) + (expr-int-add (expr-bvar 0) (expr-int 1))) + (expr-int 5))) + (check-equal? (whnf redex) (expr-int 6) "β lands the contractum") + (define net (unbox pb)) + (define redex-cid (eclass-lookup net hc (pce-digest PCE-KIND-GROUND-TERM redex))) + ;; the RESULT is interned as its own class (Phase 4a — pre-4a it was not) + (define result-cid (eclass-lookup net hc (pce-digest PCE-KIND-GROUND-TERM (expr-int 6)))) + (check-true (and redex-cid #t) "the β redex has a class") + (check-true (and result-cid #t) "Phase 4a: the result is interned as its OWN class") + ;; the union joined them — redex + result share a canonical + (check-equal? (hash-ref (eclass-read net redex-cid) ':canonical) + (hash-ref (eclass-read net result-cid) ':canonical) + "the β step is a UNION: redex and result share a canonical")) diff --git a/racket/prologos/tests/test-propagator-bsp.rkt b/racket/prologos/tests/test-propagator-bsp.rkt index 42b4b48e4..7b6955208 100644 --- a/racket/prologos/tests/test-propagator-bsp.rkt +++ b/racket/prologos/tests/test-propagator-bsp.rkt @@ -349,3 +349,66 @@ ;; All 20 outputs should be 42 (for ([cid (in-list outputs)]) (check-equal? (net-cell-read result cid) 42))) + +;; ======================================== +;; PTF Track 2 Phase 2b: Tier-1 observer coverage (gap G3) +;; ======================================== +;; Tier-1 (zero worldview, fire-once + empty-inputs worklist) used to skip the +;; BSP observer entirely — fire-once-only programs traced EMPTY. These cases +;; pin the armed path (one bsp-round, precise diffs + attribution) and the +;; unarmed path (behavior unchanged). + +(test-case "tier-1: armed observer sees the fire-once flush" + (define net0 (make-prop-network)) + (define-values (net1 ca) (net-new-cell net0 'bot flat-merge)) + (define-values (net2 p1) + (net-add-fire-once-propagator net1 '() (list ca) + (lambda (net) (net-cell-write net ca 'hello)))) + (define seen '()) + (define result + (parameterize ([current-bsp-observer (lambda (r) (set! seen (cons r seen)))]) + (run-to-quiescence-bsp net2))) + (check-equal? (net-cell-read result ca) 'hello) + (check-equal? (length seen) 1 "exactly one bsp-round from the Tier-1 flush") + (define r (car seen)) + (check-equal? (bsp-round-propagators-fired r) (list p1)) + (check-false (bsp-round-contradiction r)) + (define diffs (bsp-round-cell-diffs r)) + (check-equal? (length diffs) 1) + (check-equal? (cell-diff-cell-id (car diffs)) ca) + (check-equal? (cell-diff-new-value (car diffs)) 'hello) + (check-equal? (cell-diff-source-propagator (car diffs)) p1 + "diff attributed to the firing propagator")) + +(test-case "tier-1: two fire-once props -> one round, both attributed" + (define net0 (make-prop-network)) + (define-values (net1 ca) (net-new-cell net0 'bot flat-merge)) + (define-values (net2 cb) (net-new-cell net1 'bot flat-merge)) + (define-values (net3 p1) + (net-add-fire-once-propagator net2 '() (list ca) + (lambda (net) (net-cell-write net ca 'x)))) + (define-values (net4 p2) + (net-add-fire-once-propagator net3 '() (list cb) + (lambda (net) (net-cell-write net cb 'y)))) + (define seen '()) + (define result + (parameterize ([current-bsp-observer (lambda (r) (set! seen (cons r seen)))]) + (run-to-quiescence-bsp net4))) + (check-equal? (net-cell-read result ca) 'x) + (check-equal? (net-cell-read result cb) 'y) + (check-equal? (length seen) 1) + (define r (car seen)) + (check-equal? (length (bsp-round-propagators-fired r)) 2) + (define srcs (map cell-diff-source-propagator (bsp-round-cell-diffs r))) + (check-true (and (member p1 srcs) (member p2 srcs) #t) + "each diff attributed to its own firing propagator")) + +(test-case "tier-1: unarmed observer -> behavior unchanged, nothing recorded" + (define net0 (make-prop-network)) + (define-values (net1 ca) (net-new-cell net0 'bot flat-merge)) + (define-values (net2 _p) + (net-add-fire-once-propagator net1 '() (list ca) + (lambda (net) (net-cell-write net ca 'quiet)))) + (define result (run-to-quiescence-bsp net2)) + (check-equal? (net-cell-read result ca) 'quiet) + (check-equal? (prop-network-worklist result) '())) diff --git a/racket/prologos/tests/test-propagator.rkt b/racket/prologos/tests/test-propagator.rkt index 2999f12b8..456366c24 100644 --- a/racket/prologos/tests/test-propagator.rkt +++ b/racket/prologos/tests/test-propagator.rkt @@ -39,7 +39,7 @@ (check-true (prop-network? net)) (check-true (net-quiescent? net)) (check-false (net-contradiction? net)) - (check-equal? (prop-network-next-cell-id net) 22) ;; well-known: 0=decomp-request, 1=worldview-cache, 2=relation-store, 3=config, 4=naf-pending, 5=pool-config, 6-9=topology subsystem cells (A1), 10=classify-inhabit-request (PPN 4C Phase 3c-iii), 11=fuel, 12=fuel-budget (Tropical Addendum), 13=retraction-stratum-request, 14=resolution-stratum-request (PPN 4C 2A.0), 15=fork-on-union-request, 16=fork-contradiction-request (PPN 4C 3A.0), 17=decomposed-positions (PPN 4C 3A.c.1), 18=contradicted-branch-aids, 19=union-derivation-chains (PPN 4C 3C.b.1), 20=congruence-sig-index, 21=congruence-request (PReduce Track 1 11b) + (check-equal? (prop-network-next-cell-id net) 24) ;; well-known 0-23 (…22=dispatch-request Phase 2, 23=reduce-request Phase 5b); was: 0=decomp-request, 1=worldview-cache, 2=relation-store, 3=config, 4=naf-pending, 5=pool-config, 6-9=topology subsystem cells (A1), 10=classify-inhabit-request (PPN 4C Phase 3c-iii), 11=fuel, 12=fuel-budget (Tropical Addendum), 13=retraction-stratum-request, 14=resolution-stratum-request (PPN 4C 2A.0), 15=fork-on-union-request, 16=fork-contradiction-request (PPN 4C 3A.0), 17=decomposed-positions (PPN 4C 3A.c.1), 18=contradicted-branch-aids, 19=union-derivation-chains (PPN 4C 3C.b.1), 20=congruence-sig-index, 21=congruence-request (PReduce Track 1 11b), 22=dispatch-request (PReduce Track 8 Phase 2) (check-equal? (prop-network-next-prop-id net) 0)) (test-case "make-prop-network: custom fuel" @@ -69,16 +69,16 @@ (define-values (net1 cid) (net-new-cell net 'bot flat-merge)) (check-true (prop-network? net1)) (check-true (cell-id? cid)) - (check-equal? (cell-id-n cid) 22)) ;; PReduce Track 1 11b: first user cell is 22 (0-19 as before + 20=congruence-sig-index, 21=congruence-request) + (check-equal? (cell-id-n cid) 24)) ;; PReduce Track 8 Phase 5b: first user cell is 24 (0-22 + 23=reduce-request) (test-case "net-new-cell: sequential cell ids" (define net (make-prop-network)) (define-values (net1 cid1) (net-new-cell net 'bot flat-merge)) (define-values (net2 cid2) (net-new-cell net1 'bot flat-merge)) (define-values (net3 cid3) (net-new-cell net2 'bot flat-merge)) - (check-equal? (cell-id-n cid1) 22) ;; PReduce Track 1 11b: offset by 22 (post 13-21 cells) - (check-equal? (cell-id-n cid2) 23) - (check-equal? (cell-id-n cid3) 24)) + (check-equal? (cell-id-n cid1) 24) ;; PReduce Track 8 Phase 5b: offset by 24 (post 13-23 cells) + (check-equal? (cell-id-n cid2) 25) + (check-equal? (cell-id-n cid3) 26)) (test-case "net-new-cell: initial value accessible" (define-values (net cid) (net-new-cell (make-prop-network) 42 max-merge)) @@ -196,12 +196,12 @@ (list 'bot flat-merge))) (define-values (net* ids) (net-new-cells-batch net specs)) (check-equal? (length ids) 3) - (check-equal? (map cell-id-n ids) '(22 23 24)) ;; PReduce Track 1 11b: offset by 22 (post 13-21 cells) + (check-equal? (map cell-id-n ids) '(24 25 26)) ;; PReduce Track 8 Phase 5b: offset by 24 (post 13-23 cells) ;; All cells readable with initial values (for ([cid (in-list ids)]) (check-equal? (net-cell-read net* cid) 'bot)) - ;; next-cell-id advanced by 3 from 22 → 25 - (check-equal? (prop-network-next-cell-id net*) 25)) + ;; next-cell-id advanced by 3 from 24 → 27 + (check-equal? (prop-network-next-cell-id net*) 27)) (test-case "net-new-cells-batch: cells are writable and merge correctly" (define net (make-prop-network)) @@ -233,7 +233,7 @@ (define specs (list (list 'bot flat-merge) (list 'bot flat-merge))) (define-values (net2 ids) (net-new-cells-batch net1 specs)) - ;; PReduce Track 1 11b: existing cell is ID 22 (post 13-21 cells); batch starts at 23 - (check-equal? (cell-id-n existing-id) 22) - (check-equal? (map cell-id-n ids) '(23 24)) - (check-equal? (prop-network-next-cell-id net2) 25)) + ;; PReduce Track 8 Phase 5b: existing cell is ID 24 (post 13-23 cells); batch starts at 25 + (check-equal? (cell-id-n existing-id) 24) + (check-equal? (map cell-id-n ids) '(25 26)) + (check-equal? (prop-network-next-cell-id net2) 27)) diff --git a/racket/prologos/tests/test-trace-serialize.rkt b/racket/prologos/tests/test-trace-serialize.rkt index 7ff9f0ebf..8de2fc211 100644 --- a/racket/prologos/tests/test-trace-serialize.rkt +++ b/racket/prologos/tests/test-trace-serialize.rkt @@ -107,7 +107,7 @@ (test-case "serialize-network-topology: empty network" (define js (serialize-network-topology (make-prop-network))) - (check-equal? (hash-ref (hash-ref js 'stats) 'totalCells) 22) ;; PReduce Track 1 11b: 22 well-known cells (0-19 as before + 20=congruence-sig-index, 21=congruence-request) + (check-equal? (hash-ref (hash-ref js 'stats) 'totalCells) 24) ;; PReduce Track 8 Phase 5b: 24 well-known cells (0-22 + 23=reduce-request) (check-equal? (hash-ref (hash-ref js 'stats) 'totalPropagators) 0) (check-equal? (hash-ref (hash-ref js 'stats) 'contradiction) (json-null))) @@ -121,7 +121,7 @@ (if (eq? v 'bot) net (net-cell-write net cid-b v))) (define-values (net3 pid) (net-add-propagator net2 (list cid-a) (list cid-b) copy-fn)) (define js (serialize-network-topology net3)) - (check-equal? (hash-ref (hash-ref js 'stats) 'totalCells) 24) ;; PReduce Track 1 11b: 22 well-known + 2 user (cid-a, cid-b) + (check-equal? (hash-ref (hash-ref js 'stats) 'totalCells) 26) ;; PReduce Track 8 Phase 5b: 24 well-known + 2 user (cid-a, cid-b) (check-equal? (hash-ref (hash-ref js 'stats) 'totalPropagators) 1) ;; Check propagator has correct inputs/outputs (define prop-json (car (hash-ref js 'propagators))) diff --git a/racket/prologos/tests/test-viz-export.rkt b/racket/prologos/tests/test-viz-export.rkt new file mode 100644 index 000000000..1e947fde8 --- /dev/null +++ b/racket/prologos/tests/test-viz-export.rkt @@ -0,0 +1,90 @@ +#lang racket/base + +;;; +;;; test-viz-export.rkt — PTF Track V golden test (vizTrace 2) +;;; +;;; Runs the exporter end-to-end on a tiny fixture and pins the vizTrace 2 +;;; envelope: schema keys, per-round topology references, the soundness +;;; invariant that a round's fired propagators + cell diffs live in THAT +;;; round's own topology (the per-network-id-space fix), identity coverage, +;;; and the validation block. Schema regression gate for the viewer. +;;; + +(require rackunit + racket/file + racket/list + json + "../tools/viz-export.rkt") + +(define fixture + (string-append + "ns examples.viz-export-golden\n\n" + "def tag : String := \"golden\"\n\n" + "spec dbl Int -> Int\n" + "defn dbl [x]\n" + " [int* x 2]\n\n" + "[dbl 21]\n\n" + "defr edge [?x ?y]\n" + " || \"a\" \"b\"\n" + " \"b\" \"c\"\n\n" + "eval (solve (edge \"a\" who))\n")) + +(define tmp (make-temporary-file "viz-export-golden-~a.prologos")) +(call-with-output-file tmp (lambda (out) (display fixture out)) #:exists 'replace) +(define env (viz-export-file tmp)) +(delete-file tmp) + +(test-case "envelope: vizTrace 2 + top-level keys" + (check-equal? (hash-ref env 'vizTrace) 2) + (for ([k (in-list '(file source wallMs commandCount errors commands + topologies rounds finalTopology roundsTruncated validation))]) + (check-true (hash-has-key? env k) (format "missing key ~a" k)))) + +(test-case "fixture runs clean: 0 errors" + (check-equal? (hash-ref env 'errors) 0 + (format "errors: ~a" (hash-ref env 'errorMessages)))) + +(test-case "rounds: timestamped, topo-referenced, command-tagged, monotone" + (define v (hash-ref env 'validation)) + (check-true (hash-ref v 'roundTimestampsMonotone)) + (check-true (> (length (hash-ref env 'rounds)) 0) "at least one observed round") + (define ntopo (length (hash-ref env 'topologies))) + (for ([r (in-list (hash-ref env 'rounds))]) + (check-true (hash-has-key? r 'timestampMs)) + (check-true (hash-has-key? r 'command)) + (define t (hash-ref r 'topo)) + (check-true (and (>= t 0) (< t ntopo)) "topo ref in range"))) + +(test-case "SOUNDNESS: fired props + diff cells live in the round's OWN topology" + (define topos (list->vector (hash-ref env 'topologies))) + (for ([r (in-list (hash-ref env 'rounds))]) + (define topo (hash-ref (vector-ref topos (hash-ref r 'topo)) 'topology)) + (define prop-ids (for/list ([p (in-list (hash-ref topo 'propagators))]) (hash-ref p 'id))) + (define cell-ids (for/list ([c (in-list (hash-ref topo 'cells))]) (hash-ref c 'id))) + (for ([pid (in-list (hash-ref r 'propagatorsFired))]) + (check-not-false (member pid prop-ids) (format "fired prop ~a in its round's topology" pid))) + (for ([d (in-list (hash-ref r 'cellDiffs))]) + (check-not-false (member (hash-ref d 'cellId) cell-ids) + (format "diff cell ~a in its round's topology" (hash-ref d 'cellId)))))) + +(test-case "topologies: cells + propagators + identity coverage" + (for ([t (in-list (hash-ref env 'topologies))]) + (define topo (hash-ref t 'topology)) + (check-true (hash-has-key? topo 'cells)) + (check-true (hash-has-key? topo 'propagators)) + (define cov (hash-ref (hash-ref t 'identity) 'coverage)) + (check-true (<= (hash-ref cov 'cellsWithDomain) (hash-ref cov 'totalCells))))) + +(test-case "solver free-path: some topology carries propagators" + (check-true + (for/or ([t (in-list (hash-ref env 'topologies))]) + (> (hash-ref (hash-ref (hash-ref t 'topology) 'stats) 'totalPropagators) 0)) + "at least one round's network carries propagators")) + +(test-case "commands: ordered distinct labels" + (define cmds (hash-ref env 'commands)) + (check-true (pair? cmds)) + (for ([c (in-list cmds)]) (check-true (hash-has-key? c 'label)))) + +(test-case "envelope is valid jsexpr (serializes to JSON)" + (check-true (string? (jsexpr->string env)))) diff --git a/racket/prologos/tools/viz-capture-probe.rkt b/racket/prologos/tools/viz-capture-probe.rkt new file mode 100644 index 000000000..2c68cded9 --- /dev/null +++ b/racket/prologos/tools/viz-capture-probe.rkt @@ -0,0 +1,137 @@ +#lang racket/base + +;;; +;;; viz-capture-probe.rkt — PTF Track 2 Phase 0.5 empirical capture probe +;;; +;;; Runs a .prologos file with the BSP observer + observatory armed and reports +;;; the magnitudes a browser visualization must handle: topology size (cells, +;;; propagators, edge-bearing propagators), round count, diff volume, fired- +;;; propagator counts. Dumps trace JSON for inspection. +;;; +;;; This is the Phase 0.5 instrument behind the findings recorded in +;;; docs/tracking/2026-06-12_PTF_TRACK2_BROWSER_VIZ_DESIGN.md. Phase 2's +;;; tools/viz-export.rkt supersedes it as the production exporter. +;;; +;;; Usage: racket tools/viz-capture-probe.rkt FILE.prologos [OUT.json] +;;; + +(require json + racket/list + "../champ.rkt" + "../driver.rkt" + "../errors.rkt" + "../propagator.rkt" + "../prop-observatory.rkt" + "../elaborator-network.rkt" + "../trace-serialize.rkt") + +(define args (current-command-line-arguments)) +(when (zero? (vector-length args)) + (eprintf "usage: racket tools/viz-capture-probe.rkt FILE.prologos [OUT.json]\n") + (exit 1)) +(define src-path (vector-ref args 0)) +(define out-path (if (> (vector-length args) 1) (vector-ref args 1) #f)) + +;; Arm the production capture surfaces (mirrors lsp/server.rkt:505-566, minus +;; the post-unwind net-box read defect: we capture the elab-network through +;; current-network-capture-box, which set-box!es per command INSIDE +;; process-file's parameterize — surviving the unwind). +(define-values (bsp-observe bsp-get-rounds) (make-trace-accumulator)) +(define obs (make-observatory (hasheq 'file src-path))) +(define cap-box (box #f)) + +(define t0 (current-inexact-milliseconds)) +(define results + (parameterize ([current-bsp-observer bsp-observe] + [current-observatory obs] + [current-network-capture-box cap-box]) + (process-file src-path))) +(define t1 (current-inexact-milliseconds)) + +(define rounds (bsp-get-rounds)) +(define enet (unbox cap-box)) + +(printf "=== viz-capture-probe: ~a ===\n" src-path) +(printf "wall: ~ams commands: ~a errors: ~a\n" + (round (- t1 t0)) + (length results) + (length (filter prologos-error? results))) +(for ([r (in-list results)] [i (in-naturals)]) + (when (prologos-error? r) + (printf " ERROR @ command ~a: ~a\n" i (prologos-error-message r)))) + +;; --- Topology from the last command's elab-network --- +(define topo + (and enet + (serialize-network-topology (elab-network-prop-net enet) + (elab-network-cell-info enet)))) +(cond + [topo + (define stats (hash-ref topo 'stats)) + (define props (hash-ref topo 'propagators)) + (define edge-bearing + (filter (lambda (p) (and (pair? (hash-ref p 'inputs)) + (pair? (hash-ref p 'outputs)))) + props)) + (define cells (hash-ref topo 'cells)) + (define by-subsystem + (for/fold ([h (hash)]) ([c (in-list cells)]) + (hash-update h (hash-ref c 'subsystem) add1 0))) + (printf "topology (last command's elab-network):\n") + (printf " cells: ~a propagators: ~a with-both-edges: ~a\n" + (hash-ref stats 'totalCells) + (hash-ref stats 'totalPropagators) + (length edge-bearing)) + (printf " cells by subsystem: ~a\n" by-subsystem) + ;; D4 identity-stack coverage audit (PTF Track 2 critique B2): does the + ;; cell-domains champ actually cover cells, or is it F4-hollow one level down? + (let* ([pnet (elab-network-prop-net enet)] + [domains-champ (prop-network-cell-domains pnet)] + [domain-cids (champ-keys domains-champ)] + [by-domain + (for/fold ([h (hash)]) ([cid (in-list domain-cids)]) + (define d (champ-lookup domains-champ (cell-id-n cid) cid)) + (hash-update h d add1 0))]) + (printf " cell-domains coverage: ~a/~a cells carry a domain\n" + (length domain-cids) (hash-ref stats 'totalCells)) + (printf " domains: ~a\n" by-domain))] + [else + (printf "topology: NO elab-network captured (cap-box empty)\n")]) + +;; --- Rounds (accumulated across ALL BSP runs under process-file, including +;; module loading — the accumulator re-stamps round numbers globally) --- +(define total-diffs + (for/sum ([r (in-list rounds)]) (length (bsp-round-cell-diffs r)))) +(define total-fired + (for/sum ([r (in-list rounds)]) (length (bsp-round-propagators-fired r)))) +(printf "rounds: ~a total cell-diffs: ~a total fires: ~a\n" + (length rounds) total-diffs total-fired) +(when (pair? rounds) + (define last-r (last rounds)) + (define last-topo (serialize-network-topology + (bsp-round-network-snapshot last-r))) + (define ls (hash-ref last-topo 'stats)) + (printf "last round's snapshot network: cells ~a, propagators ~a\n" + (hash-ref ls 'totalCells) (hash-ref ls 'totalPropagators))) + +;; --- Observatory captures --- +(define caps (observatory-captures obs)) +(printf "observatory captures: ~a\n" (length caps)) + +;; --- JSON dump (summary-only above 50k diffs to avoid multi-MB artifacts) --- +(when out-path + (define payload + (hasheq 'topology (or topo (json-null)) + 'rounds (if (<= total-diffs 50000) + (map serialize-bsp-round rounds) + (for/list ([r (in-list rounds)]) + (hasheq 'roundNumber (bsp-round-round-number r) + 'diffCount (length (bsp-round-cell-diffs r)) + 'firedCount (length (bsp-round-propagators-fired r))))) + 'metadata (hasheq 'file src-path + 'wallMs (- t1 t0) + 'roundsTruncated (> total-diffs 50000)))) + (call-with-output-file out-path + (lambda (out) (write-json payload out)) + #:exists 'replace) + (printf "JSON written: ~a (~a bytes)\n" out-path (file-size out-path))) diff --git a/racket/prologos/tools/viz-export.rkt b/racket/prologos/tools/viz-export.rkt new file mode 100644 index 000000000..10634c8d9 --- /dev/null +++ b/racket/prologos/tools/viz-export.rkt @@ -0,0 +1,380 @@ +#lang racket/base + +;;; +;;; viz-export.rkt — PTF Track 2 Phase 2c: headless trace exporter +;;; +;;; Runs a .prologos file with the trace observer + observatory armed and +;;; writes ONE self-contained JSON envelope (vizTrace 1) for the standalone +;;; browser viewer: per-command captures with topology, epoch-bucketed BSP +;;; rounds (timestamped by the observer wrapper), per-epoch last-snapshot +;;; topologies (the solver free-path — design doc §7.7 A4), an identity +;;; section per topology (D4: best-available with measured coverage), and +;;; bounded semantic value detail (D7). +;;; +;;; Design: docs/tracking/2026-06-12_PTF_TRACK2_BROWSER_VIZ_DESIGN.md §7. +;;; Schema posture (D2 PATH B): trace-serialize.rkt is reused VERBATIM; +;;; everything viz-specific lives in this envelope, not the core schema. +;;; Identity is PER-TOPOLOGY (not global): cell-id spaces are per-network +;;; (each prop-network counts from 0), so a global map would conflate the +;;; elab net with solver nets (implementation note for D1). +;;; +;;; Usage: +;;; racket tools/viz-export.rkt FILE.prologos -o out.json +;;; [--max-diffs N] [--max-rounds N] [--validate] +;;; + +(require json + racket/list + racket/string + racket/file + "../champ.rkt" + "../driver.rkt" + "../errors.rkt" + "../propagator.rkt" + "../prop-observatory.rkt" + "../elaborator-network.rkt" + (only-in "../eclass-graph.rkt" current-eclass-containment-box + current-eclass-hashcons-cell-id init-eclass-hashcons-cell!) + (only-in "../reduction.rkt" whnf-via-egraph-network) + (only-in "../rule-registry.rkt" + current-rule-registry-cell-id init-rule-registry-cell!) + (only-in "../kernel-rules-seed.rkt" register-arithmetic-seed!) + (only-in "../metavar-store.rkt" current-persistent-registry-net-box) + "../trace-serialize.rkt") + +(provide viz-export-file viz-export-expr-network) + +;; Well-known infrastructure cells (propagator.rkt:500-800, 2323). Identity is +;; exporter-local per D2 PATH B; this table mirrors the named constants — when +;; a new well-known cell is added there, extend here (cheap; viz-only). +(define well-known-cells + (hasheq 0 "decomp-request" 1 "worldview-cache" 2 "relation-store" + 3 "config" 4 "naf-pending" 5 "pool-config" + 6 "constraint-propagators-topology" 7 "elaborator-topology" + 8 "narrowing-topology" 9 "sre-topology" 10 "classify-inhabit-request" + 11 "fuel" 12 "fuel-budget" 13 "retraction-stratum-request" + 14 "resolution-stratum-request" 15 "fork-on-union-request" + 16 "fork-contradiction-request" 17 "decomposed-positions" + 18 "contradicted-branch-aids" 19 "union-derivation-chains" + 20 "congruence-sig-index" 21 "congruence-request" + 22 "dispatch-request" 23 "reduce-request")) + +(define (num-key n) (string->symbol (number->string n))) + +;; D4 identity stack for one network: domains champ + well-known + srclocs, +;; with measured coverage stats (the lock's "best-available with MEASURED +;; coverage" — never claim more than the data shows). +(define (identity-for-network pnet) + (define cells-champ (prop-network-cells pnet)) + (define cell-ids (champ-keys cells-champ)) + (define domains-champ (prop-network-cell-domains pnet)) + (define domain-cids (champ-keys domains-champ)) + (define cell-domains + (for/hasheq ([cid (in-list domain-cids)]) + (values (num-key (cell-id-n cid)) + (format "~a" (champ-lookup domains-champ (cell-id-n cid) cid))))) + (define wkc + (for/hasheq ([cid (in-list cell-ids)] + #:when (hash-has-key? well-known-cells (cell-id-n cid))) + (values (num-key (cell-id-n cid)) + (hash-ref well-known-cells (cell-id-n cid))))) + (define props-champ (prop-network-propagators pnet)) + (define prop-srclocs + (for/hasheq ([pid (in-list (champ-keys props-champ))] + #:when (let ([p (champ-lookup props-champ (prop-id-hash pid) pid)]) + (and (not (eq? p 'none)) (propagator-srcloc p)))) + (values (num-key (prop-id-n pid)) + (format "~a" (propagator-srcloc + (champ-lookup props-champ (prop-id-hash pid) pid)))))) + (define total (length cell-ids)) + (hasheq 'cellDomains cell-domains + 'wellKnownCells wkc + 'propagatorSrclocs prop-srclocs + 'coverage (hasheq 'cellsWithDomain (length domain-cids) + 'cellsWellKnown (hash-count wkc) + 'totalCells total))) + +;; Cheap topology signature (incremental observer, Phase 4a perf): cell-ids + +;; propagator connections ONLY — skips the per-cell `serialize-lattice-value` +;; that DOMINATES `serialize-network-topology` (e-class values carry +;; :best/:alts/:canonical/:provenance sets). Produces the SAME dedup key the full +;; serialize would, so the full serialize runs ONLY on a NEW topology — not once +;; per round. Under monotone network growth most rounds share a topology, so this +;; turns the per-round cost from O(net + every cell's value) into O(cells+props +;; ids). Matches the legacy sig format exactly (cell `id`s sorted | `pid:in>out` +;; sorted). +(define (topology-signature pnet) + (define cells-champ (prop-network-cells pnet)) + (define props-champ (prop-network-propagators pnet)) + (string-append + (string-join (sort (map (lambda (cid) (number->string (cell-id-n cid))) + (champ-keys cells-champ)) string~a" (prop-id-n pid) + (map cell-id-n (propagator-inputs prop)) + (map cell-id-n (propagator-outputs prop)))) + (champ-keys props-champ)) + string (hash-count v) VALUE-DETAIL-MAX-KEYS))))) + +(define (topology-section pnet [cell-info #f]) + (hasheq 'topology (serialize-network-topology pnet cell-info) + 'identity (identity-for-network pnet) + 'valueDetail (value-detail-for-network pnet))) + +;; Epoch bucketing: capture timestamps partition the round timeline. Round r +;; belongs to the first capture whose timestamp >= r's; trailing rounds (file +;; close: residuation fixpoint etc.) get a final "file-close" epoch. +(define (epoch-index-for ts capture-ts-list) + (let loop ([k 0] [cs capture-ts-list]) + (cond [(null? cs) k] + [(<= ts (car cs)) k] + [else (loop (add1 k) (cdr cs))]))) + +;; Source lines keyed by line-number string → trimmed text. Lets the viewer +;; label propagators by the SOURCE CONSTRUCT that installed them (fire-fns are +;; anonymous closures; srcloc is the meaningful identity). Best-effort: a +;; missing/unreadable file yields an empty map. +(define (read-source-lines path) + (with-handlers ([exn:fail? (lambda (_) (hasheq))]) + (define lines (file->lines path)) + (for/hasheq ([ln (in-list lines)] [i (in-naturals 1)] + #:when (positive? (string-length (string-trim ln)))) + (define t (string-trim ln)) + (values (string->symbol (number->string i)) + (if (> (string-length t) 120) (substring t 0 120) t))))) + +;; --- the trace harness, factored so both the file driver (process-file) and the +;; --- Phase 5b network driver (whnf-via-egraph-network) share the SAME envelope. +;; run-thunk : (-> (listof result)) — runs under the armed observer + e-graph +;; capture; its run-to-quiescence rounds are what the envelope renders. +(define (run-with-viz-trace run-thunk src-label source-lines max-diffs max-rounds) + (define-values (bsp-observe bsp-get-rounds) (make-trace-accumulator)) + (define round-times (box '())) ;; reversed; one ts per observed round + (define (timed-observer r) + (set-box! round-times (cons (current-inexact-milliseconds) + (unbox round-times))) + (bsp-observe r)) + (define obs (make-observatory (hasheq 'file (format "~a" src-label)))) + (define cap-box (box #f)) + ;; containment capture (reduction DAG): parent-alloc → (listof child-alloc). + (define containment-box (box (make-hash))) + (define t0 (current-inexact-milliseconds)) + (define results + (parameterize ([current-bsp-observer timed-observer] + [current-observatory obs] + [current-network-capture-box cap-box] + [current-eclass-containment-box containment-box]) + (run-thunk))) + (define t1 (current-inexact-milliseconds)) + (build-viz-envelope src-label source-lines results + bsp-get-rounds round-times obs cap-box containment-box + t0 t1 max-diffs max-rounds)) + +;; viz-export-file : path → envelope jsexpr. Reduction is ON-NETWORK (β/δ/ι/int-folds +;; route through the e-graph: redex⇒result rewrites become union propagators). +(define (viz-export-file src-path + #:max-diffs [max-diffs 50000] + #:max-rounds [max-rounds 5000]) + (run-with-viz-trace (lambda () (process-file src-path)) + (format "~a" src-path) (read-source-lines src-path) + max-diffs max-rounds)) + +;; viz-export-expr-network : expr → envelope jsexpr. Phase 5b — drive reduction of a +;; (constructed, ground) expr through whnf-via-egraph-network: the SCHEDULER-DRIVEN +;; reduce-stratum cascade. The trace then SHOWS the network DRIVING reduction (the +;; reduce-request cell, the emitter, the per-step union cascade), not just recording +;; it. Sets up the e-graph plumbing (registry + arithmetic seed + hashcons) here. +(define (viz-export-expr-network e + #:label [label "whnf-via-egraph-network"] + #:source-lines [source-lines (hash)] + #:max-diffs [max-diffs 50000] + #:max-rounds [max-rounds 5000]) + (run-with-viz-trace + (lambda () + (parameterize ([current-rule-registry-cell-id #f] + [current-eclass-hashcons-cell-id #f] + [current-persistent-registry-net-box (box (make-prop-network))]) + (define pb (current-persistent-registry-net-box)) + (init-rule-registry-cell! pb) + (set-box! pb (run-to-quiescence + (register-arithmetic-seed! (unbox pb) (current-rule-registry-cell-id)))) + (init-eclass-hashcons-cell! pb) + (list (whnf-via-egraph-network e)))) + label source-lines max-diffs max-rounds)) + +;; the shared envelope builder (vizTrace 2): per-round deduped topology + identity + +;; containment, from the observer's captured rounds. +(define (build-viz-envelope src-label source-lines results + bsp-get-rounds round-times obs cap-box containment-box + t0 t1 max-diffs max-rounds) + (define containment ;; cid → (listof child-cid), deduped + (for/hash ([(k v) (in-hash (unbox containment-box))]) + (values k (remove-duplicates v)))) + (define rounds (bsp-get-rounds)) + (define times (reverse (unbox round-times))) + (define captures (sort (observatory-captures obs) < + #:key net-capture-sequence-number)) + (define capture-ts (map net-capture-timestamp-ms captures)) + + ;; Validation block (the locked 2c criteria) — always computed, never silent. + (define monotone? + (or (null? times) + (for/and ([a (in-list times)] [b (in-list (cdr times))]) (<= a b)))) + (define n-cmds (length results)) + (define n-caps (length captures)) + (define round-epochs (for/list ([ts (in-list times)]) + (epoch-index-for ts capture-ts))) + (define validation + (hasheq 'roundTimestampsMonotone monotone? + 'commandCount n-cmds + 'captureCount n-caps + 'capturesMatchCommands (= n-cmds n-caps) + 'roundsTotal (length rounds) + 'roundsBucketed (length round-epochs))) + + ;; --- vizTrace 2: per-ROUND topology. Each round is rendered against its + ;; OWN network snapshot, fixing the per-epoch id-space mismatch (a round's + ;; fired props / cell diffs reference cells in the id-space of the network + ;; THAT ROUND ran on — elaboration vs a solve-fork — which an epoch's + ;; last-round snapshot need not match). Topologies are globally deduped by + ;; signature; each round carries an index into the table. + (define (friendly s) (regexp-replace #rx"^elab:" s "")) + (define capped-rounds (if (> (length rounds) max-rounds) (take rounds max-rounds) rounds)) + (define capped-times (if (> (length times) max-rounds) (take times max-rounds) times)) + (define capped-epochs (if (> (length round-epochs) max-rounds) (take round-epochs max-rounds) round-epochs)) + (define topo-table (make-hash)) ;; signature → index + (define topo-rev '()) ;; reversed topology sections + (define topo-count 0) + (define (intern-topology! pnet) + ;; Cheap sig per round (no value serialization); full serialize ONLY on a NEW + ;; topology (the incremental-observer win — most rounds reuse a topology). + (define sig (topology-signature pnet)) + (or (hash-ref topo-table sig #f) + (let ([idx topo-count] + [topo (serialize-network-topology pnet)]) + (hash-set! topo-table sig idx) + (set! topo-count (add1 topo-count)) + ;; containment edges present in THIS topology (both ends are cells here) + (define present + (for/hasheq ([c (in-list (hash-ref topo 'cells))]) (values (hash-ref c 'id) #t))) + (define cont-edges + (for*/list ([(parent kids) (in-hash containment)] + #:when (hash-ref present parent #f) + [kid (in-list kids)] + #:when (hash-ref present kid #f)) + (hasheq 'from parent 'to kid))) + (set! topo-rev (cons (hasheq 'topology topo + 'identity (identity-for-network pnet) + 'containment cont-edges) topo-rev)) + idx))) + (define total-diffs (for/sum ([r (in-list rounds)]) (length (bsp-round-cell-diffs r)))) + (define diffs-capped? (> total-diffs max-diffs)) + (define (round-label e) + (friendly (cond [(< e n-caps) (format "~a" (net-capture-label (list-ref captures e)))] + [else "file-close"]))) + (define rounds-json + (for/list ([r (in-list capped-rounds)] [ts (in-list capped-times)] [e (in-list capped-epochs)]) + (hasheq 'roundNumber (bsp-round-round-number r) + 'timestampMs ts + 'command (round-label e) + 'topo (intern-topology! (bsp-round-network-snapshot r)) + 'propagatorsFired (map prop-id-n (bsp-round-propagators-fired r)) + 'cellDiffs (if diffs-capped? '() + (map serialize-cell-diff (bsp-round-cell-diffs r)))))) + (define topologies-json (reverse topo-rev)) + ;; Ordered distinct commands (label + first round) for the timeline readout. + (define commands-json + (let loop ([rs rounds-json] [seen (hash)] [acc '()] [seq 0]) + (cond [(null? rs) (reverse acc)] + [(hash-has-key? seen (hash-ref (car rs) 'command)) (loop (cdr rs) seen acc seq)] + [else (loop (cdr rs) (hash-set seen (hash-ref (car rs) 'command) #t) + (cons (hasheq 'seq seq 'label (hash-ref (car rs) 'command) + 'firstRound (hash-ref (car rs) 'roundNumber)) acc) + (add1 seq))]))) + + (define enet (unbox cap-box)) + (hasheq 'vizTrace 2 + 'file src-label + 'source (hasheq 'path src-label + 'lines source-lines) + 'wallMs (- t1 t0) + 'commandCount n-cmds + 'errors (length (filter prologos-error? results)) + 'errorMessages (for/list ([r (in-list results)] + #:when (prologos-error? r)) + (prologos-error-message r)) + 'commands commands-json + 'topologies topologies-json + 'rounds rounds-json + 'finalTopology + (if enet + (hash-set (topology-section (elab-network-prop-net enet) + (elab-network-cell-info enet)) + 'present #t) + (hasheq 'present #f)) + 'roundsTruncated (or (> (length rounds) max-rounds) diffs-capped?) + 'validation (hash-set validation 'topologyCount topo-count))) + +(module+ main + (define args (vector->list (current-command-line-arguments))) + (define (flag-val flag lst) (let loop ([l lst]) + (cond [(or (null? l) (null? (cdr l))) #f] + [(equal? (car l) flag) (cadr l)] + [else (loop (cdr l))]))) + (define src (findf (lambda (a) (not (string-prefix? a "-"))) args)) + (define out (flag-val "-o" args)) + (define validate? (member "--validate" args)) + (unless src + (eprintf "usage: racket tools/viz-export.rkt FILE.prologos -o out.json [--max-diffs N] [--max-rounds N] [--validate]\n") + (eprintf " reduction is on-network (propagator-native) on this branch — always.\n") + (exit 1)) + (define envelope + (viz-export-file src + #:max-diffs (cond [(flag-val "--max-diffs" args) => string->number] + [else 50000]) + #:max-rounds (cond [(flag-val "--max-rounds" args) => string->number] + [else 5000]))) + (when out + (call-with-output-file out + (lambda (port) (write-json envelope port)) + #:exists 'replace) + (printf "wrote ~a (~a bytes)\n" out (file-size out))) + (define v (hash-ref envelope 'validation)) + (define rs (hash-ref envelope 'rounds)) + (define fired (map (lambda (r) (length (hash-ref r 'propagatorsFired))) rs)) + (define max-fired (if (null? fired) 0 (apply max fired))) + (printf "vizTrace2 errors ~a rounds ~a topologies ~a monotone ~a captures==commands ~a\n" + (hash-ref envelope 'errors) (length rs) + (hash-ref v 'topologyCount) + (hash-ref v 'roundTimestampsMonotone) + (hash-ref v 'capturesMatchCommands)) + (printf "concurrency: max propagators fired in one round = ~a (rounds with >1: ~a)\n" + max-fired (length (filter (lambda (n) (> n 1)) fired))) + (when (and validate? + (not (and (hash-ref v 'roundTimestampsMonotone) + (hash-ref v 'capturesMatchCommands) + (zero? (hash-ref envelope 'errors))))) + (eprintf "VALIDATION FAILED: ~a\n" v) + (exit 2))) diff --git a/racket/prologos/tools/viz/check.js b/racket/prologos/tools/viz/check.js new file mode 100644 index 000000000..12433a90b --- /dev/null +++ b/racket/prologos/tools/viz/check.js @@ -0,0 +1,52 @@ +// Headless verification of the viewer's pure core (the @PURE block) against +// real vizTrace 2 envelopes. node tools/viz/check.js index.html t1.vizjson ... +const fs = require('fs'); +const html = fs.readFileSync(process.argv[2], 'utf8'); +const m = html.match(/\/\* @PURE-BEGIN \*\/([\s\S]*?)\/\* @PURE-END \*\//); +if (!m) { console.error('PURE block not found'); process.exit(1); } +eval(m[1]); + +let failures = 0; +const check = (c, msg) => { if (!c) { console.error('FAIL: ' + msg); failures++; } }; + +for (const file of process.argv.slice(3)) { + if (!fs.existsSync(file)) { console.log(file, 'MISSING'); continue; } + const env = JSON.parse(fs.readFileSync(file, 'utf8')); + check(env.vizTrace === 2, 'vizTrace 2'); + const rounds = timelineRounds(env); + const changed = changedCellIdsGlobal(env); + + let prevRN = -Infinity, anyFired = false, anyGrowth = false, maxFired = 0, labeledOK = true; + // per-round invariants (cheap) over every round + for (let i = 0; i < rounds.length; i++) { + const r = rounds[i]; + check(r.roundNumber >= prevRN, 'rounds ordered'); prevRN = r.roundNumber; + check(env.topologies[r.topo] !== undefined, 'round topo ref valid'); + const topo = env.topologies[r.topo].topology; + const propIds = new Set(topo.propagators.map(p => p.id)); + const cellIds = new Set(topo.cells.map(c => c.id)); + for (const pid of r.propagatorsFired) check(propIds.has(pid), `fired prop ${pid} in round's own topo`); + for (const d of (r.cellDiffs || [])) check(cellIds.has(d.cell ?? d.cellId), `diff cell in round's own topo`); + maxFired = Math.max(maxFired, r.propagatorsFired.length); + if (r.propagatorsFired.length) anyFired = true; + if (i > 0 && r.topo !== rounds[i - 1].topo) anyGrowth = true; + } + // graph + both layouts: ONCE per distinct topology (layout is expensive) + for (let ti = 0; ti < env.topologies.length; ti++) { + const g = buildGraphFromTopo(env.topologies[ti], env.source, changed, false); + for (const e of g.edges) check(g.byId.has(e.from) && g.byId.has(e.to), 'edge endpoints'); + for (const lay of [layoutLayered(g), layoutForce(g, null, 800, 600)]) + for (const n of g.nodes) { const p = lay.pos.get(n.key); check(p && isFinite(p.x) && isFinite(p.y), 'positioned ' + n.key); } + const labeled = g.nodes.filter(n => n.kind === 'prop' && !/^p\d+$/.test(n.label)).length; + const props = g.nodes.filter(n => n.kind === 'prop').length; + if (props && labeled === 0) labeledOK = false; + } + check(anyFired, 'some round fires a propagator'); + check(rounds.length <= 1 || anyGrowth, 'network changes across timeline'); + + console.log(`${file.split('/').pop()}: vizTrace${env.vizTrace} rounds=${rounds.length} ` + + `topologies=${env.topologies.length} commands=${(env.commands||[]).length} ` + + `maxFired/round=${maxFired} propLabelsFromSource=${labeledOK}`); +} +console.log(failures === 0 ? 'ALL CHECKS PASS' : failures + ' FAILURES'); +process.exit(failures === 0 ? 0 : 1); diff --git a/racket/prologos/tools/viz/index.html b/racket/prologos/tools/viz/index.html new file mode 100644 index 000000000..d4633535a --- /dev/null +++ b/racket/prologos/tools/viz/index.html @@ -0,0 +1,689 @@ + + + + + +Prologos propagator network + + + + + + + +
+ + + + + + diff --git a/racket/prologos/tools/viz/render-mp4.js b/racket/prologos/tools/viz/render-mp4.js new file mode 100644 index 000000000..ca08a2389 --- /dev/null +++ b/racket/prologos/tools/viz/render-mp4.js @@ -0,0 +1,89 @@ +// render-mp4.js — render a vizTrace 2 timeline to one SVG per round, reusing +// the viewer's @PURE core (force-directed layout) so the video matches the +// browser. Pipeline (driven by the sibling shell command): node -> frames/*.svg +// -> rsvg-convert -> *.png -> ffmpeg -> mp4. +// node tools/viz/render-mp4.js index.html trace.vizjson outdir [W H] +const fs = require('fs'), path = require('path'); +const html = fs.readFileSync(process.argv[2], 'utf8'); +const m = html.match(/\/\* @PURE-BEGIN \*\/([\s\S]*?)\/\* @PURE-END \*\//); +if (!m) { console.error('PURE block not found'); process.exit(1); } +eval(m[1]); + +const env = JSON.parse(fs.readFileSync(process.argv[3], 'utf8')); +const outdir = process.argv[4]; +const W = +(process.argv[5] || 1280), H = +(process.argv[6] || 720); +fs.mkdirSync(outdir, { recursive: true }); + +const rounds = timelineRounds(env); +const changed = changedCellIdsGlobal(env); +const esc = s => String(s).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); +const trim = (s, n) => { s = String(s); return s.length > n ? s.slice(0, n - 1) + '…' : s; }; + +// Build + force-layout each topology once (seed from previous for stability). +const built = new Map(); let prevPos = null; +function GL(topoIdx) { + if (built.has(topoIdx)) return built.get(topoIdx); + const graph = buildGraphFromTopo(env.topologies[topoIdx], env.source, changed, false); + const layout = layoutForce(graph, prevPos, 1000, 700); + prevPos = layout.pos; + const v = { graph, layout }; built.set(topoIdx, v); return v; +} + +const tfCache = new Map(); +function tf(topoIdx) { + if (tfCache.has(topoIdx)) return tfCache.get(topoIdx); + const { graph, layout } = GL(topoIdx); const M = 70, TOP = 70; + const keys = graph.nodes.map(n => n.key); + if (!keys.length) { const t = { k: 1, tx: M, ty: TOP }; tfCache.set(topoIdx, t); return t; } + const xs = keys.map(k => layout.pos.get(k).x), ys = keys.map(k => layout.pos.get(k).y); + const minX = Math.min(...xs), maxX = Math.max(...xs), minY = Math.min(...ys), maxY = Math.max(...ys); + const k = Math.min(1.7, Math.max(0.08, Math.min((W - 2 * M) / Math.max(maxX - minX, 1), (H - TOP - M) / Math.max(maxY - minY, 1)))); + const t = { k, tx: M - minX * k + (W - 2 * M - (maxX - minX) * k) / 2, ty: TOP - minY * k + (H - TOP - M - (maxY - minY) * k) / 2 }; + tfCache.set(topoIdx, t); return t; +} + +function bornKeys(i) { + if (i === 0) return topoNodeKeys(env, rounds[0].topo); + const prev = topoNodeKeys(env, rounds[i - 1].topo), cur = topoNodeKeys(env, rounds[i].topo); + const b = new Set(); for (const k of cur) if (!prev.has(k)) b.add(k); return b; +} + +function svgFrame(i) { + const r = rounds[i], { graph, layout } = GL(r.topo), t = tf(r.topo); + const P = (x, y) => [t.tx + x * t.k, t.ty + y * t.k]; + const fired = new Set(r.propagatorsFired), born = bornKeys(i), changedC = new Set((r.cellDiffs || []).map(d => 'c' + d.cellId)); + const showLabels = t.k > 0.42; + let s = ``; + s += ``; + for (const e of graph.edges) { + const a = layout.pos.get(e.from), b = layout.pos.get(e.to); if (!a || !b) continue; + const [ax, ay] = P(a.x, a.y), [bx, by] = P(b.x, b.y), live = fired.has(e.pid); + if (e.kind === "containment") { // reduction DAG — dashed violet + s += ``; + continue; + } + if (e.from === e.to) s += ``; + else { s += ``; + if (live) { const dx = bx - ax, dy = by - ay, L = Math.hypot(dx, dy) || 1, ux = dx / L, uy = dy / L, hx = bx - ux * 12, hy = by - uy * 12; + s += ``; } } + } + for (const n of graph.nodes) { + const p = layout.pos.get(n.key); const [x, y] = P(p.x, p.y); + const isFired = n.kind === 'prop' && fired.has(n.id), wrote = changedC.has(n.key), isBorn = born.has(n.key); + const stroke = isFired ? '#ff6b6b' : isBorn ? '#4ec9b0' : wrote ? '#e9c46a' : '#222', sw = (isFired || isBorn || wrote) ? 2.6 : 1, fill = wrote ? '#e9c46a' : n.color; + if (n.kind === 'cell') s += ``; + else s += ``; + if (showLabels) s += `${esc(trim(n.label, 30))}`; + } + const cmds = env.commands || [], seq = (cmds.findIndex(c => c.label === r.command) + 1) || '?'; + const st = env.topologies[r.topo].topology.stats; + s += ``; + s += `Prologos network`; + s += `● command ${seq}/${cmds.length}: ${esc(trim(r.command, 42))}`; + s += `round ${r.roundNumber} (${i + 1}/${rounds.length}) · fired ${fired.size} · ${st.totalCells}c/${st.totalPropagators}p`; + s += ``; + return s; +} + +for (let i = 0; i < rounds.length; i++) fs.writeFileSync(path.join(outdir, 'f' + String(i).padStart(4, '0') + '.svg'), svgFrame(i)); +console.log('wrote ' + rounds.length + ' svg frames to ' + outdir); diff --git a/racket/prologos/trace-serialize.rkt b/racket/prologos/trace-serialize.rkt index a85ac1500..e26430f03 100644 --- a/racket/prologos/trace-serialize.rkt +++ b/racket/prologos/trace-serialize.rkt @@ -41,6 +41,18 @@ [(eq? v type-bot) "⊥"] [(eq? v type-top) "⊤"] [(eq? v 'bot) "⊥"] ;; raw bot symbol used in some cells + ;; e-class product cell (eclass-cell.rkt): a hasheq whose ':best is a + ;; (cost . reduced-form) pair. Surface the reduced form so reduction + ;; visualizations read as actual values/terms, not "hash(N entries)". + [(and (hash? v) (hash-has-key? v ':best)) + (let ([best (hash-ref v ':best #f)]) + (cond + [(not best) "e-class ⊥"] + [(pair? best) + (with-handlers ([exn:fail? (lambda (_) (format "~v" (cdr best)))]) + (pp-expr (cdr best)))] + [else (with-handlers ([exn:fail? (lambda (_) (format "~v" best))]) + (pp-expr best))]))] [(hash? v) (format "hash(~a entries)" (hash-count v))] [(pair? v) diff --git a/tools/check-parens.sh b/tools/check-parens.sh index c0c94609c..fbe574c63 100755 --- a/tools/check-parens.sh +++ b/tools/check-parens.sh @@ -9,6 +9,12 @@ # Exit 0 = all balanced. Exit 1 = error (with location). RACKET="/Applications/Racket v9.0/bin/racket" +# Fallback to PATH lookup when the pinned binary is absent (e.g., Linux CI/containers) +[ -x "$RACKET" ] || RACKET="$(command -v racket)" +if [ -z "$RACKET" ]; then + echo "check-parens.sh: no Racket executable found (pinned binary absent and 'racket' not on PATH)" >&2 + exit 2 +fi check_file() { local f="$1"