diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 6393025..e950e18 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,3 +1,8 @@ +--- +doc_revision: 1 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + # Copilot Instructions Follow `POLICY_SEED.md` as the authoritative control policy. @@ -9,4 +14,4 @@ Key requirements: - For workflow changes, run `python scripts/policy_check.py --workflows`. - Use `mise exec -- python` for policy tooling to ensure dependencies resolve. -If a request conflicts with `POLICY_SEED.md`, stop and ask for clarification. +If a request conflicts with `POLICY_SEED.md`, stop and ask for clarification. \ No newline at end of file diff --git a/.github/workflows/ci-milestones.yml b/.github/workflows/ci-milestones.yml index 834d04d..d5b47d2 100644 --- a/.github/workflows/ci-milestones.yml +++ b/.github/workflows/ci-milestones.yml @@ -23,6 +23,9 @@ permissions: env: PYTHON_VERSION: "3.14" + XLA_PYTHON_CLIENT_PREALLOCATE: "false" + XLA_PYTHON_CLIENT_ALLOCATOR: "platform" + PRISM_JAX_GPU_MEM_CAP_MB: "128" jobs: changes: @@ -158,15 +161,95 @@ jobs: run: | scripts/check_agda.sh + preflight-smoke: + needs: [changes, policy-check, audit, smoke-exports, agda-check] + if: needs.changes.outputs.code_changed == 'true' + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 + with: + python-version: ${{ env.PYTHON_VERSION }} + - name: Install dependencies (preflight) + run: | + python -m pip install --upgrade pip + python -m pip install pytest jax jaxlib + - name: Import smoke (ic_vm, prism_vm) + run: | + python -c "import ic_vm; import prism_vm" + - name: Collect-only harness import graph + run: | + mkdir -p artifacts + set -euo pipefail + pytest -c pytest.baseline.ini \ + --collect-only tests/harness.py \ + 2>&1 | tee artifacts/pytest-preflight-collect.txt || true + - name: Run pytest smokes (preflight) + run: | + mkdir -p artifacts + set -euo pipefail + pytest -c pytest.baseline.ini -q \ + tests/test_harness_jit_cfg_smoke.py \ + tests/test_harness_cnf2_cfg_smoke.py \ + tests/test_ic_guard_cfg_smoke.py \ + --junitxml artifacts/pytest-preflight.xml \ + 2>&1 | tee artifacts/pytest-preflight.txt + - name: Upload preflight artifacts + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: pytest-preflight + path: artifacts/ + if-no-files-found: ignore + + baseline-tests: + needs: [changes, policy-check, audit, smoke-exports, agda-check, preflight-smoke] + if: needs.changes.outputs.code_changed == 'true' + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 + with: + python-version: ${{ env.PYTHON_VERSION }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install pytest pytest-xdist jax jaxlib jaxtyping beartype + - name: Record telemetry metadata (baseline) + run: | + mkdir -p artifacts + python scripts/record_telemetry_metadata.py \ + --out artifacts/telemetry_metadata_baseline.json \ + --milestone baseline \ + --label "pytest-baseline" \ + --extra job=baseline-tests + - name: Run pytest (baseline) + run: | + mkdir -p artifacts + set -euo pipefail + pytest -c pytest.baseline.ini \ + -n auto -vv -ra \ + --junitxml artifacts/pytest-baseline.xml \ + 2>&1 | tee artifacts/pytest-baseline.txt + - name: Upload pytest artifact (baseline) + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: pytest-baseline + path: artifacts/ + if-no-files-found: ignore + tests: - needs: [changes, policy-check, audit, smoke-exports] + needs: [changes, policy-check, audit, smoke-exports, agda-check, preflight-smoke, baseline-tests] if: needs.changes.outputs.code_changed == 'true' runs-on: ubuntu-latest timeout-minutes: 30 strategy: fail-fast: false matrix: - milestone: [m1, m2, m3, m4, m5] + suite: [m2, m3, m4, m5] steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 @@ -175,25 +258,26 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - python -m pip install pytest pytest-xdist jax jaxlib - - name: Record telemetry metadata + python -m pip install pytest pytest-xdist jax jaxlib jaxtyping beartype + - name: Record telemetry metadata (suite) run: | mkdir -p artifacts python scripts/record_telemetry_metadata.py \ - --out artifacts/telemetry_metadata_${{ matrix.milestone }}.json \ - --milestone ${{ matrix.milestone }} \ - --label "pytest-${{ matrix.milestone }}" \ + --out artifacts/telemetry_metadata_${{ matrix.suite }}.json \ + --milestone ${{ matrix.suite }} \ + --label "pytest-${{ matrix.suite }}" \ --extra job=tests - - name: Run pytest (milestone) + - name: Run pytest (suite) run: | mkdir -p artifacts set -euo pipefail - pytest -c pytest.${{ matrix.milestone }}.ini \ + config="pytest.${{ matrix.suite }}.ini" + pytest -c "$config" \ -n auto \ - --junitxml artifacts/pytest-${{ matrix.milestone }}.xml \ - 2>&1 | tee artifacts/pytest-${{ matrix.milestone }}.txt + --junitxml artifacts/pytest-${{ matrix.suite }}.xml \ + 2>&1 | tee artifacts/pytest-${{ matrix.suite }}.txt - name: Run damage metrics fixture (arena) - if: matrix.milestone == 'm4' + if: matrix.suite == 'm4' run: | mkdir -p artifacts set -euo pipefail @@ -210,7 +294,7 @@ jobs: --inputs artifacts/damage_metrics.txt artifacts/damage_metrics_tile32.txt \ --out artifacts/damage_metrics_delta.txt - name: Capture host performance baselines - if: matrix.milestone == 'm4' + if: matrix.suite == 'm4' run: | mkdir -p artifacts python scripts/audit_host_performance.py \ @@ -226,7 +310,7 @@ jobs: --engine cnf2 --iterations 5 --warmup 1 \ --json-out artifacts/host_memory_cnf2.json - name: Capture CPU trace baselines - if: matrix.milestone == 'm4' + if: matrix.suite == 'm4' run: | mkdir -p artifacts python scripts/capture_trace.py \ @@ -235,7 +319,7 @@ jobs: python scripts/trace_analyze.py \ --report-only --json-out artifacts/trace_cpu_report.json - name: Capture m5 servo trace baselines - if: matrix.milestone == 'm5' + if: matrix.suite == 'm5' run: | mkdir -p artifacts rm -rf /tmp/jax-trace @@ -266,12 +350,76 @@ jobs: if: always() uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 with: - name: pytest-${{ matrix.milestone }} + name: pytest-${{ matrix.suite }} + path: artifacts/ + if-no-files-found: ignore + + baseline-tests-gpu: + needs: [changes, policy-check, audit, smoke-exports, agda-check, preflight-smoke, baseline-tests] + if: needs.changes.outputs.code_changed == 'true' && github.actor == github.repository_owner && needs.policy-check.result == 'success' && needs.audit.result == 'success' + runs-on: [self-hosted, cassian, gpu, local] + timeout-minutes: 30 + env: + XLA_FLAGS: "--xla_gpu_enable_command_buffer=" + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + - name: Ensure mise Python is installed (self-hosted) + run: | + if ! command -v mise >/dev/null 2>&1; then + echo "mise is required on the self-hosted runner" >&2 + exit 1 + fi + mise install + - name: Verify python version (self-hosted) + run: | + mise exec -- python - <<'PY' + import os + import sys + want = os.environ["PYTHON_VERSION"] + version = sys.version.split()[0] + if not version.startswith(want): + raise SystemExit(f"Expected Python {want}, found {version}") + print("Python version:", version) + PY + - name: Install test dependencies (GPU) + run: | + mise exec -- python -m pip install --upgrade pip + mise exec -- python -m pip install pytest nvidia-ml-py jaxtyping beartype "jax[cuda12]" -f https://storage.googleapis.com/jax-releases/jax_cuda_releases.html + - name: Verify GPU backend + run: | + mise exec -- python - <<'PY' + import jax + gpus = jax.devices("gpu") + if not gpus: + raise SystemExit("GPU backend required but not available") + print("GPU devices:", gpus) + PY + - name: Record telemetry metadata (gpu baseline) + run: | + mkdir -p artifacts + mise exec -- python scripts/record_telemetry_metadata.py \ + --out artifacts/telemetry_metadata_gpu_baseline.json \ + --milestone baseline \ + --backend gpu \ + --label "pytest-gpu-baseline" \ + --extra job=baseline-tests-gpu + - name: Run pytest (baseline, gpu) + run: | + mkdir -p artifacts + set -euo pipefail + mise exec -- python -m pytest -c pytest.baseline.ini \ + --junitxml artifacts/pytest-gpu-baseline.xml \ + 2>&1 | tee artifacts/pytest-gpu-baseline.txt + - name: Upload pytest artifact (gpu baseline) + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: pytest-gpu-baseline path: artifacts/ if-no-files-found: ignore tests-gpu: - needs: [changes, policy-check, audit, smoke-exports] + needs: [changes, policy-check, audit, smoke-exports, agda-check, preflight-smoke, baseline-tests, baseline-tests-gpu] if: needs.changes.outputs.code_changed == 'true' && github.actor == github.repository_owner && needs.policy-check.result == 'success' && needs.audit.result == 'success' runs-on: [self-hosted, cassian, gpu, local] timeout-minutes: 30 @@ -304,7 +452,7 @@ jobs: - name: Install test dependencies (GPU) run: | mise exec -- python -m pip install --upgrade pip - mise exec -- python -m pip install pytest nvidia-ml-py "jax[cuda12]" -f https://storage.googleapis.com/jax-releases/jax_cuda_releases.html + mise exec -- python -m pip install pytest nvidia-ml-py jaxtyping beartype "jax[cuda12]" -f https://storage.googleapis.com/jax-releases/jax_cuda_releases.html - name: Verify GPU backend run: | mise exec -- python - <<'PY' @@ -356,7 +504,7 @@ jobs: collect-report: runs-on: ubuntu-latest - needs: [changes, policy-check, audit, smoke-exports, agda-check, tests, tests-gpu] + needs: [changes, policy-check, audit, smoke-exports, agda-check, preflight-smoke, baseline-tests, baseline-tests-gpu, tests, tests-gpu] if: always() && needs.policy-check.result == 'success' && needs.audit.result == 'success' steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 diff --git a/.github/workflows/pr-dataflow-grammar.yml b/.github/workflows/pr-dataflow-grammar.yml new file mode 100644 index 0000000..694c7e9 --- /dev/null +++ b/.github/workflows/pr-dataflow-grammar.yml @@ -0,0 +1,78 @@ +name: pr-dataflow-grammar + +on: + pull_request: + paths: + - "scripts/**" + - "src/**" + - ".github/workflows/pr-dataflow-grammar.yml" + +permissions: + contents: read + +jobs: + dataflow-grammar: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 + with: + python-version: "3.14" + - name: Install graphviz + run: | + sudo apt-get update + sudo apt-get install -y graphviz + - name: Render dataflow grammar graph + run: | + ./scripts/render_dataflow_grammar.sh src artifacts/dataflow_grammar + - name: Upload dataflow artifact + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: dataflow-grammar + path: artifacts/dataflow_grammar + - name: Comment on PR with artifact link + if: github.event.pull_request.head.repo.full_name == github.repository + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + REPO: ${{ github.repository }} + RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: | + set -euo pipefail + python - <<'PY' + import json + import os + import pathlib + import urllib.request + + token = os.environ["GITHUB_TOKEN"] + repo = os.environ["REPO"] + issue = os.environ["PR_NUMBER"] + run_url = os.environ["RUN_URL"] + report_path = pathlib.Path("artifacts/dataflow_grammar/report.md") + if report_path.exists(): + report = report_path.read_text() + else: + report = "\nDataflow grammar graph generated.\n" + footer = f"\n\n- Artifact: dataflow-grammar\n- Download: {run_url}\n" + body = report + footer + if len(body) > 60000: + body = body[:60000] + "\n\n(truncated)\n" + footer + + url = f"https://api.github.com/repos/{repo}/issues/{issue}/comments" + req = urllib.request.Request( + url, + data=json.dumps({"body": body}).encode("utf-8"), + headers={ + "Authorization": f"Bearer {token}", + "Accept": "application/vnd.github+json", + "Content-Type": "application/json", + }, + method="POST", + ) + with urllib.request.urlopen(req) as resp: + resp.read() + PY diff --git a/AGENTS.md b/AGENTS.md index a66adf8..265516e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,3 +1,8 @@ +--- +doc_revision: 1 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + # AGENTS.md This repository is governed by `POLICY_SEED.md`. Treat it as authoritative. @@ -13,4 +18,4 @@ This repository is governed by `POLICY_SEED.md`. Treat it as authoritative. - Hooks are advisory; CI policy checks are authoritative. - Use `mise exec -- python` for policy tooling so dependencies resolve as expected. -If any request conflicts with `POLICY_SEED.md`, stop and ask for guidance. +If any request conflicts with `POLICY_SEED.md`, stop and ask for guidance. \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fd64a13..35dfba5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,3 +1,8 @@ +--- +doc_revision: 3 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + # Contributing Thanks for contributing. This repo enforces a strict execution policy to protect @@ -34,6 +39,14 @@ mise exec -- python scripts/policy_check.py --workflows CI also runs `scripts/policy_check.py --workflows --posture`, which checks the GitHub Actions settings for this repository. +## Doc front-matter (reader-only re-internment signal) +Markdown docs include a YAML front-matter block with: +- `doc_revision` (integer) +- `reader_reintern` (reader-only guidance) + +When you make a conceptual change, bump `doc_revision`. This is a reader-only +signal to re-intern; it is not enforced by tooling or repo state. + ## GPU tests and sandboxed environments Some tests rely on CUDA/JAX GPU backends. If you are running in a sandboxed environment, GPU access may require explicit sandbox escalation/privileged @@ -49,9 +62,36 @@ scripts/run_tests.sh [pytest args...] If running under Codex or another sandboxed runner, increase the sandbox command timeout before invoking the helper. +## Profiling targeted tests (bounded time) +To capture a short profile of a slow test without waiting for completion: +``` +mise exec -- python scripts/profile_pytest_slice.py \ + --seconds 60 \ + --out artifacts/profiles/pytest_slice.prof \ + -- -q tests/test_strata_random_programs.py +``` +This dumps a `.prof` file even if the test would take longer than the allotted +time, so you can inspect hotspots without running the full suite. + ## Agda proofs Agda checks run in a pinned container image. See `agda/README.md` for details. Local run: ``` scripts/check_agda_container.sh ``` + +## Dataflow grammar invariant +Recurring parameter bundles are treated as type-level smells. Any bundle that +crosses function boundaries must be promoted to a dataclass (config or local +bundle), or explicitly documented. + +The dataflow grammar audit enforces: +- Tier 1/2 bundles (crossing config or recurring across functions) must be + promoted to a dataclass bundle. +- Tier 3 bundles (single-site) must either be promoted or documented in-place. + +To document an intentional unbundled tuple, add a marker comment: +``` +# dataflow-bundle: a1, a2, a3 +``` +and explain why it must remain unbundled. diff --git a/HYPERSTRATA_PROPOSAL.md b/HYPERSTRATA_PROPOSAL.md index d0cd65b..192e350 100644 --- a/HYPERSTRATA_PROPOSAL.md +++ b/HYPERSTRATA_PROPOSAL.md @@ -1,3 +1,8 @@ +--- +doc_revision: 1 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + # Hyperstrata and Hypervalue CNF-2 Proposal Status: m3 semantic commitment (implementation staged; no code changes implied). @@ -192,4 +197,4 @@ Two-dimensional strata and hypervalues can be added without changing the core CNF-2 arity or slot layout. The key requirements are explicit pre-step immutability and a clear read model. Hypervalues let CNF-2 act as a hyperoperator over orthogonal dimensions, while strata and micro-strata provide the staging -needed to preserve determinism and canonical identity. +needed to preserve determinism and canonical identity. \ No newline at end of file diff --git a/IMPLEMENTATION_PLAN.md b/IMPLEMENTATION_PLAN.md index 3e12cf2..294e076 100644 --- a/IMPLEMENTATION_PLAN.md +++ b/IMPLEMENTATION_PLAN.md @@ -1,3 +1,8 @@ +--- +doc_revision: 1 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + # Prism VM Evolution Implementation Plan This plan implements the features described in `in/in-4.md` through @@ -711,7 +716,7 @@ Tasks: - Local block sort and merge once the contract is stable. ## Acceptance Gates -- **m1 gate:** univalence + no aliasing + baseline equivalence on small suite. +- **baseline gate (m1 suite):** univalence + no aliasing + baseline equivalence on small suite. - **m2 gate:** strata validator passes + `q` projection total on emitted strata. - **m3 gate:** denotation invariance across unsorted/rank/morton/block schedulers, plus pre-step immutability enforced as a hyperstrata visibility rule. @@ -755,7 +760,7 @@ m5: - CLI (banded): per-milestone configs use `--milestone-band` so each band is exclusive; running `m1`→`m5` covers the full suite without reruns. - `pytest -c pytest.m2.ini` runs only tests marked `m2`. - - `pytest -c pytest.m1.ini` runs `m1` plus unmarked tests (via + - `pytest -c pytest.baseline.ini` runs `m1` plus unmarked tests (via `--include-unmarked`). - CLI (inclusive): `pytest --milestone=m2` runs all tests at or below `m2`. - VS Code: edit `.vscode/pytest.env` to set `PRISM_MILESTONE=m2`, then refresh @@ -915,4 +920,4 @@ Planned steps (tests-first, pytest + program fixtures): - M7 ✅: Active-pair matching + stream compaction (null: no active pairs). - M8 ✅: Rule table and wiring templates (annihilation/commutation/erasure). - M9 ✅: Allocation via prefix sums + FreeStack reuse/overflow guards. -- M10 ⏳: Match/alloc/rewire/commit kernel pipeline and end-to-end reductions. +- M10 ⏳: Match/alloc/rewire/commit kernel pipeline and end-to-end reductions. \ No newline at end of file diff --git a/MILESTONES.md b/MILESTONES.md index 4619a88..c9bd0a7 100644 --- a/MILESTONES.md +++ b/MILESTONES.md @@ -1,11 +1,19 @@ +--- +doc_revision: 1 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + # Milestones -## m1 (2026-01-23) +## m1 (2026-01-23) — completed Tag: `m1` -Gate command: -- `mise exec -- pytest -c pytest.m1.ini` +Baseline gate (m1 suite, runs under the current baseline milestone): +- `mise exec -- pytest -c pytest.baseline.ini` + +m1-only mode is deprecated. The m1 suite should run as baseline coverage, not +under m1-restricted semantics. -Expected xfails (gated above m1): +Expected xfails (in later milestones): - `tests/test_coord_batch.py::test_coord_xor_batch_uses_single_intern_call` - m4: no batched coord_xor_batch / coord_norm_batch yet. - `tests/test_coord_batch.py::test_coord_norm_batch_matches_host` - m4: no batched coord_norm_batch yet. @@ -41,4 +49,4 @@ Ordered by semantic risk first, then verification depth, then hygiene. - No‑copy / alpha‑equivalence tests for ledger sharing (in‑17). ✅ - CQRS replay harness beyond Min(Prism) (optional audit mode). ✅ - Agda boundary theorems (no‑termination / negative capability). ✅ -- Interaction‑combinator backend (in‑8) beyond roadmap: data model + kernel prototype. ✅ +- Interaction‑combinator backend (in‑8) beyond roadmap: data model + kernel prototype. ✅ \ No newline at end of file diff --git a/POLICY_SEED.md b/POLICY_SEED.md index 9a1910b..f10b1c1 100644 --- a/POLICY_SEED.md +++ b/POLICY_SEED.md @@ -1,3 +1,8 @@ +--- +doc_revision: 2 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + Excellent. What you’re asking for is not “documentation” in the usual sense. You’re asking for a **self-stabilizing policy nucleus**: a document that is simultaneously * **normative** (it constrains behavior), @@ -45,6 +50,12 @@ commutation, and test obligations for polysemous terms: `in/glossary.md`. execution safety). The glossary governs *what the code means* and *what must commute* (semantic correctness). Both contracts must be satisfied for any change to be valid. +**Dataflow grammar invariant:** The repository enforces a dataflow grammar audit +that treats recurring parameter bundles as type-level obligations. Any bundle that +crosses function boundaries must be promoted to a dataclass (config or local bundle), +or explicitly documented with a `# dataflow-bundle:` marker. This is enforced in CI +as part of semantic correctness. + --- ## 1. Prime Invariant (Unbreakable) @@ -158,6 +169,20 @@ Additional **read-only** permissions are allowed when required for enforcement (e.g. `actions: read` for posture checks), but write scopes are forbidden by default. +**Narrow exception (PR discourse enrichment):** + +Workflows running on **GitHub-hosted runners** may request minimal write +permissions to post PR comments **only** for the purpose of enriching PR +discussion (e.g. attaching rendered graphs or diagnostics). This exception +applies only to: + +* `pull-requests: write` (no other write scopes), +* actions pinned to full commit SHAs and allow-listed, +* jobs that do **not** run on self-hosted runners, +* comments that are purely informational (no code execution side effects). + +Self-hosted workflows MUST NOT request any write scopes. + ### 4.5 Action Supply Chain * Only explicitly allow-listed actions may be used. diff --git a/README.md b/README.md index 508a18c..7a65c7f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ +--- +doc_revision: 1 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + # Prism VM Prism VM is a small JAX-backed interpreter for a tiny IR (zero/suc/add/mul) with @@ -109,6 +114,20 @@ Run the suite: ``` mise exec -- pytest ``` +Optional: persist JAX compilation cache across runs (faster re-runs): +``` +PRISM_JAX_CACHE_DIR=/tmp/jax-cache mise exec -- pytest +``` + +GPU memory cap (optional, for tests on GPU machines): +``` +XLA_PYTHON_CLIENT_PREALLOCATE=false \ +XLA_PYTHON_CLIENT_ALLOCATOR=platform \ +XLA_PYTHON_CLIENT_MEM_FRACTION=0.05 \ +mise exec -- pytest +``` +Note: JAX exposes a fractional cap, not an absolute MiB cap. Adjust the fraction +to approximate 128MiB for your GPU size. ## Agda proofs Agda checks run in a pinned container image. See `agda/README.md` for the @@ -190,6 +209,10 @@ hard-cap is enforced (overflow => corrupt), corrupt/oom are sticky stop-paths Changes to these commitments require a milestone bump and updates in `MILESTONES.md`. +m1-only mode is deprecated. The baseline suite (see `pytest.baseline.ini`) +still runs the m1 test set, but under the current baseline milestone (from +`.pytest-milestone`, currently `m3`), not under an m1-restricted semantic mode. + ## Repo layout - `prism_vm.py` - VM, kernels, and REPL - `tests/` - pytest suite and sample program fixtures diff --git a/agda/Prism/Prism.agda b/agda/Prism/Prism.agda index 67516bf..1f29453 100644 --- a/agda/Prism/Prism.agda +++ b/agda/Prism/Prism.agda @@ -8,3 +8,4 @@ import Prism.Novelty import Prism.FixedPoint import Prism.MinPrism import Prism.Boundaries +import Prism.Spillway diff --git a/agda/Prism/Spillway.agda b/agda/Prism/Spillway.agda new file mode 100644 index 0000000..0551d94 --- /dev/null +++ b/agda/Prism/Spillway.agda @@ -0,0 +1,35 @@ +module Prism.Spillway where + +open import Agda.Builtin.Nat + +import Prism.Novelty as N +import Prism.Vec as V + +-- 2:1 spillway bound (per microstratum): +-- If each active item yields at most one net new item, total spillway usage +-- is bounded by the active count. This is the arithmetic core of the +-- spillway witness (see in/in-29.md). + +data AtMost1 : Nat -> Set where + ≤0 : AtMost1 zero + ≤1 : AtMost1 (suc zero) + +data AllAtMost1 : {n : Nat} -> V.Vec Nat n -> Set where + all[] : AllAtMost1 V.[] + all::_ : {n : Nat} {x : Nat} {xs : V.Vec Nat n} -> + AtMost1 x -> AllAtMost1 xs -> AllAtMost1 (x V.:: xs) + +sum : {n : Nat} -> V.Vec Nat n -> Nat +sum V.[] = zero +sum (x V.:: xs) = x + sum xs + +n<=sucn : {n : Nat} -> n N.<= suc n +n<=sucn {zero} = N.z<=n +n<=sucn {suc n} = N.s<=s n<=sucn + +spillway-bound : {n : Nat} {xs : V.Vec Nat n} -> + AllAtMost1 xs -> sum xs N.<= n +spillway-bound {zero} all[] = N.z<=n +spillway-bound {suc n} (all::_ bound rest) with bound +... | ≤0 = N.<=-trans (spillway-bound rest) n<=sucn +... | ≤1 = N.s<=s (spillway-bound rest) diff --git a/agda/README.md b/agda/README.md index 180cb65..c5d8011 100644 --- a/agda/README.md +++ b/agda/README.md @@ -1,3 +1,8 @@ +--- +doc_revision: 1 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + # Agda Proof Kernel (Scaffold) This directory hosts the initial Agda module scaffolding for the semantic @@ -22,4 +27,4 @@ CI note: Agda checks run inside the same pinned container image. Agda version pin: - The pinned version lives in `agda/AGDA_VERSION`. - The container image digest lives in `agda/AGDA_IMAGE`. -- Keep the workflow image digest and `agda/AGDA_IMAGE` in sync. +- Keep the workflow image digest and `agda/AGDA_IMAGE` in sync. \ No newline at end of file diff --git a/audit_in_versions.md b/audit_in_versions.md index 15e737a..a3bdd64 100644 --- a/audit_in_versions.md +++ b/audit_in_versions.md @@ -1,3 +1,8 @@ +--- +doc_revision: 1 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + # Audit: in/in-*.md + in/glossary.md Generated by `scripts/audit_in_versions.py`. Do not edit by hand. @@ -14,16 +19,16 @@ Methodology: - Unique bigrams: 721 - Prior version: none - Compare: prism_vm.py - - Intersection: 212 | top: jnp, int32, self, count, int, dtype, a1, a2, op, manifest, opcode, arg1 - - Symmetric difference: 1616 (only in in/in-1.md: 94, only in prism_vm.py: 1522) - - Only in in/in-1.md: instruction, data, optimized_ptr, python, type, types, construct, construction, instructions, next_a1, next_active, next_ops - - Only in prism_vm.py: ledger, arena, none, astype, size, value, ids, uint32, host_int_value, morton, perm, shape - - Wedge product (bigram intersection): 370 | top: jnp int32, dtype jnp, jnp array, a1 a2, self manifest, jnp ndarray, jnp zeros, int self, ops a1, print f, parse tokens, manifest opcode + - Intersection: 8 | top: op, jax, dtype, int32, repl, main__, name__, note + - Symmetric difference: 323 (only in in/in-1.md: 298, only in prism_vm.py: 25) + - Only in in/in-1.md: self, manifest, ptr, jnp, int, zero, ir, suc, f, print, x, a1 + - Only in prism_vm.py: path, src, os, sys, exports, f401, main, noqa, prism_vm_core, root, all__, appears + - Wedge product (bigram intersection): 1 | top: name__ main__ - Compare: IMPLEMENTATION_PLAN.md - - Intersection: 144 | top: add, self, null, manifest, zero, ptr, implementation, op, host, x, count, suc - - Symmetric difference: 1420 (only in in/in-1.md: 162, only in IMPLEMENTATION_PLAN.md: 1258) + - Intersection: 143 | top: add, self, null, manifest, zero, ptr, implementation, op, host, x, count, suc + - Symmetric difference: 1435 (only in in/in-1.md: 163, only in IMPLEMENTATION_PLAN.md: 1272) - Only in in/in-1.md: jnp, int, ir, f, tokens, analysis, token, instruction, cons, is_suc, res_ptr, b_count - - Only in IMPLEMENTATION_PLAN.md: expected, pytest, tests, key, must, program, denotation, full, encoding, txt, canonical, md + - Only in IMPLEMENTATION_PLAN.md: tests, expected, pytest, key, must, program, denotation, full, encoding, txt, canonical, id - Wedge product (bigram intersection): 28 | top: add zero, manifest opcode, suc zero, a1 a2, x y, zero suc, e g, add suc, arg1 arg2, op_add op_mul, opcode arg1, x x ## in/in-2.md - Unique tokens: 338 @@ -35,16 +40,16 @@ Methodology: - Only in in/in-1.md: analysis, trace_cache, optimized_ptr, prism, rows, analyze_and_optimize, deduplication, exec_allocs, ir_allocs, mid_rows, optimization, telemetry - Wedge product (bigram intersection): 309 | top: self manifest, manifest opcode, print f, int self, a1 a2, dtype jnp, jnp int32, parse tokens, op a1, jnp ndarray, self parse, time perf_counter - Compare: prism_vm.py - - Intersection: 186 | top: jnp, int32, self, count, int, dtype, a1, a2, op, opcode, arg1, manifest - - Symmetric difference: 1700 (only in in/in-2.md: 152, only in prism_vm.py: 1548) - - Only in in/in-2.md: data, code, instruction, synthesis, python, registry, b, function, unified, active_flag, addition, compile - - Only in prism_vm.py: ledger, arena, none, astype, size, value, ids, uint32, host_int_value, morton, perm, shape - - Wedge product (bigram intersection): 230 | top: jnp int32, dtype jnp, jnp array, a1 a2, jnp ndarray, self manifest, jnp zeros, ops a1, parse tokens, int self, print f, manifest opcode + - Intersection: 8 | top: op, dtype, int32, jax, repl, main__, name__, note + - Symmetric difference: 355 (only in in/in-2.md: 330, only in prism_vm.py: 25) + - Only in in/in-2.md: self, manifest, jnp, ptr, ir, a1, opcode, a2, data, f, cons, node + - Only in prism_vm.py: path, src, os, sys, exports, f401, main, noqa, prism_vm_core, root, all__, appears + - Wedge product (bigram intersection): 1 | top: name__ main__ - Compare: IMPLEMENTATION_PLAN.md - - Intersection: 149 | top: add, null, self, key, manifest, must, ptr, count, op, a1, zero, host - - Symmetric difference: 1442 (only in in/in-2.md: 189, only in IMPLEMENTATION_PLAN.md: 1253) + - Intersection: 150 | top: add, null, self, key, manifest, must, ptr, count, op, a1, zero, host + - Symmetric difference: 1453 (only in in/in-2.md: 188, only in IMPLEMENTATION_PLAN.md: 1265) - Only in in/in-2.md: jnp, ir, f, cons, tokens, instruction, int, interpreter, synthesis, token, b_count, is_suc - - Only in IMPLEMENTATION_PLAN.md: expected, pytest, tests, program, denotation, full, encoding, txt, canonical, md, id, m1 + - Only in IMPLEMENTATION_PLAN.md: tests, expected, pytest, program, denotation, full, encoding, txt, canonical, id, ledger, md - Wedge product (bigram intersection): 15 | top: a1 a2, manifest opcode, e g, arg1 arg2, op_add op_mul, opcode arg1, normal form, hash consing, op_zero op_suc, suc x, instructions op_add, jax numpy ## in/in-3.md - Unique tokens: 291 @@ -56,16 +61,16 @@ Methodology: - Only in in/in-2.md: ir, data, cons, node, interpreter, op_add, synthesis, vm, python, memo, execution, heap - Wedge product (bigram intersection): 202 | top: self manifest, print f, dtype jnp, int self, manifest opcode, arg1 arg2, jnp int32, jnp ndarray, opcode arg1, a1 a2, jnp zeros, max_rows dtype - Compare: prism_vm.py - - Intersection: 176 | top: jnp, int32, self, count, int, dtype, a1, a2, arg1, opcode, op, arg2 - - Symmetric difference: 1673 (only in in/in-3.md: 115, only in prism_vm.py: 1558) - - Only in in/in-3.md: func_name, parse_expr, alloc_memoized, alloc_raw, candidate_idx, compile, compile_kernel, compiler, end_count, memoized, op_code, op_map - - Only in prism_vm.py: ledger, arena, none, astype, size, value, ids, uint32, host_int_value, morton, perm, shape - - Wedge product (bigram intersection): 198 | top: jnp int32, dtype jnp, jnp array, a1 a2, jnp ndarray, self manifest, jnp zeros, ops a1, print f, int self, arg1 arg2, manifest opcode + - Intersection: 9 | top: op, jax, dtype, src, int32, path, repl, md, note + - Symmetric difference: 306 (only in in/in-3.md: 282, only in prism_vm.py: 24) + - Only in in/in-3.md: self, jnp, arg1, f, manifest, opcode, arg2, print, state, int, active_count, suc + - Only in prism_vm.py: os, sys, exports, f401, main, noqa, prism_vm_core, root, all__, appears, assumed, dirname + - Wedge product (bigram intersection): 0 | top: - Compare: IMPLEMENTATION_PLAN.md - - Intersection: 120 | top: expected, self, add, null, md, zero, count, arg1, manifest, opcode, host, arg2 - - Symmetric difference: 1453 (only in in/in-3.md: 171, only in IMPLEMENTATION_PLAN.md: 1282) + - Intersection: 121 | top: expected, self, add, null, md, zero, count, arg1, manifest, opcode, host, state + - Symmetric difference: 1464 (only in in/in-3.md: 170, only in IMPLEMENTATION_PLAN.md: 1294) - Only in in/in-3.md: jnp, f, int, tokens, func_name, token, signature, name, parse_expr, v, is_suc, max_rows - - Only in IMPLEMENTATION_PLAN.md: pytest, tests, key, must, program, denotation, full, encoding, txt, canonical, id, m1 + - Only in IMPLEMENTATION_PLAN.md: tests, pytest, key, must, program, denotation, full, encoding, txt, canonical, id, ledger - Wedge product (bigram intersection): 19 | top: arg1 arg2, opcode arg1, e g, suc zero, x y, manifest opcode, md md, add suc, a1 a2, hash consing, op_zero op_suc, suc x ## in/in-4.md - Unique tokens: 410 @@ -77,17 +82,17 @@ Methodology: - Only in in/in-3.md: arg1, f, opcode, arg2, print, int, active_count, op, suc, tokens, zero, idx - Wedge product (bigram intersection): 3 | top: e g, jax array, x y - Compare: prism_vm.py - - Intersection: 124 | top: jnp, ledger, arena, self, astype, size, manifest, morton, jax, cache, set, perm - - Symmetric difference: 1896 (only in in/in-4.md: 286, only in prism_vm.py: 1610) - - Only in in/in-4.md: arenas, graph, hierarchy, level, shatter, bits, data, dormant, l1, waiting, alternating, architecture - - Only in prism_vm.py: int32, count, int, dtype, none, a1, a2, op, opcode, arg1, idx, value - - Wedge product (bigram intersection): 17 | top: astype jnp, arena rank, perm jnp, x y, argsort sort_key, jnp argsort, fixed size, rank astype, block size, cnf pipeline, morton astype, arena bsp + - Intersection: 5 | top: path, jax, root, md, note + - Symmetric difference: 433 (only in in/in-4.md: 405, only in prism_vm.py: 28) + - Only in in/in-4.md: arena, memory, active, morton, rank, sort, nodes, manifest, arenas, bit, bsp, graph + - Only in prism_vm.py: src, os, sys, exports, f401, main, noqa, prism_vm_core, all__, appears, assumed, dirname + - Wedge product (bigram intersection): 0 | top: - Compare: IMPLEMENTATION_PLAN.md - - Intersection: 184 | top: key, arena, rank, sort, md, morton, nodes, ledger, cnf, coordinate, implementation, semantic - - Symmetric difference: 1444 (only in in/in-4.md: 226, only in IMPLEMENTATION_PLAN.md: 1218) + - Intersection: 184 | top: key, arena, rank, sort, ledger, md, morton, nodes, cnf, coordinate, implementation, semantic + - Symmetric difference: 1457 (only in in/in-4.md: 226, only in IMPLEMENTATION_PLAN.md: 1231) - Only in in/in-4.md: hierarchy, close, dormant, waiting, alternating, connected, jnp, tree, z, adjacent, composition, coords - - Only in IMPLEMENTATION_PLAN.md: expected, pytest, tests, add, null, must, program, denotation, full, encoding, txt, canonical - - Wedge product (bigram intersection): 17 | top: e g, sort key, x y, cnf pipeline, rank morton, interaction combinator, bsp path, fixed size, ledger cnf, self hosted, block size, blocks e + - Only in IMPLEMENTATION_PLAN.md: tests, expected, pytest, add, null, must, program, denotation, full, encoding, txt, canonical + - Wedge product (bigram intersection): 17 | top: e g, sort key, x y, cnf pipeline, interaction combinator, rank morton, bsp path, fixed size, ledger cnf, self hosted, block size, blocks e ## in/in-5.md - Unique tokens: 366 - Unique bigrams: 647 @@ -98,16 +103,16 @@ Methodology: - Only in in/in-4.md: manifest, op_sort, key, close, data, dormant, architecture, fluid, status, based, child, composition - Wedge product (bigram intersection): 39 | top: bsp tree, shatter effect, x y, bit rank, e g, alternating bsp, arena hierarchy, l1 arenas, spatial locality, active nodes, binary space, cache line - Compare: prism_vm.py - - Intersection: 127 | top: jnp, arena, dtype, uint32, jax, cache, morton, perm, array, x, rank, state - - Symmetric difference: 1846 (only in in/in-5.md: 239, only in prism_vm.py: 1607) - - Only in in/in-5.md: children, shatter, bits, address, arenas, l2, locality, tree, hierarchy, square, alternating, become - - Only in prism_vm.py: ledger, int32, count, self, int, none, a1, a2, op, opcode, arg1, astype - - Wedge product (bigram intersection): 26 | top: dtype jnp, jnp uint32, arena rank, jnp zeros_like, x y, arena perm, perm jnp, arena inv_perm, jnp argsort, new nodes, y z, swizzle_2to1 x + - Intersection: 4 | top: jax, dtype, note, md + - Symmetric difference: 391 (only in in/in-5.md: 362, only in prism_vm.py: 29) + - Only in in/in-5.md: arena, bsp, x, nodes, memory, y, children, z, bit, hot, jnp, shatter + - Only in prism_vm.py: path, src, os, sys, exports, f401, main, noqa, prism_vm_core, root, all__, appears + - Wedge product (bigram intersection): 0 | top: - Compare: IMPLEMENTATION_PLAN.md - - Intersection: 161 | top: arena, full, bsp, md, rank, nodes, implementation, sort, x, y, invariant, locality - - Symmetric difference: 1446 (only in in/in-5.md: 205, only in IMPLEMENTATION_PLAN.md: 1241) + - Intersection: 161 | top: arena, full, bsp, rank, md, nodes, implementation, sort, x, y, invariant, locality + - Symmetric difference: 1459 (only in in/in-5.md: 205, only in IMPLEMENTATION_PLAN.md: 1254) - Only in in/in-5.md: z, jnp, address, tree, hierarchy, square, alternating, become, using, axis, bottom, contiguous - - Only in IMPLEMENTATION_PLAN.md: expected, pytest, tests, add, null, key, must, program, denotation, encoding, txt, canonical + - Only in IMPLEMENTATION_PLAN.md: tests, expected, pytest, add, null, key, must, program, denotation, encoding, txt, canonical - Wedge product (bigram intersection): 14 | top: x y, e g, new nodes, hierarchical arenas, hot nodes, python jax, warm cold, blocks e, classify nodes, free region, jax numpy, l1 l2 ## in/in-6.md - Unique tokens: 408 @@ -119,17 +124,17 @@ Methodology: - Only in in/in-5.md: jnp, tree, become, blocks, range, bottom, coordinates, corresponds, dtype, execution, l1, new - Wedge product (bigram intersection): 57 | top: x y, alternating bsp, shatter effect, address swizzling, bit interleaving, bsp block, linear arena, address space, bit rank, bsp layout, cache line, e g - Compare: prism_vm.py - - Intersection: 118 | top: ledger, arena, size, idx, jax, ops, morton, cache, array, x, rank, lax - - Symmetric difference: 1906 (only in in/in-6.md: 290, only in prism_vm.py: 1616) - - Only in in/in-6.md: graph, address, alternating, hvm, linear, reduction, shatter, standard, divergence, hierarchical, latency, locality - - Only in prism_vm.py: jnp, int32, count, self, int, dtype, none, a1, a2, op, opcode, arg1 - - Wedge product (bigram intersection): 12 | top: bspˢ layout, x y, x x, cold free, hot warm, jax numpy, pallas triton, warm cold, arena bsp, bsp arena, ledger cnf, swizzle x + - Intersection: 4 | top: jax, path, md, note + - Symmetric difference: 433 (only in in/in-6.md: 404, only in prism_vm.py: 29) + - Only in in/in-6.md: bsp, memory, graph, bit, gpu, address, alternating, hvm, layout, linear, nodes, reduction + - Only in prism_vm.py: src, os, sys, exports, f401, main, noqa, prism_vm_core, root, all__, appears, assumed + - Wedge product (bigram intersection): 0 | top: - Compare: IMPLEMENTATION_PLAN.md - - Intersection: 163 | top: key, encoding, md, implementation, jax, ledger, arena, bsp, cnf, rank, semantic, rewrite - - Symmetric difference: 1484 (only in in/in-6.md: 245, only in IMPLEMENTATION_PLAN.md: 1239) - - Only in in/in-6.md: address, alternating, hvm, standard, divergence, latency, contiguous, graphblas, pallas, scan, superior, alu - - Only in IMPLEMENTATION_PLAN.md: expected, pytest, tests, add, null, must, program, denotation, full, txt, canonical, id - - Wedge product (bigram intersection): 11 | top: e g, x y, cold free, hot warm, warm cold, jax numpy, ledger cnf, higher order, stream compaction, use jax, x x + - Intersection: 163 | top: key, encoding, implementation, ledger, md, jax, arena, bsp, cnf, rank, semantic, rewrite + - Symmetric difference: 1497 (only in in/in-6.md: 245, only in IMPLEMENTATION_PLAN.md: 1252) + - Only in in/in-6.md: address, alternating, hvm, standard, divergence, latency, contiguous, graphblas, pallas, scan, superior, allows + - Only in IMPLEMENTATION_PLAN.md: tests, expected, pytest, add, null, must, program, denotation, full, txt, canonical, id + - Wedge product (bigram intersection): 12 | top: e g, x y, cold free, hot warm, warm cold, jax numpy, ledger cnf, higher order, prefix sum, stream compaction, use jax, x x ## in/in-7.md - Unique tokens: 511 - Unique bigrams: 1144 @@ -140,17 +145,17 @@ Methodology: - Only in in/in-6.md: gpu, hvm, divergence, hierarchical, bottleneck, compute, graphblas, level, pallas, superior, vs, allows - Wedge product (bigram intersection): 25 | top: x y, address space, free space, shatter effect, alternating bsp, bit rank, calculate offsets, cold free, hot warm, jax numpy, warm cold, x x - Compare: prism_vm.py - - Intersection: 253 | top: jnp, int32, arena, self, count, int, dtype, a1, a2, opcode, op, arg1 - - Symmetric difference: 1739 (only in in/in-7.md: 258, only in prism_vm.py: 1481) - - Only in in/in-7.md: bits, shatter, child1_idx, fluid, every, linear, pointers, address, python, spawn_counts, alternating, assume - - Only in prism_vm.py: ledger, none, astype, size, value, ids, host_int_value, true, shape, block_size, m1, max_key - - Wedge product (bigram intersection): 243 | top: jnp int32, dtype jnp, jnp uint32, jnp array, a1 a2, jnp ndarray, arena rank, jnp arange, self arena, jnp zeros_like, jnp zeros, arena count + - Intersection: 10 | top: jax, op, root, dtype, note, int32, repl, main__, md, name__ + - Symmetric difference: 524 (only in in/in-7.md: 501, only in prism_vm.py: 23) + - Only in in/in-7.md: arena, jnp, self, rank, add, y, x, free, sort, nodes, suc, a1 + - Only in prism_vm.py: path, src, os, sys, exports, f401, main, noqa, prism_vm_core, all__, appears, assumed + - Wedge product (bigram intersection): 2 | top: name__ main__, note jax - Compare: IMPLEMENTATION_PLAN.md - - Intersection: 261 | top: add, arena, rank, null, key, self, y, must, sort, count, x, full - - Symmetric difference: 1391 (only in in/in-7.md: 250, only in IMPLEMENTATION_PLAN.md: 1141) + - Intersection: 262 | top: add, arena, rank, null, key, self, y, must, sort, count, x, full + - Symmetric difference: 1402 (only in in/in-7.md: 249, only in IMPLEMENTATION_PLAN.md: 1153) - Only in in/in-7.md: jnp, mask_suc, z, child1_idx, f, ndarray, new_rank, token, tokens, address, final_a1, final_a2 - - Only in IMPLEMENTATION_PLAN.md: expected, pytest, tests, program, denotation, encoding, txt, canonical, id, m1, q, corrupt - - Wedge product (bigram intersection): 55 | top: x y, rank sort, add suc, add zero, sort swizzle, new nodes, y y, a1 a2, suc zero, add x, fluid arena, suc add + - Only in IMPLEMENTATION_PLAN.md: tests, expected, pytest, program, denotation, encoding, txt, canonical, id, ledger, q, corrupt + - Wedge product (bigram intersection): 56 | top: x y, rank sort, add suc, add zero, sort swizzle, new nodes, y y, a1 a2, suc zero, add x, fluid arena, suc add ## in/in-8.md - Unique tokens: 1141 - Unique bigrams: 2556 @@ -161,17 +166,17 @@ Methodology: - Only in in/in-7.md: self, sort, suc, a1, a2, count, opcode, mask_suc, ptr, swizzle, arg1, hot - Wedge product (bigram intersection): 16 | top: new nodes, x y, new node, prefix sum, free nodes, must point, nodes free, structure arrays, active nodes, allocate new, execution kernel, jax jit - Compare: prism_vm.py - - Intersection: 224 | top: jnp, arena, dtype, jax, op, idx, size, value, ops, uint32, set, n - - Symmetric difference: 2427 (only in in/in-8.md: 917, only in prism_vm.py: 1510) - - Only in in/in-8.md: interaction, https, tensor, port, graph, ports, accessed, b, january, delta, gamma, linear - - Only in prism_vm.py: ledger, int32, count, self, int, none, a1, a2, opcode, arg1, astype, ids - - Wedge product (bigram intersection): 15 | top: implementation_plan md, see implementation_plan, x y, new nodes, control flow, device id, different pointer, k k, shape n, allocate new, indices size, per step + - Intersection: 10 | top: jax, path, dtype, main, normalization, implementation_plan, md, note, op, see + - Symmetric difference: 1154 (only in in/in-8.md: 1131, only in prism_vm.py: 23) + - Only in in/in-8.md: interaction, n, https, tensor, port, node, nodes, active, graph, ports, accessed, b + - Only in prism_vm.py: src, os, sys, exports, f401, noqa, prism_vm_core, root, all__, appears, assumed, dirname + - Wedge product (bigram intersection): 2 | top: implementation_plan md, see implementation_plan - Compare: IMPLEMENTATION_PLAN.md - - Intersection: 335 | top: interaction, add, n, jax, nodes, tensor, must, port, node, encoding, active, full - - Symmetric difference: 1873 (only in in/in-8.md: 806, only in IMPLEMENTATION_PLAN.md: 1067) + - Intersection: 336 | top: interaction, add, n, jax, nodes, tensor, must, port, active, node, encoding, rule + - Symmetric difference: 1884 (only in in/in-8.md: 805, only in IMPLEMENTATION_PLAN.md: 1079) - Only in in/in-8.md: https, accessed, january, logic, gamma, matrix, nets, neighbor, principal, net, aux, en - - Only in IMPLEMENTATION_PLAN.md: expected, pytest, tests, null, key, program, denotation, txt, m1, q, corrupt, ledger - - Wedge product (bigram intersection): 34 | top: new nodes, active pair, e g, rewrite rules, rule table, active pairs, x y, annihilation commutation, interaction combinator, commutation erasure, use jax, branchless interaction + - Only in IMPLEMENTATION_PLAN.md: tests, expected, pytest, null, key, program, denotation, txt, ledger, q, corrupt, m1 + - Wedge product (bigram intersection): 37 | top: new nodes, active pair, rule table, e g, rewrite rules, active pairs, interaction combinator, x y, annihilation commutation, commutation erasure, prefix sum, use jax ## in/in-9.md - Unique tokens: 804 - Unique bigrams: 2308 @@ -182,16 +187,16 @@ Methodology: - Only in in/in-8.md: interaction, https, tensor, port, graph, ports, accessed, january, logic, delta, gpu, memory - Wedge product (bigram intersection): 24 | top: new nodes, rewrite rules, control flow, e g, exactly one, k k, new node, x y, newly allocated, allocated nodes, creates nodes, execution makes - Compare: prism_vm.py - - Intersection: 206 | top: ledger, arena, count, f, a1, a2, op, ids, size, stratum, manifest, n - - Symmetric difference: 2126 (only in in/in-9.md: 598, only in prism_vm.py: 1528) - - Only in in/in-9.md: mathcal, l, mathrm, text, c, eager, code, exactly, two, prior, reviewer, model - - Only in prism_vm.py: jnp, int32, self, int, dtype, none, opcode, arg1, astype, idx, value, arg2 - - Wedge product (bigram intersection): 30 | top: arena count, x y, ledger intern_nodes, enabled candidates, zero suc, add suc, may reference, new nodes, add zero, bsp ledger, cnf pipeline, control flow + - Intersection: 7 | top: op, root, md, jax, normalization, note, see + - Symmetric difference: 823 (only in in/in-9.md: 797, only in prism_vm.py: 26) + - Only in in/in-9.md: mathcal, l, rewrite, f, n, mathrm, text, c, eager, cnf, stratum, step + - Only in prism_vm.py: path, src, os, sys, exports, f401, main, noqa, prism_vm_core, all__, appears, assumed + - Wedge product (bigram intersection): 0 | top: - Compare: IMPLEMENTATION_PLAN.md - - Intersection: 305 | top: rewrite, tests, cnf, add, n, key, c, stratum, eager, step, ledger, semantic - - Symmetric difference: 1596 (only in in/in-9.md: 499, only in IMPLEMENTATION_PLAN.md: 1097) + - Intersection: 303 | top: rewrite, tests, cnf, add, n, key, c, stratum, eager, step, ledger, semantic + - Symmetric difference: 1613 (only in in/in-9.md: 501, only in IMPLEMENTATION_PLAN.md: 1112) - Only in in/in-9.md: mathcal, l, f, mathrm, text, reviewer, a_1, a_2, refinement, identities, mathsf, triangleq - - Only in IMPLEMENTATION_PLAN.md: expected, pytest, null, program, encoding, txt, m1, q, corrupt, coordinate, rank, m2 + - Only in IMPLEMENTATION_PLAN.md: expected, pytest, null, program, encoding, txt, q, corrupt, m1, coordinate, rank, m2 - Wedge product (bigram intersection): 65 | top: exactly two, candidate slots, cnf symmetric, fixed arity, prior strata, normal form, identity creation, slots per, x y, canonical ids, current code, add zero ## in/in-10.md - Unique tokens: 383 @@ -203,16 +208,16 @@ Methodology: - Only in in/in-9.md: mathcal, l, f, n, mathrm, c, eager, step, exactly, prior, k, candidates - Wedge product (bigram intersection): 17 | top: normal form, address space, arena ledger, rewrite rules, core semantic, local rewrite, rewrite allocation, candidate emission, consolidated md, execution model, frontier propagation, note consolidated - Compare: prism_vm.py - - Intersection: 112 | top: ledger, arena, self, op, arg1, opcode, arg2, value, ops, morton, shape, array - - Symmetric difference: 1893 (only in in/in-10.md: 271, only in prism_vm.py: 1622) - - Only in in/in-10.md: parity, tree, canonicalization, coordinates, adjacency, aggregate, cut, diff, idempotent, address, addressed, aggregates - - Only in prism_vm.py: jnp, int32, count, int, dtype, none, a1, a2, astype, size, idx, ids - - Wedge product (bigram intersection): 9 | top: arg1 arg2, opcode arg1, op arg1, op_add op_mul, x x, arg2 op, cd lift, tier references, within tier + - Intersection: 4 | top: normalization, md, op, note + - Symmetric difference: 408 (only in in/in-10.md: 379, only in prism_vm.py: 29) + - Only in in/in-10.md: cd, aggregation, coordinate, parity, tree, milestone, explicit, invariant, local, canonicalization, coordinates, frontier + - Only in prism_vm.py: path, src, os, sys, exports, f401, main, noqa, prism_vm_core, root, all__, appears + - Wedge product (bigram intersection): 0 | top: - Compare: IMPLEMENTATION_PLAN.md - Intersection: 193 | top: key, coordinate, cd, canonical, ledger, md, semantic, cnf, arena, invariant, rewrite, milestone - - Symmetric difference: 1399 (only in in/in-10.md: 190, only in IMPLEMENTATION_PLAN.md: 1209) + - Symmetric difference: 1412 (only in in/in-10.md: 190, only in IMPLEMENTATION_PLAN.md: 1222) - Only in in/in-10.md: tree, adjacency, cut, elimination, address, addressed, aggregates, gf, canon, deduplication, engineering, important - - Only in IMPLEMENTATION_PLAN.md: expected, pytest, tests, add, null, must, program, denotation, full, encoding, txt, id + - Only in IMPLEMENTATION_PLAN.md: tests, expected, pytest, add, null, must, program, denotation, full, encoding, txt, id - Wedge product (bigram intersection): 27 | top: cd coordinates, arg1 arg2, rank sort, normal form, cayley dickson, op_add op_mul, opcode arg1, within tier, pointer equality, rewrite rules, sort swizzle, aggregation scope ## in/in-11.md - Unique tokens: 297 @@ -224,16 +229,16 @@ Methodology: - Only in in/in-10.md: explicit, frontier, adjacency, aggregate, diff, addressed, aggregates, changes, code, form, key, normalization - Wedge product (bigram intersection): 33 | top: cd coordinate, cd coordinates, cayley dickson, cut elimination, address space, arg1 arg2, canonicalization hook, milestone δ, acceptance criteria, equality pointer, finitely observable, local rewrite - Compare: prism_vm.py - - Intersection: 97 | top: ledger, int32, self, a1, a2, op, opcode, arg1, size, arg2, ops, true - - Symmetric difference: 1837 (only in in/in-11.md: 200, only in prism_vm.py: 1637) - - Only in in/in-11.md: text, coordinates, parity, exactly, program, already, arithmetic, cayley, coord_ptr, dag, dickson, finite - - Only in prism_vm.py: jnp, arena, count, int, dtype, none, astype, idx, value, ids, ptr, f - - Wedge product (bigram intersection): 10 | top: a1 a2, implementation_plan md, op a1, arg1 arg2, opcode arg1, x x, left right, a2 op, cd lift, within tier + - Intersection: 5 | top: op, md, implementation_plan, int32, note + - Symmetric difference: 320 (only in in/in-11.md: 292, only in prism_vm.py: 28) + - Only in in/in-11.md: coordinate, xor, cd, text, x, coordinates, equality, ledger, op_coord_zero, parity, pointer, depth + - Only in prism_vm.py: path, src, os, sys, exports, f401, main, noqa, prism_vm_core, root, all__, appears + - Wedge product (bigram intersection): 1 | top: implementation_plan md - Compare: IMPLEMENTATION_PLAN.md - Intersection: 157 | top: coordinate, program, ledger, canonical, md, cnf, equality, semantic, rewrite, strata, milestone, invariant - - Symmetric difference: 1385 (only in in/in-11.md: 140, only in IMPLEMENTATION_PLAN.md: 1245) + - Symmetric difference: 1398 (only in in/in-11.md: 140, only in IMPLEMENTATION_PLAN.md: 1258) - Only in in/in-11.md: text, arithmetic, coord_ptr, dag, p, recursion, reviewer, trade, δ, base, bitstring, chasing - - Only in IMPLEMENTATION_PLAN.md: expected, pytest, tests, add, null, key, must, denotation, full, encoding, txt, id + - Only in IMPLEMENTATION_PLAN.md: tests, expected, pytest, add, null, key, must, denotation, full, encoding, txt, id - Wedge product (bigram intersection): 21 | top: cd coordinates, arg1 arg2, cnf objects, coordinates interned, interned cnf, opcode arg1, pointer equality, within tier, a1 a2, cayley dickson, equality pointer, rewrite rules ## in/in-12.md - Unique tokens: 358 @@ -245,16 +250,16 @@ Methodology: - Only in in/in-11.md: coordinate, xor, cd, text, coordinates, op_coord_zero, parity, depth, interned, local, milestone, program - Wedge product (bigram intersection): 7 | top: a1 a2, op a1, consolidated md, gives literal, implementation_plan md, md yes, note consolidated - Compare: prism_vm.py - - Intersection: 122 | top: ledger, count, a1, a2, op, ids, ops, jax, set, cache, array, key - - Symmetric difference: 1848 (only in in/in-12.md: 236, only in prism_vm.py: 1612) - - Only in in/in-12.md: bytes, collision, collisions, event, log, compare, gives, model, vtable, canonicality, children, current - - Only in prism_vm.py: jnp, int32, arena, self, int, dtype, none, opcode, arg1, astype, size, idx - - Wedge product (bigram intersection): 7 | top: a1 a2, implementation_plan md, op a1, full key, key equality, packed key, intern op + - Intersection: 7 | top: op, jax, md, implementation_plan, insert, note, see + - Symmetric difference: 377 (only in in/in-12.md: 351, only in prism_vm.py: 26) + - Only in in/in-12.md: key, id, hash, bytes, canonical, collision, full, ambiguity, collisions, event, index, log + - Only in prism_vm.py: path, src, os, sys, exports, f401, main, noqa, prism_vm_core, root, all__, appears + - Wedge product (bigram intersection): 1 | top: implementation_plan md - Compare: IMPLEMENTATION_PLAN.md - - Intersection: 198 | top: key, add, id, full, canonical, must, encoding, md, univalence, ledger, rewrite, semantic - - Symmetric difference: 1364 (only in in/in-12.md: 160, only in IMPLEMENTATION_PLAN.md: 1204) + - Intersection: 199 | top: key, add, id, full, canonical, must, encoding, ledger, md, univalence, rewrite, semantic + - Symmetric difference: 1375 (only in in/in-12.md: 159, only in IMPLEMENTATION_PLAN.md: 1216) - Only in in/in-12.md: gives, vtable, append, canonicality, different, materialized, snapshot, tables, events, key_bytes, resolution, sourcing - - Only in IMPLEMENTATION_PLAN.md: expected, pytest, tests, null, program, denotation, txt, m1, q, corrupt, cnf, coordinate + - Only in IMPLEMENTATION_PLAN.md: tests, expected, pytest, null, program, denotation, txt, q, corrupt, m1, cnf, coordinate - Wedge product (bigram intersection): 38 | top: full key, key equality, read model, e g, key bytes, a1 a2, hash collisions, collision free, truncation aliasing, hash bucket, univalence contract, canonical key ## in/in-13.md - Unique tokens: 266 @@ -266,16 +271,16 @@ Methodology: - Only in in/in-12.md: gives, children, different, h, tables, two, arrays, derived, encode, events, hashing, order - Wedge product (bigram intersection): 64 | top: full key, collision free, key bytes, a1 a2, hash collisions, op a1, read model, canonicality without, child id, event log, event sourced, key equality - Compare: prism_vm.py - - Intersection: 104 | top: ledger, int32, arena, self, a1, a2, op, opcode, arg1, arg2, jax, shape - - Symmetric difference: 1792 (only in in/in-13.md: 162, only in prism_vm.py: 1630) - - Only in in/in-13.md: coordinates, collisions, event, canonicalization, collision, parity, trie, canon, canonicality, log, materialized, model - - Only in prism_vm.py: jnp, count, int, dtype, none, astype, size, idx, value, ids, ptr, f - - Wedge product (bigram intersection): 14 | top: a1 a2, implementation_plan md, op a1, arg1 arg2, opcode arg1, full key, canonical ledger, key equality, ledger cnf, a2 ledger, deterministic interning, intern op + - Intersection: 8 | top: normalization, path, md, op, implementation_plan, int32, jax, note + - Symmetric difference: 283 (only in in/in-13.md: 258, only in prism_vm.py: 25) + - Only in in/in-13.md: coordinate, equality, coordinates, cd, id, key, ledger, univalence, aggregation, becomes, hash, ambiguity + - Only in prism_vm.py: src, os, sys, exports, f401, main, noqa, prism_vm_core, root, all__, appears, assumed + - Wedge product (bigram intersection): 1 | top: implementation_plan md - Compare: IMPLEMENTATION_PLAN.md - - Intersection: 172 | top: key, coordinate, full, id, canonical, ledger, must, cnf, encoding, equality, md, univalence - - Symmetric difference: 1324 (only in in/in-13.md: 94, only in IMPLEMENTATION_PLAN.md: 1230) + - Intersection: 172 | top: key, coordinate, full, id, ledger, canonical, must, cnf, encoding, equality, univalence, md + - Symmetric difference: 1337 (only in in/in-13.md: 94, only in IMPLEMENTATION_PLAN.md: 1243) - Only in in/in-13.md: canon, canonicality, materialized, tree, commands, constraint, reconciled, reconciliation, together, vtable, way, address - - Only in IMPLEMENTATION_PLAN.md: expected, pytest, tests, add, null, program, denotation, txt, m1, q, corrupt, semantic + - Only in IMPLEMENTATION_PLAN.md: tests, expected, pytest, add, null, program, denotation, txt, q, corrupt, m1, semantic - Wedge product (bigram intersection): 59 | top: full key, key equality, read model, cd coordinates, cnf objects, coordinate normalization, coordinates interned, e g, interned cnf, pointer equality, arg1 arg2, event log ## in/in-14.md - Unique tokens: 392 @@ -287,58 +292,58 @@ Methodology: - Only in in/in-13.md: aggregation, collisions, collision, structure, trie, canon, canonicality, free, materialized, space, tree, acceptance - Wedge product (bigram intersection): 32 | top: full key, ledger cnf, cd coordinates, pointer equality, event log, read model, a1 a2, canonical coordinate, coordinate equality, equality pointer, key bytes, key equality - Compare: prism_vm.py - - Intersection: 167 | top: ledger, arena, count, a1, a2, op, ids, ops, morton, m1, shape, stratum - - Symmetric difference: 1792 (only in in/in-14.md: 225, only in prism_vm.py: 1567) - - Only in in/in-14.md: rep, encode, make, say, two, bytes, canonicalize, contract, coordinates, equivalence, event, everywhere - - Only in prism_vm.py: jnp, int32, self, int, dtype, none, opcode, arg1, astype, size, idx, value - - Wedge product (bigram intersection): 35 | top: a1 a2, arena rank, implementation_plan md, x y, ledger stratum, zero suc, candidate pipeline, canonical ledger, ledger cnf, zero x, add suc, add zero + - Intersection: 9 | top: path, main, normalization, op, drift, implementation_plan, md, note, tighten + - Symmetric difference: 407 (only in in/in-14.md: 383, only in prism_vm.py: 24) + - Only in in/in-14.md: add, ledger, arena, canonical, equality, key, must, rewrite, tests, cnf, one, suc + - Only in prism_vm.py: src, os, sys, exports, f401, noqa, prism_vm_core, root, all__, appears, assumed, dirname + - Wedge product (bigram intersection): 1 | top: implementation_plan md - Compare: IMPLEMENTATION_PLAN.md - - Intersection: 270 | top: tests, add, key, ledger, must, canonical, arena, denotation, full, m1, cnf, id - - Symmetric difference: 1254 (only in in/in-14.md: 122, only in IMPLEMENTATION_PLAN.md: 1132) + - Intersection: 272 | top: tests, add, key, ledger, must, canonical, arena, denotation, full, cnf, id, m1 + - Symmetric difference: 1263 (only in in/in-14.md: 120, only in IMPLEMENTATION_PLAN.md: 1143) - Only in in/in-14.md: say, everywhere, asserts, constitutes, fine, generator, good, makes, needs, property, reconciliation, right - - Only in IMPLEMENTATION_PLAN.md: expected, pytest, null, txt, q, corrupt, tasks, host, implement, mode, objective, slot1 + - Only in IMPLEMENTATION_PLAN.md: expected, pytest, null, txt, q, corrupt, py, mode, tasks, host, implement, objective - Wedge product (bigram intersection): 145 | top: full key, denotation invariance, key equality, candidate pipeline, fixed width, rank sort, read model, x y, canonical ids, add zero, arena scheduling, baseline prismvm ## in/in-15.md -- Unique tokens: 173 +- Unique tokens: 174 - Unique bigrams: 323 - Prior version: in/in-14.md - - Intersection: 48 | top: milestone, tests, m1, m2, must, plan, one, test, vs, explicit, m3, runs - - Symmetric difference: 469 (only in in/in-15.md: 125, only in in/in-14.md: 344) + - Intersection: 49 | top: milestone, tests, m2, m1, must, plan, baseline, one, test, vs, explicit, m3 + - Symmetric difference: 468 (only in in/in-15.md: 125, only in in/in-14.md: 343) - Only in in/in-15.md: pytest, ini, testing, c, code, env, band, banded, gate, inclusive, mode, acceptance - Only in in/in-14.md: add, ledger, arena, canonical, equality, key, rewrite, cnf, suc, coordinate, engine, semantic - Wedge product (bigram intersection): 3 | top: m1 m2, implementation_plan md, m1 m5 - Compare: prism_vm.py - - Intersection: 58 | top: m1, set, milestone, state, mode, implementation_plan, m2, md, pytest, reads, token, expected - - Symmetric difference: 1791 (only in in/in-15.md: 115, only in prism_vm.py: 1676) - - Only in in/in-15.md: ini, testing, c, code, band, banded, inclusive, conftest, gates, m5, unmarked, vscode - - Only in prism_vm.py: jnp, ledger, int32, arena, count, self, int, dtype, none, a1, a2, op - - Wedge product (bigram intersection): 3 | top: implementation_plan md, m1 m2, pytest milestone + - Intersection: 4 | top: note, root, implementation_plan, md + - Symmetric difference: 199 (only in in/in-15.md: 170, only in prism_vm.py: 29) + - Only in in/in-15.md: pytest, milestone, tests, m2, ini, testing, c, code, env, m1, vs, band + - Only in prism_vm.py: path, src, os, sys, exports, f401, main, noqa, prism_vm_core, all__, appears, assumed + - Wedge product (bigram intersection): 1 | top: implementation_plan md - Compare: IMPLEMENTATION_PLAN.md - - Intersection: 105 | top: pytest, tests, expected, milestone, m1, must, m2, full, md, implementation, m4, mode - - Symmetric difference: 1365 (only in in/in-15.md: 68, only in IMPLEMENTATION_PLAN.md: 1297) - - Only in in/in-15.md: makes, ide, keeps, option, shows, skips, supported, toggle, want, allowing, available, avoids - - Only in IMPLEMENTATION_PLAN.md: add, null, key, program, denotation, encoding, txt, canonical, id, q, corrupt, ledger - - Wedge product (bigram intersection): 62 | top: c pytest, pytest c, vs code, conftest py, m1 m2, pytest env, pytest milestone, unmarked tests, vscode pytest, acceptance gates, full suite, ini runs + - Intersection: 107 | top: pytest, tests, expected, milestone, m2, must, m1, full, md, implementation, m4, mode + - Symmetric difference: 1375 (only in in/in-15.md: 67, only in IMPLEMENTATION_PLAN.md: 1308) + - Only in in/in-15.md: makes, ide, keeps, option, shows, skips, supported, toggle, want, allowing, avoids, back + - Only in IMPLEMENTATION_PLAN.md: add, null, key, program, denotation, encoding, txt, canonical, id, ledger, q, corrupt + - Wedge product (bigram intersection): 61 | top: c pytest, pytest c, vs code, conftest py, m1 m2, pytest env, pytest milestone, unmarked tests, vscode pytest, acceptance gates, baseline ini, full suite ## in/in-16.md - Unique tokens: 514 - Unique bigrams: 1088 - Prior version: in/in-15.md - - Intersection: 44 | top: milestone, tests, c, gate, code, full, must, testing, contract, runs, within, via - - Symmetric difference: 599 (only in in/in-16.md: 470, only in in/in-15.md: 129) + - Intersection: 45 | top: milestone, tests, c, gate, code, full, must, testing, contract, runs, within, via + - Symmetric difference: 598 (only in in/in-16.md: 469, only in in/in-15.md: 129) - Only in in/in-16.md: q, p, canonical, evaluator, key, nodes, op, stratum, provisional, ledger, ids, semantic - - Only in in/in-15.md: pytest, m2, ini, m1, env, vs, band, banded, inclusive, mode, conftest, default + - Only in in/in-15.md: pytest, m2, ini, env, m1, vs, band, banded, inclusive, mode, conftest, default - Wedge product (bigram intersection): 3 | top: full suite, acceptance criteria, milestone gated - Compare: prism_vm.py - - Intersection: 180 | top: ledger, arena, a1, a2, op, ids, f, manifest, ops, morton, set, stratum - - Symmetric difference: 1888 (only in in/in-16.md: 334, only in prism_vm.py: 1554) - - Only in in/in-16.md: p, c, canonicalization, encode, node_ref, structural, contract, convergence, evaluation, model, objects, c1 - - Only in prism_vm.py: jnp, int32, count, self, int, dtype, none, opcode, arg1, astype, size, idx - - Wedge product (bigram intersection): 36 | top: a1 a2, op a1, x y, denotation invariance, canonical ids, add mul, full key, key equality, within tier, zero suc, projection q, tier references + - Intersection: 4 | top: op, normalization, implementation_plan, md + - Symmetric difference: 539 (only in in/in-16.md: 510, only in prism_vm.py: 29) + - Only in in/in-16.md: q, p, canonical, evaluator, key, nodes, stratum, provisional, ledger, ids, semantic, c + - Only in prism_vm.py: path, src, os, sys, exports, f401, main, noqa, prism_vm_core, root, all__, appears + - Wedge product (bigram intersection): 0 | top: - Compare: IMPLEMENTATION_PLAN.md - - Intersection: 297 | top: tests, q, add, key, canonical, denotation, full, ledger, must, semantic, cnf, id - - Symmetric difference: 1322 (only in in/in-16.md: 217, only in IMPLEMENTATION_PLAN.md: 1105) + - Intersection: 296 | top: tests, q, add, key, canonical, denotation, ledger, full, must, semantic, cnf, id + - Symmetric difference: 1337 (only in in/in-16.md: 218, only in IMPLEMENTATION_PLAN.md: 1119) - Only in in/in-16.md: p, node_ref, convergence, c1, c2, dynamics, schedule, canon, freedom, homomorphism, memo_q, univalent - - Only in IMPLEMENTATION_PLAN.md: expected, pytest, null, txt, m1, corrupt, m2, count, m4, tasks, host, m3 + - Only in IMPLEMENTATION_PLAN.md: expected, pytest, null, txt, corrupt, m1, m2, count, m4, py, mode, tasks - Wedge product (bigram intersection): 118 | top: denotation invariance, full key, canonical ids, key equality, read model, coordinate normalization, key encoding, within tier, a1 a2, e g, provisional nodes, rank sort ## in/in-17.md - Unique tokens: 529 @@ -350,16 +355,16 @@ Methodology: - Only in in/in-16.md: evaluator, op, ids, c, a1, a2, invariance, node, strata, canonicalization, tests, encode - Wedge product (bigram intersection): 21 | top: map q, full key, quotient map, canonical identity, e g, projection q, canonical id, homomorphic collapse, q x, structure ledger, bounded fanout, coordinate based - Compare: prism_vm.py - - Intersection: 151 | top: ledger, opcode, size, value, f, ops, set, morton, oom, stratum, x, state - - Symmetric difference: 1961 (only in in/in-17.md: 378, only in prism_vm.py: 1583) - - Only in in/in-17.md: ca, calculus, locality, structural, meaning, pretty, style, bounded, entropy, interactions, physics, testable - - Only in prism_vm.py: jnp, int32, arena, count, self, int, dtype, none, a1, a2, op, arg1 - - Wedge product (bigram intersection): 18 | top: ledger capacity, canonical ledger, canonical identity, x x, bspᵗ temporal, corrupt semantic, full key, projection q, semantic id, bspᵗ bspˢ, byte equality, cdₐ cdᵣ + - Intersection: 3 | top: path, normalization, note + - Symmetric difference: 556 (only in in/in-17.md: 526, only in prism_vm.py: 30) + - Only in in/in-17.md: ca, damage, canonical, explicit, identity, semantic, structure, interning, local, q, denote, must + - Only in prism_vm.py: src, os, sys, exports, f401, main, noqa, prism_vm_core, root, all__, appears, assumed + - Wedge product (bigram intersection): 0 | top: - Compare: IMPLEMENTATION_PLAN.md - - Intersection: 267 | top: key, canonical, must, q, denotation, semantic, program, corrupt, full, id, ledger, cnf - - Symmetric difference: 1397 (only in in/in-17.md: 262, only in IMPLEMENTATION_PLAN.md: 1135) + - Intersection: 267 | top: key, canonical, must, q, denotation, semantic, program, corrupt, full, ledger, id, cnf + - Symmetric difference: 1410 (only in in/in-17.md: 262, only in IMPLEMENTATION_PLAN.md: 1148) - Only in in/in-17.md: ca, calculus, cdᵣ, cdₐ, interactions, physics, thermodynamic, λ, algebraic, claim, duplication, lemma - - Only in IMPLEMENTATION_PLAN.md: expected, pytest, tests, add, null, encoding, txt, md, m1, arena, ids, rank + - Only in IMPLEMENTATION_PLAN.md: tests, expected, pytest, add, null, encoding, txt, md, m1, arena, ids, rank - Wedge product (bigram intersection): 73 | top: full key, fixed width, fixed arity, e g, id cap, key byte, pretty denote, alpha equivalence, byte equality, candidate slots, canonical identity, erased q ## in/in-18.md - Unique tokens: 639 @@ -371,16 +376,16 @@ Methodology: - Only in in/in-17.md: ca, damage, denote, x, calculus, locality, per, tile, boundary, bspᵗ, pretty, style - Wedge product (bigram intersection): 23 | top: oom corrupt, semantic identity, canonical id, canonical nodes, e g, erased q, arity bounded, arity cnf, decided full, fixed arity, full key, identity decided - Compare: prism_vm.py - - Intersection: 188 | top: ledger, arena, count, a1, a2, opcode, arg1, ids, size, prism, arg2, f - - Symmetric difference: 1997 (only in in/in-18.md: 451, only in prism_vm.py: 1546) - - Only in in/in-18.md: finite, canon_state, states, transition, π_k, proof, higher, σ, lemma, π, appendix, ρ - - Only in prism_vm.py: jnp, int32, self, int, dtype, none, op, astype, idx, value, ptr, manifest - - Wedge product (bigram intersection): 39 | top: a1 a2, arg1 arg2, count count, bspˢ layout, opcode arg1, arg2 count, bspˢ renormalization, count oom, suc zero, k set, live ids, a2 count + - Intersection: 3 | top: path, md, see + - Symmetric difference: 666 (only in in/in-18.md: 636, only in prism_vm.py: 30) + - Only in in/in-18.md: prism, min, semantic, finite, r, canon_state, state, canonical, order, states, k, count + - Only in prism_vm.py: src, os, sys, exports, f401, main, noqa, prism_vm_core, root, all__, appears, assumed + - Wedge product (bigram intersection): 0 | top: - Compare: IMPLEMENTATION_PLAN.md - - Intersection: 333 | top: prism, tests, semantic, add, min, canonical, null, key, corrupt, md, must, q - - Symmetric difference: 1375 (only in in/in-18.md: 306, only in IMPLEMENTATION_PLAN.md: 1069) + - Intersection: 333 | top: tests, prism, semantic, add, min, canonical, null, key, corrupt, md, must, q + - Symmetric difference: 1388 (only in in/in-18.md: 306, only in IMPLEMENTATION_PLAN.md: 1082) - Only in in/in-18.md: r, states, lemma, π, appendix, transitions, exhaustive, t_k, corresponds, padding, refinement, relation - - Only in IMPLEMENTATION_PLAN.md: expected, pytest, program, encoding, txt, tasks, host, implement, objective, slot1, normalization, per + - Only in IMPLEMENTATION_PLAN.md: expected, pytest, program, encoding, txt, tasks, host, implement, objective, slot1, boundary, normalization - Wedge product (bigram intersection): 66 | top: min prism, full key, higher order, key equality, canonical ids, denotation invariance, oom corrupt, arg1 arg2, e g, md md, opcode arg1, read model ## in/in-19.md - Unique tokens: 242 @@ -392,17 +397,17 @@ Methodology: - Only in in/in-18.md: r, canon_state, state, canonical, states, count, transition, π_k, corrupt, ids, proof, higher - Wedge product (bigram intersection): 24 | top: min prism, md md, prism semantics, read model, new semantics, prism semantic, semantic behavior, audience prism, core developers, cycle stratum, draft normative, end md - Compare: prism_vm.py - - Intersection: 84 | top: ledger, arena, op, f, set, true, stratum, x, md, reads, false, k - - Symmetric difference: 1808 (only in in/in-19.md: 158, only in prism_vm.py: 1650) - - Only in in/in-19.md: κ, c, morphisms, presheaf, sheaf, contexts, every, mathbf, site, topos, κ_i, τ - - Only in prism_vm.py: jnp, int32, count, self, int, dtype, none, a1, a2, opcode, arg1, astype - - Wedge product (bigram intersection): 8 | top: hyperstrata visibility, pre step, q ledger, q map, coarser view, micro strata, slot0 slot1, staging context + - Intersection: 3 | top: md, op, note + - Symmetric difference: 269 (only in in/in-19.md: 239, only in prism_vm.py: 30) + - Only in in/in-19.md: κ, c, arena, j, prism, f, hyperstrata, morphisms, presheaf, q, sheaf, staging + - Only in prism_vm.py: path, src, os, sys, exports, f401, main, noqa, prism_vm_core, root, all__, appears + - Wedge product (bigram intersection): 0 | top: - Compare: IMPLEMENTATION_PLAN.md - Intersection: 128 | top: md, arena, q, ledger, semantic, cnf, coordinate, strata, fixed, univalence, semantics, cycle - - Symmetric difference: 1388 (only in in/in-19.md: 114, only in IMPLEMENTATION_PLAN.md: 1274) + - Symmetric difference: 1401 (only in in/in-19.md: 114, only in IMPLEMENTATION_PLAN.md: 1287) - Only in in/in-19.md: κ, j, f, presheaf, contexts, mathbf, κ_i, category, context, l_, makes, boolean - - Only in IMPLEMENTATION_PLAN.md: expected, pytest, tests, add, null, key, must, program, denotation, full, encoding, txt - - Wedge product (bigram intersection): 23 | top: hyperstrata visibility, read model, visibility rule, pre step, md md, min prism, slot0 slot1, frozen read, micro strata, per cycle, prior strata, reads pre + - Only in IMPLEMENTATION_PLAN.md: tests, expected, pytest, add, null, key, must, program, denotation, full, encoding, txt + - Wedge product (bigram intersection): 23 | top: hyperstrata visibility, read model, visibility rule, pre step, min prism, md md, slot0 slot1, frozen read, micro strata, per cycle, prior strata, reads pre ## in/in-20.md - Unique tokens: 191 - Unique bigrams: 289 @@ -413,17 +418,17 @@ Methodology: - Only in in/in-19.md: f, morphisms, presheaf, contexts, cycle, every, read, rule, site, strata, stratum, visibility - Wedge product (bigram intersection): 43 | top: c j, cayley dickson, md md, context κ, mathbf sh, min prism, sh c, gf cancellation, audience prism, bounds k, core developers, d restriction - Compare: prism_vm.py - - Intersection: 56 | top: ledger, arena, opcode, ids, set, x, md, k, y, lower, valid, end - - Symmetric difference: 1813 (only in in/in-20.md: 135, only in prism_vm.py: 1678) - - Only in in/in-20.md: l, hyperlattice, boolean, c, cayley, dickson, elements, preorder, recursion, sheaf, γ, κ - - Only in prism_vm.py: jnp, int32, count, self, int, dtype, none, a1, a2, op, arg1, astype - - Wedge product (bigram intersection): 8 | top: x y, canonical ledger, canonical ids, ledger ids, must preserve, staging context, y x, y y + - Intersection: 3 | top: md, assumed, join + - Symmetric difference: 218 (only in in/in-20.md: 188, only in prism_vm.py: 30) + - Only in in/in-20.md: l, prism, semantic, hyperlattice, ledger, cd, structure, boolean, c, cayley, dickson, x + - Only in prism_vm.py: path, src, os, sys, exports, f401, main, noqa, prism_vm_core, root, all__, appears + - Wedge product (bigram intersection): 0 | top: - Compare: IMPLEMENTATION_PLAN.md - Intersection: 93 | top: md, ledger, semantic, must, canonical, q, cnf, ids, arena, fixed, semantics, interning - - Symmetric difference: 1407 (only in in/in-20.md: 98, only in IMPLEMENTATION_PLAN.md: 1309) + - Symmetric difference: 1420 (only in in/in-20.md: 98, only in IMPLEMENTATION_PLAN.md: 1322) - Only in in/in-20.md: l, boolean, context, elements, preorder, recursion, γ, κ, algebra, correspond, distinguished, gf - - Only in IMPLEMENTATION_PLAN.md: expected, pytest, tests, add, null, key, program, denotation, full, encoding, txt, id - - Wedge product (bigram intersection): 9 | top: canonical ids, md md, x y, min prism, cayley dickson, must preserve, y y, erased q, glossary md + - Only in IMPLEMENTATION_PLAN.md: tests, expected, pytest, add, null, key, program, denotation, full, encoding, txt, id + - Wedge product (bigram intersection): 9 | top: canonical ids, md md, min prism, x y, cayley dickson, must preserve, y y, erased q, glossary md ## in/in-21.md - Unique tokens: 427 - Unique bigrams: 817 @@ -434,17 +439,17 @@ Methodology: - Only in in/in-20.md: cd, boolean, c, cayley, dickson, x, y, bounds, context, elements, preorder, recursion - Wedge product (bigram intersection): 30 | top: md md, min prism, already enforced, ledger ids, arena accumulation, audience prism, canonical ledger, core developers, developers semantics, draft normative, end md, enforced prism - Compare: prism_vm.py - - Intersection: 102 | top: ledger, arena, ids, state, md, q, bspˢ, see, semantic, canonical, prism, semantics - - Symmetric difference: 1957 (only in in/in-21.md: 325, only in prism_vm.py: 1632) - - Only in in/in-21.md: entropy, lemma, gauge, patch, g, novelty, coherence, direct, justification, adjunction, l, recursive - - Only in prism_vm.py: jnp, int32, count, self, int, dtype, none, a1, a2, op, opcode, arg1 - - Wedge product (bigram intersection): 6 | top: canonical ledger, bspᵗ bspˢ, denotation invariance, ledger ids, bspᵗ temporal, semantics ledger + - Intersection: 4 | top: md, root, see, tighten + - Symmetric difference: 452 (only in in/in-21.md: 423, only in prism_vm.py: 29) + - Only in in/in-21.md: entropy, q, semantic, bspˢ, canonical, semantics, prism, arena, lemma, gauge, pl, patch + - Only in prism_vm.py: path, src, os, sys, exports, f401, main, noqa, prism_vm_core, all__, appears, assumed + - Wedge product (bigram intersection): 0 | top: - Compare: IMPLEMENTATION_PLAN.md - - Intersection: 204 | top: tests, q, canonical, semantic, md, arena, must, denotation, semantics, entropy, ledger, cnf - - Symmetric difference: 1421 (only in in/in-21.md: 223, only in IMPLEMENTATION_PLAN.md: 1198) + - Intersection: 203 | top: tests, q, canonical, semantic, md, arena, must, denotation, semantics, ledger, entropy, cnf + - Symmetric difference: 1436 (only in in/in-21.md: 224, only in IMPLEMENTATION_PLAN.md: 1212) - Only in in/in-21.md: lemma, pl, patch, direct, l, recursive, tree, ct, monotone, operator, different, hyperoperators - - Only in IMPLEMENTATION_PLAN.md: expected, pytest, add, null, key, program, full, encoding, txt, id, m1, corrupt - - Wedge product (bigram intersection): 39 | top: denotation invariance, canonical novelty, md md, gauge symmetry, see md, coarse graining, e g, min prism, decoded normal, normal forms, fixed points, irreversible coarse + - Only in IMPLEMENTATION_PLAN.md: expected, pytest, add, null, key, program, full, encoding, txt, id, corrupt, m1 + - Wedge product (bigram intersection): 39 | top: denotation invariance, canonical novelty, md md, gauge symmetry, min prism, see md, coarse graining, e g, decoded normal, normal forms, fixed points, irreversible coarse ## in/in-22.md - Unique tokens: 304 - Unique bigrams: 626 @@ -455,16 +460,16 @@ Methodology: - Only in in/in-21.md: patch, coherence, adjunction, bspᵗ, coarse, ct, explicit, graining, irreversible, symmetry, time, commutation - Wedge product (bigram intersection): 105 | top: canonical novelty, pl semantics, direct pl, min prism, md md, semantics lemma, novelty saturation, ledger ids, semantic monotone, arena entropy, fixed points, lemma bspˢ - Compare: prism_vm.py - - Intersection: 83 | top: ledger, arena, opcode, ids, set, md, canonical, semantic, k, prism, bspˢ, e - - Symmetric difference: 1872 (only in in/in-22.md: 221, only in prism_vm.py: 1651) - - Only in in/in-22.md: novelty, lemma, saturation, justification, tree, direct, representation, σ, monotone, ν, bounded, document - - Only in prism_vm.py: jnp, int32, count, self, int, dtype, none, a1, a2, op, arg1, astype - - Wedge product (bigram intersection): 11 | top: canonical ids, canonical ledger, ledger ids, full key, bspˢ renormalization, existing keys, ids k, key equality, canonical identity, keys ledger, new keys + - Intersection: 3 | top: md, appears, normalization + - Symmetric difference: 331 (only in in/in-22.md: 301, only in prism_vm.py: 30) + - Only in in/in-22.md: novelty, canonical, semantic, prism, e, lemma, saturation, semantics, ids, justification, pl, tree + - Only in prism_vm.py: path, src, os, sys, exports, f401, main, noqa, prism_vm_core, root, all__, assumed + - Wedge product (bigram intersection): 0 | top: - Compare: IMPLEMENTATION_PLAN.md - - Intersection: 155 | top: tests, canonical, semantic, key, novelty, md, full, id, ids, ledger, fixed, semantics - - Symmetric difference: 1396 (only in in/in-22.md: 149, only in IMPLEMENTATION_PLAN.md: 1247) + - Intersection: 155 | top: tests, canonical, semantic, key, novelty, md, full, id, ledger, ids, fixed, semantics + - Symmetric difference: 1409 (only in in/in-22.md: 149, only in IMPLEMENTATION_PLAN.md: 1260) - Only in in/in-22.md: lemma, saturation, pl, tree, direct, monotone, ν, hyperoperators, intuition, operator, precise, implies - - Only in IMPLEMENTATION_PLAN.md: expected, pytest, add, null, must, program, denotation, encoding, txt, m1, corrupt, implementation + - Only in IMPLEMENTATION_PLAN.md: expected, pytest, add, null, must, program, denotation, encoding, txt, corrupt, m1, implementation - Wedge product (bigram intersection): 27 | top: canonical novelty, full key, min prism, canonical ids, key equality, md md, e g, fixed points, rewrite rules, test obligations, decided full, canonical identity ## in/in-23.md - Unique tokens: 320 @@ -476,17 +481,17 @@ Methodology: - Only in in/in-22.md: ν, intuition, appears, l, measure, monotonicity, n, unchanged, canonically, change, corresponds, defines - Wedge product (bigram intersection): 130 | top: canonical novelty, fixed points, pl semantics, min prism, direct pl, semantics lemma, fixed point, representation fixed, md md, canonical ids, novelty saturation, semantic keys - Compare: prism_vm.py - - Intersection: 91 | top: ledger, arena, ids, value, set, md, fixed, semantic, prism, op_add, canonical, k - - Symmetric difference: 1872 (only in in/in-23.md: 229, only in prism_vm.py: 1643) - - Only in in/in-23.md: lemma, representation, points, hyperoperator, hyperoperators, novelty, tree, point, justification, direct, document, operators - - Only in prism_vm.py: jnp, int32, count, self, int, dtype, none, a1, a2, op, opcode, arg1 - - Wedge product (bigram intersection): 9 | top: canonical ids, canonical ledger, op_add op_mul, full key, ledger ids, bspᵗ bspˢ, key equality, new keys, per step + - Intersection: 2 | top: md, normalization + - Symmetric difference: 349 (only in in/in-23.md: 318, only in prism_vm.py: 31) + - Only in in/in-23.md: fixed, prism, semantic, canonical, lemma, representation, points, hyperoperator, hyperoperators, novelty, tree, point + - Only in prism_vm.py: path, src, os, sys, exports, f401, main, noqa, prism_vm_core, root, all__, appears + - Wedge product (bigram intersection): 0 | top: - Compare: IMPLEMENTATION_PLAN.md - - Intersection: 160 | top: tests, fixed, canonical, semantic, key, md, full, cnf, id, ids, ledger, q - - Symmetric difference: 1402 (only in in/in-23.md: 160, only in IMPLEMENTATION_PLAN.md: 1242) + - Intersection: 160 | top: tests, fixed, canonical, semantic, key, md, full, ledger, cnf, id, ids, q + - Symmetric difference: 1415 (only in in/in-23.md: 160, only in IMPLEMENTATION_PLAN.md: 1255) - Only in in/in-23.md: lemma, hyperoperators, tree, direct, pl, operators, finitely, introduces, processes, stabilization, application, e₀ - - Only in IMPLEMENTATION_PLAN.md: expected, pytest, add, null, must, program, denotation, encoding, txt, m1, corrupt, coordinate - - Wedge product (bigram intersection): 26 | top: fixed points, full key, fixed point, representation fixed, canonical ids, min prism, canonical novelty, md md, key equality, op_add op_mul, candidate slots, rewrite rules + - Only in IMPLEMENTATION_PLAN.md: expected, pytest, add, null, must, program, denotation, encoding, txt, corrupt, m1, coordinate + - Wedge product (bigram intersection): 26 | top: fixed points, full key, fixed point, min prism, representation fixed, canonical ids, canonical novelty, md md, key equality, op_add op_mul, candidate slots, rewrite rules ## in/in-24.md - Unique tokens: 356 - Unique bigrams: 632 @@ -497,16 +502,16 @@ Methodology: - Only in in/in-23.md: keys, pl, execution, preorder, already, exist, existence, gauge, monotone, normalization, candidate, chain - Wedge product (bigram intersection): 106 | top: fixed point, fixed points, representation fixed, md md, min prism, canonical novelty, canonical ids, tree operators, md tree, operator forms, semantic sketch, tree processes - Compare: prism_vm.py - - Intersection: 93 | top: ledger, arena, self, op, opcode, size, ids, value, f, manifest, set, md - - Symmetric difference: 1904 (only in in/in-24.md: 263, only in prism_vm.py: 1641) - - Only in in/in-24.md: tree, operator, representation, sketch, finite, growth, operators, explosion, point, recursive, saturation, termination - - Only in prism_vm.py: jnp, int32, count, int, dtype, none, a1, a2, arg1, astype, idx, arg2 - - Wedge product (bigram intersection): 8 | top: arena size, arena arena, canonical ids, full key, ledger canonical, bspᵗ controls, key equality, rewrite steps + - Intersection: 4 | top: md, see, join, op + - Symmetric difference: 381 (only in in/in-24.md: 352, only in prism_vm.py: 29) + - Only in in/in-24.md: tree, prism, semantic, arena, canonical, rewrite, fixed, operator, representation, sketch, collapse, finite + - Only in prism_vm.py: path, src, os, sys, exports, f401, main, noqa, prism_vm_core, root, all__, appears + - Wedge product (bigram intersection): 0 | top: - Compare: IMPLEMENTATION_PLAN.md - - Intersection: 170 | top: expected, md, semantic, key, canonical, arena, full, fixed, ledger, rewrite, encoding, prism - - Symmetric difference: 1418 (only in in/in-24.md: 186, only in IMPLEMENTATION_PLAN.md: 1232) + - Intersection: 169 | top: expected, semantic, md, key, canonical, arena, full, fixed, ledger, prism, rewrite, encoding + - Symmetric difference: 1433 (only in in/in-24.md: 187, only in IMPLEMENTATION_PLAN.md: 1246) - Only in in/in-24.md: tree, operator, sketch, operators, explosion, recursive, saturation, worked, claim, hyperoperators, lemma, application - - Only in IMPLEMENTATION_PLAN.md: pytest, tests, add, null, must, program, denotation, txt, m1, corrupt, coordinate, rank + - Only in IMPLEMENTATION_PLAN.md: tests, pytest, add, null, must, program, denotation, txt, corrupt, m1, coordinate, rank - Wedge product (bigram intersection): 27 | top: full key, canonical ids, key equality, md md, min prism, see md, fixed point, fixed points, representation fixed, rewrite rules, canonical novelty, higher order ## in/in-25.md - Unique tokens: 365 @@ -518,16 +523,16 @@ Methodology: - Only in in/in-24.md: sketch, collapse, ledger, recursive, worked, cnf, construction, hyperoperators, ids, application, continues, e₀ - Wedge product (bigram intersection): 73 | top: md md, fixed points, canonical fixed, ordinal indexed, indexed rewrite, min prism, fixed point, canonical novelty, representation fixed, operator forms, proof theory, tree operators - Compare: prism_vm.py - - Intersection: 79 | top: arena, md, enabled, prism, false, semantic, milestone, canonical, end, yes, rewrite, fixed - - Symmetric difference: 1941 (only in in/in-25.md: 286, only in prism_vm.py: 1655) - - Only in in/in-25.md: ordinal, termination, tree, descent, ordinals, representation, claims, proof, theory, lemma, indexed, points - - Only in prism_vm.py: jnp, ledger, int32, count, self, int, dtype, none, a1, a2, op, opcode - - Wedge product (bigram intersection): 1 | top: rewrite steps + - Intersection: 1 | top: md + - Symmetric difference: 396 (only in in/in-25.md: 364, only in prism_vm.py: 32) + - Only in in/in-25.md: ordinal, prism, termination, semantic, rewrite, tree, canonical, descent, ordinals, representation, claims, fixed + - Only in prism_vm.py: path, src, os, sys, exports, f401, main, noqa, prism_vm_core, root, all__, appears + - Wedge product (bigram intersection): 0 | top: - Compare: IMPLEMENTATION_PLAN.md - - Intersection: 135 | top: md, semantic, canonical, prism, ordinal, rewrite, fixed, semantics, arena, termination, univalence, m4 - - Symmetric difference: 1497 (only in in/in-25.md: 230, only in IMPLEMENTATION_PLAN.md: 1267) + - Intersection: 134 | top: md, semantic, prism, canonical, ordinal, rewrite, fixed, semantics, termination, arena, univalence, boundary + - Symmetric difference: 1512 (only in in/in-25.md: 231, only in IMPLEMENTATION_PLAN.md: 1281) - Only in in/in-25.md: tree, descent, ordinals, theory, lemma, claim, methods, decreasing, systems, well, different, direct - - Only in IMPLEMENTATION_PLAN.md: expected, pytest, tests, add, null, key, must, program, denotation, full, encoding, txt + - Only in IMPLEMENTATION_PLAN.md: tests, expected, pytest, add, null, key, must, program, denotation, full, encoding, txt - Wedge product (bigram intersection): 19 | top: fixed points, md md, min prism, ordinal indexed, canonical novelty, rewrite rules, fixed point, proof theoretic, theoretic strength, glossary md, imply termination, m3 m4 ## in/in-26.md - Unique tokens: 323 @@ -539,17 +544,17 @@ Methodology: - Only in in/in-25.md: rewrite, theory, lemma, indexed, claim, document, methods, new, normative, two, decreasing, may - Wedge product (bigram intersection): 37 | top: canonical novelty, min prism, fixed points, md md, ordinal descent, finite semantic, fixed point, novelty monotone, prism claims, reviewer facing, well founded, ordinal measure - Compare: prism_vm.py - - Intersection: 79 | top: ledger, arena, op, value, set, shape, state, md, zero, semantic, false, add - - Symmetric difference: 1899 (only in in/in-26.md: 244, only in prism_vm.py: 1655) - - Only in in/in-26.md: agda, novelty, finite, forall, prove, arity, sigma, formalize, termination, closure, proof, equivalence - - Only in prism_vm.py: jnp, int32, count, self, int, dtype, none, a1, a2, opcode, arg1, astype - - Wedge product (bigram intersection): 8 | top: suc zero, add mul, k1 k2, arena arena, op op, q q, root canonical, canonical identity + - Intersection: 4 | top: op, md, root, normalization + - Symmetric difference: 348 (only in in/in-26.md: 319, only in prism_vm.py: 29) + - Only in in/in-26.md: agda, semantic, novelty, prism, finite, canonical, step, forall, q, prove, arity, key + - Only in prism_vm.py: path, src, os, sys, exports, f401, main, noqa, prism_vm_core, all__, appears, assumed + - Wedge product (bigram intersection): 0 | top: - Compare: IMPLEMENTATION_PLAN.md - - Intersection: 133 | top: add, agda, key, semantic, canonical, q, ledger, md, full, univalence, arena, fixed - - Symmetric difference: 1459 (only in in/in-26.md: 190, only in IMPLEMENTATION_PLAN.md: 1269) + - Intersection: 135 | top: add, agda, key, semantic, canonical, q, ledger, full, md, univalence, arena, fixed + - Symmetric difference: 1468 (only in in/in-26.md: 188, only in IMPLEMENTATION_PLAN.md: 1280) - Only in in/in-26.md: forall, prove, sigma, formalize, m, monotone, everything, fun, reviewers, statement, e1, e2 - - Only in IMPLEMENTATION_PLAN.md: expected, pytest, tests, null, must, program, denotation, encoding, txt, id, m1, corrupt - - Wedge product (bigram intersection): 28 | top: min prism, canonical novelty, md md, bspˢ gauge, finite closure, fixed points, proof roadmap, suc zero, agda proof, define canonical, fixed point, gauge symmetry + - Only in IMPLEMENTATION_PLAN.md: tests, expected, pytest, null, must, program, denotation, encoding, txt, id, corrupt, m1 + - Wedge product (bigram intersection): 30 | top: min prism, canonical novelty, md md, bspˢ gauge, finite closure, fixed points, proof roadmap, suc zero, agda proof, define canonical, fixed point, gauge symmetry ## in/in-27.md - Unique tokens: 406 - Unique bigrams: 647 @@ -560,16 +565,16 @@ Methodology: - Only in in/in-26.md: agda, semantic, novelty, finite, step, forall, prove, arity, ledger, set, sigma, e - Wedge product (bigram intersection): 3 | top: bspˢ gauge, erased q, gauge invariance - Compare: prism_vm.py - - Intersection: 159 | top: arena, count, none, size, opcode, value, ids, uint32, jax, morton, array, state - - Symmetric difference: 1822 (only in in/in-27.md: 247, only in prism_vm.py: 1575) - - Only in in/in-27.md: entropy, gauge, geometric, holographic, assertion, implementation, k_, m5, p, property, renormˢ, aperture - - Only in prism_vm.py: jnp, ledger, int32, self, int, dtype, a1, a2, op, arg1, astype, idx - - Wedge product (bigram intersection): 12 | top: denotation invariance, k p_buffer, projection q, spill p_buffer, arena namedtuple, arena tuple, k k, lax stop_gradient, must commute, p_buffer d_active, prism vm, sum hist + - Intersection: 2 | top: jax, normalization + - Symmetric difference: 435 (only in in/in-27.md: 404, only in prism_vm.py: 31) + - Only in in/in-27.md: k, bspˢ, entropyₐ, h, must, q, size, servo, entropy, gauge, p_buffer, spatial + - Only in prism_vm.py: path, src, os, sys, exports, f401, main, noqa, prism_vm_core, root, all__, appears + - Wedge product (bigram intersection): 0 | top: - Compare: IMPLEMENTATION_PLAN.md - Intersection: 209 | top: key, must, q, denotation, canonical, implementation, id, arena, fixed, ids, k, m4 - - Symmetric difference: 1390 (only in in/in-27.md: 197, only in IMPLEMENTATION_PLAN.md: 1193) + - Symmetric difference: 1403 (only in in/in-27.md: 197, only in IMPLEMENTATION_PLAN.md: 1206) - Only in in/in-27.md: holographic, assertion, axis, k_, p, property, z, aperture, check, curve, design, hist - - Only in IMPLEMENTATION_PLAN.md: expected, pytest, tests, add, null, program, full, encoding, txt, md, m1, corrupt + - Only in IMPLEMENTATION_PLAN.md: tests, expected, pytest, add, null, program, full, encoding, txt, ledger, md, corrupt - Wedge product (bigram intersection): 43 | top: denotation invariance, bspˢ gauge, entropy h, milestone gated, must commute, boundary crossings, canonical interning, h bits, arena namedtuple, behind prism_enable_servo, composite key, damage metrics ## in/in-28.md - Unique tokens: 1460 @@ -581,16 +586,16 @@ Methodology: - Only in in/in-27.md: entropyₐ, h, p_buffer, geometric, holographic, order, assertion, d_active, packing, aligned, aperture, autonomic - Wedge product (bigram intersection): 19 | top: k k, k n, k_ k_t, sum_ k, size k, axis bspˢ, bspˢ spatial, commutation q, glossary compliance, spatial layout, buffer zone, control law - Compare: prism_vm.py - - Intersection: 254 | top: jnp, k, arena, count, self, see, summary, int, semantics, opcode, astype, size - - Symmetric difference: 2686 (only in in/in-28.md: 1206, only in prism_vm.py: 1480) - - Only in in/in-28.md: details, strong, agda, python, latex, machine, servoobjects, servo_objects, mathrm, p, code, high - - Only in prism_vm.py: ledger, int32, dtype, none, a1, a2, op, arg1, idx, ids, arg2, ptr - - Wedge product (bigram intersection): 39 | top: astype jnp, jnp uint32, arena rank, k k, jnp maximum, mask k, jnp minimum, start jnp, arena servo, arena replace, jnp float32, uint32 mask + - Intersection: 5 | top: see, jax, drift, md, note + - Symmetric difference: 1483 (only in in/in-28.md: 1455, only in prism_vm.py: 28) + - Only in in/in-28.md: k, details, strong, summary, agda, semantics, python, latex, machine, servoobjects, servo, py + - Only in prism_vm.py: path, src, os, sys, exports, f401, main, noqa, prism_vm_core, root, all__, appears + - Wedge product (bigram intersection): 0 | top: - Compare: IMPLEMENTATION_PLAN.md - - Intersection: 374 | top: k, summary, agda, details, semantics, see, python, py, servo, add, mask, key - - Symmetric difference: 2114 (only in in/in-28.md: 1086, only in IMPLEMENTATION_PLAN.md: 1028) + - Intersection: 376 | top: k, summary, agda, details, semantics, see, python, py, servo, add, mask, key + - Symmetric difference: 2123 (only in in/in-28.md: 1084, only in IMPLEMENTATION_PLAN.md: 1039) - Only in in/in-28.md: strong, latex, machine, servoobjects, servo_objects, mathrm, p, high, low, ge, adaptive, density - - Only in IMPLEMENTATION_PLAN.md: expected, pytest, tests, null, denotation, encoding, txt, id, m1, corrupt, ledger, cnf + - Only in IMPLEMENTATION_PLAN.md: tests, expected, pytest, null, denotation, encoding, txt, id, ledger, corrupt, m1, cnf - Wedge product (bigram intersection): 20 | top: e g, servo mask, x x, hot warm, sort key, warm cold, add new, control loop, erased q, fixed size, jax numpy, proofs proof ## in/in-27-update-audit.md - Unique tokens: 655 @@ -602,30 +607,30 @@ Methodology: - Only in in/in-28.md: details, agda, semantics, see, python, latex, machine, servoobjects, py, servo_objects, mathrm, p - Wedge product (bigram intersection): 13 | top: e g, erased q, spectral energy, active density, buffer pressure, uses jnp, control law, current implementation, current system, hysteresis stability, q servo, servo q - Compare: prism_vm.py - - Intersection: 175 | top: jnp, arena, f, jax, morton, true, host, sync, md, bspˢ, lax, test - - Symmetric difference: 2039 (only in in/in-27-update-audit.md: 480, only in prism_vm.py: 1559) - - Only in in/in-27-update-audit.md: glossary, thresholds, entropy, still, implementation, without, explicitly, gauge, holographic, need, histogram, requirement - - Only in prism_vm.py: ledger, int32, count, self, int, dtype, none, a1, a2, op, opcode, arg1 - - Wedge product (bigram intersection): 10 | top: implementation_plan md, bspˢ layout, jnp argsort, masked morton, denotation invariance, bspᵗ bspˢ, q q, projection q, must commute, p_buffer d_active + - Intersection: 9 | top: normalization, md, path, drift, implementation_plan, jax, main, note, tighten + - Symmetric difference: 670 (only in in/in-27-update-audit.md: 646, only in prism_vm.py: 24) + - Only in in/in-27-update-audit.md: sort, glossary, thresholds, test, bspˢ, stability, entropy, q, stable, still, explicit, implementation + - Only in prism_vm.py: src, os, sys, exports, f401, noqa, prism_vm_core, root, all__, appears, assumed, dirname + - Wedge product (bigram intersection): 1 | top: implementation_plan md - Compare: IMPLEMENTATION_PLAN.md - - Intersection: 274 | top: tests, expected, add, key, q, sort, implementation, must, md, test, denotation, stable - - Symmetric difference: 1509 (only in in/in-27-update-audit.md: 381, only in IMPLEMENTATION_PLAN.md: 1128) + - Intersection: 273 | top: tests, expected, add, key, q, sort, implementation, must, test, md, denotation, stable + - Symmetric difference: 1524 (only in in/in-27-update-audit.md: 382, only in IMPLEMENTATION_PLAN.md: 1142) - Only in in/in-27-update-audit.md: still, holographic, need, histogram, requirement, calibration, formulas, revision, appendix, axis, design, remaining - - Only in IMPLEMENTATION_PLAN.md: pytest, null, program, full, encoding, txt, canonical, m1, corrupt, ledger, cnf, coordinate + - Only in IMPLEMENTATION_PLAN.md: pytest, null, program, full, encoding, txt, canonical, ledger, corrupt, m1, cnf, coordinate - Wedge product (bigram intersection): 32 | top: stable sort, denotation invariance, e g, glossary md, bspˢ gauge, erased q, composite key, masked morton, must commute, q q, test obligations, gauge symmetry ## in/glossary.md -- Unique tokens: 918 -- Unique bigrams: 2176 +- Unique tokens: 1094 +- Unique bigrams: 2635 - Prior version: none - Compare: prism_vm.py - - Intersection: 264 | top: ledger, arena, count, tests, none, py, ids, op, size, value, m1, q - - Symmetric difference: 2124 (only in in/glossary.md: 654, only in prism_vm.py: 1470) - - Only in in/glossary.md: obligations, commutation, axes, failure, meanings, qualified, desired, meaning, pretty, interpretation, test_m1_gate, p - - Only in prism_vm.py: jnp, int32, self, int, dtype, a1, a2, opcode, arg1, astype, idx, arg2 - - Wedge product (bigram intersection): 55 | top: m4 tests, bspˢ layout, canonical ids, x y, test_coord_ops py, tests test_coord_ops, canonical ledger, ledger ids, py test_arena_denotation_invariance_random_suite, test_arena_denotation_invariance py, tests test_arena_denotation_invariance, x x + - Intersection: 9 | top: normalization, path, op, note, drift, root, assumed, md, see + - Symmetric difference: 1109 (only in in/glossary.md: 1085, only in prism_vm.py: 24) + - Only in in/glossary.md: tests, py, q, must, normative, semantic, vs, test, obligations, rule, commutation, axes + - Only in prism_vm.py: src, os, sys, exports, f401, main, noqa, prism_vm_core, all__, appears, dirname, dtype + - Wedge product (bigram intersection): 0 | top: - Compare: IMPLEMENTATION_PLAN.md - - Intersection: 455 | top: tests, py, q, pytest, must, semantic, add, normative, canonical, m1, key, vs - - Symmetric difference: 1410 (only in in/glossary.md: 463, only in IMPLEMENTATION_PLAN.md: 947) - - Only in in/glossary.md: meanings, qualified, desired, interpretation, test_m1_gate, p, test_invariants, ungated, axis, test_coord_ops, presheaf, test_ledger_intern - - Only in IMPLEMENTATION_PLAN.md: expected, program, txt, md, rank, tasks, implement, objective, baseline, per, zero, enabled - - Wedge product (bigram intersection): 166 | top: test obligations, m1 tests, denote q, pretty denote, canonical ids, full key, denotation invariance, key encoding, fixed point, key equality, test_candidate_cycle py, tests test_candidate_cycle + - Intersection: 520 | top: tests, py, q, must, pytest, semantic, normative, add, m1, vs, test, canonical + - Symmetric difference: 1469 (only in in/glossary.md: 574, only in IMPLEMENTATION_PLAN.md: 895) + - Only in in/glossary.md: meanings, qualified, desired, interpretation, test_m1_gate, p, test_invariants, ungated, axis, test_coord_ops, di, oob + - Only in IMPLEMENTATION_PLAN.md: expected, program, txt, rank, baseline, tasks, implement, objective, model, pipeline, zero, enabled + - Wedge product (bigram intersection): 178 | top: test obligations, m1 tests, denote q, pretty denote, canonical ids, full key, erased q, e g, denotation invariance, key encoding, test_candidate_cycle py, tests test_candidate_cycle diff --git a/in/glossary.md b/in/glossary.md index f6f44ec..7181a3b 100644 --- a/in/glossary.md +++ b/in/glossary.md @@ -1,3 +1,8 @@ +--- +doc_revision: 2 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + # Commuting Acronyms & Polysemous Terms (Normative) > **Glossary Contract (Normative):** @@ -900,6 +905,17 @@ q ∘ Arena_step = Ledger_step ∘ q Canonical IDs are global elements of the Ledger sheaf. +### Ledger (Settlement / Root Arena) + +The Ledger is the **terminal (“settled”)** level of the hierarchical BSP arena model. + +- Arenas: locality envelopes + spillways bound expansion within a microstratum. +- Ledger: expansion is forbidden; identity is canonical and globally stable. +- Operationally: arena work is provisional; ledger identities are univalent and globally visible. + +**Implementation note:** the Ledger is an append-only canonical store + index (CQRS / event-sourced read model), +not a spillway arena buffer. + ### Erasure by `q` Nothing: the Ledger is post-erasure. diff --git a/in/in-1.md b/in/in-1.md index 6e02c16..f7dba84 100644 --- a/in/in-1.md +++ b/in/in-1.md @@ -1,3 +1,8 @@ +--- +doc_revision: 1 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + This is the **Prism IR** implementation. In this architecture, the **Host** acts as the static analyzer and deduplicator. It constructs a "perfect" (maximally shared) Intermediate Representation in the JAX memory space. The **Device** (JAX) serves as the evaluation engine, only running kernels when the IR explicitly demands computation that cannot be resolved statically. diff --git a/in/in-10.md b/in/in-10.md index e4c5567..37e665e 100644 --- a/in/in-10.md +++ b/in/in-10.md @@ -1,3 +1,8 @@ +--- +doc_revision: 1 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + NOTE: CONSOLIDATED by in-13.md; CD coordinates are made self-hosted in in-11.md. Perfect. Here is a **clean, engineering-grade technical diff** you can use as a milestone definition. It is written so that: @@ -236,4 +241,4 @@ This milestone is **complete** when: If you want, next I can: * draft a **code-level checklist** mapping this diff onto specific functions in your file, or -* help you write the **Milestone PR description / design doc header** that would survive a serious technical review. +* help you write the **Milestone PR description / design doc header** that would survive a serious technical review. \ No newline at end of file diff --git a/in/in-11.md b/in/in-11.md index 661ca96..e835174 100644 --- a/in/in-11.md +++ b/in/in-11.md @@ -1,3 +1,8 @@ +--- +doc_revision: 1 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + NOTE: CONSOLIDATED by in-13.md; acceptance criteria are reflected in IMPLEMENTATION_PLAN.md. Yes — this closes the loop cleanly, and as a reviewer/engineer I would now say: **Milestone Δ is no longer speculative; it is mechanically well-founded.** What you’ve articulated is exactly the missing representational commitment that makes all earlier claims true *by construction*. @@ -237,4 +242,4 @@ At this point, the architecture is **self-consistent**: * Sheaf-style locality is real, not metaphorical. * Cut elimination is local and constructive. * Cayley–Dickson is operational, not interpretive. -* The VM is self-hosting its own geometry. +* The VM is self-hosting its own geometry. \ No newline at end of file diff --git a/in/in-12.md b/in/in-12.md index c0c5f7e..0757fef 100644 --- a/in/in-12.md +++ b/in/in-12.md @@ -1,3 +1,8 @@ +--- +doc_revision: 1 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + NOTE: CONSOLIDATED by in-13.md; univalence contract formalized in IMPLEMENTATION_PLAN.md. Yes—event sourcing + CQRS is basically the *right mental model* for “strong canonicality without ambiguity,” and it also explains why the vtable analogy is so apt. @@ -165,4 +170,4 @@ If you want, I can outline a concrete data layout that fits JAX constraints (pur * a radix-trie index over packed bytes, or * a hash-bucket index with full-key compare and periodic rebuild snapshots. -Both give “strong canonicality with no ambiguity”; the trie gives literal collision-free indexing, the bucketed hash gives “collisions are irrelevant.” +Both give “strong canonicality with no ambiguity”; the trie gives literal collision-free indexing, the bucketed hash gives “collisions are irrelevant.” \ No newline at end of file diff --git a/in/in-13.md b/in/in-13.md index 9b9818f..fa53d86 100644 --- a/in/in-13.md +++ b/in/in-13.md @@ -1,3 +1,8 @@ +--- +doc_revision: 1 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + NOTE: REFINED by in-14.md and IMPLEMENTATION_PLAN.md; this doc remains the consolidation rationale. Here’s the reconciliation: **in-10 defines the milestone delta**, **in-11 makes the delta “self-hosting” by turning coordinates into IR objects**, and **in-12 supplies the *systems* pattern that guarantees univalence (canonicality without ambiguity) even under collisions**. Together they form one coherent architecture. @@ -107,4 +112,4 @@ If you want the next “hard step,” it’s to choose the read-model index stru * **Trie/radix on packed bytes** (literal collision-free indexing) * **Hash buckets + full-key compare** (collision-tolerant, still univalent) -Either way, the reconciliation above is consistent: **event log → deterministic materialized index → canonical Ledger**, with **CD coordinates self-hosted as CNF-2 nodes** and **aggregation defined as coordinate-normalization**. +Either way, the reconciliation above is consistent: **event log → deterministic materialized index → canonical Ledger**, with **CD coordinates self-hosted as CNF-2 nodes** and **aggregation defined as coordinate-normalization**. \ No newline at end of file diff --git a/in/in-14.md b/in/in-14.md index 23ab1b8..4c98498 100644 --- a/in/in-14.md +++ b/in/in-14.md @@ -1,3 +1,8 @@ +--- +doc_revision: 1 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + NOTE: Incorporated into IMPLEMENTATION_PLAN.md; this document remains the rationale and reconciliation pass. This is a strong plan. The main thing to tighten is **the separation of semantic spine vs. performance spine**, and to make **univalence** a first-class invariant (with explicit “what constitutes a key” and “what constitutes equality” written down once and reused everywhere). @@ -192,4 +197,4 @@ If you want the fastest path, change only these three parts: 2. In **Milestones**, reorder so M1–M3 are Ledger-only; move Arena sorting/morton to M4+. -3. Add a short **Univalence Contract** section defining key bytes + equality + no truncation. +3. Add a short **Univalence Contract** section defining key bytes + equality + no truncation. \ No newline at end of file diff --git a/in/in-15.md b/in/in-15.md index a9f14f2..b882572 100644 --- a/in/in-15.md +++ b/in/in-15.md @@ -1,3 +1,8 @@ +--- +doc_revision: 1 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + NOTE: Implemented via tests/conftest.py and documented in IMPLEMENTATION_PLAN.md; this note records the workflow. This note formalizes **milestone-gated testing** and the **VS Code workflow** @@ -27,12 +32,12 @@ We use: - A gate in `conftest.py` that can select either: - **Inclusive mode** (<= milestone) via `--milestone`, or - **Banded mode** (exact band) via `--milestone-band` -- Per-milestone pytest configs: `pytest.m1.ini` .. `pytest.m5.ini` (banded) +- Per-milestone pytest configs: `pytest.baseline.ini`, `pytest.m2.ini` .. `pytest.m5.ini` (banded) Example: - `pytest -c pytest.m2.ini` runs **only** tests marked `m2` -- `pytest -c pytest.m1.ini` runs tests marked `m1` **plus** unmarked tests +- `pytest -c pytest.baseline.ini` runs tests marked `m1` **plus** unmarked tests - Running `m1` → `m5` in order runs the full suite without rerunning tests - Inclusive mode is still available: `pytest --milestone=m2` runs `m1` + `m2` @@ -113,7 +118,7 @@ As a result, "milestone complete" becomes a testable, tool-supported state. CLI (banded): -- `pytest -c pytest.m1.ini` +- `pytest -c pytest.baseline.ini` - `pytest -c pytest.m2.ini` - `pytest -c pytest.m3.ini` - `pytest -c pytest.m4.ini` @@ -132,4 +137,4 @@ VS Code: If we later want multiple milestones in the UI at once, a multi-root workspace can be added (Option B). For now, the single env toggle keeps the workflow -simple and deterministic. +simple and deterministic. \ No newline at end of file diff --git a/in/in-16.md b/in/in-16.md index 45db507..ee640e5 100644 --- a/in/in-16.md +++ b/in/in-16.md @@ -1,3 +1,8 @@ +--- +doc_revision: 1 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + # in-16.md — Homomorphic Collapse of Evaluation and Canonicalization ## Purpose @@ -437,4 +442,4 @@ function commit_stratum(stratum_nodes): list[CanonID] * `memo_q` is mandatory for performance and termination (DAG sharing). * The strata rule makes `q` well-founded and permits batched interning. -* If the evaluator mutates nodes in-place, it must do so only within its own provisional space; meaning is always measured after projection. +* If the evaluator mutates nodes in-place, it must do so only within its own provisional space; meaning is always measured after projection. \ No newline at end of file diff --git a/in/in-17.md b/in/in-17.md index 539871a..eafe81c 100644 --- a/in/in-17.md +++ b/in/in-17.md @@ -1,3 +1,8 @@ +--- +doc_revision: 1 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + # Prism CA–Lambda Clarifications: Braided Lemmas and Design Commitments > **Purpose** @@ -343,4 +348,4 @@ What emerges is a system where: This is not “λ-calculus on a CA” as a stunt. It is **λ-calculus as a compressible trajectory in a constrained physical phase space**, with Prism acting as the physics engine. -That framing is internally coherent, implementable, and — crucially — testable. +That framing is internally coherent, implementable, and — crucially — testable. \ No newline at end of file diff --git a/in/in-18.md b/in/in-18.md index 6431f6e..804851d 100644 --- a/in/in-18.md +++ b/in/in-18.md @@ -1,3 +1,8 @@ +--- +doc_revision: 1 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + # in-18.md — **Min(Prism): Finite Semantic Core and Exhaustive Verification** > **Status:** Draft (normative, milestone-adjacent) @@ -537,4 +542,4 @@ Failure of this test indicates **semantic leakage**: some behavior in the large --- -**End of in-18.md** +**End of in-18.md** \ No newline at end of file diff --git a/in/in-19.md b/in/in-19.md index 8a43b48..ba4d134 100644 --- a/in/in-19.md +++ b/in/in-19.md @@ -1,3 +1,8 @@ +--- +doc_revision: 1 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + # in-19.md — **The Prism Site (C,J): Staging Contexts and Grothendieck Topology** > **Status:** Draft (normative) @@ -155,4 +160,4 @@ The corresponding topos of sheaves is finitely presented, and its global semanti --- -**End of in-19.md** +**End of in-19.md** \ No newline at end of file diff --git a/in/in-2.md b/in/in-2.md index e781543..1911516 100644 --- a/in/in-2.md +++ b/in/in-2.md @@ -1,3 +1,8 @@ +--- +doc_revision: 1 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + This is a significant architectural pivot. By moving from string-based metaprogramming (`exec`) to a **First-Class Intermediate Representation (IR)**, we align the system with the principles of homoiconicity. In this model, the **instruction** to perform an addition is structurally identical to the **data** representing the number it adds. Both are just triplets in the `Manifest`. The "Compiler" is replaced by a static **Interpreter** that walks this graph. @@ -293,4 +298,4 @@ if __name__ == "__main__": 1. **No Code Gen**: There is no `compile()` method generating string source code. `kernel_add` is a standard Python function decorated with `@jit`. 2. **Explicit IR**: In the previous version, `parse` called `kernel(a,b)` immediately or returned raw ints. Here, `parse` returns a **pointer to an instruction node** (e.g., an `ADD` node). 3. **Lazy/Deferred Execution**: The parser builds the *entire* expression tree in the Manifest (including the `add` node) *before* execution starts. -4. **Homoiconicity**: The instruction `(ADD, ptr_a, ptr_b)` sits in the exact same array as the data `(SUC, ptr_val)`. The `eval` function is the only thing that distinguishes "Code" from "Data". +4. **Homoiconicity**: The instruction `(ADD, ptr_a, ptr_b)` sits in the exact same array as the data `(SUC, ptr_val)`. The `eval` function is the only thing that distinguishes "Code" from "Data". \ No newline at end of file diff --git a/in/in-20.md b/in/in-20.md index 515f2a3..7ccf528 100644 --- a/in/in-20.md +++ b/in/in-20.md @@ -1,3 +1,8 @@ +--- +doc_revision: 1 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + # in-20.md — **The Ledger Object and the Semantic Hyperlattice** > **Status:** Draft (normative) @@ -139,5 +144,4 @@ By refinement (Appendix D of `in-18.md`), invariants proven over this finite hyp --- -**End of in-20.md** - +**End of in-20.md** \ No newline at end of file diff --git a/in/in-21.md b/in/in-21.md index f9c5f14..61cf125 100644 --- a/in/in-21.md +++ b/in/in-21.md @@ -1,3 +1,8 @@ +--- +doc_revision: 1 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + Understood. I’ll now **finalize and tighten the patch as a peer reviewer**, not as a co-author riffing. What follows is a **clean, publication-ready version** of the patch you started, with: * tightened claims (what is proved vs. interpreted), @@ -406,4 +411,4 @@ If you want the next patch, the natural continuations are: * **in-22.md — Canonical Novelty as a Semantic Monotone** (explicit fixed-point measure), or * **in-23.md — TREE-Class Operators as Interned Fixed Points** (worked operator-level sketch). -Say which one you want, or if you want this one tightened further for publication. +Say which one you want, or if you want this one tightened further for publication. \ No newline at end of file diff --git a/in/in-22.md b/in/in-22.md index 10c9a1a..4ded045 100644 --- a/in/in-22.md +++ b/in/in-22.md @@ -1,3 +1,8 @@ +--- +doc_revision: 1 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + Excellent. I’ll proceed in the same **peer-review rigor + house style**, and I’ll make **in-22.md** do one very specific job: > **Turn “canonical novelty” from an intuition into a formally usable semantic monotone**, strong enough to justify saturation, fixed points, and TREE-class representation—without sneaking in termination claims. @@ -353,4 +358,4 @@ If you want to continue in sequence, the natural next patches are: * **in-23.md — Fixed Points of Interned Hyperoperators** (formalizing operator-level saturation), or * **in-24.md — TREE-Class Encodings in Prism (Worked Semantic Sketch)** (concrete, adversarial example). -Say which one you want next. +Say which one you want next. \ No newline at end of file diff --git a/in/in-23.md b/in/in-23.md index f7b58ca..0d8f0a4 100644 --- a/in/in-23.md +++ b/in/in-23.md @@ -1,3 +1,8 @@ +--- +doc_revision: 1 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + Excellent. I’ll proceed exactly in-sequence and at the same rigor level. This document will do a very specific job: @@ -349,4 +354,4 @@ or, alternatively, * **in-24.md — Limits of Canonical Novelty: What Prism Cannot Represent** (to pre-empt reviewer skepticism by drawing the boundary sharply). -Tell me which direction you want next. +Tell me which direction you want next. \ No newline at end of file diff --git a/in/in-24.md b/in/in-24.md index 1600a0e..a7bc9e8 100644 --- a/in/in-24.md +++ b/in/in-24.md @@ -1,3 +1,8 @@ +--- +doc_revision: 1 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + Perfect. I’ll proceed with **`in-24.md — TREE-Class Operators: A Worked Semantic Sketch`**, written as a **peer-review–grade explanatory construction**, not an implementation plan and not a proof, but a *worked semantic example* that makes the TREE-class claim concrete and falsifiable. This document’s job is very specific: @@ -359,4 +364,4 @@ If you want to continue, the next two natural directions are: * **in-25.md — Ordinal-Indexed Rewrite vs Canonical Fixed Points** (explicitly contrasting Prism with ordinal-based termination arguments). -Say which one you want next. +Say which one you want next. \ No newline at end of file diff --git a/in/in-25.md b/in/in-25.md index 46a2b28..107ec52 100644 --- a/in/in-25.md +++ b/in/in-25.md @@ -1,3 +1,8 @@ +--- +doc_revision: 1 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + Excellent. This is the **right capstone** for the sequence: it draws a clean, defensible boundary between two traditions that are often conflated, and it explains *why Prism deliberately does not play the ordinal game* while still handling TREE-class phenomena. Below is a **publication-ready `in-25.md`**, written in the same **normative, lemma-driven, reviewer-facing style** as the previous documents. It is careful about claims, explicit about non-claims, and pre-empts the most obvious objections from proof theorists and rewriting theorists. @@ -332,4 +337,4 @@ If you want to continue, the natural next (and final) documents in this arc woul * **in-26.md — From Semantics to Practice: Why This Matters for Real Systems** (translating the theory into engineering consequences). -Say which one you want, or if you want to revise any claim here for even tighter defensibility. +Say which one you want, or if you want to revise any claim here for even tighter defensibility. \ No newline at end of file diff --git a/in/in-26.md b/in/in-26.md index d1fb06f..af325d8 100644 --- a/in/in-26.md +++ b/in/in-26.md @@ -1,3 +1,8 @@ +--- +doc_revision: 1 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + Below is a concrete, reviewer-facing proposal of what to formalize in Agda, structured as a proof roadmap rather than a grab-bag of lemmas. The goal is to maximize semantic leverage per proof hour and to respect Prism's @@ -369,4 +374,4 @@ If you prove only five things in Agda, prove these: 4) Finiteness of semantic closure 5) Existence of representation fixed points without termination -That set alone is enough to make Prism's claims extremely hard to dismiss. +That set alone is enough to make Prism's claims extremely hard to dismiss. \ No newline at end of file diff --git a/in/in-27-update-audit.md b/in/in-27-update-audit.md index 52434cb..8dd820e 100644 --- a/in/in-27-update-audit.md +++ b/in/in-27-update-audit.md @@ -1,3 +1,8 @@ +--- +doc_revision: 1 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + # in-27 Update Audit (Delta + Correctness Impacts) This note captures the impact of the edits made to `in/in-27.md` after it was staged. It focuses on semantic correctness, testability, and alignment with existing Prism contracts (`q`/denotation invariance, BSPˢ gauge symmetry). @@ -306,4 +311,4 @@ This revision fully conforms to the Glossary Contract by explicitly tagging BSP Thresholds are explicit but still need empirical validation. ### Correctness verdict -This version is **normatively compliant** with the glossary (BSPˢ, Renormˢ, Gauge, Entropyₐ). It is suitable for implementation and test development, with remaining work focused on calibration and glossary duplication cleanup. +This version is **normatively compliant** with the glossary (BSPˢ, Renormˢ, Gauge, Entropyₐ). It is suitable for implementation and test development, with remaining work focused on calibration and glossary duplication cleanup. \ No newline at end of file diff --git a/in/in-27.md b/in/in-27.md index 68f3b48..37e627e 100644 --- a/in/in-27.md +++ b/in/in-27.md @@ -1,3 +1,8 @@ +--- +doc_revision: 1 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + # in-27: The Geometric Servo (Holographic Architecture) | Metadata | Value | @@ -159,4 +164,4 @@ The `m5` milestone is gated by strict numeric assertions and glossary compliance ### 5.4 BSPˢ Gauge Invariance (Servo) * **Denotation Invariance:** `test_servo_denotation_invariance` (servo on/off yields identical denotation). -* **Stable Tie-Break:** `test_servo_sort_stable_tiebreaker` (equal masked keys preserve index order). +* **Stable Tie-Break:** `test_servo_sort_stable_tiebreaker` (equal masked keys preserve index order). \ No newline at end of file diff --git a/in/in-28.md b/in/in-28.md index 47c194a..b898725 100644 --- a/in/in-28.md +++ b/in/in-28.md @@ -1,3 +1,8 @@ +--- +doc_revision: 1 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + # JAX Servo Update Logic Explained — Reconstructed Markdown (LaTeX + Python + Agda) Glossary compliance (normative): @@ -1706,4 +1711,4 @@ The code relies on this invertibility to store the state in the mask. If the map 3. Reduction of stiffness and mass matrices | AIAA Journal \- Aerospace Research Central, accessed January 26, 2026, [https://arc.aiaa.org/doi/10.2514/3.2874](https://arc.aiaa.org/doi/10.2514/3.2874) 4. Adaptive Optics Systems VIII | (2022) | Publications \- SPIE, accessed January 26, 2026, [https://spie.org/Publications/Proceedings/Volume/12185](https://spie.org/Publications/Proceedings/Volume/12185) 5. STIM1- dependent peripheral coupling governs the contractility of vascular smooth muscle cells \- eLife, accessed January 26, 2026, [https://elifesciences.org/articles/70278.pdf](https://elifesciences.org/articles/70278.pdf) -6. Striosomes Mediate Value-Based Learning Vulnerable in Age and Huntington's Disease Model \- PMC \- PubMed Central, accessed January 26, 2026, [https://pmc.ncbi.nlm.nih.gov/articles/PMC7932131/](https://pmc.ncbi.nlm.nih.gov/articles/PMC7932131/) +6. Striosomes Mediate Value-Based Learning Vulnerable in Age and Huntington's Disease Model \- PMC \- PubMed Central, accessed January 26, 2026, [https://pmc.ncbi.nlm.nih.gov/articles/PMC7932131/](https://pmc.ncbi.nlm.nih.gov/articles/PMC7932131/) \ No newline at end of file diff --git a/in/in-29.md b/in/in-29.md new file mode 100644 index 0000000..babe026 --- /dev/null +++ b/in/in-29.md @@ -0,0 +1,344 @@ +Perfect. Given everything we’ve now locked in, the right move is a **ground-up rewrite** of `in-29.md` so that: + +* it no longer reads like an incremental clarification, +* it stands on its own as a **semantic keystone**, +* it encodes *hierarchy, BSP, Morton coding, spillway, IC collapse, and facade control* as one coherent design, +* and it is **unambiguous enough for Codex to implement mechanically**. + +Below is a **full replacement** for `in/in-29.md`. This is not a patch or addendum; it assumes the reader has access to earlier `in-*` files (especially 8–27) but does not rely on them for correctness. + +--- + +```markdown +# in-29.md — Hierarchical BSP Arenas, Spillway Semantics, and Facade-Controlled Backoff + +## Status +Normative + +## Scope +This document defines the **hierarchical execution semantics** of ConeVM, unifying: + +- Binary Space Partitioning (BSP), +- Interaction Combinators (IC), +- Contract / Sort / Reduce (CSR), +- Morton-coded arena layouts, +- Spillway-based expansion detection, +- and facade-controlled servo actuation. + +This document supersedes all prior informal interpretations of “spill”, “damage”, and “backoff”. +All future implementation and optimization work MUST preserve the invariants defined here. + +--- + +## 1. Background and Lineage + +This design is enabled by, and should be read in the context of: + +- `audit_in_versions.md` — semantic epoch mapping +- `in-8.md` — interaction combinators and locality +- `in-17.md` — lawful representations and binding without names +- CNF-2 candidate normalization +- Existing damage telemetry infrastructure + +What was previously *observational* (damage metrics) is here made *semantic*. + +--- + +## 2. Dual Meaning of BSP (Intentional) + +In ConeVM, **BSP has two meanings, intentionally unified**: + +1. **Bulk Synchronous Parallelism** + Execution proceeds in uniform, warp-safe passes (“microstrata”). + +2. **Binary Space Partitioning** + The node index space is hierarchically partitioned into nested binary arenas. + +These are not metaphors. +They are the *same structure*, viewed operationally and spatially. + +--- + +## 3. Hierarchical Arena Model + +### 3.1 Binary Arena Tree + +The node index space is partitioned as a perfect binary tree: + +- Level 0: arenas of size 1 +- Level 1: arenas of size 2 +- Level 2: arenas of size 4 +- … +- Level k: arenas of size 2^k + +Each arena is the union of exactly two child arenas. + +Arenas are **logical envelopes**, not heap objects. +They are views over contiguous index ranges. + +--- + +### 3.2 Morton Coding as Arena Arithmetic + +Nodes are indexed using **Morton (Z-order) codes**. + +Properties: + +- High-order bits identify the arena (partition) at a given level. +- Shifting right corresponds to promotion to the parent arena. +- Contiguous indices correspond to spatial and interaction locality. + +Morton coding is therefore the arithmetic realization of the BSP tree. + +--- + +## 4. Active Region and Spillway + +Each arena at level k is structurally divided into: + +- **Active region**: size 2^k + The current work frontier. + +- **Spillway region**: size 2^k + Reserved exclusively for expansive emission. + +Total arena capacity: 2^(k+1). + +This 2:1 structure is mandatory and invariant. + +--- + +## 5. Spillway Is Not Slack + +The spillway is **not** a performance buffer. + +It is a **constructive bound on worst-case state expansion** for a single microstratum pass. + +### 5.1 CNF-2 Justification + +In CNF-2 candidate generation: + +- Each active item produces at most 2 outputs. +- Baseline replacement consumes 1 slot. +- Maximum expansion is +1 per item. + +Thus, for N active items: + +``` + +E_max = N + +``` + +A spillway of size N is both necessary and sufficient. + +### 5.2 Wrapper (Depth) Emission + +Wrapper emission (e.g. `OP_SUC`) proceeds across microstrata: + +- Each microstep is 1→1. +- Expansion manifests across depth, not as a burst. + +Spillway usage therefore remains a valid witness of expansion pressure, +even when depth-driven. + +--- + +## 6. Normative Spillway Rule + +**Invariant (non-negotiable):** + +``` + +If spillway_used > 0 during a microstratum pass, +then the current arena level is no longer safe. + +``` + +There are: + +- no thresholds, +- no hysteresis, +- no tuning parameters. + +**Any** spillway usage is sufficient. + +--- + +## 7. Backoff Semantics + +“Backoff” has a precise meaning: + +> **Escalation to a strictly larger arena in the BSP tree.** + +This may involve one or more of: + +- promotion to the parent arena (bit-shift of Morton codes), +- widening of the locality envelope, +- termination of the current microstratum and macrostratum reconciliation. + +Backoff is: + +- uniform (one decision per pass), +- deterministic, +- executed only at stratum boundaries. + +--- + +## 8. Relationship to Masking and Noops + +Masking and identity/noop operators are reserved for: + +- semantic ineligibility: + - already resolved, + - not ready, + - forbidden by policy. + +Spillway usage is **not** semantic failure. + +Therefore: + +- Spillway usage MUST NOT cause noops. +- Spillway usage MUST cause backoff. + +These concerns are orthogonal. + +--- + +## 9. Telemetry as Semantic Witness + +Telemetry is elevated from observation to **proof**. + +At minimum, each microstratum pass MUST produce: + +- `spillway_used` (boolean or count). + +Optional corroborating witnesses include: + +- damage rate (cross-arena edges), +- CNF-2 churn counters. + +Servo decisions MUST be derivable solely from these witnesses. + +--- + +## 10. Facade as Hierarchical Controller + +The **facade** is the unique locus of hierarchical control. + +Responsibilities: + +- track current arena level, +- invoke a single microstratum pass at that level, +- inspect structural witnesses (spillway usage), +- decide escalation or continuation, +- select Morton resolution and swizzle policy, +- define macrostratum boundaries. + +Kernels: +- operate strictly within a single arena, +- are branchless and warp-uniform, +- emit witnesses but never decide escalation. + +The REPL is observational only. + +--- + +## 10.x Ledger as Terminal Arena (Settlement) + +The ledger is the **terminal** node of the hierarchical BSP arena model. + +### Normative meaning (“settled”) + +The ledger is where hierarchical exploration stops: + +- There is **no spillway**. +- Expansion is **forbidden**. +- Identities are **canonical** (univalent) and globally visible. +- The pre-step ledger segment is **read-only** during a cycle; interning is **append-only** relative to that base. + +This is the precise sense in which the ledger is *settled*. + +### Implementation note (not an arena buffer) + +The ledger is not implemented as “a bigger arena buffer.” +It is an append-only canonical store plus derived indexes (CQRS / event-sourced read model): +- `key_bytes -> id` +- `id -> (op, a1, a2)` (or equivalent packed form) + +This difference is intentional: settlement requires globally canonical lookup, not locality-bounded rewrite. + +### Facade role + +Promotion/backoff traverses the BSP hierarchy over arenas. +At the root (no parent arena exists), the only lawful resolution of remaining work is settlement: +the facade must commit into the ledger (canonicalization) rather than attempting further arena-local expansion. + +### Test obligations (existing) + +- (m1) `tests/test_m1_gate.py::test_ledger_full_key_equality` +- (m1) `tests/test_candidate_cycle.py::test_cycle_candidates_does_not_mutate_preexisting_rows` +- (m2) `tests/test_ledger_intern.py::test_intern_nodes_never_mutates_pre_step_segment` + +--- + +## 11. IC and BSP Reconciliation + +Interaction Combinators (IC) and BSP normalization are not competing models. + +They are: + +- the same rewrite rules, +- applied at different depths of the BSP tree. + +IC naturally occupies lower levels (tight locality). +BSP normalization naturally occurs at higher levels. + +The facade mediates between them via arena level selection. + +--- + +## 12. Consequences + +This design: + +- forbids global scans at low levels, +- guarantees bounded expansion per pass, +- enforces locality without heuristics, +- prevents warp divergence, +- aligns execution, memory hierarchy, and semantics, +- explains the prevalence of CNF-2 and 2:1 structures, +- makes “damage” and “spill” mathematically precise. + +--- + +## 13. Summary + +ConeVM execution is traversal of a binary space partition over node index space. + +- Morton coding defines the partition. +- CSR defines work resolution within a partition. +- Spillway usage detects boundary crossing. +- Backoff is escalation in the BSP tree. +- The facade is the traversal controller. + +This structure is fundamental and final. +``` + +--- + +### What you now have + +* A **clean semantic keystone document** +* No dangling heuristics +* No ambiguity between IC/BSP/CSR +* A direct mapping from theory → code → memory hierarchy +* Something Codex can follow *without interpretation* + +If you want, next we can: + +* add an `in-29` glossary delta, +* write an `implementation obligations` checklist for Codex, +* or draft `in-30.md` as the *facade execution contract* that turns this into API-level requirements. + +Just point to the next lock-in. diff --git a/in/in-3.md b/in/in-3.md index 5211c7d..0111e6a 100644 --- a/in/in-3.md +++ b/in/in-3.md @@ -1,3 +1,8 @@ +--- +doc_revision: 1 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + NOTE: OBSOLETED by in-1.md/in-2.md. This is a discarded prototype retained for history. Import jax @@ -278,4 +283,4 @@ class TelemetricREPL: 1. **`trace_cache`**: I added a dictionary in `TelemetricREPL` that maps an instruction signature `(opcode, arg1, arg2)` to its existing row index in the JAX array. 2. **`_alloc_memoized`**: This new method replaces the direct call to `_alloc_simple` (now renamed `_alloc_raw`) in the parser. It checks the cache first. If the expression exists, it returns the existing pointer; otherwise, it allocates a new row on the device and updates the cache. -3. **Telemetry Update**: The REPL output now reports Cache HITs/MISSES. If you type the same expression twice (e.g., `(suc zero)`), the second time will show `+0 new rows`, indicating the GPU memory was untouched during the parse phase. +3. **Telemetry Update**: The REPL output now reports Cache HITs/MISSES. If you type the same expression twice (e.g., `(suc zero)`), the second time will show `+0 new rows`, indicating the GPU memory was untouched during the parse phase. \ No newline at end of file diff --git a/in/in-4.md b/in/in-4.md index 310157f..9d89532 100644 --- a/in/in-4.md +++ b/in/in-4.md @@ -1,3 +1,8 @@ +--- +doc_revision: 1 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + # The Hierarchical Fluid Memory: Mitigating Shatter via Morton-Ordered BSP Arenas NOTE: REFINED by in-14.md to treat the arena/BSP path as performance-only; the ledger + CNF-2 pipeline is the semantic spine. @@ -163,4 +168,4 @@ def op_sort(manifest): | **BSP Arena** | Bandwidth saturation | Localizes memory access patterns; aligns data with hardware cache hierarchy. | | **Hierarchical Sort** | Scalability | Replaces global sort with local sorts + merge, enabling massive parallelism. | -This revised architecture transforms the interaction net evaluator into a **Spatially Aware Fluid Machine**, where data flows through memory not just by priority, but by geometry, ensuring optimal utilization of modern accelerator hardware. +This revised architecture transforms the interaction net evaluator into a **Spatially Aware Fluid Machine**, where data flows through memory not just by priority, but by geometry, ensuring optimal utilization of modern accelerator hardware. \ No newline at end of file diff --git a/in/in-5.md b/in/in-5.md index 6023a42..67867ba 100644 --- a/in/in-5.md +++ b/in/in-5.md @@ -1,3 +1,8 @@ +--- +doc_revision: 1 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + # 2:1 Alternating BSP Layout for Hierarchical Graph Locality NOTE: REFINED by in-14.md; hierarchical arenas are a performance-only layer (M5 optional). @@ -152,4 +157,4 @@ In the **2:1 BSP Arena**: 1. and are likely in the same or adjacent BSP blocks (spatial locality). 2. When they produce children, the allocator prefers the `FREE` slots *within that same BSP block*. 3. The 2-bit sort preserves the relative order of blocks (stable sort). -4. Therefore, the children remain clustered in memory. When they become `HOT` in the next cycle, they are fetched as a contiguous cache line. +4. Therefore, the children remain clustered in memory. When they become `HOT` in the next cycle, they are fetched as a contiguous cache line. \ No newline at end of file diff --git a/in/in-6.md b/in/in-6.md index ca283b7..aea7298 100644 --- a/in/in-6.md +++ b/in/in-6.md @@ -1,3 +1,8 @@ +--- +doc_revision: 1 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + # Evaluation of the 2:1 Alternating BSP Architecture in JAX NOTE: REFINED by in-14.md to treat BSP/morton as a performance-only path; ledger/CNF-2 is the semantic spine. @@ -93,4 +98,4 @@ The **2:1 Alternating BSP Layout** is a sophisticated optimization that correctl 1. **Adopt 2-Bit Rank:** The simplification to 4 buckets (HOT/WARM/COLD/FREE) enables scheduling, which is critical. 2. **Use Pallas for Swizzling:** Do not attempt to implement the Morton encoding in pure `jax.numpy`; the overhead will negate the gains. -3. **Hierarchical Bitmasks:** Manage the "Free Space" using a hierarchical bitmask (like a quadtree of `1`s and `0`s) to allow rapid finding of local free slots during the "Shatter" phase. +3. **Hierarchical Bitmasks:** Manage the "Free Space" using a hierarchical bitmask (like a quadtree of `1`s and `0`s) to allow rapid finding of local free slots during the "Shatter" phase. \ No newline at end of file diff --git a/in/in-7.md b/in/in-7.md index fdb1e58..5d7fa88 100644 --- a/in/in-7.md +++ b/in/in-7.md @@ -1,3 +1,8 @@ +--- +doc_revision: 1 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + NOTE: REFINED by in-14.md. The arena loop is performance-only; pointer 0 remains a reserved NULL sentinel. This is a radical redesign of the `PrismVM`. We move from a static heap to a **Fluid Arena**. @@ -374,4 +379,4 @@ if __name__ == "__main__": 1. **Locality:** By sorting on `Rank`, we ensure that all `ADD` operations (Rank 0) are contiguous. This implies that `op_interact` executes as a single, dense vector operation, maximizing SIMD utilization. 2. **Implicit GC:** The `op_sort_and_swizzle` creates a strict separation between Active and Free nodes. "Garbage" (unreferenced nodes) naturally falls to the bottom (Rank 3) and is overwritten by the `count` pointer in the next `Interact` phase. We effectively get **Stop-the-World Copying GC** for free every cycle. -3. **Shatter Mitigation:** While we implemented a linear allocation in `op_interact` for simplicity, the *next* `Sort` phase will re-linearize these scattered writes. If we augmented the Rank key with the **2:1 Morton Code** of the parent's address, the Sort would physically place children next to parents, healing the shatter effect completely. +3. **Shatter Mitigation:** While we implemented a linear allocation in `op_interact` for simplicity, the *next* `Sort` phase will re-linearize these scattered writes. If we augmented the Rank key with the **2:1 Morton Code** of the parent's address, the Sort would physically place children next to parents, healing the shatter effect completely. \ No newline at end of file diff --git a/in/in-8.md b/in/in-8.md index ec64236..0fe8eb7 100644 --- a/in/in-8.md +++ b/in/in-8.md @@ -1,3 +1,8 @@ +--- +doc_revision: 1 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + # **Implementing Branchless Interaction Combinator Rewrite Rules on GPUs: A Tensor-Theoretic Approach to Optimal Graph Reduction** NOTE: DEFERRED. This is a future roadmap track (see IMPLEMENTATION_PLAN.md "Roadmap Extension: in-8 Pivot"). @@ -429,4 +434,4 @@ References: 19. Linear logic \- Wikipedia, accessed January 20, 2026, [https://en.wikipedia.org/wiki/Linear\_logic](https://en.wikipedia.org/wiki/Linear_logic) 20. Geometry of Interaction explained \- RIMS, Kyoto University, accessed January 20, 2026, [https://www.kurims.kyoto-u.ac.jp/\~hassei/algi-13/kokyuroku/19\_shirahata.pdf](https://www.kurims.kyoto-u.ac.jp/~hassei/algi-13/kokyuroku/19_shirahata.pdf) 21. Automatic vectorization \- JAX documentation, accessed January 20, 2026, [https://docs.jax.dev/en/latest/automatic-vectorization.html](https://docs.jax.dev/en/latest/automatic-vectorization.html) -22. OBSERVATIONAL EQUIVALENCE AND FULL ABSTRACTION IN ..., accessed January 20, 2026, [https://lmcs.episciences.org/1150/pdf](https://lmcs.episciences.org/1150/pdf) +22. OBSERVATIONAL EQUIVALENCE AND FULL ABSTRACTION IN ..., accessed January 20, 2026, [https://lmcs.episciences.org/1150/pdf](https://lmcs.episciences.org/1150/pdf) \ No newline at end of file diff --git a/in/in-9.md b/in/in-9.md index 7a78e48..6b4205c 100644 --- a/in/in-9.md +++ b/in/in-9.md @@ -1,3 +1,8 @@ +--- +doc_revision: 1 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + NOTE: CONSOLIDATED by in-13.md and in-14.md. This is the source analysis for CNF-2 pipeline semantics. Below is a **formal, reviewer-grade comparison** between: @@ -1039,4 +1044,4 @@ A reviewer-level “difference statement” you can put right after the lemma: * canonicalization/stratum append is isolated (CAN) * (A1) is guaranteed by construction (no within-stratum refs) -So the refinement lemma is exactly the formal bridge: it explains how your existing implementation can be *systematically normalized* into the new model without changing denotation—only staging and invariants. +So the refinement lemma is exactly the formal bridge: it explains how your existing implementation can be *systematically normalized* into the new model without changing denotation—only staging and invariants. \ No newline at end of file diff --git a/pytest.m1.ini b/pytest.baseline.ini similarity index 84% rename from pytest.m1.ini rename to pytest.baseline.ini index 1753700..243f44b 100644 --- a/pytest.m1.ini +++ b/pytest.baseline.ini @@ -1,4 +1,5 @@ [pytest] +# Baseline suite: m1 tests + unmarked, running under the current milestone. pythonpath = src addopts = --milestone-band=m1 --include-unmarked testpaths = tests diff --git a/scripts/audit_in_versions.py b/scripts/audit_in_versions.py index 69fb2df..36959fd 100644 --- a/scripts/audit_in_versions.py +++ b/scripts/audit_in_versions.py @@ -12,6 +12,15 @@ TOKEN_PATTERN = analyze.TOKEN_PATTERN +def _strip_front_matter(text): + if not text.startswith("---\n"): + return text + parts = text.split("\n---\n", 1) + if len(parts) == 2: + return parts[1].lstrip("\n") + return text + + def _tokenize(text, stopwords, pattern=TOKEN_PATTERN): token_re = re.compile(pattern) tokens = [t.lower() for t in token_re.findall(text)] @@ -23,7 +32,7 @@ def _bigrams(tokens): def _stats_for(path, stopwords): - text = Path(path).read_text() + text = _strip_front_matter(Path(path).read_text()) tokens = _tokenize(text, stopwords) token_counts = Counter(tokens) token_set = set(token_counts) @@ -141,6 +150,13 @@ def _build_report(docs_dir, compare_paths, stopwords): glossary = Path(docs_dir) / "glossary.md" has_glossary = glossary.exists() title = "# Audit: in/in-*.md + in/glossary.md" if has_glossary else "# Audit: in/in-*.md" + front_matter = [ + "---", + "doc_revision: 1", + 'reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc."', + "---", + "", + ] header = [ title, "", @@ -163,7 +179,7 @@ def _build_report(docs_dir, compare_paths, stopwords): prior = doc if has_glossary: sections.append(_section_for(glossary, None, compare_paths, stopwords)) - return "\n".join(header + sections) + "\n" + return "\n".join(front_matter + header + sections) + "\n" def main(): diff --git a/scripts/dataflow_grammar_audit.py b/scripts/dataflow_grammar_audit.py new file mode 100644 index 0000000..2431031 --- /dev/null +++ b/scripts/dataflow_grammar_audit.py @@ -0,0 +1,1119 @@ +#!/usr/bin/env python3 +"""Infer forwarding-based parameter bundles and propagate them across calls. + +This script performs a two-stage analysis: + 1) Local grouping: within a function, parameters used *only* as direct + call arguments are grouped by identical forwarding signatures. + 2) Propagation: if a function f calls g, and g has local bundles, then + f's parameters passed into g's bundled positions are linked as a + candidate bundle. This is iterated to a fixed point. + +The goal is to surface "dataflow grammar" candidates for config dataclasses. + +It can also emit a DOT graph (see --dot) so downstream tooling can render +bundle candidates as a dependency graph. +""" +from __future__ import annotations + +import argparse +import ast +import os +from collections import defaultdict, deque +from dataclasses import dataclass +from pathlib import Path +from typing import Iterable, Iterator +import re + +try: # Prefer the upgraded analyzer when available (e.g. via ../gabion). + from gabion.analysis.dataflow_audit import main as _gabion_main +except Exception: # pragma: no cover - optional dependency. + _gabion_main = None + + +@dataclass +class ParamUse: + direct_forward: set[tuple[str, str]] + non_forward: bool + + +@dataclass(frozen=True) +class CallArgs: + callee: str + pos_map: dict[str, str] + kw_map: dict[str, str] + const_pos: dict[str, str] + const_kw: dict[str, str] + non_const_pos: set[str] + non_const_kw: set[str] + is_test: bool + + +class ParentAnnotator(ast.NodeVisitor): + def __init__(self) -> None: + self.parents: dict[ast.AST, ast.AST] = {} + + def generic_visit(self, node: ast.AST) -> None: + for child in ast.iter_child_nodes(node): + self.parents[child] = node + self.visit(child) + + +def _call_context(node: ast.AST, parents: dict[ast.AST, ast.AST]) -> tuple[ast.Call | None, bool]: + child = node + parent = parents.get(child) + while parent is not None: + if isinstance(parent, ast.Call): + if child in parent.args: + return parent, True + for kw in parent.keywords: + if child is kw or child is kw.value: + return parent, True + return parent, False + child = parent + parent = parents.get(child) + return None, False + + +def _callee_name(call: ast.Call) -> str: + try: + return ast.unparse(call.func) + except Exception: + return "" + + +def _iter_paths(paths: Iterable[str]) -> list[Path]: + out: list[Path] = [] + for p in paths: + path = Path(p) + if path.is_dir(): + out.extend(sorted(path.rglob("*.py"))) + else: + out.append(path) + return out + + +def _collect_functions(tree: ast.AST): + funcs = [] + for node in ast.walk(tree): + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): + funcs.append(node) + return funcs + + +def _param_names(fn: ast.FunctionDef | ast.AsyncFunctionDef) -> list[str]: + args = ( + fn.args.posonlyargs + fn.args.args + fn.args.kwonlyargs + ) + names = [a.arg for a in args] + if fn.args.vararg: + names.append(fn.args.vararg.arg) + if fn.args.kwarg: + names.append(fn.args.kwarg.arg) + if names and names[0] in {"self", "cls"}: + names = names[1:] + return names + + +def _param_annotations(fn: ast.FunctionDef | ast.AsyncFunctionDef) -> dict[str, str | None]: + args = fn.args.posonlyargs + fn.args.args + fn.args.kwonlyargs + names = [a.arg for a in args] + annots: dict[str, str | None] = {} + for name, arg in zip(names, args): + if arg.annotation is None: + annots[name] = None + else: + try: + annots[name] = ast.unparse(arg.annotation) + except Exception: + annots[name] = None + if fn.args.vararg: + annots[fn.args.vararg.arg] = None + if fn.args.kwarg: + annots[fn.args.kwarg.arg] = None + if names and names[0] in {"self", "cls"}: + annots.pop(names[0], None) + return annots + + +def _const_repr(node: ast.AST) -> str | None: + if isinstance(node, ast.Constant): + return repr(node.value) + if isinstance(node, ast.UnaryOp) and isinstance( + node.op, (ast.USub, ast.UAdd) + ) and isinstance(node.operand, ast.Constant): + try: + return ast.unparse(node) + except Exception: + return None + if isinstance(node, ast.Attribute): + try: + return ast.unparse(node) + except Exception: + return None + return None + + +def _is_test_path(path: Path) -> bool: + if "tests" in path.parts: + return True + return path.name.startswith("test_") + + +def _analyze_function(fn, parents, *, is_test: bool): + params = _param_names(fn) + use_map = {p: ParamUse(set(), False) for p in params} + call_args: list[CallArgs] = [] + + class UseVisitor(ast.NodeVisitor): + def visit_Call(self, node: ast.Call) -> None: + callee = _callee_name(node) + pos_map = {} + kw_map = {} + const_pos: dict[str, str] = {} + const_kw: dict[str, str] = {} + non_const_pos: set[str] = set() + non_const_kw: set[str] = set() + for idx, arg in enumerate(node.args): + const = _const_repr(arg) + if const is not None: + const_pos[str(idx)] = const + continue + if isinstance(arg, ast.Name) and arg.id in use_map: + pos_map[str(idx)] = arg.id + else: + non_const_pos.add(str(idx)) + for kw in node.keywords: + if kw.arg is None: + continue + const = _const_repr(kw.value) + if const is not None: + const_kw[kw.arg] = const + continue + if isinstance(kw.value, ast.Name) and kw.value.id in use_map: + kw_map[kw.arg] = kw.value.id + else: + non_const_kw.add(kw.arg) + call_args.append( + CallArgs( + callee=callee, + pos_map=pos_map, + kw_map=kw_map, + const_pos=const_pos, + const_kw=const_kw, + non_const_pos=non_const_pos, + non_const_kw=non_const_kw, + is_test=is_test, + ) + ) + self.generic_visit(node) + + def visit_Name(self, node: ast.Name) -> None: + if not isinstance(node.ctx, ast.Load): + return + if node.id not in use_map: + return + call, direct = _call_context(node, parents) + if call is None or not direct: + use_map[node.id].non_forward = True + return + callee = _callee_name(call) + # Determine arg slot. + slot = None + for idx, arg in enumerate(call.args): + if arg is node: + slot = f"arg[{idx}]" + break + if slot is None: + for kw in call.keywords: + if kw.value is node and kw.arg is not None: + slot = f"kw[{kw.arg}]" + break + if slot is None: + slot = "arg[?]" + use_map[node.id].direct_forward.add((callee, slot)) + + UseVisitor().visit(fn) + return use_map, call_args + + +def _group_by_signature(use_map: dict[str, ParamUse]) -> list[set[str]]: + sig_map: dict[tuple[tuple[str, str], ...], list[str]] = defaultdict(list) + for name, info in use_map.items(): + if info.non_forward: + continue + sig = tuple(sorted(info.direct_forward)) + sig_map[sig].append(name) + groups = [set(names) for names in sig_map.values() if len(names) > 1] + return groups + + +def _union_groups(groups: list[set[str]]) -> list[set[str]]: + changed = True + while changed: + changed = False + out = [] + while groups: + base = groups.pop() + merged = True + while merged: + merged = False + for i, other in enumerate(groups): + if base & other: + base |= other + groups.pop(i) + merged = True + changed = True + break + out.append(base) + groups = out + return groups + + +def _propagate_groups( + fn_name: str, + fn_params: list[str], + call_args: list[CallArgs], + callee_groups: dict[str, list[set[str]]], + callee_param_orders: dict[str, list[str]], +) -> list[set[str]]: + groups: list[set[str]] = [] + for call in call_args: + if call.callee not in callee_groups: + continue + callee_params = callee_param_orders[call.callee] + # Build mapping from callee param to caller param. + callee_to_caller: dict[str, str] = {} + for idx, pname in enumerate(callee_params): + key = str(idx) + if key in call.pos_map: + callee_to_caller[pname] = call.pos_map[key] + for kw, caller_name in call.kw_map.items(): + callee_to_caller[kw] = caller_name + for group in callee_groups[call.callee]: + mapped = {callee_to_caller.get(p) for p in group} + mapped.discard(None) + if len(mapped) > 1: + groups.append(set(mapped)) + return groups + + +def analyze_file(path: Path, recursive: bool = True) -> dict[str, list[set[str]]]: + tree = ast.parse(path.read_text()) + parent = ParentAnnotator() + parent.visit(tree) + parents = parent.parents + is_test = _is_test_path(path) + + funcs = _collect_functions(tree) + fn_param_orders = {f.name: _param_names(f) for f in funcs} + fn_use = {} + fn_calls = {} + for f in funcs: + use_map, call_args = _analyze_function(f, parents, is_test=is_test) + fn_use[f.name] = use_map + fn_calls[f.name] = call_args + + groups_by_fn = {fn: _group_by_signature(use_map) for fn, use_map in fn_use.items()} + + if not recursive: + return groups_by_fn + + changed = True + while changed: + changed = False + for fn in fn_use: + propagated = _propagate_groups( + fn, + fn_param_orders[fn], + fn_calls[fn], + groups_by_fn, + fn_param_orders, + ) + if not propagated: + continue + combined = _union_groups(groups_by_fn.get(fn, []) + propagated) + if combined != groups_by_fn.get(fn, []): + groups_by_fn[fn] = combined + changed = True + return groups_by_fn + + +def _callee_key(name: str) -> str: + if not name: + return name + return name.split(".")[-1] + + +def _is_broad_type(annot: str | None) -> bool: + if annot is None: + return True + base = annot.replace("typing.", "") + return base in {"Any", "object"} + + +@dataclass +class FunctionInfo: + name: str + qual: str + path: Path + params: list[str] + annots: dict[str, str | None] + calls: list[CallArgs] + + +def _module_name(path: Path) -> str: + rel = path.with_suffix("") + return ".".join(rel.parts) + + +def _build_function_index(paths: list[Path]) -> tuple[dict[str, list[FunctionInfo]], dict[str, FunctionInfo]]: + by_name: dict[str, list[FunctionInfo]] = defaultdict(list) + by_qual: dict[str, FunctionInfo] = {} + for path in paths: + try: + tree = ast.parse(path.read_text()) + except Exception: + continue + funcs = _collect_functions(tree) + if not funcs: + continue + parents = ParentAnnotator() + parents.visit(tree) + parent_map = parents.parents + module = _module_name(path) + for fn in funcs: + _, call_args = _analyze_function( + fn, parent_map, is_test=_is_test_path(path) + ) + info = FunctionInfo( + name=fn.name, + qual=f"{module}.{fn.name}", + path=path, + params=_param_names(fn), + annots=_param_annotations(fn), + calls=call_args, + ) + by_name[fn.name].append(info) + by_qual[info.qual] = info + return by_name, by_qual + + +def _resolve_callee( + callee_name: str, + caller: FunctionInfo, + by_name: dict[str, list[FunctionInfo]], + by_qual: dict[str, FunctionInfo], +) -> FunctionInfo | None: + if not callee_name: + return None + # Exact qualified name match. + if callee_name in by_qual: + return by_qual[callee_name] + # If call uses module.func, try match by module suffix. + if "." in callee_name: + parts = callee_name.split(".") + func = parts[-1] + module = ".".join(parts[:-1]) + candidates = [ + info + for info in by_name.get(func, []) + if info.qual.endswith(f"{module}.{func}") + ] + if len(candidates) == 1: + return candidates[0] + # If caller's module matches a candidate, prefer it. + same_module = [ + info + for info in candidates + if info.path == caller.path + ] + if len(same_module) == 1: + return same_module[0] + return None + # Fallback: unique function name across repo. + candidates = by_name.get(callee_name, []) + if len(candidates) == 1: + return candidates[0] + # Prefer same-module definition when ambiguous. + same_module = [info for info in candidates if info.path == caller.path] + if len(same_module) == 1: + return same_module[0] + # If callee is self.foo/cls.foo, prefer same-module foo. + if callee_name.startswith(("self.", "cls.")): + func = callee_name.split(".")[-1] + same_module = [ + info for info in by_name.get(func, []) if info.path == caller.path + ] + if len(same_module) == 1: + return same_module[0] + return None + + +def analyze_type_flow_repo(paths: list[Path]) -> tuple[list[str], list[str]]: + """Repo-wide fixed-point pass for downstream type tightening.""" + by_name, by_qual = _build_function_index(paths) + inferred: dict[str, dict[str, str | None]] = {} + for infos in by_name.values(): + for info in infos: + inferred[info.qual] = dict(info.annots) + + def _get_annot(info: FunctionInfo, param: str) -> str | None: + return inferred.get(info.qual, {}).get(param) + + suggestions: set[str] = set() + ambiguities: set[str] = set() + changed = True + while changed: + changed = False + for infos in by_name.values(): + for info in infos: + downstream: dict[str, set[str]] = defaultdict(set) + for call in info.calls: + callee = _resolve_callee(call.callee, info, by_name, by_qual) + if callee is None: + continue + callee_params = callee.params + for pos_idx, param in call.pos_map.items(): + try: + idx = int(pos_idx) + except ValueError: + continue + if idx >= len(callee_params): + continue + callee_param = callee_params[idx] + annot = _get_annot(callee, callee_param) + if annot: + downstream[param].add(annot) + for kw_name, param in call.kw_map.items(): + annot = _get_annot(callee, kw_name) + if annot: + downstream[param].add(annot) + for param, annots in downstream.items(): + if not annots: + continue + if len(annots) > 1: + ambiguities.add( + f"{info.path.name}:{info.name}.{param} downstream types conflict: {sorted(annots)}" + ) + continue + downstream_annot = next(iter(annots)) + current = _get_annot(info, param) + if _is_broad_type(current) and downstream_annot: + if inferred[info.qual].get(param) != downstream_annot: + inferred[info.qual][param] = downstream_annot + changed = True + suggestions.add( + f"{info.path.name}:{info.name}.{param} can tighten to {downstream_annot}" + ) + return sorted(suggestions), sorted(ambiguities) + + +def analyze_constant_flow_repo(paths: list[Path]) -> list[str]: + """Detect parameters that only receive a single constant value (non-test).""" + by_name, by_qual = _build_function_index(paths) + const_values: dict[tuple[str, str], set[str]] = defaultdict(set) + non_const: dict[tuple[str, str], bool] = defaultdict(bool) + call_counts: dict[tuple[str, str], int] = defaultdict(int) + + for infos in by_name.values(): + for info in infos: + for call in info.calls: + if call.is_test: + continue + callee = _resolve_callee(call.callee, info, by_name, by_qual) + if callee is None: + continue + callee_params = callee.params + + for idx_str, value in call.const_pos.items(): + try: + idx = int(idx_str) + except ValueError: + continue + if idx >= len(callee_params): + continue + key = (callee.qual, callee_params[idx]) + const_values[key].add(value) + call_counts[key] += 1 + for idx_str in call.pos_map: + try: + idx = int(idx_str) + except ValueError: + continue + if idx >= len(callee_params): + continue + key = (callee.qual, callee_params[idx]) + non_const[key] = True + call_counts[key] += 1 + for idx_str in call.non_const_pos: + try: + idx = int(idx_str) + except ValueError: + continue + if idx >= len(callee_params): + continue + key = (callee.qual, callee_params[idx]) + non_const[key] = True + call_counts[key] += 1 + + for kw, value in call.const_kw.items(): + if kw not in callee_params: + continue + key = (callee.qual, kw) + const_values[key].add(value) + call_counts[key] += 1 + for kw in call.kw_map: + if kw not in callee_params: + continue + key = (callee.qual, kw) + non_const[key] = True + call_counts[key] += 1 + for kw in call.non_const_kw: + if kw not in callee_params: + continue + key = (callee.qual, kw) + non_const[key] = True + call_counts[key] += 1 + + smells: list[str] = [] + for key, values in const_values.items(): + if non_const.get(key): + continue + if not values: + continue + if len(values) == 1: + qual, param = key + info = by_qual.get(qual) + path_name = info.path.name if info is not None else qual + count = call_counts.get(key, 0) + smells.append( + f"{path_name}:{qual.split('.')[-1]}.{param} only observed constant {next(iter(values))} across {count} non-test call(s)" + ) + return sorted(smells) + + +def _iter_config_fields(path: Path) -> dict[str, set[str]]: + """Best-effort extraction of @dataclass config bundles (fields ending with _fn).""" + try: + tree = ast.parse(path.read_text()) + except Exception: + return {} + bundles: dict[str, set[str]] = {} + + def _is_dataclass(dec: ast.AST) -> bool: + if isinstance(dec, ast.Name): + return dec.id == "dataclass" + if isinstance(dec, ast.Attribute): + return dec.attr == "dataclass" + if isinstance(dec, ast.Call): + func = dec.func + if isinstance(func, ast.Name): + return func.id == "dataclass" + if isinstance(func, ast.Attribute): + return func.attr == "dataclass" + try: + return "dataclass" in ast.unparse(dec) + except Exception: + return False + try: + return "dataclass" in ast.unparse(dec) + except Exception: + return False + + for node in ast.walk(tree): + if not isinstance(node, ast.ClassDef): + continue + is_dataclass = any(_is_dataclass(dec) for dec in node.decorator_list) + if not is_dataclass and not node.name.endswith("Config"): + continue + fields: set[str] = set() + for stmt in node.body: + if isinstance(stmt, ast.AnnAssign) and isinstance(stmt.target, ast.Name): + name = stmt.target.id + if name.endswith("_fn"): + fields.add(name) + elif isinstance(stmt, ast.Assign): + for target in stmt.targets: + if isinstance(target, ast.Name) and target.id.endswith("_fn"): + fields.add(target.id) + if fields: + bundles[node.name] = fields + return bundles + + +_BUNDLE_MARKER = re.compile(r"dataflow-bundle:\s*(.*)") + + +def _iter_documented_bundles(path: Path) -> set[tuple[str, ...]]: + """Return bundles documented via '# dataflow-bundle: a, b' markers.""" + bundles: set[tuple[str, ...]] = set() + try: + text = path.read_text() + except Exception: + return bundles + for line in text.splitlines(): + match = _BUNDLE_MARKER.search(line) + if not match: + continue + payload = match.group(1) + if not payload: + continue + parts = [p.strip() for p in re.split(r"[,\s]+", payload) if p.strip()] + if len(parts) < 2: + continue + bundles.add(tuple(sorted(parts))) + return bundles + + +def _iter_dataclass_call_bundles(path: Path) -> set[tuple[str, ...]]: + """Return bundles promoted via local @dataclass constructor calls.""" + bundles: set[tuple[str, ...]] = set() + try: + tree = ast.parse(path.read_text()) + except Exception: + return bundles + dataclass_names: set[str] = set() + for node in ast.walk(tree): + if not isinstance(node, ast.ClassDef): + continue + decorators = { + ast.unparse(dec) if hasattr(ast, "unparse") else "" + for dec in node.decorator_list + } + if any("dataclass" in dec for dec in decorators): + dataclass_names.add(node.name) + if not dataclass_names: + return bundles + + def _callee_name(call: ast.Call) -> str | None: + if isinstance(call.func, ast.Name): + return call.func.id + if isinstance(call.func, ast.Attribute): + return call.func.attr + return None + + for node in ast.walk(tree): + if not isinstance(node, ast.Call): + continue + callee = _callee_name(node) + if callee not in dataclass_names: + continue + names: list[str] = [] + ok = True + for arg in node.args: + if isinstance(arg, ast.Name): + names.append(arg.id) + else: + ok = False + break + if not ok: + continue + for kw in node.keywords: + if kw.arg is None: + ok = False + break + if isinstance(kw.value, ast.Name): + names.append(kw.value.id) + else: + ok = False + break + if not ok or len(names) < 2: + continue + bundles.add(tuple(sorted(names))) + return bundles + + +def _emit_dot(groups_by_path: dict[Path, dict[str, list[set[str]]]]) -> str: + lines = [ + "digraph dataflow_grammar {", + " rankdir=LR;", + " node [fontsize=10];", + ] + for path, groups in groups_by_path.items(): + file_id = str(path).replace("/", "_").replace(".", "_") + lines.append(f" subgraph cluster_{file_id} {{") + lines.append(f" label=\"{path}\";") + for fn, bundles in groups.items(): + if not bundles: + continue + fn_id = f"fn_{file_id}_{fn}" + lines.append(f" {fn_id} [shape=box,label=\"{fn}\"];") + for idx, bundle in enumerate(bundles): + bundle_id = f"b_{file_id}_{fn}_{idx}" + label = ", ".join(sorted(bundle)) + lines.append( + f" {bundle_id} [shape=ellipse,label=\"{label}\"];" + ) + lines.append(f" {fn_id} -> {bundle_id};") + lines.append(" }") + lines.append("}") + return "\n".join(lines) + + +def _component_graph(groups_by_path: dict[Path, dict[str, list[set[str]]]]): + nodes: dict[str, dict[str, str]] = {} + adj: dict[str, set[str]] = defaultdict(set) + bundle_map: dict[str, set[str]] = {} + for path, groups in groups_by_path.items(): + file_id = str(path) + for fn, bundles in groups.items(): + if not bundles: + continue + fn_id = f"fn::{file_id}::{fn}" + nodes[fn_id] = {"kind": "fn", "label": f"{path.name}:{fn}"} + for idx, bundle in enumerate(bundles): + bundle_id = f"b::{file_id}::{fn}::{idx}" + nodes[bundle_id] = { + "kind": "bundle", + "label": ", ".join(sorted(bundle)), + } + bundle_map[bundle_id] = bundle + adj[fn_id].add(bundle_id) + adj[bundle_id].add(fn_id) + return nodes, adj, bundle_map + + +def _connected_components(nodes: dict[str, dict[str, str]], adj: dict[str, set[str]]) -> list[list[str]]: + seen: set[str] = set() + comps: list[list[str]] = [] + for node in nodes: + if node in seen: + continue + q: deque[str] = deque([node]) + seen.add(node) + comp: list[str] = [] + while q: + curr = q.popleft() + comp.append(curr) + for nxt in adj.get(curr, ()): + if nxt not in seen: + seen.add(nxt) + q.append(nxt) + comps.append(sorted(comp)) + return comps + + +def _render_mermaid_component( + nodes: dict[str, dict[str, str]], + bundle_map: dict[str, set[str]], + adj: dict[str, set[str]], + component: list[str], + config_bundles_by_path: dict[Path, dict[str, set[str]]], + documented_bundles_by_path: dict[Path, set[tuple[str, ...]]], +) -> tuple[str, str]: + lines = ["```mermaid", "flowchart LR"] + fn_nodes = [n for n in component if nodes[n]["kind"] == "fn"] + bundle_nodes = [n for n in component if nodes[n]["kind"] == "bundle"] + for n in fn_nodes: + label = nodes[n]["label"].replace('"', "'") + lines.append(f' {abs(hash(n))}["{label}"]') + for n in bundle_nodes: + label = nodes[n]["label"].replace('"', "'") + lines.append(f' {abs(hash(n))}(({label}))') + for n in component: + for nxt in adj.get(n, ()): + if nxt in component and nodes[n]["kind"] == "fn": + lines.append(f" {abs(hash(n))} --> {abs(hash(nxt))}") + lines.append(" classDef fn fill:#cfe8ff,stroke:#2b6cb0,stroke-width:1px;") + lines.append(" classDef bundle fill:#ffe9c6,stroke:#c05621,stroke-width:1px;") + if fn_nodes: + lines.append( + " class " + + ",".join(str(abs(hash(n))) for n in fn_nodes) + + " fn;" + ) + if bundle_nodes: + lines.append( + " class " + + ",".join(str(abs(hash(n))) for n in bundle_nodes) + + " bundle;" + ) + lines.append("```") + + observed = [bundle_map[n] for n in bundle_nodes if n in bundle_map] + bundle_counts: dict[tuple[str, ...], int] = defaultdict(int) + for bundle in observed: + bundle_counts[tuple(sorted(bundle))] += 1 + component_paths: set[Path] = set() + for n in fn_nodes: + parts = n.split("::", 2) + if len(parts) == 3: + component_paths.add(Path(parts[1])) + declared = set() + documented = set() + for path in component_paths: + config_path = path.parent / "config.py" + bundles = config_bundles_by_path.get(config_path) + if bundles: + for fields in bundles.values(): + declared.add(tuple(sorted(fields))) + documented |= documented_bundles_by_path.get(path, set()) + observed_norm = {tuple(sorted(b)) for b in observed} + observed_only = sorted(observed_norm - declared) if declared else sorted(observed_norm) + declared_only = sorted(declared - observed_norm) + documented_only = sorted(observed_norm & documented) + def _tier(bundle: tuple[str, ...]) -> str: + count = bundle_counts.get(bundle, 1) + if declared: + return "tier-1" + if count > 1: + return "tier-2" + return "tier-3" + summary_lines = [ + f"Functions: {len(fn_nodes)}", + f"Observed bundles: {len(observed_norm)}", + ] + if not declared: + summary_lines.append("Declared Config bundles: none found for this component.") + if observed_only: + summary_lines.append("Observed-only bundles (not declared in Configs):") + for bundle in observed_only: + tier = _tier(bundle) + documented_flag = "documented" if bundle in documented else "undocumented" + summary_lines.append( + f" - {', '.join(bundle)} ({tier}, {documented_flag})" + ) + if documented_only: + summary_lines.append( + "Documented bundles (dataflow-bundle markers or local dataclass calls):" + ) + summary_lines.extend(f" - {', '.join(bundle)}" for bundle in documented_only) + if declared_only: + summary_lines.append("Declared Config bundles not observed in this component:") + summary_lines.extend(f" - {', '.join(bundle)}" for bundle in declared_only) + summary = "\n".join(summary_lines) + return "\n".join(lines), summary + + +def _emit_report( + groups_by_path: dict[Path, dict[str, list[set[str]]]], + max_components: int, + *, + type_suggestions: list[str] | None = None, + type_ambiguities: list[str] | None = None, + constant_smells: list[str] | None = None, +) -> tuple[str, list[str]]: + nodes, adj, bundle_map = _component_graph(groups_by_path) + components = _connected_components(nodes, adj) + if groups_by_path: + common = os.path.commonpath([str(p) for p in groups_by_path]) + root = Path(common) + else: + root = Path(".") + config_bundles_by_path = {} + documented_bundles_by_path = {} + for path in sorted(root.rglob("config.py")): + config_bundles_by_path[path] = _iter_config_fields(path) + protocols = root / "prism_vm_core" / "protocols.py" + if protocols.exists(): + config_bundles_by_path[protocols] = _iter_config_fields(protocols) + for path in sorted(root.rglob("*.py")): + documented = _iter_documented_bundles(path) + promoted = _iter_dataclass_call_bundles(path) + documented_bundles_by_path[path] = documented | promoted + lines = [ + "", + "Dataflow grammar audit (observed forwarding bundles).", + "", + ] + if not components: + return "\n".join(lines + ["No bundle components detected."]), [] + if len(components) > max_components: + lines.append( + f"Showing top {max_components} components of {len(components)}." + ) + violations: list[str] = [] + for idx, comp in enumerate(components[:max_components], start=1): + lines.append(f"### Component {idx}") + mermaid, summary = _render_mermaid_component( + nodes, + bundle_map, + adj, + comp, + config_bundles_by_path, + documented_bundles_by_path, + ) + lines.append(mermaid) + lines.append("") + lines.append("Summary:") + lines.append("```") + lines.append(summary) + lines.append("```") + lines.append("") + for line in summary.splitlines(): + if "(tier-3, undocumented)" in line: + violations.append(line.strip()) + if "(tier-1," in line or "(tier-2," in line: + if "undocumented" in line: + violations.append(line.strip()) + if violations: + lines.append("Violations:") + lines.append("```") + lines.extend(violations) + lines.append("```") + if type_suggestions or type_ambiguities: + lines.append("Type-flow audit:") + if type_suggestions or type_ambiguities: + lines.append(_render_type_mermaid(type_suggestions or [], type_ambiguities or [])) + if type_suggestions: + lines.append("Type tightening candidates:") + lines.append("```") + lines.extend(type_suggestions) + lines.append("```") + if type_ambiguities: + lines.append("Type ambiguities (conflicting downstream expectations):") + lines.append("```") + lines.extend(type_ambiguities) + lines.append("```") + if constant_smells: + lines.append("Constant-propagation smells (non-test call sites):") + lines.append("```") + lines.extend(constant_smells) + lines.append("```") + return "\n".join(lines), violations + + +def _render_type_mermaid( + suggestions: list[str], + ambiguities: list[str], +) -> str: + lines = ["```mermaid", "flowchart LR"] + node_id = 0 + def _node(label: str) -> str: + nonlocal node_id + node_id += 1 + node = f"type_{node_id}" + safe = label.replace('"', "'") + lines.append(f' {node}["{safe}"]') + return node + + for entry in suggestions: + # Format: file:func.param can tighten to Type + if " can tighten to " not in entry: + continue + lhs, rhs = entry.split(" can tighten to ", 1) + src = _node(lhs) + dst = _node(rhs) + lines.append(f" {src} --> {dst}") + for entry in ambiguities: + if " downstream types conflict: " not in entry: + continue + lhs, rhs = entry.split(" downstream types conflict: ", 1) + src = _node(lhs) + # rhs is a repr of list; keep as string nodes per type + rhs = rhs.strip() + if rhs.startswith("[") and rhs.endswith("]"): + rhs = rhs[1:-1] + type_names = [] + for item in rhs.split(","): + item = item.strip() + if not item: + continue + item = item.strip("'\"") + type_names.append(item) + for type_name in type_names: + dst = _node(type_name) + lines.append(f" {src} -.-> {dst}") + lines.append("```") + return "\n".join(lines) + + +def _local_main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("paths", nargs="+") + parser.add_argument("--no-recursive", action="store_true") + parser.add_argument("--dot", default=None, help="Write DOT graph to file or '-' for stdout.") + parser.add_argument("--report", default=None, help="Write Markdown report (mermaid) to file.") + parser.add_argument("--max-components", type=int, default=10, help="Max components in report.") + parser.add_argument( + "--type-audit", + action="store_true", + help="Emit type-tightening suggestions based on downstream annotations.", + ) + parser.add_argument( + "--type-audit-max", + type=int, + default=50, + help="Max type-tightening entries to print.", + ) + parser.add_argument( + "--type-audit-report", + action="store_true", + help="Include type-flow audit summary in the markdown report.", + ) + parser.add_argument( + "--fail-on-violations", + action="store_true", + help="Exit non-zero if undocumented/undeclared bundle violations are detected.", + ) + args = parser.parse_args() + paths = _iter_paths(args.paths) + groups_by_path: dict[Path, dict[str, list[set[str]]]] = {} + for path in paths: + groups_by_path[path] = analyze_file(path, recursive=not args.no_recursive) + if args.dot is not None: + dot = _emit_dot(groups_by_path) + if args.dot.strip() == "-": + print(dot) + else: + Path(args.dot).write_text(dot) + if args.report is None: + return + type_suggestions: list[str] = [] + type_ambiguities: list[str] = [] + if args.type_audit or args.type_audit_report: + type_suggestions, type_ambiguities = analyze_type_flow_repo(paths) + if args.type_audit: + if type_suggestions: + print("Type tightening candidates:") + for line in type_suggestions[: args.type_audit_max]: + print(f"- {line}") + if type_ambiguities: + print("Type ambiguities (conflicting downstream expectations):") + for line in type_ambiguities[: args.type_audit_max]: + print(f"- {line}") + return + if args.type_audit_report: + type_suggestions = type_suggestions[: args.type_audit_max] + type_ambiguities = type_ambiguities[: args.type_audit_max] + if args.report is not None: + constant_smells = analyze_constant_flow_repo(paths) + report, violations = _emit_report( + groups_by_path, + args.max_components, + type_suggestions=type_suggestions if args.type_audit_report else None, + type_ambiguities=type_ambiguities if args.type_audit_report else None, + constant_smells=constant_smells, + ) + Path(args.report).write_text(report) + if args.fail_on_violations and violations: + raise SystemExit( + f"dataflow grammar violations detected: {len(violations)}" + ) + return + for path, groups in groups_by_path.items(): + print(f"# {path}") + for fn, bundles in groups.items(): + if not bundles: + continue + print(f"{fn}:") + for bundle in bundles: + print(f" bundle: {sorted(bundle)}") + print() + + +def main() -> None: + """Dispatch to gabion's analyzer when available, else run local audit.""" + if _gabion_main is not None: + try: + return _gabion_main() + except Exception as exc: # pragma: no cover - fallback for CI robustness. + print( + f"dataflow_grammar_audit: gabion failed ({exc}); falling back to local audit.", + file=sys.stderr, + ) + return _local_main() + + +if __name__ == "__main__": + main() diff --git a/scripts/forwarded_args_audit.py b/scripts/forwarded_args_audit.py new file mode 100644 index 0000000..431f501 --- /dev/null +++ b/scripts/forwarded_args_audit.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python3 +"""Audit function parameters used only for forwarding. + +Classifies parameter usage as: + - direct_forward: used as the entire argument / keyword value in a Call + - derived_forward: used inside an expression that is itself a Call argument + - non_forward: used outside of any Call argument + - unused: no Load uses found + +This is a best-effort static analysis to surface candidates for config bundling. +""" +from __future__ import annotations + +import argparse +import ast +from dataclasses import dataclass +from pathlib import Path +from typing import Iterable + + +@dataclass +class UseInfo: + direct_forward: int = 0 + derived_forward: int = 0 + non_forward: int = 0 + + @property + def total(self) -> int: + return self.direct_forward + self.derived_forward + self.non_forward + + def kind(self) -> str: + if self.total == 0: + return "unused" + if self.non_forward: + return "non_forward" + if self.derived_forward: + return "derived_forward" + return "direct_forward" + + +class ParentAnnotator(ast.NodeVisitor): + def __init__(self) -> None: + self.parents: dict[ast.AST, ast.AST] = {} + + def generic_visit(self, node: ast.AST) -> None: + for child in ast.iter_child_nodes(node): + self.parents[child] = node + self.visit(child) + + +def _call_context(node: ast.AST, parents: dict[ast.AST, ast.AST]) -> tuple[ast.Call | None, bool]: + """Return (call_node, direct_arg) if node is inside a Call arg.""" + child = node + parent = parents.get(child) + while parent is not None: + if isinstance(parent, ast.Call): + # Direct if child is one of args or keyword value. + if child in parent.args: + return parent, True + if any(child is kw for kw in parent.keywords): + return parent, True + # If child is the value of a keyword. + for kw in parent.keywords: + if child is kw.value: + return parent, True + # Otherwise, child is likely part of call.func (not forwarding). + return parent, False + child = parent + parent = parents.get(child) + return None, False + + +def _is_within_call_arg(node: ast.AST, parents: dict[ast.AST, ast.AST]) -> bool: + call, direct = _call_context(node, parents) + if call is None: + return False + if direct: + return True + # If not direct, see if node is within an argument expression. + # Walk upward until the Call; if the first Call encountered is reached + # through args/keywords, treat as derived_forward. + child = node + parent = parents.get(child) + while parent is not None: + if isinstance(parent, ast.Call): + # Determine if child is within args/keywords. + if child in parent.args: + return True + for kw in parent.keywords: + if child is kw or child is kw.value: + return True + return False + child = parent + parent = parents.get(child) + return False + + +def analyze_file(path: Path) -> dict[str, dict[str, UseInfo]]: + tree = ast.parse(path.read_text()) + parent = ParentAnnotator() + parent.visit(tree) + parents = parent.parents + results: dict[str, dict[str, UseInfo]] = {} + + for node in ast.walk(tree): + if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): + continue + fn_name = node.name + args = [ + a.arg + for a in ( + node.args.posonlyargs + + node.args.args + + node.args.kwonlyargs + ) + ] + if node.args.vararg: + args.append(node.args.vararg.arg) + if node.args.kwarg: + args.append(node.args.kwarg.arg) + use_map = {a: UseInfo() for a in args} + + class UseVisitor(ast.NodeVisitor): + def visit_Name(self, n: ast.Name) -> None: + if not isinstance(n.ctx, ast.Load): + return + if n.id not in use_map: + return + call, direct = _call_context(n, parents) + if call is None: + use_map[n.id].non_forward += 1 + else: + if direct: + use_map[n.id].direct_forward += 1 + else: + if _is_within_call_arg(n, parents): + use_map[n.id].derived_forward += 1 + else: + use_map[n.id].non_forward += 1 + + UseVisitor().visit(node) + results[fn_name] = use_map + return results + + +def iter_paths(paths: Iterable[str]) -> list[Path]: + out: list[Path] = [] + for p in paths: + path = Path(p) + if path.is_dir(): + out.extend(sorted(path.rglob("*.py"))) + else: + out.append(path) + return out + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("paths", nargs="+", help="Python files or directories.") + args = parser.parse_args() + paths = iter_paths(args.paths) + for path in paths: + print(f"# {path}") + results = analyze_file(path) + for fn, use_map in results.items(): + forward_only = [] + derived_only = [] + unused = [] + for name, info in use_map.items(): + kind = info.kind() + if kind == "direct_forward": + forward_only.append(name) + elif kind == "derived_forward": + derived_only.append(name) + elif kind == "unused": + unused.append(name) + if forward_only or derived_only or unused: + print(f"{fn}:") + if forward_only: + print(f" direct_forward_only: {sorted(forward_only)}") + if derived_only: + print(f" derived_forward_only: {sorted(derived_only)}") + if unused: + print(f" unused: {sorted(unused)}") + print() + + +if __name__ == "__main__": + main() diff --git a/scripts/policy_check.py b/scripts/policy_check.py index 258b18e..e1e40a3 100755 --- a/scripts/policy_check.py +++ b/scripts/policy_check.py @@ -96,7 +96,7 @@ def _event_names(on_block): return set() -def _check_permissions(doc, path, errors): +def _check_permissions(doc, path, errors, *, allow_pr_write=False): permissions = doc.get("permissions") if permissions is None: errors.append(f"{path}: missing top-level permissions") @@ -108,11 +108,14 @@ def _check_permissions(doc, path, errors): if contents != "read": errors.append(f"{path}: permissions.contents must be 'read'") for key, value in permissions.items(): - if value not in ("read", "none"): - errors.append(f"{path}: permissions.{key} must be 'read' or 'none'") + if value in ("read", "none"): + continue + if allow_pr_write and key == "pull-requests" and value == "write": + continue + errors.append(f"{path}: permissions.{key} must be 'read' or 'none'") -def _check_job_permissions(job, path, job_name, errors): +def _check_job_permissions(job, path, job_name, errors, *, allow_pr_write=False): permissions = job.get("permissions") if permissions is None: return @@ -122,11 +125,20 @@ def _check_job_permissions(job, path, job_name, errors): contents = permissions.get("contents") if contents != "read": errors.append(f"{path}:{job_name}: permissions.contents must be 'read'") + is_self_hosted = _is_self_hosted(job.get("runs-on")) for key, value in permissions.items(): - if value not in ("read", "none"): - errors.append( - f"{path}:{job_name}: permissions.{key} must be 'read' or 'none'" - ) + if value in ("read", "none"): + continue + if ( + allow_pr_write + and not is_self_hosted + and key == "pull-requests" + and value == "write" + ): + continue + errors.append( + f"{path}:{job_name}: permissions.{key} must be 'read' or 'none'" + ) def _check_actions(job, path, job_name, errors): @@ -205,12 +217,21 @@ def check_workflows(): errors = [] for path in sorted(WORKFLOW_DIR.glob("*.yml")): doc = _load_yaml(path) - _check_permissions(doc, path, errors) - _check_self_hosted_constraints(doc, path, errors) jobs = doc.get("jobs", {}) + has_self_hosted = False + if isinstance(jobs, dict): + for name, job in jobs.items(): + if _is_self_hosted(job.get("runs-on")): + has_self_hosted = True + events = _event_names(doc.get("on")) + allow_pr_write = (("pull_request" in events) or ("pull_request_target" in events)) and (not has_self_hosted) + _check_permissions(doc, path, errors, allow_pr_write=allow_pr_write) + _check_self_hosted_constraints(doc, path, errors) if isinstance(jobs, dict): for name, job in jobs.items(): - _check_job_permissions(job, path, name, errors) + _check_job_permissions( + job, path, name, errors, allow_pr_write=allow_pr_write + ) _check_actions(job, path, name, errors) if errors: _fail(errors) diff --git a/scripts/profile_pytest_slice.py b/scripts/profile_pytest_slice.py new file mode 100644 index 0000000..f5ab5af --- /dev/null +++ b/scripts/profile_pytest_slice.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python +"""Profile a pytest slice for a bounded duration. + +Example: + mise exec -- python scripts/profile_pytest_slice.py \ + --seconds 60 \ + --out artifacts/profiles/strata_random_programs.prof \ + -- -q tests/test_strata_random_programs.py +""" + +from __future__ import annotations + +import argparse +import cProfile +import os +import signal +import sys +import time +from pathlib import Path + +import pytest + + +def _parse_args() -> tuple[argparse.Namespace, list[str]]: + parser = argparse.ArgumentParser( + description="Run pytest with cProfile for a bounded duration.", + epilog="Pass pytest args after '--'.", + ) + parser.add_argument( + "--seconds", + type=int, + default=60, + help="Max profiling duration in seconds (default: 60).", + ) + parser.add_argument( + "--out", + type=str, + default="", + help="Output .prof path (default: artifacts/profiles/pytest_slice_.prof).", + ) + args, rest = parser.parse_known_args() + if rest and rest[0] == "--": + rest = rest[1:] + return args, rest + + +def _default_out_path() -> Path: + stamp = time.strftime("%Y%m%d_%H%M%S") + return Path("artifacts") / "profiles" / f"pytest_slice_{stamp}.prof" + + +def main() -> int: + args, pytest_args = _parse_args() + out_path = Path(args.out) if args.out else _default_out_path() + out_path.parent.mkdir(parents=True, exist_ok=True) + + profile = cProfile.Profile() + + def _alarm_handler(_sig, _frame): + profile.disable() + profile.dump_stats(out_path) + print(f"[profile] dumped partial stats to {out_path}", file=sys.stderr) + os._exit(0) + + signal.signal(signal.SIGALRM, _alarm_handler) + signal.alarm(max(1, int(args.seconds))) + + profile.enable() + exit_code = pytest.main(pytest_args) + profile.disable() + profile.dump_stats(out_path) + print(f"[profile] dumped stats to {out_path}", file=sys.stderr) + return int(exit_code) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/render_dataflow_grammar.sh b/scripts/render_dataflow_grammar.sh new file mode 100755 index 0000000..8da5592 --- /dev/null +++ b/scripts/render_dataflow_grammar.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="${1:-src}" +OUT_DIR="${2:-artifacts/dataflow_grammar}" +DOT_FILE="${OUT_DIR}/dataflow.dot" +PNG_FILE="${OUT_DIR}/dataflow.png" +REPORT_FILE="${OUT_DIR}/report.md" + +mkdir -p "${OUT_DIR}" + +python scripts/dataflow_grammar_audit.py "${ROOT}" \ + --dot "${DOT_FILE}" \ + --report "${REPORT_FILE}" \ + --type-audit-report \ + --type-audit-max 50 \ + --fail-on-violations + +if command -v dot >/dev/null 2>&1; then + dot -Tpng "${DOT_FILE}" -o "${PNG_FILE}" + echo "Wrote ${PNG_FILE}" +else + echo "Graphviz 'dot' not found; wrote ${DOT_FILE} only." +fi diff --git a/semantic_audit.md b/semantic_audit.md index e4291f8..3c6fdfc 100644 --- a/semantic_audit.md +++ b/semantic_audit.md @@ -1,3 +1,8 @@ +--- +doc_revision: 1 +reader_reintern: "Reader-only: re-intern if doc_revision changed since you last read this doc." +--- + Manual semantic analysis based on `in/in-*.md`, `in/glossary.md`, `prism_vm.py`, and `IMPLEMENTATION_PLAN.md`. ## Definitions @@ -177,4 +182,4 @@ Manual semantic analysis based on `in/in-*.md`, `in/glossary.md`, `prism_vm.py`, - Core semantics: glossary defines commutation constraints for overloaded terms (BSP, CD, canonicalization, collapse, normalize, aggregate, scheduler, identity) and adds Arena/`q`/Ledger sheaf framing, hyperstrata, hyperlattice, gauge symmetry, canonical novelty, hyperoperator fixed points, adjunction/coherence discipline, and an entropy taxonomy; includes explicit pre-step immutability, `q` as coarse-graining, and a glossary contract that requires axis/commutation/erasure declarations for any reused term. Damage/Locality now splits linear Damageₗ (m4) from Entropyₐ + Apertureₛ (m5), and §29 formalizes Holographic Collapse / Super‑Particle as BSPˢ gauge artifacts. - Prior: none (glossary). - vs `prism_vm.py`: Intersection: scheduler invariance tests, coordinate opcodes, strata discipline, explicit `q` projection helpers, hyperstrata validation, and hyperlattice join tests; SymDiff: no runtime-enforced sheaf/gauge types beyond test boundaries; Wedge: keep commutation-focused tests as the enforceable contract and avoid semantic drift in naming (`q` vs layout swizzles). -- vs `IMPLEMENTATION_PLAN.md`: Intersection: plan includes `q` projection, scheduler invariance, coordinate normalization, and hyperstrata visibility; SymDiff: plan still does not cite the glossary or require hyperlattice commutation tests; Wedge: reference the glossary in plan/test obligations and treat commutation as a named acceptance requirement. +- vs `IMPLEMENTATION_PLAN.md`: Intersection: plan includes `q` projection, scheduler invariance, coordinate normalization, and hyperstrata visibility; SymDiff: plan still does not cite the glossary or require hyperlattice commutation tests; Wedge: reference the glossary in plan/test obligations and treat commutation as a named acceptance requirement. \ No newline at end of file diff --git a/src/ic_core/bundles.py b/src/ic_core/bundles.py new file mode 100644 index 0000000..bff8af2 --- /dev/null +++ b/src/ic_core/bundles.py @@ -0,0 +1,53 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import TYPE_CHECKING, NamedTuple + +import jax.numpy as jnp + + +class WireEndpoints(NamedTuple): + """Bundle of endpoints for node/port wiring.""" + + node_a: jnp.ndarray + port_a: jnp.ndarray + node_b: jnp.ndarray + port_b: jnp.ndarray + + +class WirePtrPair(NamedTuple): + """Bundle of encoded pointer endpoints.""" + + ptr_a: jnp.ndarray + ptr_b: jnp.ndarray + + +class WireStarEndpoints(NamedTuple): + """Bundle of endpoints for star wiring.""" + + center_node: jnp.ndarray + center_port: jnp.ndarray + leaf_nodes: jnp.ndarray + leaf_ports: jnp.ndarray + + +if TYPE_CHECKING: + from ic_core.graph import ICState + + +@dataclass(frozen=True) +class TemplateApplyArgs: + """Bundle of inputs for template application.""" + + state: "ICState" + node_a: jnp.ndarray + node_b: jnp.ndarray + template_id: jnp.ndarray + + +__all__ = [ + "TemplateApplyArgs", + "WireEndpoints", + "WirePtrPair", + "WireStarEndpoints", +] diff --git a/src/ic_core/config.py b/src/ic_core/config.py index 5c32754..0d9d1cb 100644 --- a/src/ic_core/config.py +++ b/src/ic_core/config.py @@ -2,10 +2,20 @@ from dataclasses import dataclass -from prism_core.safety import PolicyBinding, SafetyPolicy +import jax.numpy as jnp + +from prism_core.errors import PrismPolicyBindingError +from prism_core.safety import ( + DEFAULT_SAFETY_POLICY, + PolicyBinding, + PolicyMode, + SafetyPolicy, + require_static_policy, +) from prism_core.compact import CompactConfig from ic_core.guards import ICGuardConfig +from ic_core.guards import resolve_safe_index_fn from ic_core.protocols import ( AllocPlanFn, ApplyAnnFn, @@ -13,7 +23,6 @@ ApplyEraseFn, ApplyTemplateFn, ApplyTemplatePlannedFn, - CompactPairsFn, CompactPairsResultFn, DecodePortFn, HaltedFn, @@ -35,6 +44,119 @@ class ICGraphConfig: DEFAULT_GRAPH_CONFIG = ICGraphConfig() +DEFAULT_IC_COMPACT_CONFIG = CompactConfig( + index_dtype=jnp.uint32, + count_dtype=jnp.uint32, +) + + +@dataclass(frozen=True, slots=True) +class ICWireConfig: + """Resolved wiring config (policy + safe_index_fn).""" + + safety_policy: SafetyPolicy + safe_index_fn: SafeIndexFn + + +def resolve_wire_config(cfg: ICGraphConfig) -> ICWireConfig: + """Resolve an ICWireConfig from ICGraphConfig. + + This enforces a single policy binding path; if safe_index_fn is already + policy-bound, callers must pass an unbound function and policy separately. + """ + safety_policy = cfg.safety_policy + if cfg.policy_binding is not None: + if safety_policy is not None: + raise PrismPolicyBindingError( + "graph config received both policy_binding and safety_policy", + context="ic_graph_config", + policy_mode="ambiguous", + ) + if cfg.policy_binding.mode == PolicyMode.VALUE: + raise PrismPolicyBindingError( + "ic graph config does not support value-mode policy_binding", + context="ic_graph_config", + policy_mode=PolicyMode.VALUE, + ) + safety_policy = require_static_policy( + cfg.policy_binding, context="ic_graph_config" + ) + if safety_policy is None: + safety_policy = DEFAULT_SAFETY_POLICY + safe_index_fn = resolve_safe_index_fn( + safe_index_fn=cfg.safe_index_fn, + policy=safety_policy, + guard_cfg=cfg.guard_cfg, + ) + return ICWireConfig(safety_policy=safety_policy, safe_index_fn=safe_index_fn) + + +DEFAULT_WIRE_CONFIG = resolve_wire_config(DEFAULT_GRAPH_CONFIG) + + +@dataclass(frozen=True, slots=True) +class ICScanConfig: + """Resolved scan config (policy + safe_index_fn + compact_cfg).""" + + safety_policy: SafetyPolicy + safe_index_fn: SafeIndexFn + compact_cfg: CompactConfig + + +def resolve_scan_config(cfg: ICGraphConfig) -> ICScanConfig: + """Resolve an ICScanConfig from ICGraphConfig.""" + safety_policy = cfg.safety_policy + if cfg.policy_binding is not None: + if safety_policy is not None: + raise PrismPolicyBindingError( + "graph config received both policy_binding and safety_policy", + context="ic_graph_config", + policy_mode="ambiguous", + ) + if cfg.policy_binding.mode == PolicyMode.VALUE: + raise PrismPolicyBindingError( + "ic graph config does not support value-mode policy_binding", + context="ic_graph_config", + policy_mode=PolicyMode.VALUE, + ) + safety_policy = require_static_policy( + cfg.policy_binding, context="ic_graph_config" + ) + if safety_policy is None: + safety_policy = DEFAULT_SAFETY_POLICY + safe_index_fn = resolve_safe_index_fn( + safe_index_fn=cfg.safe_index_fn, + policy=safety_policy, + guard_cfg=cfg.guard_cfg, + ) + compact_cfg = cfg.compact_cfg or DEFAULT_IC_COMPACT_CONFIG + return ICScanConfig( + safety_policy=safety_policy, + safe_index_fn=safe_index_fn, + compact_cfg=compact_cfg, + ) + + +DEFAULT_SCAN_CONFIG = resolve_scan_config(DEFAULT_GRAPH_CONFIG) + + +@dataclass(frozen=True, slots=True) +class ICGraphResolved: + """Resolved graph bundle (wire + scan configs).""" + + wire: ICWireConfig + scan: ICScanConfig + + +def resolve_graph_config(cfg: ICGraphConfig) -> ICGraphResolved: + """Resolve ICGraphConfig into fully bound wire/scan configs.""" + return ICGraphResolved( + wire=resolve_wire_config(cfg), + scan=resolve_scan_config(cfg), + ) + + +DEFAULT_GRAPH_RESOLVED = resolve_graph_config(DEFAULT_GRAPH_CONFIG) @dataclass(frozen=True, slots=True) @@ -54,18 +176,110 @@ class ICRuleConfig: class ICEngineConfig: """Engine-level DI bundle for IC reduction.""" - compact_pairs_fn: CompactPairsFn decode_port_fn: DecodePortFn alloc_plan_fn: AllocPlanFn apply_template_planned_fn: ApplyTemplatePlannedFn halted_fn: HaltedFn scan_corrupt_fn: ScanCorruptFn - compact_pairs_result_fn: CompactPairsResultFn | None = None + compact_pairs_result_fn: CompactPairsResultFn + + +@dataclass(frozen=True, slots=True) +class ICEngineResolved: + """Resolved engine bundle (no optional branches).""" + + decode_port_fn: DecodePortFn + alloc_plan_fn: AllocPlanFn + apply_template_planned_fn: ApplyTemplatePlannedFn + halted_fn: HaltedFn + scan_corrupt_fn: ScanCorruptFn + compact_pairs_result_fn: CompactPairsResultFn + + +def resolve_engine_config(cfg: ICEngineConfig) -> ICEngineResolved: + """Resolve ICEngineConfig into an ICEngineResolved bundle.""" + return ICEngineResolved( + decode_port_fn=cfg.decode_port_fn, + alloc_plan_fn=cfg.alloc_plan_fn, + apply_template_planned_fn=cfg.apply_template_planned_fn, + halted_fn=cfg.halted_fn, + scan_corrupt_fn=cfg.scan_corrupt_fn, + compact_pairs_result_fn=cfg.compact_pairs_result_fn, + ) + + +@dataclass(frozen=True, slots=True) +class ICExecutionConfig: + """Execution-level bundle: graph + engine config.""" + + graph_cfg: ICGraphConfig + engine_cfg: ICEngineConfig + + +@dataclass(frozen=True, slots=True) +class ICExecutionResolved: + """Resolved execution bundle (graph + engine).""" + + graph: ICGraphResolved + engine: ICEngineResolved + + +def resolve_execution_config(cfg: ICExecutionConfig) -> ICExecutionResolved: + """Resolve ICExecutionConfig into fully bound graph + engine bundles.""" + return ICExecutionResolved( + graph=resolve_graph_config(cfg.graph_cfg), + engine=resolve_engine_config(cfg.engine_cfg), + ) + + +@dataclass(frozen=True, slots=True) +class ICRuntimeConfig: + """Runtime bundle: graph + rule + engine configs.""" + + graph_cfg: ICGraphConfig + rule_cfg: ICRuleConfig + engine_cfg: ICEngineConfig + + +@dataclass(frozen=True, slots=True) +class ICRuntimeResolved: + """Resolved runtime bundle (graph + rule + engine).""" + + graph: ICGraphResolved + rule: ICRuleConfig + engine: ICEngineResolved + + +def resolve_runtime_config(cfg: ICRuntimeConfig) -> ICRuntimeResolved: + """Resolve ICRuntimeConfig into bound graph + engine bundles.""" + return ICRuntimeResolved( + graph=resolve_graph_config(cfg.graph_cfg), + rule=cfg.rule_cfg, + engine=resolve_engine_config(cfg.engine_cfg), + ) __all__ = [ "ICRuleConfig", "ICEngineConfig", + "ICEngineResolved", + "ICExecutionConfig", + "ICExecutionResolved", + "ICRuntimeConfig", + "ICRuntimeResolved", "ICGraphConfig", + "ICWireConfig", + "ICScanConfig", + "ICGraphResolved", "DEFAULT_GRAPH_CONFIG", + "DEFAULT_GRAPH_RESOLVED", + "DEFAULT_IC_COMPACT_CONFIG", + "DEFAULT_WIRE_CONFIG", + "DEFAULT_SCAN_CONFIG", + "resolve_wire_config", + "resolve_scan_config", + "resolve_graph_config", + "resolve_execution_config", + "resolve_engine_config", + "resolve_runtime_config", ] diff --git a/src/ic_core/engine.py b/src/ic_core/engine.py index d5714de..f7e781c 100644 --- a/src/ic_core/engine.py +++ b/src/ic_core/engine.py @@ -6,14 +6,12 @@ ICState, PORT_PRINCIPAL, decode_port, - ic_compact_active_pairs, ic_compact_active_pairs_result, _halted, _scan_corrupt_ports, ) from ic_core.rules import TEMPLATE_NONE, _alloc_plan, _apply_template_planned -from ic_core.config import ICEngineConfig -from functools import partial +from ic_core.config import ICEngineConfig, ICEngineResolved, resolve_engine_config class ICRewriteStats(NamedTuple): @@ -24,7 +22,6 @@ class ICRewriteStats(NamedTuple): DEFAULT_ENGINE_CONFIG = ICEngineConfig( - compact_pairs_fn=ic_compact_active_pairs, compact_pairs_result_fn=ic_compact_active_pairs_result, decode_port_fn=decode_port, alloc_plan_fn=_alloc_plan, @@ -33,30 +30,16 @@ class ICRewriteStats(NamedTuple): scan_corrupt_fn=_scan_corrupt_ports, ) +DEFAULT_ENGINE_RESOLVED = resolve_engine_config(DEFAULT_ENGINE_CONFIG) -@jax.jit( - static_argnames=( - "compact_pairs_fn", - "compact_pairs_result_fn", - "decode_port_fn", - "alloc_plan_fn", - "apply_template_planned_fn", - "halted_fn", - "scan_corrupt_fn", - ) -) + +@jax.jit(static_argnames=("cfg",)) def ic_apply_active_pairs( state: ICState, *, - compact_pairs_fn=ic_compact_active_pairs, - compact_pairs_result_fn=None, - decode_port_fn=decode_port, - alloc_plan_fn=_alloc_plan, - apply_template_planned_fn=_apply_template_planned, - halted_fn=_halted, - scan_corrupt_fn=_scan_corrupt_ports, + cfg: ICEngineResolved = DEFAULT_ENGINE_RESOLVED, ) -> Tuple[ICState, ICRewriteStats]: - state = scan_corrupt_fn(state) + state = cfg.scan_corrupt_fn(state) zero_stats = ICRewriteStats( active_pairs=jnp.uint32(0), alloc_nodes=jnp.uint32(0), @@ -68,20 +51,17 @@ def _halt(s): return s, zero_stats def _run(s): - if compact_pairs_result_fn is not None: - result, _ = compact_pairs_result_fn(s) - pairs = jnp.where(result.valid, result.idx, jnp.uint32(0)) - count = result.count - else: - pairs, count, _ = compact_pairs_fn(s) + result, _ = cfg.compact_pairs_result_fn(s) + pairs = jnp.where(result.valid, result.idx, jnp.uint32(0)) + count = result.count count_i = count.astype(jnp.int32) def body(i, carry): s_in, alloc, freed, tmpl_counts, tmpl_ids, alloc_counts, alloc_ids = carry node_a = pairs[i] - node_b = decode_port_fn(s_in.ports[node_a, PORT_PRINCIPAL])[0] + node_b = cfg.decode_port_fn(s_in.ports[node_a, PORT_PRINCIPAL])[0] tmpl = tmpl_ids[i] - s2 = apply_template_planned_fn( + s2 = cfg.apply_template_planned_fn( s_in, node_a, node_b, tmpl, alloc_ids[i] ) ok = (~s_in.oom) & (~s_in.corrupt) & (~s2.oom) & (~s2.corrupt) @@ -102,7 +82,7 @@ def body(i, carry): ) def _apply(s_in): - s2, tmpl_ids, alloc_counts, alloc_ids, _ = alloc_plan_fn( + s2, tmpl_ids, alloc_counts, alloc_ids, _ = cfg.alloc_plan_fn( s_in, pairs, count ) @@ -138,18 +118,17 @@ def _run_plan(s_plan): count_i == 0, lambda s_in: (s_in, zero_stats), _apply, s ) - return jax.lax.cond(halted_fn(state), _halt, _run, state) + return jax.lax.cond(cfg.halted_fn(state), _halt, _run, state) -@jax.jit(static_argnames=("apply_active_pairs_fn", "scan_corrupt_fn")) +@jax.jit(static_argnames=("cfg",)) def ic_reduce( state: ICState, max_steps: int, *, - apply_active_pairs_fn=ic_apply_active_pairs, - scan_corrupt_fn=_scan_corrupt_ports, + cfg: ICEngineResolved = DEFAULT_ENGINE_RESOLVED, ) -> Tuple[ICState, ICRewriteStats, jnp.ndarray]: - state = scan_corrupt_fn(state) + state = cfg.scan_corrupt_fn(state) max_steps_i = jnp.int32(max_steps) zero_stats = ICRewriteStats( active_pairs=jnp.uint32(0), @@ -169,7 +148,7 @@ def cond(carry): def body(carry): s, stats, steps, _ = carry - s2, batch = apply_active_pairs_fn(s) + s2, batch = ic_apply_active_pairs(s, cfg=cfg) stats = ICRewriteStats( active_pairs=stats.active_pairs + batch.active_pairs, alloc_nodes=stats.alloc_nodes + batch.alloc_nodes, @@ -187,41 +166,22 @@ def ic_apply_active_pairs_cfg( state: ICState, *, cfg: ICEngineConfig = DEFAULT_ENGINE_CONFIG ) -> Tuple[ICState, ICRewriteStats]: """Interface/Control wrapper for IC apply_active_pairs with DI bundle.""" - return ic_apply_active_pairs( - state, - compact_pairs_fn=cfg.compact_pairs_fn, - decode_port_fn=cfg.decode_port_fn, - alloc_plan_fn=cfg.alloc_plan_fn, - apply_template_planned_fn=cfg.apply_template_planned_fn, - halted_fn=cfg.halted_fn, - scan_corrupt_fn=cfg.scan_corrupt_fn, - ) + resolved = resolve_engine_config(cfg) + return ic_apply_active_pairs(state, cfg=resolved) def ic_reduce_cfg( state: ICState, max_steps: int, *, cfg: ICEngineConfig = DEFAULT_ENGINE_CONFIG ) -> Tuple[ICState, ICRewriteStats, jnp.ndarray]: """Interface/Control wrapper for IC reduce with DI bundle.""" - apply_fn = partial( - ic_apply_active_pairs, - compact_pairs_fn=cfg.compact_pairs_fn, - decode_port_fn=cfg.decode_port_fn, - alloc_plan_fn=cfg.alloc_plan_fn, - apply_template_planned_fn=cfg.apply_template_planned_fn, - halted_fn=cfg.halted_fn, - scan_corrupt_fn=cfg.scan_corrupt_fn, - ) - return ic_reduce( - state, - max_steps, - apply_active_pairs_fn=apply_fn, - scan_corrupt_fn=cfg.scan_corrupt_fn, - ) + resolved = resolve_engine_config(cfg) + return ic_reduce(state, max_steps, cfg=resolved) __all__ = [ "ICRewriteStats", "DEFAULT_ENGINE_CONFIG", + "DEFAULT_ENGINE_RESOLVED", "ic_apply_active_pairs", "ic_reduce", "ic_apply_active_pairs_cfg", diff --git a/src/ic_core/facade.py b/src/ic_core/facade.py index 82cf534..1e72ad9 100644 --- a/src/ic_core/facade.py +++ b/src/ic_core/facade.py @@ -6,18 +6,14 @@ from __future__ import annotations -from dataclasses import replace +from dataclasses import dataclass, replace from functools import partial +from typing import Callable -from prism_core.errors import PrismPolicyBindingError from prism_core.safety import ( - DEFAULT_SAFETY_POLICY, PolicyBinding, - PolicyMode, SafetyPolicy, - require_static_policy, ) -from prism_core.guards import resolve_safe_index_fn from prism_core.alloc import ( AllocConfig, DEFAULT_ALLOC_CONFIG, @@ -28,10 +24,34 @@ from ic_core.guards import ( ICGuardConfig, DEFAULT_IC_GUARD_CONFIG, + resolve_safe_index_fn, safe_index_1d_cfg, ) -from ic_core.config import ICEngineConfig, ICGraphConfig, ICRuleConfig, DEFAULT_GRAPH_CONFIG +from ic_core.config import ( + ICEngineConfig, + ICEngineResolved, + ICGraphConfig, + ICGraphResolved, + ICExecutionConfig, + ICExecutionResolved, + ICRuntimeConfig, + ICRuntimeResolved, + ICRuleConfig, + DEFAULT_GRAPH_CONFIG, + DEFAULT_GRAPH_RESOLVED, + DEFAULT_WIRE_CONFIG, + ICWireConfig, + DEFAULT_SCAN_CONFIG, + ICScanConfig, + resolve_wire_config, + resolve_scan_config, + resolve_graph_config, + resolve_engine_config, + resolve_execution_config, + resolve_runtime_config, +) +from ic_core.bundles import WireEndpoints, WirePtrPair, WireStarEndpoints from ic_core.domains import ( HostBool, HostInt, @@ -57,7 +77,6 @@ ApplyEraseFn, ApplyTemplateFn, ApplyTemplatePlannedFn, - CompactPairsFn, DecodePortFn, HaltedFn, RuleForTypesFn, @@ -65,7 +84,10 @@ ) from ic_core.engine import ( DEFAULT_ENGINE_CONFIG, + DEFAULT_ENGINE_RESOLVED, ICRewriteStats, + ic_apply_active_pairs as _ic_apply_active_pairs_core, + ic_reduce as _ic_reduce_core, ic_apply_active_pairs_cfg, ic_reduce_cfg, ) @@ -100,26 +122,59 @@ from ic_core.jit_entrypoints import ( apply_active_pairs_jit, apply_active_pairs_jit_cfg, + apply_active_pairs_jit_exec, + apply_active_pairs_jit_resolved, + apply_active_pairs_jit_runtime, reduce_jit, reduce_jit_cfg, + reduce_jit_exec, + reduce_jit_resolved, + reduce_jit_runtime, find_active_pairs_jit, find_active_pairs_jit_cfg, + find_active_pairs_jit_exec, + find_active_pairs_jit_resolved, + find_active_pairs_jit_runtime, compact_active_pairs_jit, compact_active_pairs_jit_cfg, + compact_active_pairs_jit_exec, + compact_active_pairs_jit_resolved, + compact_active_pairs_jit_runtime, compact_active_pairs_result_jit, compact_active_pairs_result_jit_cfg, + compact_active_pairs_result_jit_exec, + compact_active_pairs_result_jit_resolved, + compact_active_pairs_result_jit_runtime, wire_jax_jit, wire_jax_jit_cfg, + wire_jax_jit_exec, + wire_jax_jit_resolved, + wire_jax_jit_runtime, wire_jax_safe_jit, wire_jax_safe_jit_cfg, + wire_jax_safe_jit_exec, + wire_jax_safe_jit_resolved, + wire_jax_safe_jit_runtime, wire_ptrs_jit, wire_ptrs_jit_cfg, + wire_ptrs_jit_exec, + wire_ptrs_jit_resolved, + wire_ptrs_jit_runtime, wire_pairs_jit, wire_pairs_jit_cfg, + wire_pairs_jit_exec, + wire_pairs_jit_resolved, + wire_pairs_jit_runtime, wire_ptr_pairs_jit, wire_ptr_pairs_jit_cfg, + wire_ptr_pairs_jit_exec, + wire_ptr_pairs_jit_resolved, + wire_ptr_pairs_jit_runtime, wire_star_jit, wire_star_jit_cfg, + wire_star_jit_exec, + wire_star_jit_resolved, + wire_star_jit_runtime, ) from ic_core.rules import ( DEFAULT_RULE_CONFIG, @@ -148,35 +203,6 @@ ) -def _resolve_safe_index_fn(cfg: ICGraphConfig): - safe_index_fn = cfg.safe_index_fn - safety_policy = cfg.safety_policy - if cfg.policy_binding is not None: - if safety_policy is not None: - raise PrismPolicyBindingError( - "graph config received both policy_binding and safety_policy", - context="ic_graph_config", - policy_mode="ambiguous", - ) - if cfg.policy_binding.mode == PolicyMode.VALUE: - raise PrismPolicyBindingError( - "ic graph config does not support value-mode policy_binding", - context="ic_graph_config", - policy_mode=PolicyMode.VALUE, - ) - safety_policy = require_static_policy( - cfg.policy_binding, context="ic_graph_config" - ) - if safety_policy is None: - if safe_index_fn is None or not getattr(safe_index_fn, "_prism_policy_bound", False): - safety_policy = DEFAULT_SAFETY_POLICY - return resolve_safe_index_fn( - safe_index_fn=cfg.safe_index_fn, - policy=safety_policy, - guard_cfg=cfg.guard_cfg, - ) - - def ic_apply_active_pairs( state: ICState, *, cfg: ICEngineConfig = DEFAULT_ENGINE_CONFIG ): @@ -242,6 +268,182 @@ def engine_config_with_compact_result_fn( return replace(cfg, compact_pairs_result_fn=compact_pairs_result_fn) +def engine_config_with_scan_cfg( + scan_cfg: ICScanConfig, + *, + cfg: ICEngineConfig = DEFAULT_ENGINE_CONFIG, +) -> ICEngineConfig: + """Return an engine config with compact_pairs_result_fn bound to scan_cfg.""" + bound = partial(ic_compact_active_pairs_result, cfg=scan_cfg) + return replace(cfg, compact_pairs_result_fn=bound) + + +def engine_config_with_graph_cfg( + graph_cfg: ICGraphConfig, + *, + cfg: ICEngineConfig = DEFAULT_ENGINE_CONFIG, +) -> ICEngineConfig: + """Return an engine config with compact pairs bound to graph scan cfg.""" + scan_cfg = resolve_graph_config(graph_cfg).scan + return engine_config_with_scan_cfg(scan_cfg, cfg=cfg) + + +def engine_resolved_with_graph_cfg( + graph_cfg: ICGraphConfig, + *, + cfg: ICEngineConfig = DEFAULT_ENGINE_CONFIG, +) -> ICEngineResolved: + """Return a resolved engine bundle wired to the graph scan cfg.""" + return resolve_engine_config(engine_config_with_graph_cfg(graph_cfg, cfg=cfg)) + + +def resolve_engine_cfg( + cfg: ICEngineConfig = DEFAULT_ENGINE_CONFIG, +) -> ICEngineResolved: + """Resolve an engine config into a fully bound bundle.""" + return resolve_engine_config(cfg) + + +def execution_config_from_graph_engine( + graph_cfg: ICGraphConfig, + *, + engine_cfg: ICEngineConfig = DEFAULT_ENGINE_CONFIG, +) -> ICExecutionConfig: + """Build an execution config from graph + engine configs.""" + return ICExecutionConfig(graph_cfg=graph_cfg, engine_cfg=engine_cfg) + + +DEFAULT_EXECUTION_CONFIG = execution_config_from_graph_engine( + DEFAULT_GRAPH_CONFIG, engine_cfg=DEFAULT_ENGINE_CONFIG +) +DEFAULT_EXECUTION_RESOLVED = resolve_execution_config(DEFAULT_EXECUTION_CONFIG) + + +def resolve_execution_cfg( + cfg: ICExecutionConfig, +) -> ICExecutionResolved: + """Resolve an execution config into a bound graph + engine bundle.""" + return resolve_execution_config(cfg) + + +def resolve_execution_cfg_default() -> ICExecutionResolved: + """Resolve the default execution config into a bound bundle.""" + return DEFAULT_EXECUTION_RESOLVED + + +def runtime_config_from_graph_rule( + graph_cfg: ICGraphConfig, + rule_cfg: ICRuleConfig, + *, + engine_cfg: ICEngineConfig | None = None, +) -> ICRuntimeConfig: + """Build a runtime config from graph + rule configs.""" + if engine_cfg is None: + engine_cfg = engine_config_from_rules(rule_cfg) + engine_cfg = engine_config_with_graph_cfg(graph_cfg, cfg=engine_cfg) + return ICRuntimeConfig( + graph_cfg=graph_cfg, + rule_cfg=rule_cfg, + engine_cfg=engine_cfg, + ) + + +def resolve_runtime_cfg(cfg: ICRuntimeConfig) -> ICRuntimeResolved: + """Resolve a runtime config into a bound bundle.""" + return resolve_runtime_config(cfg) + + +def resolve_runtime_cfg_default() -> ICRuntimeResolved: + """Resolve the default runtime config into a bound bundle.""" + return DEFAULT_RUNTIME_RESOLVED + + +@dataclass(frozen=True, slots=True) +class ICRuntimeOps: + """Bound runtime operations for a resolved IC runtime bundle.""" + + cfg: ICRuntimeResolved + wire_jax: Callable + wire_jax_safe: Callable + wire_ptrs: Callable + wire_pairs: Callable + wire_ptr_pairs: Callable + wire_star: Callable + find_active_pairs: Callable + compact_active_pairs: Callable + compact_active_pairs_result: Callable + apply_active_pairs: Callable + reduce: Callable + + +def make_runtime_ops(cfg: ICRuntimeResolved) -> ICRuntimeOps: + """Bind runtime operations to a resolved bundle.""" + return ICRuntimeOps( + cfg=cfg, + wire_jax=partial(ic_wire_jax, cfg=cfg.graph.wire), + wire_jax_safe=partial(ic_wire_jax_safe, cfg=cfg.graph.wire), + wire_ptrs=partial(ic_wire_ptrs_jax, cfg=cfg.graph.wire), + wire_pairs=partial(ic_wire_pairs_jax, cfg=cfg.graph.wire), + wire_ptr_pairs=partial(ic_wire_ptr_pairs_jax, cfg=cfg.graph.wire), + wire_star=partial(ic_wire_star_jax, cfg=cfg.graph.wire), + find_active_pairs=partial(ic_find_active_pairs, cfg=cfg.graph.scan), + compact_active_pairs=partial(ic_compact_active_pairs, cfg=cfg.graph.scan), + compact_active_pairs_result=partial( + ic_compact_active_pairs_result, cfg=cfg.graph.scan + ), + apply_active_pairs=partial(_ic_apply_active_pairs_core, cfg=cfg.engine), + reduce=partial(_ic_reduce_core, cfg=cfg.engine), + ) + + +def make_runtime_ops_from_cfg(cfg: ICRuntimeConfig) -> ICRuntimeOps: + """Resolve and bind runtime operations from a runtime config.""" + return make_runtime_ops(resolve_runtime_config(cfg)) + + + +def ic_apply_active_pairs_resolved( + state: ICState, *, cfg: ICEngineResolved = DEFAULT_ENGINE_RESOLVED +): + """Interface/Control wrapper for apply_active_pairs with resolved DI.""" + return _ic_apply_active_pairs_core(state, cfg=cfg) + + +def ic_reduce_resolved( + state: ICState, max_steps: int, *, cfg: ICEngineResolved = DEFAULT_ENGINE_RESOLVED +): + """Interface/Control wrapper for reduce with resolved DI.""" + return _ic_reduce_core(state, max_steps, cfg=cfg) + + +def ic_apply_active_pairs_exec( + state: ICState, *, cfg: ICExecutionResolved +): + """Interface/Control wrapper for apply_active_pairs with execution bundle.""" + return _ic_apply_active_pairs_core(state, cfg=cfg.engine) + + +def ic_reduce_exec( + state: ICState, max_steps: int, *, cfg: ICExecutionResolved +): + """Interface/Control wrapper for reduce with execution bundle.""" + return _ic_reduce_core(state, max_steps, cfg=cfg.engine) + + +def ic_apply_active_pairs_runtime( + state: ICState, *, cfg: ICRuntimeResolved +): + """Interface/Control wrapper for apply_active_pairs with runtime bundle.""" + return _ic_apply_active_pairs_core(state, cfg=cfg.engine) + + +def ic_reduce_runtime( + state: ICState, max_steps: int, *, cfg: ICRuntimeResolved +): + """Interface/Control wrapper for reduce with runtime bundle.""" + return _ic_reduce_core(state, max_steps, cfg=cfg.engine) + + def graph_config_with_guard( *, safety_policy: SafetyPolicy | None = None, @@ -258,6 +460,13 @@ def graph_config_with_guard( return cfg +def resolve_graph_cfg( + cfg: ICGraphConfig = DEFAULT_GRAPH_CONFIG, +) -> ICGraphResolved: + """Resolve a graph config into bound wire/scan bundles.""" + return resolve_graph_config(cfg) + + def rule_config_with_alloc( alloc_cfg: AllocConfig | None, *, @@ -295,171 +504,382 @@ def rule_config_with_alloc( def ic_wire_jax_cfg( state: ICState, - node_a: jnp.ndarray, - port_a: jnp.ndarray, - node_b: jnp.ndarray, - port_b: jnp.ndarray, + endpoints: WireEndpoints, *, cfg: ICGraphConfig = DEFAULT_GRAPH_CONFIG, ) -> ICState: """Interface/Control wrapper for ic_wire_jax with safety policy.""" - safe_index_fn = _resolve_safe_index_fn(cfg) + wire_cfg = resolve_graph_config(cfg).wire return ic_wire_jax( state, - node_a, - port_a, - node_b, - port_b, - safety_policy=cfg.safety_policy, - safe_index_fn=safe_index_fn, + endpoints, + cfg=wire_cfg, ) +def ic_wire_jax_resolved( + state: ICState, + endpoints: WireEndpoints, + *, + cfg: ICGraphResolved, +) -> ICState: + """Interface/Control wrapper for ic_wire_jax with resolved graph cfg.""" + return ic_wire_jax(state, endpoints, cfg=cfg.wire) + + +def ic_wire_jax_exec( + state: ICState, + endpoints: WireEndpoints, + *, + cfg: ICExecutionResolved, +) -> ICState: + """Interface/Control wrapper for ic_wire_jax with execution bundle.""" + return ic_wire_jax(state, endpoints, cfg=cfg.graph.wire) + + +def ic_wire_jax_runtime( + state: ICState, + endpoints: WireEndpoints, + *, + cfg: ICRuntimeResolved, +) -> ICState: + """Interface/Control wrapper for ic_wire_jax with runtime bundle.""" + return ic_wire_jax(state, endpoints, cfg=cfg.graph.wire) + + def ic_wire_jax_safe_cfg( state: ICState, - node_a: jnp.ndarray, - port_a: jnp.ndarray, - node_b: jnp.ndarray, - port_b: jnp.ndarray, + endpoints: WireEndpoints, *, cfg: ICGraphConfig = DEFAULT_GRAPH_CONFIG, ) -> ICState: """Interface/Control wrapper for ic_wire_jax_safe with safety policy.""" - safe_index_fn = _resolve_safe_index_fn(cfg) + wire_cfg = resolve_graph_config(cfg).wire return ic_wire_jax_safe( state, - node_a, - port_a, - node_b, - port_b, - safety_policy=cfg.safety_policy, - safe_index_fn=safe_index_fn, + endpoints, + cfg=wire_cfg, ) +def ic_wire_jax_safe_resolved( + state: ICState, + endpoints: WireEndpoints, + *, + cfg: ICGraphResolved, +) -> ICState: + """Interface/Control wrapper for ic_wire_jax_safe with resolved graph cfg.""" + return ic_wire_jax_safe(state, endpoints, cfg=cfg.wire) + + +def ic_wire_jax_safe_exec( + state: ICState, + endpoints: WireEndpoints, + *, + cfg: ICExecutionResolved, +) -> ICState: + """Interface/Control wrapper for ic_wire_jax_safe with execution bundle.""" + return ic_wire_jax_safe(state, endpoints, cfg=cfg.graph.wire) + + +def ic_wire_jax_safe_runtime( + state: ICState, + endpoints: WireEndpoints, + *, + cfg: ICRuntimeResolved, +) -> ICState: + """Interface/Control wrapper for ic_wire_jax_safe with runtime bundle.""" + return ic_wire_jax_safe(state, endpoints, cfg=cfg.graph.wire) + + def ic_wire_ptrs_jax_cfg( state: ICState, - ptr_a: jnp.ndarray, - ptr_b: jnp.ndarray, + ptrs: WirePtrPair, *, cfg: ICGraphConfig = DEFAULT_GRAPH_CONFIG, ) -> ICState: """Interface/Control wrapper for ic_wire_ptrs_jax with safety policy.""" - safe_index_fn = _resolve_safe_index_fn(cfg) + wire_cfg = resolve_graph_config(cfg).wire return ic_wire_ptrs_jax( state, - ptr_a, - ptr_b, - safety_policy=cfg.safety_policy, - safe_index_fn=safe_index_fn, + ptrs, + cfg=wire_cfg, ) +def ic_wire_ptrs_jax_resolved( + state: ICState, + ptrs: WirePtrPair, + *, + cfg: ICGraphResolved, +) -> ICState: + """Interface/Control wrapper for ic_wire_ptrs_jax with resolved graph cfg.""" + return ic_wire_ptrs_jax(state, ptrs, cfg=cfg.wire) + + +def ic_wire_ptrs_jax_exec( + state: ICState, + ptrs: WirePtrPair, + *, + cfg: ICExecutionResolved, +) -> ICState: + """Interface/Control wrapper for ic_wire_ptrs_jax with execution bundle.""" + return ic_wire_ptrs_jax(state, ptrs, cfg=cfg.graph.wire) + + +def ic_wire_ptrs_jax_runtime( + state: ICState, + ptrs: WirePtrPair, + *, + cfg: ICRuntimeResolved, +) -> ICState: + """Interface/Control wrapper for ic_wire_ptrs_jax with runtime bundle.""" + return ic_wire_ptrs_jax(state, ptrs, cfg=cfg.graph.wire) + + def ic_wire_pairs_jax_cfg( state: ICState, - node_a: jnp.ndarray, - port_a: jnp.ndarray, - node_b: jnp.ndarray, - port_b: jnp.ndarray, + endpoints: WireEndpoints, *, cfg: ICGraphConfig = DEFAULT_GRAPH_CONFIG, ) -> ICState: """Interface/Control wrapper for ic_wire_pairs_jax with safety policy.""" - safe_index_fn = _resolve_safe_index_fn(cfg) + wire_cfg = resolve_graph_config(cfg).wire return ic_wire_pairs_jax( state, - node_a, - port_a, - node_b, - port_b, - safety_policy=cfg.safety_policy, - safe_index_fn=safe_index_fn, + endpoints, + cfg=wire_cfg, ) +def ic_wire_pairs_jax_resolved( + state: ICState, + endpoints: WireEndpoints, + *, + cfg: ICGraphResolved, +) -> ICState: + """Interface/Control wrapper for ic_wire_pairs_jax with resolved graph cfg.""" + return ic_wire_pairs_jax(state, endpoints, cfg=cfg.wire) + + +def ic_wire_pairs_jax_exec( + state: ICState, + endpoints: WireEndpoints, + *, + cfg: ICExecutionResolved, +) -> ICState: + """Interface/Control wrapper for ic_wire_pairs_jax with execution bundle.""" + return ic_wire_pairs_jax(state, endpoints, cfg=cfg.graph.wire) + + +def ic_wire_pairs_jax_runtime( + state: ICState, + endpoints: WireEndpoints, + *, + cfg: ICRuntimeResolved, +) -> ICState: + """Interface/Control wrapper for ic_wire_pairs_jax with runtime bundle.""" + return ic_wire_pairs_jax(state, endpoints, cfg=cfg.graph.wire) + + def ic_wire_ptr_pairs_jax_cfg( state: ICState, - ptr_a: jnp.ndarray, - ptr_b: jnp.ndarray, + ptrs: WirePtrPair, *, cfg: ICGraphConfig = DEFAULT_GRAPH_CONFIG, ) -> ICState: """Interface/Control wrapper for ic_wire_ptr_pairs_jax with safety policy.""" - safe_index_fn = _resolve_safe_index_fn(cfg) + wire_cfg = resolve_graph_config(cfg).wire return ic_wire_ptr_pairs_jax( state, - ptr_a, - ptr_b, - safety_policy=cfg.safety_policy, - safe_index_fn=safe_index_fn, + ptrs, + cfg=wire_cfg, ) +def ic_wire_ptr_pairs_jax_resolved( + state: ICState, + ptrs: WirePtrPair, + *, + cfg: ICGraphResolved, +) -> ICState: + """Interface/Control wrapper for ic_wire_ptr_pairs_jax with resolved graph cfg.""" + return ic_wire_ptr_pairs_jax(state, ptrs, cfg=cfg.wire) + + +def ic_wire_ptr_pairs_jax_exec( + state: ICState, + ptrs: WirePtrPair, + *, + cfg: ICExecutionResolved, +) -> ICState: + """Interface/Control wrapper for ic_wire_ptr_pairs_jax with execution bundle.""" + return ic_wire_ptr_pairs_jax(state, ptrs, cfg=cfg.graph.wire) + + +def ic_wire_ptr_pairs_jax_runtime( + state: ICState, + ptrs: WirePtrPair, + *, + cfg: ICRuntimeResolved, +) -> ICState: + """Interface/Control wrapper for ic_wire_ptr_pairs_jax with runtime bundle.""" + return ic_wire_ptr_pairs_jax(state, ptrs, cfg=cfg.graph.wire) + + def ic_wire_star_jax_cfg( state: ICState, - center_node: jnp.ndarray, - center_port: jnp.ndarray, - leaf_nodes: jnp.ndarray, - leaf_ports: jnp.ndarray, + endpoints: WireStarEndpoints, *, cfg: ICGraphConfig = DEFAULT_GRAPH_CONFIG, ) -> ICState: """Interface/Control wrapper for ic_wire_star_jax with safety policy.""" - safe_index_fn = _resolve_safe_index_fn(cfg) + wire_cfg = resolve_graph_config(cfg).wire return ic_wire_star_jax( state, - center_node, - center_port, - leaf_nodes, - leaf_ports, - safety_policy=cfg.safety_policy, - safe_index_fn=safe_index_fn, + endpoints, + cfg=wire_cfg, ) +def ic_wire_star_jax_resolved( + state: ICState, + endpoints: WireStarEndpoints, + *, + cfg: ICGraphResolved, +) -> ICState: + """Interface/Control wrapper for ic_wire_star_jax with resolved graph cfg.""" + return ic_wire_star_jax(state, endpoints, cfg=cfg.wire) + + +def ic_wire_star_jax_exec( + state: ICState, + endpoints: WireStarEndpoints, + *, + cfg: ICExecutionResolved, +) -> ICState: + """Interface/Control wrapper for ic_wire_star_jax with execution bundle.""" + return ic_wire_star_jax(state, endpoints, cfg=cfg.graph.wire) + + +def ic_wire_star_jax_runtime( + state: ICState, + endpoints: WireStarEndpoints, + *, + cfg: ICRuntimeResolved, +) -> ICState: + """Interface/Control wrapper for ic_wire_star_jax with runtime bundle.""" + return ic_wire_star_jax(state, endpoints, cfg=cfg.graph.wire) + + def ic_find_active_pairs_cfg( state: ICState, *, cfg: ICGraphConfig = DEFAULT_GRAPH_CONFIG ) -> Tuple[jnp.ndarray, jnp.ndarray]: """Interface/Control wrapper for active pair detection with DI bundle.""" - safe_index_fn = _resolve_safe_index_fn(cfg) - return ic_find_active_pairs( - state, - safety_policy=cfg.safety_policy, - safe_index_fn=safe_index_fn, - compact_cfg=cfg.compact_cfg, - ) + scan_cfg = resolve_graph_config(cfg).scan + return ic_find_active_pairs(state, cfg=scan_cfg) + + +def ic_find_active_pairs_resolved( + state: ICState, + *, + cfg: ICGraphResolved, +) -> Tuple[jnp.ndarray, jnp.ndarray]: + """Interface/Control wrapper for active pairs with resolved graph cfg.""" + return ic_find_active_pairs(state, cfg=cfg.scan) + + +def ic_find_active_pairs_exec( + state: ICState, + *, + cfg: ICExecutionResolved, +) -> Tuple[jnp.ndarray, jnp.ndarray]: + """Interface/Control wrapper for active pairs with execution bundle.""" + return ic_find_active_pairs(state, cfg=cfg.graph.scan) + + +def ic_find_active_pairs_runtime( + state: ICState, + *, + cfg: ICRuntimeResolved, +) -> Tuple[jnp.ndarray, jnp.ndarray]: + """Interface/Control wrapper for active pairs with runtime bundle.""" + return ic_find_active_pairs(state, cfg=cfg.graph.scan) def ic_compact_active_pairs_cfg( state: ICState, *, cfg: ICGraphConfig = DEFAULT_GRAPH_CONFIG ) -> Tuple[jnp.ndarray, jnp.ndarray, jnp.ndarray]: """Interface/Control wrapper for compact active pairs with DI bundle.""" - safe_index_fn = _resolve_safe_index_fn(cfg) - return ic_compact_active_pairs( - state, - safety_policy=cfg.safety_policy, - safe_index_fn=safe_index_fn, - compact_cfg=cfg.compact_cfg, - ) + scan_cfg = resolve_graph_config(cfg).scan + return ic_compact_active_pairs(state, cfg=scan_cfg) + + +def ic_compact_active_pairs_resolved( + state: ICState, + *, + cfg: ICGraphResolved, +) -> Tuple[jnp.ndarray, jnp.ndarray, jnp.ndarray]: + """Interface/Control wrapper for compact pairs with resolved graph cfg.""" + return ic_compact_active_pairs(state, cfg=cfg.scan) + + +def ic_compact_active_pairs_exec( + state: ICState, + *, + cfg: ICExecutionResolved, +) -> Tuple[jnp.ndarray, jnp.ndarray, jnp.ndarray]: + """Interface/Control wrapper for compact pairs with execution bundle.""" + return ic_compact_active_pairs(state, cfg=cfg.graph.scan) + + +def ic_compact_active_pairs_runtime( + state: ICState, + *, + cfg: ICRuntimeResolved, +) -> Tuple[jnp.ndarray, jnp.ndarray, jnp.ndarray]: + """Interface/Control wrapper for compact pairs with runtime bundle.""" + return ic_compact_active_pairs(state, cfg=cfg.graph.scan) def ic_compact_active_pairs_result_cfg( state: ICState, *, cfg: ICGraphConfig = DEFAULT_GRAPH_CONFIG ): """Interface/Control wrapper for CompactResult active pairs with DI bundle.""" - safe_index_fn = _resolve_safe_index_fn(cfg) - return ic_compact_active_pairs_result( - state, - safety_policy=cfg.safety_policy, - safe_index_fn=safe_index_fn, - compact_cfg=cfg.compact_cfg, - ) + scan_cfg = resolve_graph_config(cfg).scan + return ic_compact_active_pairs_result(state, cfg=scan_cfg) + + +def ic_compact_active_pairs_result_resolved( + state: ICState, + *, + cfg: ICGraphResolved, +): + """Interface/Control wrapper for CompactResult pairs with resolved graph cfg.""" + return ic_compact_active_pairs_result(state, cfg=cfg.scan) + + +def ic_compact_active_pairs_result_exec( + state: ICState, + *, + cfg: ICExecutionResolved, +): + """Interface/Control wrapper for CompactResult pairs with execution bundle.""" + return ic_compact_active_pairs_result(state, cfg=cfg.graph.scan) + + +def ic_compact_active_pairs_result_runtime( + state: ICState, + *, + cfg: ICRuntimeResolved, +): + """Interface/Control wrapper for CompactResult pairs with runtime bundle.""" + return ic_compact_active_pairs_result(state, cfg=cfg.graph.scan) def engine_config_from_rules( rule_cfg: ICRuleConfig, *, - compact_pairs_fn=ic_compact_active_pairs, compact_pairs_result_fn=ic_compact_active_pairs_result, decode_port_fn=decode_port, halted_fn=_halted, @@ -467,7 +887,6 @@ def engine_config_from_rules( ) -> ICEngineConfig: """Build an engine config from a rule config (Interface/Control).""" return ICEngineConfig( - compact_pairs_fn=compact_pairs_fn, compact_pairs_result_fn=compact_pairs_result_fn, decode_port_fn=decode_port_fn, alloc_plan_fn=rule_cfg.alloc_plan_fn, @@ -477,10 +896,16 @@ def engine_config_from_rules( ) +DEFAULT_RUNTIME_CONFIG = runtime_config_from_graph_rule( + DEFAULT_GRAPH_CONFIG, DEFAULT_RULE_CONFIG +) +DEFAULT_RUNTIME_RESOLVED = resolve_runtime_config(DEFAULT_RUNTIME_CONFIG) +DEFAULT_RUNTIME_OPS = make_runtime_ops(DEFAULT_RUNTIME_RESOLVED) + + def apply_active_pairs_jit_from_rules( rule_cfg: ICRuleConfig, *, - compact_pairs_fn=ic_compact_active_pairs, compact_pairs_result_fn=ic_compact_active_pairs_result, decode_port_fn=decode_port, halted_fn=_halted, @@ -489,7 +914,6 @@ def apply_active_pairs_jit_from_rules( """Return jitted apply_active_pairs using a rule config.""" cfg = engine_config_from_rules( rule_cfg, - compact_pairs_fn=compact_pairs_fn, compact_pairs_result_fn=compact_pairs_result_fn, decode_port_fn=decode_port_fn, halted_fn=halted_fn, @@ -501,7 +925,6 @@ def apply_active_pairs_jit_from_rules( def reduce_jit_from_rules( rule_cfg: ICRuleConfig, *, - compact_pairs_fn=ic_compact_active_pairs, compact_pairs_result_fn=ic_compact_active_pairs_result, decode_port_fn=decode_port, halted_fn=_halted, @@ -510,7 +933,6 @@ def reduce_jit_from_rules( """Return jitted reduce using a rule config.""" cfg = engine_config_from_rules( rule_cfg, - compact_pairs_fn=compact_pairs_fn, compact_pairs_result_fn=compact_pairs_result_fn, decode_port_fn=decode_port_fn, halted_fn=halted_fn, @@ -539,10 +961,16 @@ def reduce_jit_from_rules( "ICRewriteStats", "ICRuleConfig", "ICEngineConfig", + "ICEngineResolved", + "ICExecutionConfig", + "ICExecutionResolved", + "ICRuntimeConfig", + "ICRuntimeResolved", "ICGraphConfig", + "ICWireConfig", + "ICScanConfig", "AllocConfig", "DEFAULT_ALLOC_CONFIG", - "CompactPairsFn", "DecodePortFn", "AllocPlanFn", "ApplyTemplatePlannedFn", @@ -555,13 +983,42 @@ def reduce_jit_from_rules( "ApplyTemplateFn", "DEFAULT_RULE_CONFIG", "DEFAULT_ENGINE_CONFIG", + "DEFAULT_ENGINE_RESOLVED", "DEFAULT_GRAPH_CONFIG", + "DEFAULT_GRAPH_RESOLVED", + "DEFAULT_EXECUTION_CONFIG", + "DEFAULT_EXECUTION_RESOLVED", + "DEFAULT_RUNTIME_CONFIG", + "DEFAULT_RUNTIME_RESOLVED", + "DEFAULT_RUNTIME_OPS", + "DEFAULT_WIRE_CONFIG", + "DEFAULT_SCAN_CONFIG", + "resolve_wire_config", + "resolve_scan_config", + "resolve_graph_config", + "resolve_graph_cfg", + "resolve_engine_config", + "resolve_engine_cfg", + "resolve_execution_config", + "resolve_execution_cfg", + "resolve_execution_cfg_default", + "resolve_runtime_config", + "resolve_runtime_cfg", + "resolve_runtime_cfg_default", + "make_runtime_ops", + "make_runtime_ops_from_cfg", + "ICRuntimeOps", "graph_config_with_policy", "graph_config_with_policy_binding", "graph_config_with_index_fn", "graph_config_with_compact_cfg", "graph_config_with_guard", + "execution_config_from_graph_engine", "engine_config_with_compact_result_fn", + "engine_config_with_scan_cfg", + "engine_config_with_graph_cfg", + "engine_resolved_with_graph_cfg", + "runtime_config_from_graph_rule", "rule_config_with_alloc", "ICGuardConfig", "DEFAULT_IC_GUARD_CONFIG", @@ -602,12 +1059,39 @@ def reduce_jit_from_rules( "ic_wire_pairs_jax_cfg", "ic_wire_ptr_pairs_jax_cfg", "ic_wire_star_jax_cfg", + "ic_wire_jax_resolved", + "ic_wire_jax_safe_resolved", + "ic_wire_ptrs_jax_resolved", + "ic_wire_pairs_jax_resolved", + "ic_wire_ptr_pairs_jax_resolved", + "ic_wire_star_jax_resolved", + "ic_wire_jax_exec", + "ic_wire_jax_safe_exec", + "ic_wire_ptrs_jax_exec", + "ic_wire_pairs_jax_exec", + "ic_wire_ptr_pairs_jax_exec", + "ic_wire_star_jax_exec", + "ic_wire_jax_runtime", + "ic_wire_jax_safe_runtime", + "ic_wire_ptrs_jax_runtime", + "ic_wire_pairs_jax_runtime", + "ic_wire_ptr_pairs_jax_runtime", + "ic_wire_star_jax_runtime", "ic_find_active_pairs", "ic_find_active_pairs_cfg", + "ic_find_active_pairs_resolved", + "ic_find_active_pairs_exec", "ic_compact_active_pairs", "ic_compact_active_pairs_cfg", + "ic_compact_active_pairs_resolved", + "ic_compact_active_pairs_exec", "ic_compact_active_pairs_result", "ic_compact_active_pairs_result_cfg", + "ic_compact_active_pairs_result_resolved", + "ic_compact_active_pairs_result_exec", + "ic_find_active_pairs_runtime", + "ic_compact_active_pairs_runtime", + "ic_compact_active_pairs_result_runtime", "ic_rule_for_types", "ic_rule_for_types_cfg", "ic_select_template", @@ -629,6 +1113,12 @@ def reduce_jit_from_rules( "engine_config_from_rules", "apply_active_pairs_jit_from_rules", "reduce_jit_from_rules", + "ic_apply_active_pairs_resolved", + "ic_reduce_resolved", + "ic_apply_active_pairs_exec", + "ic_reduce_exec", + "ic_apply_active_pairs_runtime", + "ic_reduce_runtime", "apply_active_pairs_jit", "apply_active_pairs_jit_cfg", "reduce_jit", @@ -651,5 +1141,38 @@ def reduce_jit_from_rules( "wire_ptr_pairs_jit_cfg", "wire_star_jit", "wire_star_jit_cfg", + "apply_active_pairs_jit_resolved", + "apply_active_pairs_jit_exec", + "apply_active_pairs_jit_runtime", + "reduce_jit_resolved", + "reduce_jit_exec", + "reduce_jit_runtime", + "find_active_pairs_jit_resolved", + "find_active_pairs_jit_exec", + "find_active_pairs_jit_runtime", + "compact_active_pairs_jit_resolved", + "compact_active_pairs_jit_exec", + "compact_active_pairs_jit_runtime", + "compact_active_pairs_result_jit_resolved", + "compact_active_pairs_result_jit_exec", + "compact_active_pairs_result_jit_runtime", + "wire_jax_jit_resolved", + "wire_jax_jit_exec", + "wire_jax_jit_runtime", + "wire_jax_safe_jit_resolved", + "wire_jax_safe_jit_exec", + "wire_jax_safe_jit_runtime", + "wire_ptrs_jit_resolved", + "wire_ptrs_jit_exec", + "wire_ptrs_jit_runtime", + "wire_pairs_jit_resolved", + "wire_pairs_jit_exec", + "wire_pairs_jit_runtime", + "wire_ptr_pairs_jit_resolved", + "wire_ptr_pairs_jit_exec", + "wire_ptr_pairs_jit_runtime", + "wire_star_jit_resolved", + "wire_star_jit_exec", + "wire_star_jit_runtime", "ic_alloc", ] diff --git a/src/ic_core/graph.py b/src/ic_core/graph.py index b351531..60ecda6 100644 --- a/src/ic_core/graph.py +++ b/src/ic_core/graph.py @@ -4,11 +4,17 @@ from typing import NamedTuple, Tuple from prism_core import alloc as _alloc -from prism_core.jax_safe import safe_index_1d from prism_core.di import call_with_optional_kwargs from prism_core.compact import CompactConfig, CompactResult, compact_mask_cfg -from prism_core.safety import DEFAULT_SAFETY_POLICY, SafetyPolicy, oob_mask +from prism_core.safety import oob_mask from ic_core.domains import _node_id, _port_id +from ic_core.bundles import WireEndpoints, WirePtrPair, WireStarEndpoints +from ic_core.config import ( + ICWireConfig, + ICScanConfig, + DEFAULT_WIRE_CONFIG, + DEFAULT_SCAN_CONFIG, +) # Interaction-combinator (IC) graph + safety helpers. @@ -127,22 +133,19 @@ def _do(p): return jax.lax.cond(do, _do, lambda p: p, ports) -@partial(jax.jit, static_argnames=("safety_policy", "safe_index_fn")) +@partial(jax.jit, static_argnames=("cfg",)) def ic_wire_jax( state: ICState, - node_a: jnp.ndarray, - port_a: jnp.ndarray, - node_b: jnp.ndarray, - port_b: jnp.ndarray, + endpoints: WireEndpoints, *, - safety_policy: SafetyPolicy | None = None, - safe_index_fn=safe_index_1d, + cfg: ICWireConfig = DEFAULT_WIRE_CONFIG, ) -> ICState: """Device-only wire: connect (node, port) <-> (node, port).""" def _do(s): - policy = safety_policy or DEFAULT_SAFETY_POLICY - index_fn = safe_index_fn + policy = cfg.safety_policy + index_fn = cfg.safe_index_fn + node_a, port_a, node_b, port_b = endpoints node_a_u = jnp.asarray(node_a, dtype=jnp.uint32) port_a_u = jnp.asarray(port_a, dtype=jnp.uint32) node_b_u = jnp.asarray(node_b, dtype=jnp.uint32) @@ -174,20 +177,19 @@ def _do(s): return jax.lax.cond(_halted(state), lambda s: s, _do, state) -@partial(jax.jit, static_argnames=("safety_policy", "safe_index_fn")) +@partial(jax.jit, static_argnames=("cfg",)) def ic_wire_ptrs_jax( state: ICState, - ptr_a: jnp.ndarray, - ptr_b: jnp.ndarray, + ptrs: WirePtrPair, *, - safety_policy: SafetyPolicy | None = None, - safe_index_fn=safe_index_1d, + cfg: ICWireConfig = DEFAULT_WIRE_CONFIG, ) -> ICState: """Device-only wire given two encoded pointers (NULL-safe).""" def _do(s): - policy = safety_policy or DEFAULT_SAFETY_POLICY - index_fn = safe_index_fn + policy = cfg.safety_policy + index_fn = cfg.safe_index_fn + ptr_a, ptr_b = ptrs ptr_a_u = jnp.asarray(ptr_a, dtype=jnp.uint32) ptr_b_u = jnp.asarray(ptr_b, dtype=jnp.uint32) do = (ptr_a_u != jnp.uint32(0)) & (ptr_b_u != jnp.uint32(0)) @@ -226,41 +228,37 @@ def _do(s): return jax.lax.cond(_halted(state), lambda s: s, _do, state) -@partial(jax.jit, static_argnames=("safety_policy", "safe_index_fn")) +@partial(jax.jit, static_argnames=("cfg",)) def ic_wire_jax_safe( state: ICState, - node_a: jnp.ndarray, - port_a: jnp.ndarray, - node_b: jnp.ndarray, - port_b: jnp.ndarray, + endpoints: WireEndpoints, *, - safety_policy: SafetyPolicy | None = None, - safe_index_fn=safe_index_1d, + cfg: ICWireConfig = DEFAULT_WIRE_CONFIG, ) -> ICState: """Device-only wire that no-ops on NULL endpoints.""" + node_a, port_a, node_b, port_b = endpoints ptr_a = encode_port(jnp.asarray(node_a, jnp.uint32), jnp.asarray(port_a, jnp.uint32)) ptr_b = encode_port(jnp.asarray(node_b, jnp.uint32), jnp.asarray(port_b, jnp.uint32)) return ic_wire_ptrs_jax( - state, ptr_a, ptr_b, safety_policy=safety_policy, safe_index_fn=safe_index_fn + state, + WirePtrPair(ptr_a, ptr_b), + cfg=cfg, ) -@partial(jax.jit, static_argnames=("safety_policy", "safe_index_fn")) +@partial(jax.jit, static_argnames=("cfg",)) def ic_wire_pairs_jax( state: ICState, - node_a: jnp.ndarray, - port_a: jnp.ndarray, - node_b: jnp.ndarray, - port_b: jnp.ndarray, + endpoints: WireEndpoints, *, - safety_policy: SafetyPolicy | None = None, - safe_index_fn=safe_index_1d, + cfg: ICWireConfig = DEFAULT_WIRE_CONFIG, ) -> ICState: """Batch wire: connect (node_a[i], port_a[i]) <-> (node_b[i], port_b[i]).""" def _do(s): - policy = safety_policy or DEFAULT_SAFETY_POLICY - index_fn = safe_index_fn + policy = cfg.safety_policy + index_fn = cfg.safe_index_fn + node_a, port_a, node_b, port_b = endpoints node_a_u = jnp.asarray(node_a, dtype=jnp.uint32) port_a_u = jnp.asarray(port_a, dtype=jnp.uint32) node_b_u = jnp.asarray(node_b, dtype=jnp.uint32) @@ -304,20 +302,19 @@ def _do(s): return jax.lax.cond(_halted(state), lambda s: s, _do, state) -@partial(jax.jit, static_argnames=("safety_policy", "safe_index_fn")) +@partial(jax.jit, static_argnames=("cfg",)) def ic_wire_ptr_pairs_jax( state: ICState, - ptr_a: jnp.ndarray, - ptr_b: jnp.ndarray, + ptrs: WirePtrPair, *, - safety_policy: SafetyPolicy | None = None, - safe_index_fn=safe_index_1d, + cfg: ICWireConfig = DEFAULT_WIRE_CONFIG, ) -> ICState: """Batch wire given encoded pointers (NULL-safe).""" def _do(s): - policy = safety_policy or DEFAULT_SAFETY_POLICY - index_fn = safe_index_fn + policy = cfg.safety_policy + index_fn = cfg.safe_index_fn + ptr_a, ptr_b = ptrs ptr_a_u = jnp.asarray(ptr_a, dtype=jnp.uint32) ptr_b_u = jnp.asarray(ptr_b, dtype=jnp.uint32) do = (ptr_a_u != jnp.uint32(0)) & (ptr_b_u != jnp.uint32(0)) @@ -360,18 +357,15 @@ def _do(s): return jax.lax.cond(_halted(state), lambda s: s, _do, state) -@partial(jax.jit, static_argnames=("safety_policy", "safe_index_fn")) +@partial(jax.jit, static_argnames=("cfg",)) def ic_wire_star_jax( state: ICState, - center_node: jnp.ndarray, - center_port: jnp.ndarray, - leaf_nodes: jnp.ndarray, - leaf_ports: jnp.ndarray, + endpoints: WireStarEndpoints, *, - safety_policy: SafetyPolicy | None = None, - safe_index_fn=safe_index_1d, + cfg: ICWireConfig = DEFAULT_WIRE_CONFIG, ) -> ICState: """Wire a single center endpoint to many leaf endpoints (device-only).""" + center_node, center_port, leaf_nodes, leaf_ports = endpoints center_node = jnp.asarray(center_node, dtype=jnp.uint32) center_port = jnp.asarray(center_port, dtype=jnp.uint32) leaf_nodes = jnp.asarray(leaf_nodes, dtype=jnp.uint32) @@ -381,26 +375,18 @@ def ic_wire_star_jax( port_a = jnp.full((k,), center_port, dtype=jnp.uint32) return ic_wire_pairs_jax( state, - node_a, - port_a, - leaf_nodes, - leaf_ports, - safety_policy=safety_policy, - safe_index_fn=safe_index_fn, + WireEndpoints(node_a, port_a, leaf_nodes, leaf_ports), + cfg=cfg, ) -@partial(jax.jit, static_argnames=("safety_policy", "safe_index_fn", "compact_cfg")) +@partial(jax.jit, static_argnames=("cfg",)) def ic_find_active_pairs( state: ICState, *, - safety_policy: SafetyPolicy | None = None, - safe_index_fn=safe_index_1d, - compact_cfg: CompactConfig | None = None, + cfg: ICScanConfig = DEFAULT_SCAN_CONFIG, ) -> Tuple[jnp.ndarray, jnp.ndarray]: """Return indices of nodes in active principal-principal pairs.""" - if safety_policy is None: - safety_policy = DEFAULT_SAFETY_POLICY ports = state.ports n = ports.shape[0] n_u = jnp.uint32(n) @@ -410,8 +396,8 @@ def ic_find_active_pairs( tgt_node, tgt_port = decode_port(ptr) is_principal = tgt_port == PORT_PRINCIPAL tgt_safe, ok = call_with_optional_kwargs( - safe_index_fn, - {"policy": safety_policy}, + cfg.safe_index_fn, + {"policy": cfg.safety_policy}, tgt_node, n_u, "ic_find_active_pairs.tgt", @@ -423,55 +409,43 @@ def ic_find_active_pairs( back_node, back_port = decode_port(back) mutual = (back_node == idx) & (back_port == PORT_PRINCIPAL) active = is_connected & is_principal & ok & mutual & (idx < tgt_node) - pairs = _compact_mask(active, compact_cfg=compact_cfg).idx + pairs = _compact_mask(active, compact_cfg=cfg.compact_cfg).idx return pairs, active def _compact_mask( - mask: jnp.ndarray, *, compact_cfg: CompactConfig | None = None + mask: jnp.ndarray, *, compact_cfg: CompactConfig ) -> CompactResult: - if compact_cfg is None: - compact_cfg = CompactConfig( - index_dtype=jnp.uint32, count_dtype=jnp.uint32 - ) return compact_mask_cfg(mask, cfg=compact_cfg) -@partial(jax.jit, static_argnames=("safety_policy", "safe_index_fn", "compact_cfg")) +@partial(jax.jit, static_argnames=("cfg",)) def ic_compact_active_pairs( state: ICState, *, - safety_policy: SafetyPolicy | None = None, - safe_index_fn=safe_index_1d, - compact_cfg: CompactConfig | None = None, + cfg: ICScanConfig = DEFAULT_SCAN_CONFIG, ) -> Tuple[jnp.ndarray, jnp.ndarray, jnp.ndarray]: """Return compacted active pair indices and a count.""" result, active = ic_compact_active_pairs_result( state, - safety_policy=safety_policy, - safe_index_fn=safe_index_fn, - compact_cfg=compact_cfg, + cfg=cfg, ) compacted = jnp.where(result.valid, result.idx, jnp.uint32(0)) return compacted, result.count, active -@partial(jax.jit, static_argnames=("safety_policy", "safe_index_fn", "compact_cfg")) +@partial(jax.jit, static_argnames=("cfg",)) def ic_compact_active_pairs_result( state: ICState, *, - safety_policy: SafetyPolicy | None = None, - safe_index_fn=safe_index_1d, - compact_cfg: CompactConfig | None = None, + cfg: ICScanConfig = DEFAULT_SCAN_CONFIG, ) -> Tuple[CompactResult, jnp.ndarray]: """Return CompactResult for active pairs and the active mask.""" _, active = ic_find_active_pairs( state, - safety_policy=safety_policy, - safe_index_fn=safe_index_fn, - compact_cfg=compact_cfg, + cfg=cfg, ) - result = _compact_mask(active, compact_cfg=compact_cfg) + result = _compact_mask(active, compact_cfg=cfg.compact_cfg) return result, active diff --git a/src/ic_core/jit_entrypoints.py b/src/ic_core/jit_entrypoints.py index a1a85cf..a0b4782 100644 --- a/src/ic_core/jit_entrypoints.py +++ b/src/ic_core/jit_entrypoints.py @@ -1,22 +1,25 @@ from __future__ import annotations -from functools import partial - from prism_core.di import cached_jit -from prism_core.errors import PrismPolicyBindingError -from prism_core.guards import resolve_safe_index_fn -from prism_core.safety import ( - DEFAULT_SAFETY_POLICY, - PolicyMode, - resolve_policy_binding, - require_static_policy, +from ic_core.config import ( + ICGraphConfig, + ICGraphResolved, + ICEngineConfig, + ICEngineResolved, + ICExecutionResolved, + ICRuntimeResolved, + DEFAULT_GRAPH_CONFIG, + DEFAULT_GRAPH_RESOLVED, + resolve_engine_config, + resolve_graph_config, ) -from ic_core.config import ICGraphConfig, ICEngineConfig, DEFAULT_GRAPH_CONFIG from ic_core.engine import ( DEFAULT_ENGINE_CONFIG, - ic_apply_active_pairs, - ic_reduce, + DEFAULT_ENGINE_RESOLVED, ) +from ic_core.engine import ic_apply_active_pairs, ic_reduce +from ic_core.bundles import WireEndpoints, WirePtrPair, WireStarEndpoints +from ic_core.graph import ICState from ic_core.graph import ( ic_compact_active_pairs, ic_compact_active_pairs_result, @@ -29,58 +32,13 @@ ic_wire_star_jax, ) -def _resolve_safe_index_fn(cfg: ICGraphConfig): - safe_index_fn = cfg.safe_index_fn - safety_policy = cfg.safety_policy - if cfg.policy_binding is not None: - if safety_policy is not None: - raise PrismPolicyBindingError( - "graph config received both policy_binding and safety_policy", - context="ic_graph_config", - policy_mode="ambiguous", - ) - if cfg.policy_binding.mode == PolicyMode.VALUE: - raise PrismPolicyBindingError( - "ic graph config does not support value-mode policy_binding", - context="ic_graph_config", - policy_mode=PolicyMode.VALUE, - ) - safety_policy = require_static_policy( - cfg.policy_binding, context="ic_graph_config" - ) - policy = safety_policy - if policy is None: - if safe_index_fn is None or not getattr(safe_index_fn, "_prism_policy_bound", False): - policy = DEFAULT_SAFETY_POLICY - else: - policy = None - if policy is not None: - binding = resolve_policy_binding( - policy=policy, - policy_value=None, - context="ic_graph_config", - ) - policy = binding.policy - return resolve_safe_index_fn( - safe_index_fn=safe_index_fn, - policy=policy, - guard_cfg=cfg.guard_cfg, - ) - @cached_jit def _apply_active_pairs_jit(cfg: ICEngineConfig): - def _impl(state): - return ic_apply_active_pairs( - state, - compact_pairs_fn=cfg.compact_pairs_fn, - compact_pairs_result_fn=cfg.compact_pairs_result_fn, - decode_port_fn=cfg.decode_port_fn, - alloc_plan_fn=cfg.alloc_plan_fn, - apply_template_planned_fn=cfg.apply_template_planned_fn, - halted_fn=cfg.halted_fn, - scan_corrupt_fn=cfg.scan_corrupt_fn, - ) + resolved = resolve_engine_config(cfg) + + def _impl(state: ICState): + return ic_apply_active_pairs(state, cfg=resolved) return _impl @@ -97,26 +55,53 @@ def apply_active_pairs_jit_cfg(cfg: ICEngineConfig | None = None): return apply_active_pairs_jit(cfg) +@cached_jit +def _apply_active_pairs_resolved_jit(cfg: ICEngineResolved): + def _impl(state: ICState): + return ic_apply_active_pairs(state, cfg=cfg) + + return _impl + + +def apply_active_pairs_jit_resolved( + cfg: ICEngineResolved = DEFAULT_ENGINE_RESOLVED, +): + """Return a jitted apply_active_pairs entrypoint for resolved DI.""" + return _apply_active_pairs_resolved_jit(cfg) + + +@cached_jit +def _apply_active_pairs_exec_jit(cfg: ICExecutionResolved): + def _impl(state: ICState): + return ic_apply_active_pairs(state, cfg=cfg.engine) + + return _impl + + +def apply_active_pairs_jit_exec(cfg: ICExecutionResolved): + """Return a jitted apply_active_pairs entrypoint for execution bundle.""" + return _apply_active_pairs_exec_jit(cfg) + + +@cached_jit +def _apply_active_pairs_runtime_jit(cfg: ICRuntimeResolved): + def _impl(state: ICState): + return ic_apply_active_pairs(state, cfg=cfg.engine) + + return _impl + + +def apply_active_pairs_jit_runtime(cfg: ICRuntimeResolved): + """Return a jitted apply_active_pairs entrypoint for runtime bundle.""" + return _apply_active_pairs_runtime_jit(cfg) + + @cached_jit def _reduce_jit(cfg: ICEngineConfig): - apply_fn = partial( - ic_apply_active_pairs, - compact_pairs_fn=cfg.compact_pairs_fn, - compact_pairs_result_fn=cfg.compact_pairs_result_fn, - decode_port_fn=cfg.decode_port_fn, - alloc_plan_fn=cfg.alloc_plan_fn, - apply_template_planned_fn=cfg.apply_template_planned_fn, - halted_fn=cfg.halted_fn, - scan_corrupt_fn=cfg.scan_corrupt_fn, - ) - - def _impl(state, max_steps): - return ic_reduce( - state, - max_steps, - apply_active_pairs_fn=apply_fn, - scan_corrupt_fn=cfg.scan_corrupt_fn, - ) + resolved = resolve_engine_config(cfg) + + def _impl(state: ICState, max_steps: int): + return ic_reduce(state, max_steps, cfg=resolved) return _impl @@ -133,17 +118,51 @@ def reduce_jit_cfg(cfg: ICEngineConfig | None = None): return reduce_jit(cfg) +@cached_jit +def _reduce_resolved_jit(cfg: ICEngineResolved): + def _impl(state: ICState, max_steps: int): + return ic_reduce(state, max_steps, cfg=cfg) + + return _impl + + +def reduce_jit_resolved(cfg: ICEngineResolved = DEFAULT_ENGINE_RESOLVED): + """Return a jitted reduce entrypoint for resolved DI.""" + return _reduce_resolved_jit(cfg) + + +@cached_jit +def _reduce_jit_exec(cfg: ICExecutionResolved): + def _impl(state: ICState, max_steps: int): + return ic_reduce(state, max_steps, cfg=cfg.engine) + + return _impl + + +def reduce_jit_exec(cfg: ICExecutionResolved): + """Return a jitted reduce entrypoint for execution bundle.""" + return _reduce_jit_exec(cfg) + + +@cached_jit +def _reduce_jit_runtime(cfg: ICRuntimeResolved): + def _impl(state: ICState, max_steps: int): + return ic_reduce(state, max_steps, cfg=cfg.engine) + + return _impl + + +def reduce_jit_runtime(cfg: ICRuntimeResolved): + """Return a jitted reduce entrypoint for runtime bundle.""" + return _reduce_jit_runtime(cfg) + + @cached_jit def _find_active_pairs_jit(cfg: ICGraphConfig): - safe_index_fn = _resolve_safe_index_fn(cfg) + scan_cfg = resolve_graph_config(cfg).scan - def _impl(state): - return ic_find_active_pairs( - state, - safety_policy=cfg.safety_policy, - safe_index_fn=safe_index_fn, - compact_cfg=cfg.compact_cfg, - ) + def _impl(state: ICState): + return ic_find_active_pairs(state, cfg=scan_cfg) return _impl @@ -160,17 +179,53 @@ def find_active_pairs_jit_cfg(cfg: ICGraphConfig | None = None): return find_active_pairs_jit(cfg) +@cached_jit +def _find_active_pairs_resolved_jit(cfg: ICGraphResolved): + def _impl(state: ICState): + return ic_find_active_pairs(state, cfg=cfg.scan) + + return _impl + + +def find_active_pairs_jit_resolved( + cfg: ICGraphResolved = DEFAULT_GRAPH_RESOLVED, +): + """Return a jitted find_active_pairs entrypoint for resolved DI.""" + return _find_active_pairs_resolved_jit(cfg) + + +@cached_jit +def _find_active_pairs_exec_jit(cfg: ICExecutionResolved): + def _impl(state: ICState): + return ic_find_active_pairs(state, cfg=cfg.graph.scan) + + return _impl + + +def find_active_pairs_jit_exec(cfg: ICExecutionResolved): + """Return a jitted find_active_pairs entrypoint for execution bundle.""" + return _find_active_pairs_exec_jit(cfg) + + +@cached_jit +def _find_active_pairs_runtime_jit(cfg: ICRuntimeResolved): + def _impl(state: ICState): + return ic_find_active_pairs(state, cfg=cfg.graph.scan) + + return _impl + + +def find_active_pairs_jit_runtime(cfg: ICRuntimeResolved): + """Return a jitted find_active_pairs entrypoint for runtime bundle.""" + return _find_active_pairs_runtime_jit(cfg) + + @cached_jit def _compact_active_pairs_jit(cfg: ICGraphConfig): - safe_index_fn = _resolve_safe_index_fn(cfg) + scan_cfg = resolve_graph_config(cfg).scan - def _impl(state): - return ic_compact_active_pairs( - state, - safety_policy=cfg.safety_policy, - safe_index_fn=safe_index_fn, - compact_cfg=cfg.compact_cfg, - ) + def _impl(state: ICState): + return ic_compact_active_pairs(state, cfg=scan_cfg) return _impl @@ -187,17 +242,53 @@ def compact_active_pairs_jit_cfg(cfg: ICGraphConfig | None = None): return compact_active_pairs_jit(cfg) +@cached_jit +def _compact_active_pairs_resolved_jit(cfg: ICGraphResolved): + def _impl(state: ICState): + return ic_compact_active_pairs(state, cfg=cfg.scan) + + return _impl + + +def compact_active_pairs_jit_resolved( + cfg: ICGraphResolved = DEFAULT_GRAPH_RESOLVED, +): + """Return a jitted compact_active_pairs entrypoint for resolved DI.""" + return _compact_active_pairs_resolved_jit(cfg) + + +@cached_jit +def _compact_active_pairs_exec_jit(cfg: ICExecutionResolved): + def _impl(state: ICState): + return ic_compact_active_pairs(state, cfg=cfg.graph.scan) + + return _impl + + +def compact_active_pairs_jit_exec(cfg: ICExecutionResolved): + """Return a jitted compact_active_pairs entrypoint for execution bundle.""" + return _compact_active_pairs_exec_jit(cfg) + + +@cached_jit +def _compact_active_pairs_runtime_jit(cfg: ICRuntimeResolved): + def _impl(state: ICState): + return ic_compact_active_pairs(state, cfg=cfg.graph.scan) + + return _impl + + +def compact_active_pairs_jit_runtime(cfg: ICRuntimeResolved): + """Return a jitted compact_active_pairs entrypoint for runtime bundle.""" + return _compact_active_pairs_runtime_jit(cfg) + + @cached_jit def _compact_active_pairs_result_jit(cfg: ICGraphConfig): - safe_index_fn = _resolve_safe_index_fn(cfg) + scan_cfg = resolve_graph_config(cfg).scan - def _impl(state): - return ic_compact_active_pairs_result( - state, - safety_policy=cfg.safety_policy, - safe_index_fn=safe_index_fn, - compact_cfg=cfg.compact_cfg, - ) + def _impl(state: ICState): + return ic_compact_active_pairs_result(state, cfg=scan_cfg) return _impl @@ -214,19 +305,56 @@ def compact_active_pairs_result_jit_cfg(cfg: ICGraphConfig | None = None): return compact_active_pairs_result_jit(cfg) +@cached_jit +def _compact_active_pairs_result_resolved_jit(cfg: ICGraphResolved): + def _impl(state: ICState): + return ic_compact_active_pairs_result(state, cfg=cfg.scan) + + return _impl + + +def compact_active_pairs_result_jit_resolved( + cfg: ICGraphResolved = DEFAULT_GRAPH_RESOLVED, +): + """Return a jitted compact_active_pairs_result entrypoint for resolved DI.""" + return _compact_active_pairs_result_resolved_jit(cfg) + + +@cached_jit +def _compact_active_pairs_result_exec_jit(cfg: ICExecutionResolved): + def _impl(state: ICState): + return ic_compact_active_pairs_result(state, cfg=cfg.graph.scan) + + return _impl + + +def compact_active_pairs_result_jit_exec(cfg: ICExecutionResolved): + """Return a jitted compact_active_pairs_result entrypoint for execution bundle.""" + return _compact_active_pairs_result_exec_jit(cfg) + + +@cached_jit +def _compact_active_pairs_result_runtime_jit(cfg: ICRuntimeResolved): + def _impl(state: ICState): + return ic_compact_active_pairs_result(state, cfg=cfg.graph.scan) + + return _impl + + +def compact_active_pairs_result_jit_runtime(cfg: ICRuntimeResolved): + """Return a jitted compact_active_pairs_result entrypoint for runtime bundle.""" + return _compact_active_pairs_result_runtime_jit(cfg) + + @cached_jit def _wire_jax_jit(cfg: ICGraphConfig): - safe_index_fn = _resolve_safe_index_fn(cfg) + wire_cfg = resolve_graph_config(cfg).wire - def _impl(state, node_a, port_a, node_b, port_b): + def _impl(state: ICState, endpoints: WireEndpoints): return ic_wire_jax( state, - node_a, - port_a, - node_b, - port_b, - safety_policy=cfg.safety_policy, - safe_index_fn=safe_index_fn, + endpoints, + cfg=wire_cfg, ) return _impl @@ -244,19 +372,54 @@ def wire_jax_jit_cfg(cfg: ICGraphConfig | None = None): return wire_jax_jit(cfg) +@cached_jit +def _wire_jax_resolved_jit(cfg: ICGraphResolved): + def _impl(state: ICState, endpoints: WireEndpoints): + return ic_wire_jax(state, endpoints, cfg=cfg.wire) + + return _impl + + +def wire_jax_jit_resolved(cfg: ICGraphResolved = DEFAULT_GRAPH_RESOLVED): + """Return a jitted ic_wire_jax entrypoint for resolved DI.""" + return _wire_jax_resolved_jit(cfg) + + +@cached_jit +def _wire_jax_exec_jit(cfg: ICExecutionResolved): + def _impl(state: ICState, endpoints: WireEndpoints): + return ic_wire_jax(state, endpoints, cfg=cfg.graph.wire) + + return _impl + + +def wire_jax_jit_exec(cfg: ICExecutionResolved): + """Return a jitted ic_wire_jax entrypoint for execution bundle.""" + return _wire_jax_exec_jit(cfg) + + +@cached_jit +def _wire_jax_runtime_jit(cfg: ICRuntimeResolved): + def _impl(state: ICState, endpoints: WireEndpoints): + return ic_wire_jax(state, endpoints, cfg=cfg.graph.wire) + + return _impl + + +def wire_jax_jit_runtime(cfg: ICRuntimeResolved): + """Return a jitted ic_wire_jax entrypoint for runtime bundle.""" + return _wire_jax_runtime_jit(cfg) + + @cached_jit def _wire_jax_safe_jit(cfg: ICGraphConfig): - safe_index_fn = _resolve_safe_index_fn(cfg) + wire_cfg = resolve_graph_config(cfg).wire - def _impl(state, node_a, port_a, node_b, port_b): + def _impl(state: ICState, endpoints: WireEndpoints): return ic_wire_jax_safe( state, - node_a, - port_a, - node_b, - port_b, - safety_policy=cfg.safety_policy, - safe_index_fn=safe_index_fn, + endpoints, + cfg=wire_cfg, ) return _impl @@ -274,17 +437,54 @@ def wire_jax_safe_jit_cfg(cfg: ICGraphConfig | None = None): return wire_jax_safe_jit(cfg) +@cached_jit +def _wire_jax_safe_resolved_jit(cfg: ICGraphResolved): + def _impl(state: ICState, endpoints: WireEndpoints): + return ic_wire_jax_safe(state, endpoints, cfg=cfg.wire) + + return _impl + + +def wire_jax_safe_jit_resolved(cfg: ICGraphResolved = DEFAULT_GRAPH_RESOLVED): + """Return a jitted ic_wire_jax_safe entrypoint for resolved DI.""" + return _wire_jax_safe_resolved_jit(cfg) + + +@cached_jit +def _wire_jax_safe_exec_jit(cfg: ICExecutionResolved): + def _impl(state: ICState, endpoints: WireEndpoints): + return ic_wire_jax_safe(state, endpoints, cfg=cfg.graph.wire) + + return _impl + + +def wire_jax_safe_jit_exec(cfg: ICExecutionResolved): + """Return a jitted ic_wire_jax_safe entrypoint for execution bundle.""" + return _wire_jax_safe_exec_jit(cfg) + + +@cached_jit +def _wire_jax_safe_runtime_jit(cfg: ICRuntimeResolved): + def _impl(state: ICState, endpoints: WireEndpoints): + return ic_wire_jax_safe(state, endpoints, cfg=cfg.graph.wire) + + return _impl + + +def wire_jax_safe_jit_runtime(cfg: ICRuntimeResolved): + """Return a jitted ic_wire_jax_safe entrypoint for runtime bundle.""" + return _wire_jax_safe_runtime_jit(cfg) + + @cached_jit def _wire_ptrs_jit(cfg: ICGraphConfig): - safe_index_fn = _resolve_safe_index_fn(cfg) + wire_cfg = resolve_graph_config(cfg).wire - def _impl(state, ptr_a, ptr_b): + def _impl(state: ICState, ptrs: WirePtrPair): return ic_wire_ptrs_jax( state, - ptr_a, - ptr_b, - safety_policy=cfg.safety_policy, - safe_index_fn=safe_index_fn, + ptrs, + cfg=wire_cfg, ) return _impl @@ -302,19 +502,54 @@ def wire_ptrs_jit_cfg(cfg: ICGraphConfig | None = None): return wire_ptrs_jit(cfg) +@cached_jit +def _wire_ptrs_resolved_jit(cfg: ICGraphResolved): + def _impl(state: ICState, ptrs: WirePtrPair): + return ic_wire_ptrs_jax(state, ptrs, cfg=cfg.wire) + + return _impl + + +def wire_ptrs_jit_resolved(cfg: ICGraphResolved = DEFAULT_GRAPH_RESOLVED): + """Return a jitted ic_wire_ptrs_jax entrypoint for resolved DI.""" + return _wire_ptrs_resolved_jit(cfg) + + +@cached_jit +def _wire_ptrs_exec_jit(cfg: ICExecutionResolved): + def _impl(state: ICState, ptrs: WirePtrPair): + return ic_wire_ptrs_jax(state, ptrs, cfg=cfg.graph.wire) + + return _impl + + +def wire_ptrs_jit_exec(cfg: ICExecutionResolved): + """Return a jitted ic_wire_ptrs_jax entrypoint for execution bundle.""" + return _wire_ptrs_exec_jit(cfg) + + +@cached_jit +def _wire_ptrs_runtime_jit(cfg: ICRuntimeResolved): + def _impl(state: ICState, ptrs: WirePtrPair): + return ic_wire_ptrs_jax(state, ptrs, cfg=cfg.graph.wire) + + return _impl + + +def wire_ptrs_jit_runtime(cfg: ICRuntimeResolved): + """Return a jitted ic_wire_ptrs_jax entrypoint for runtime bundle.""" + return _wire_ptrs_runtime_jit(cfg) + + @cached_jit def _wire_pairs_jit(cfg: ICGraphConfig): - safe_index_fn = _resolve_safe_index_fn(cfg) + wire_cfg = resolve_graph_config(cfg).wire - def _impl(state, node_a, port_a, node_b, port_b): + def _impl(state: ICState, endpoints: WireEndpoints): return ic_wire_pairs_jax( state, - node_a, - port_a, - node_b, - port_b, - safety_policy=cfg.safety_policy, - safe_index_fn=safe_index_fn, + endpoints, + cfg=wire_cfg, ) return _impl @@ -332,17 +567,54 @@ def wire_pairs_jit_cfg(cfg: ICGraphConfig | None = None): return wire_pairs_jit(cfg) +@cached_jit +def _wire_pairs_resolved_jit(cfg: ICGraphResolved): + def _impl(state: ICState, endpoints: WireEndpoints): + return ic_wire_pairs_jax(state, endpoints, cfg=cfg.wire) + + return _impl + + +def wire_pairs_jit_resolved(cfg: ICGraphResolved = DEFAULT_GRAPH_RESOLVED): + """Return a jitted ic_wire_pairs_jax entrypoint for resolved DI.""" + return _wire_pairs_resolved_jit(cfg) + + +@cached_jit +def _wire_pairs_exec_jit(cfg: ICExecutionResolved): + def _impl(state: ICState, endpoints: WireEndpoints): + return ic_wire_pairs_jax(state, endpoints, cfg=cfg.graph.wire) + + return _impl + + +def wire_pairs_jit_exec(cfg: ICExecutionResolved): + """Return a jitted ic_wire_pairs_jax entrypoint for execution bundle.""" + return _wire_pairs_exec_jit(cfg) + + +@cached_jit +def _wire_pairs_runtime_jit(cfg: ICRuntimeResolved): + def _impl(state: ICState, endpoints: WireEndpoints): + return ic_wire_pairs_jax(state, endpoints, cfg=cfg.graph.wire) + + return _impl + + +def wire_pairs_jit_runtime(cfg: ICRuntimeResolved): + """Return a jitted ic_wire_pairs_jax entrypoint for runtime bundle.""" + return _wire_pairs_runtime_jit(cfg) + + @cached_jit def _wire_ptr_pairs_jit(cfg: ICGraphConfig): - safe_index_fn = _resolve_safe_index_fn(cfg) + wire_cfg = resolve_graph_config(cfg).wire - def _impl(state, ptr_a, ptr_b): + def _impl(state: ICState, ptrs: WirePtrPair): return ic_wire_ptr_pairs_jax( state, - ptr_a, - ptr_b, - safety_policy=cfg.safety_policy, - safe_index_fn=safe_index_fn, + ptrs, + cfg=wire_cfg, ) return _impl @@ -360,19 +632,54 @@ def wire_ptr_pairs_jit_cfg(cfg: ICGraphConfig | None = None): return wire_ptr_pairs_jit(cfg) +@cached_jit +def _wire_ptr_pairs_resolved_jit(cfg: ICGraphResolved): + def _impl(state: ICState, ptrs: WirePtrPair): + return ic_wire_ptr_pairs_jax(state, ptrs, cfg=cfg.wire) + + return _impl + + +def wire_ptr_pairs_jit_resolved(cfg: ICGraphResolved = DEFAULT_GRAPH_RESOLVED): + """Return a jitted ic_wire_ptr_pairs_jax entrypoint for resolved DI.""" + return _wire_ptr_pairs_resolved_jit(cfg) + + +@cached_jit +def _wire_ptr_pairs_exec_jit(cfg: ICExecutionResolved): + def _impl(state: ICState, ptrs: WirePtrPair): + return ic_wire_ptr_pairs_jax(state, ptrs, cfg=cfg.graph.wire) + + return _impl + + +def wire_ptr_pairs_jit_exec(cfg: ICExecutionResolved): + """Return a jitted ic_wire_ptr_pairs_jax entrypoint for execution bundle.""" + return _wire_ptr_pairs_exec_jit(cfg) + + +@cached_jit +def _wire_ptr_pairs_runtime_jit(cfg: ICRuntimeResolved): + def _impl(state: ICState, ptrs: WirePtrPair): + return ic_wire_ptr_pairs_jax(state, ptrs, cfg=cfg.graph.wire) + + return _impl + + +def wire_ptr_pairs_jit_runtime(cfg: ICRuntimeResolved): + """Return a jitted ic_wire_ptr_pairs_jax entrypoint for runtime bundle.""" + return _wire_ptr_pairs_runtime_jit(cfg) + + @cached_jit def _wire_star_jit(cfg: ICGraphConfig): - safe_index_fn = _resolve_safe_index_fn(cfg) + wire_cfg = resolve_graph_config(cfg).wire - def _impl(state, center_node, center_port, leaf_nodes, leaf_ports): + def _impl(state: ICState, endpoints: WireStarEndpoints): return ic_wire_star_jax( state, - center_node, - center_port, - leaf_nodes, - leaf_ports, - safety_policy=cfg.safety_policy, - safe_index_fn=safe_index_fn, + endpoints, + cfg=wire_cfg, ) return _impl @@ -390,27 +697,99 @@ def wire_star_jit_cfg(cfg: ICGraphConfig | None = None): return wire_star_jit(cfg) +@cached_jit +def _wire_star_resolved_jit(cfg: ICGraphResolved): + def _impl(state: ICState, endpoints: WireStarEndpoints): + return ic_wire_star_jax(state, endpoints, cfg=cfg.wire) + + return _impl + + +def wire_star_jit_resolved(cfg: ICGraphResolved = DEFAULT_GRAPH_RESOLVED): + """Return a jitted ic_wire_star_jax entrypoint for resolved DI.""" + return _wire_star_resolved_jit(cfg) + + +@cached_jit +def _wire_star_exec_jit(cfg: ICExecutionResolved): + def _impl(state: ICState, endpoints: WireStarEndpoints): + return ic_wire_star_jax(state, endpoints, cfg=cfg.graph.wire) + + return _impl + + +def wire_star_jit_exec(cfg: ICExecutionResolved): + """Return a jitted ic_wire_star_jax entrypoint for execution bundle.""" + return _wire_star_exec_jit(cfg) + + +@cached_jit +def _wire_star_runtime_jit(cfg: ICRuntimeResolved): + def _impl(state: ICState, endpoints: WireStarEndpoints): + return ic_wire_star_jax(state, endpoints, cfg=cfg.graph.wire) + + return _impl + + +def wire_star_jit_runtime(cfg: ICRuntimeResolved): + """Return a jitted ic_wire_star_jax entrypoint for runtime bundle.""" + return _wire_star_runtime_jit(cfg) + + __all__ = [ "apply_active_pairs_jit", "apply_active_pairs_jit_cfg", + "apply_active_pairs_jit_resolved", + "apply_active_pairs_jit_exec", + "apply_active_pairs_jit_runtime", "reduce_jit", "reduce_jit_cfg", + "reduce_jit_resolved", + "reduce_jit_exec", + "reduce_jit_runtime", "find_active_pairs_jit", "find_active_pairs_jit_cfg", + "find_active_pairs_jit_resolved", + "find_active_pairs_jit_exec", + "find_active_pairs_jit_runtime", "compact_active_pairs_jit", "compact_active_pairs_jit_cfg", + "compact_active_pairs_jit_resolved", + "compact_active_pairs_jit_exec", + "compact_active_pairs_jit_runtime", "compact_active_pairs_result_jit", "compact_active_pairs_result_jit_cfg", + "compact_active_pairs_result_jit_resolved", + "compact_active_pairs_result_jit_exec", + "compact_active_pairs_result_jit_runtime", "wire_jax_jit", "wire_jax_jit_cfg", + "wire_jax_jit_resolved", + "wire_jax_jit_exec", + "wire_jax_jit_runtime", "wire_jax_safe_jit", "wire_jax_safe_jit_cfg", + "wire_jax_safe_jit_resolved", + "wire_jax_safe_jit_exec", + "wire_jax_safe_jit_runtime", "wire_ptrs_jit", "wire_ptrs_jit_cfg", + "wire_ptrs_jit_resolved", + "wire_ptrs_jit_exec", + "wire_ptrs_jit_runtime", "wire_pairs_jit", "wire_pairs_jit_cfg", + "wire_pairs_jit_resolved", + "wire_pairs_jit_exec", + "wire_pairs_jit_runtime", "wire_ptr_pairs_jit", "wire_ptr_pairs_jit_cfg", + "wire_ptr_pairs_jit_resolved", + "wire_ptr_pairs_jit_exec", + "wire_ptr_pairs_jit_runtime", "wire_star_jit", "wire_star_jit_cfg", + "wire_star_jit_resolved", + "wire_star_jit_exec", + "wire_star_jit_runtime", ] diff --git a/src/ic_core/protocols.py b/src/ic_core/protocols.py index d417168..868f876 100644 --- a/src/ic_core/protocols.py +++ b/src/ic_core/protocols.py @@ -1,20 +1,15 @@ from __future__ import annotations -from typing import Protocol, Tuple, runtime_checkable +from typing import TYPE_CHECKING, Protocol, Tuple, runtime_checkable import jax.numpy as jnp -from ic_core.graph import ICState from prism_core.compact import CompactResult from prism_core.protocols import SafeIndexFn +from ic_core.bundles import TemplateApplyArgs - -@runtime_checkable -class CompactPairsFn(Protocol): - def __call__( - self, state: ICState - ) -> Tuple[jnp.ndarray, jnp.ndarray, jnp.ndarray]: - ... +if TYPE_CHECKING: + from ic_core.graph import ICState @runtime_checkable @@ -31,6 +26,7 @@ def __call__(self, ptr: jnp.ndarray) -> Tuple[jnp.ndarray, jnp.ndarray]: @runtime_checkable class AllocPlanFn(Protocol): + # dataflow-bundle: state, pairs, count def __call__( self, state: ICState, pairs: jnp.ndarray, count: jnp.ndarray ) -> Tuple[ICState, jnp.ndarray, jnp.ndarray, jnp.ndarray, jnp.ndarray]: @@ -39,6 +35,7 @@ def __call__( @runtime_checkable class ApplyTemplatePlannedFn(Protocol): + # dataflow-bundle: state, node_a, node_b, template_id, alloc_ids def __call__( self, state: ICState, @@ -64,6 +61,7 @@ def __call__(self, state: ICState) -> ICState: @runtime_checkable class RuleForTypesFn(Protocol): + # dataflow-bundle: type_a, type_b def __call__( self, type_a: jnp.ndarray, type_b: jnp.ndarray ) -> jnp.ndarray: @@ -72,18 +70,21 @@ def __call__( @runtime_checkable class ApplyAnnFn(Protocol): + # dataflow-bundle: state, node_a, node_b def __call__(self, state: ICState, node_a, node_b) -> ICState: ... @runtime_checkable class ApplyEraseFn(Protocol): + # dataflow-bundle: state, node_a, node_b def __call__(self, state: ICState, node_a, node_b) -> ICState: ... @runtime_checkable class ApplyCommuteFn(Protocol): + # dataflow-bundle: state, node_a, node_b def __call__(self, state: ICState, node_a, node_b) -> ICState: ... @@ -92,16 +93,12 @@ def __call__(self, state: ICState, node_a, node_b) -> ICState: class ApplyTemplateFn(Protocol): def __call__( self, - state: ICState, - node_a: jnp.ndarray, - node_b: jnp.ndarray, - template_id: jnp.ndarray, + args: TemplateApplyArgs, ) -> ICState: ... __all__ = [ - "CompactPairsFn", "CompactPairsResultFn", "DecodePortFn", "AllocPlanFn", diff --git a/src/ic_core/rules.py b/src/ic_core/rules.py index 98dee22..8c4665a 100644 --- a/src/ic_core/rules.py +++ b/src/ic_core/rules.py @@ -22,6 +22,7 @@ decode_port, ) from ic_core.config import ICRuleConfig +from ic_core.bundles import TemplateApplyArgs RULE_ALLOC_ANNIHILATE = jnp.uint32(0) RULE_ALLOC_ERASE = jnp.uint32(2) @@ -381,16 +382,16 @@ def _do(s): def ic_apply_template( - state: ICState, - node_a: jnp.ndarray, - node_b: jnp.ndarray, - template_id: jnp.ndarray, + args: TemplateApplyArgs, *, apply_annihilate_fn=ic_apply_annihilate, apply_erase_fn=ic_apply_erase, apply_commute_fn=ic_apply_commute, ) -> ICState: - template_id = template_id.astype(jnp.int32) + state = args.state + node_a = args.node_a + node_b = args.node_b + template_id = args.template_id.astype(jnp.int32) def _noop(s): return s @@ -428,17 +429,14 @@ def ic_apply_template_planned_cfg( def ic_apply_template_cfg( - state: ICState, - node_a: jnp.ndarray, - node_b: jnp.ndarray, - template_id: jnp.ndarray, + args: TemplateApplyArgs, *, cfg: ICRuleConfig | None = None, ) -> ICState: """Interface/Control wrapper for apply_template with DI bundle.""" if cfg is None: cfg = DEFAULT_RULE_CONFIG - return cfg.apply_template_fn(state, node_a, node_b, template_id) + return cfg.apply_template_fn(args) def _alloc_plan( diff --git a/src/ic_core/types.py b/src/ic_core/types.py index 2f6d262..5af8f6b 100644 --- a/src/ic_core/types.py +++ b/src/ic_core/types.py @@ -31,12 +31,32 @@ PORT_AUX_LEFT, PORT_AUX_RIGHT, ) -from ic_core.engine import ICRewriteStats +from ic_core.engine import ICRewriteStats, DEFAULT_ENGINE_RESOLVED +from ic_core.facade import ( + DEFAULT_RUNTIME_CONFIG, + DEFAULT_RUNTIME_RESOLVED, + DEFAULT_RUNTIME_OPS, + ICRuntimeOps, +) +from ic_core.bundles import WireEndpoints, WirePtrPair, WireStarEndpoints +from ic_core.config import ( + ICWireConfig, + ICScanConfig, + ICGraphResolved, + ICEngineResolved, + ICExecutionConfig, + ICExecutionResolved, + ICRuntimeConfig, + ICRuntimeResolved, + DEFAULT_WIRE_CONFIG, + DEFAULT_SCAN_CONFIG, + DEFAULT_GRAPH_RESOLVED, +) from prism_core.compact import ( CompactResult, CompactConfig, - DEFAULT_COMPACT_CONFIG, ) +from ic_core.config import DEFAULT_IC_COMPACT_CONFIG from prism_core.alloc import AllocConfig, DEFAULT_ALLOC_CONFIG __all__ = [ @@ -58,9 +78,28 @@ "_host_bool_value", "ICState", "ICRewriteStats", + "WireEndpoints", + "WirePtrPair", + "WireStarEndpoints", + "ICWireConfig", + "ICScanConfig", + "ICGraphResolved", + "ICEngineResolved", + "ICExecutionConfig", + "ICExecutionResolved", + "ICRuntimeConfig", + "ICRuntimeResolved", + "DEFAULT_WIRE_CONFIG", + "DEFAULT_SCAN_CONFIG", + "DEFAULT_GRAPH_RESOLVED", + "DEFAULT_ENGINE_RESOLVED", + "DEFAULT_RUNTIME_CONFIG", + "DEFAULT_RUNTIME_RESOLVED", + "DEFAULT_RUNTIME_OPS", + "ICRuntimeOps", "CompactResult", "CompactConfig", - "DEFAULT_COMPACT_CONFIG", + "DEFAULT_IC_COMPACT_CONFIG", "AllocConfig", "DEFAULT_ALLOC_CONFIG", "TYPE_FREE", diff --git a/src/prism_bsp/arena_step.py b/src/prism_bsp/arena_step.py index 9b3f86f..ccd7576 100644 --- a/src/prism_bsp/arena_step.py +++ b/src/prism_bsp/arena_step.py @@ -1,4 +1,5 @@ -from dataclasses import replace +from dataclasses import asdict, dataclass, replace +from typing import Callable import jax import jax.numpy as jnp @@ -7,7 +8,12 @@ from prism_core import jax_safe as _jax_safe from prism_core.errors import PrismPolicyBindingError from prism_core.di import call_with_optional_kwargs -from prism_core.guards import resolve_safe_gather_fn, resolve_safe_gather_value_fn +from prism_core.guards import ( + GuardConfig, + resolve_safe_gather_fn, + resolve_safe_gather_value_fn, +) +from prism_core.protocols import SafeGatherFn, SafeGatherValueFn from prism_core.safety import ( PolicyBinding, PolicyMode, @@ -20,6 +26,8 @@ from prism_vm_core.guards import _guard_max from prism_vm_core.hashes import _arena_root_hash_host from prism_vm_core.ontology import OP_ADD, OP_SUC, OP_ZERO +from prism_vm_core.protocols import OpSortWithPermFn +from prism_vm_core.structures import Arena from prism_bsp.space import ( RANK_HOT, @@ -40,13 +48,44 @@ from prism_bsp.config import ( ArenaInteractConfig, ArenaCycleConfig, + ArenaSortConfig, + DEFAULT_ARENA_SORT_CONFIG, DEFAULT_ARENA_INTERACT_CONFIG, DEFAULT_ARENA_CYCLE_CONFIG, + SwizzleWithPermFns, + SwizzleWithPermFnsBound, ) _TEST_GUARDS = _jax_safe.TEST_GUARDS +_DEFAULT_SWIZZLE_WITH_PERM_FNS = SwizzleWithPermFnsBound( + with_perm=op_sort_and_swizzle_with_perm, + morton_with_perm=op_sort_and_swizzle_morton_with_perm, + blocked_with_perm=op_sort_and_swizzle_blocked_with_perm, + hierarchical_with_perm=op_sort_and_swizzle_hierarchical_with_perm, + servo_with_perm=op_sort_and_swizzle_servo_with_perm, +) + +_DEFAULT_SWIZZLE_WITH_PERM_VALUE_FNS = SwizzleWithPermFnsBound( + with_perm=op_sort_and_swizzle_with_perm_value, + morton_with_perm=op_sort_and_swizzle_morton_with_perm_value, + blocked_with_perm=op_sort_and_swizzle_blocked_with_perm_value, + hierarchical_with_perm=op_sort_and_swizzle_hierarchical_with_perm_value, + servo_with_perm=op_sort_and_swizzle_servo_with_perm_value, +) + + +@dataclass(frozen=True, slots=True) +class SwizzleWithPermFnsBundle: + """Local bundle documenting swizzle-with-perm forwarding group.""" + + with_perm: OpSortWithPermFn + morton_with_perm: OpSortWithPermFn + blocked_with_perm: OpSortWithPermFn + hierarchical_with_perm: OpSortWithPermFn + servo_with_perm: OpSortWithPermFn + def _op_interact_core( arena, @@ -84,6 +123,9 @@ def _op_interact_core( new_a2 = jnp.where(mask_zero, y_a2, a2) # Second: allocation for suc-case. + # Spillway bound (2:1): each active hot-add emits at most one net new node, + # so spillway usage is bounded by the active region size. See in/in-29.md + # "Ledger as Terminal Arena (Settlement)" and agda/Prism/Spillway.agda. available = jnp.maximum(cap - arena.count, 0) spawn = mask_suc.astype(jnp.int32) prefix = jnp.cumsum(spawn) @@ -374,30 +416,27 @@ def op_interact_bound_cfg( def cycle_core( arena, root_ptr, - do_sort=True, - use_morton=False, - block_size=None, - morton=None, - l2_block_size=None, - l1_block_size=None, - do_global=False, + sort_cfg: ArenaSortConfig, *, op_rank_fn=op_rank, servo_enabled_fn=_servo_enabled, servo_update_fn=_servo_update, op_morton_fn=op_morton, - op_sort_and_swizzle_with_perm_fn=op_sort_and_swizzle_with_perm, - op_sort_and_swizzle_morton_with_perm_fn=op_sort_and_swizzle_morton_with_perm, - op_sort_and_swizzle_blocked_with_perm_fn=op_sort_and_swizzle_blocked_with_perm, - op_sort_and_swizzle_hierarchical_with_perm_fn=op_sort_and_swizzle_hierarchical_with_perm, - op_sort_and_swizzle_servo_with_perm_fn=op_sort_and_swizzle_servo_with_perm, - safe_gather_fn=_jax_safe.safe_gather_1d, - guard_cfg=None, + swizzle_with_perm_fns: SwizzleWithPermFnsBound = _DEFAULT_SWIZZLE_WITH_PERM_FNS, + safe_gather_fn: SafeGatherFn = _jax_safe.safe_gather_1d, + guard_cfg: GuardConfig | None = None, arena_root_hash_fn=_arena_root_hash_host, damage_tile_size_fn=_damage_tile_size, damage_metrics_update_fn=_damage_metrics_update, op_interact_fn=op_interact, ): + do_sort = sort_cfg.do_sort + use_morton = sort_cfg.use_morton + block_size = sort_cfg.block_size + morton = sort_cfg.morton + l2_block_size = sort_cfg.l2_block_size + l1_block_size = sort_cfg.l1_block_size + do_global = sort_cfg.do_global # BSPˢ is renormalization only: must preserve denotation after q (m3). # BSPᵗ controls when identity is created via commit_stratum barriers. # COMMUTES: BSPᵗ ⟂ BSPˢ [test: tests/test_arena_denotation_invariance.py::test_arena_denotation_invariance_random_suite] @@ -406,6 +445,7 @@ def cycle_core( arena = op_rank_fn(arena) root_arr = jnp.asarray(root_ptr, dtype=jnp.int32) if do_sort: + swizzle = swizzle_with_perm_fns pre_hash = arena_root_hash_fn(arena, root_arr) morton_arr = None if servo_enabled_fn(): @@ -413,7 +453,7 @@ def cycle_core( morton_arr = morton if morton is not None else op_morton_fn(arena) servo_mask = arena.servo[0] arena, inv_perm = call_with_optional_kwargs( - op_sort_and_swizzle_servo_with_perm_fn, + swizzle.servo_with_perm, {"guard_cfg": guard_cfg, "safe_gather_fn": safe_gather_fn}, arena, morton_arr, @@ -428,7 +468,7 @@ def cycle_core( if l1_block_size is None: l1_block_size = l2_block_size arena, inv_perm = call_with_optional_kwargs( - op_sort_and_swizzle_hierarchical_with_perm_fn, + swizzle.hierarchical_with_perm, {"guard_cfg": guard_cfg, "safe_gather_fn": safe_gather_fn}, arena, l2_block_size, @@ -438,7 +478,7 @@ def cycle_core( ) elif block_size is not None: arena, inv_perm = call_with_optional_kwargs( - op_sort_and_swizzle_blocked_with_perm_fn, + swizzle.blocked_with_perm, {"guard_cfg": guard_cfg, "safe_gather_fn": safe_gather_fn}, arena, block_size, @@ -446,14 +486,14 @@ def cycle_core( ) elif morton_arr is not None: arena, inv_perm = call_with_optional_kwargs( - op_sort_and_swizzle_morton_with_perm_fn, + swizzle.morton_with_perm, {"guard_cfg": guard_cfg, "safe_gather_fn": safe_gather_fn}, arena, morton_arr, ) else: arena, inv_perm = call_with_optional_kwargs( - op_sort_and_swizzle_with_perm_fn, + swizzle.with_perm, {"guard_cfg": guard_cfg, "safe_gather_fn": safe_gather_fn}, arena, ) @@ -477,31 +517,28 @@ def cycle_core_value( arena, root_ptr, policy_value, - do_sort=True, - use_morton=False, - block_size=None, - morton=None, - l2_block_size=None, - l1_block_size=None, - do_global=False, + sort_cfg: ArenaSortConfig, *, op_rank_fn=op_rank, servo_enabled_fn=_servo_enabled, servo_update_fn=_servo_update, op_morton_fn=op_morton, - op_sort_and_swizzle_with_perm_value_fn=op_sort_and_swizzle_with_perm_value, - op_sort_and_swizzle_morton_with_perm_value_fn=op_sort_and_swizzle_morton_with_perm_value, - op_sort_and_swizzle_blocked_with_perm_value_fn=op_sort_and_swizzle_blocked_with_perm_value, - op_sort_and_swizzle_hierarchical_with_perm_value_fn=op_sort_and_swizzle_hierarchical_with_perm_value, - op_sort_and_swizzle_servo_with_perm_value_fn=op_sort_and_swizzle_servo_with_perm_value, - safe_gather_value_fn=_jax_safe.safe_gather_1d_value, - guard_cfg=None, + swizzle_with_perm_fns: SwizzleWithPermFnsBound = _DEFAULT_SWIZZLE_WITH_PERM_VALUE_FNS, + safe_gather_value_fn: SafeGatherValueFn = _jax_safe.safe_gather_1d_value, + guard_cfg: GuardConfig | None = None, arena_root_hash_fn=_arena_root_hash_host, damage_tile_size_fn=_damage_tile_size, damage_metrics_update_fn=_damage_metrics_update, - op_interact_value_fn=op_interact_value, + op_interact_value_fn: Callable[..., Arena] = op_interact_value, ): """Run one BSP cycle with policy_value as data (JAX value).""" + do_sort = sort_cfg.do_sort + use_morton = sort_cfg.use_morton + block_size = sort_cfg.block_size + morton = sort_cfg.morton + l2_block_size = sort_cfg.l2_block_size + l1_block_size = sort_cfg.l1_block_size + do_global = sort_cfg.do_global safe_gather_value_fn_guarded = resolve_safe_gather_value_fn( safe_gather_value_fn=safe_gather_value_fn, guard_cfg=guard_cfg, @@ -509,6 +546,7 @@ def cycle_core_value( arena = op_rank_fn(arena) root_arr = jnp.asarray(root_ptr, dtype=jnp.int32) if do_sort: + swizzle = swizzle_with_perm_fns pre_hash = arena_root_hash_fn(arena, root_arr) morton_arr = None if servo_enabled_fn(): @@ -516,7 +554,7 @@ def cycle_core_value( morton_arr = morton if morton is not None else op_morton_fn(arena) servo_mask = arena.servo[0] arena, inv_perm = call_with_optional_kwargs( - op_sort_and_swizzle_servo_with_perm_value_fn, + swizzle.servo_with_perm, {"guard_cfg": guard_cfg, "safe_gather_value_fn": safe_gather_value_fn}, arena, morton_arr, @@ -532,7 +570,7 @@ def cycle_core_value( if l1_block_size is None: l1_block_size = l2_block_size arena, inv_perm = call_with_optional_kwargs( - op_sort_and_swizzle_hierarchical_with_perm_value_fn, + swizzle.hierarchical_with_perm, {"guard_cfg": guard_cfg, "safe_gather_value_fn": safe_gather_value_fn}, arena, l2_block_size, @@ -543,7 +581,7 @@ def cycle_core_value( ) elif block_size is not None: arena, inv_perm = call_with_optional_kwargs( - op_sort_and_swizzle_blocked_with_perm_value_fn, + swizzle.blocked_with_perm, {"guard_cfg": guard_cfg, "safe_gather_value_fn": safe_gather_value_fn}, arena, block_size, @@ -552,7 +590,7 @@ def cycle_core_value( ) elif morton_arr is not None: arena, inv_perm = call_with_optional_kwargs( - op_sort_and_swizzle_morton_with_perm_value_fn, + swizzle.morton_with_perm, {"guard_cfg": guard_cfg, "safe_gather_value_fn": safe_gather_value_fn}, arena, morton_arr, @@ -560,7 +598,7 @@ def cycle_core_value( ) else: arena, inv_perm = call_with_optional_kwargs( - op_sort_and_swizzle_with_perm_value_fn, + swizzle.with_perm, {"guard_cfg": guard_cfg, "safe_gather_value_fn": safe_gather_value_fn}, arena, policy_value, @@ -586,25 +624,15 @@ def cycle_core_value( def cycle( arena, root_ptr, - do_sort=True, - use_morton=False, - block_size=None, - morton=None, - l2_block_size=None, - l1_block_size=None, - do_global=False, *, + sort_cfg: ArenaSortConfig = DEFAULT_ARENA_SORT_CONFIG, op_rank_fn=op_rank, servo_enabled_fn=_servo_enabled, servo_update_fn=_servo_update, op_morton_fn=op_morton, - op_sort_and_swizzle_with_perm_fn=op_sort_and_swizzle_with_perm, - op_sort_and_swizzle_morton_with_perm_fn=op_sort_and_swizzle_morton_with_perm, - op_sort_and_swizzle_blocked_with_perm_fn=op_sort_and_swizzle_blocked_with_perm, - op_sort_and_swizzle_hierarchical_with_perm_fn=op_sort_and_swizzle_hierarchical_with_perm, - op_sort_and_swizzle_servo_with_perm_fn=op_sort_and_swizzle_servo_with_perm, - safe_gather_fn=_jax_safe.safe_gather_1d, - guard_cfg=None, + swizzle_with_perm_fns: SwizzleWithPermFnsBound = _DEFAULT_SWIZZLE_WITH_PERM_FNS, + safe_gather_fn: SafeGatherFn = _jax_safe.safe_gather_1d, + guard_cfg: GuardConfig | None = None, arena_root_hash_fn=_arena_root_hash_host, damage_tile_size_fn=_damage_tile_size, damage_metrics_update_fn=_damage_metrics_update, @@ -613,22 +641,12 @@ def cycle( return cycle_core( arena, root_ptr, - do_sort=do_sort, - use_morton=use_morton, - block_size=block_size, - morton=morton, - l2_block_size=l2_block_size, - l1_block_size=l1_block_size, - do_global=do_global, + sort_cfg=sort_cfg, op_rank_fn=op_rank_fn, servo_enabled_fn=servo_enabled_fn, servo_update_fn=servo_update_fn, op_morton_fn=op_morton_fn, - op_sort_and_swizzle_with_perm_fn=op_sort_and_swizzle_with_perm_fn, - op_sort_and_swizzle_morton_with_perm_fn=op_sort_and_swizzle_morton_with_perm_fn, - op_sort_and_swizzle_blocked_with_perm_fn=op_sort_and_swizzle_blocked_with_perm_fn, - op_sort_and_swizzle_hierarchical_with_perm_fn=op_sort_and_swizzle_hierarchical_with_perm_fn, - op_sort_and_swizzle_servo_with_perm_fn=op_sort_and_swizzle_servo_with_perm_fn, + swizzle_with_perm_fns=swizzle_with_perm_fns, safe_gather_fn=safe_gather_fn, guard_cfg=guard_cfg, arena_root_hash_fn=arena_root_hash_fn, @@ -642,50 +660,30 @@ def cycle_value( arena, root_ptr, policy_value, - do_sort=True, - use_morton=False, - block_size=None, - morton=None, - l2_block_size=None, - l1_block_size=None, - do_global=False, *, + sort_cfg: ArenaSortConfig = DEFAULT_ARENA_SORT_CONFIG, op_rank_fn=op_rank, servo_enabled_fn=_servo_enabled, servo_update_fn=_servo_update, op_morton_fn=op_morton, - op_sort_and_swizzle_with_perm_value_fn=op_sort_and_swizzle_with_perm_value, - op_sort_and_swizzle_morton_with_perm_value_fn=op_sort_and_swizzle_morton_with_perm_value, - op_sort_and_swizzle_blocked_with_perm_value_fn=op_sort_and_swizzle_blocked_with_perm_value, - op_sort_and_swizzle_hierarchical_with_perm_value_fn=op_sort_and_swizzle_hierarchical_with_perm_value, - op_sort_and_swizzle_servo_with_perm_value_fn=op_sort_and_swizzle_servo_with_perm_value, - safe_gather_value_fn=_jax_safe.safe_gather_1d_value, - guard_cfg=None, + swizzle_with_perm_fns: SwizzleWithPermFnsBound = _DEFAULT_SWIZZLE_WITH_PERM_VALUE_FNS, + safe_gather_value_fn: SafeGatherValueFn = _jax_safe.safe_gather_1d_value, + guard_cfg: GuardConfig | None = None, arena_root_hash_fn=_arena_root_hash_host, damage_tile_size_fn=_damage_tile_size, damage_metrics_update_fn=_damage_metrics_update, - op_interact_value_fn=op_interact_value, + op_interact_value_fn: Callable[..., Arena] = op_interact_value, ): return cycle_core_value( arena, root_ptr, policy_value, - do_sort=do_sort, - use_morton=use_morton, - block_size=block_size, - morton=morton, - l2_block_size=l2_block_size, - l1_block_size=l1_block_size, - do_global=do_global, + sort_cfg=sort_cfg, op_rank_fn=op_rank_fn, servo_enabled_fn=servo_enabled_fn, servo_update_fn=servo_update_fn, op_morton_fn=op_morton_fn, - op_sort_and_swizzle_with_perm_value_fn=op_sort_and_swizzle_with_perm_value_fn, - op_sort_and_swizzle_morton_with_perm_value_fn=op_sort_and_swizzle_morton_with_perm_value_fn, - op_sort_and_swizzle_blocked_with_perm_value_fn=op_sort_and_swizzle_blocked_with_perm_value_fn, - op_sort_and_swizzle_hierarchical_with_perm_value_fn=op_sort_and_swizzle_hierarchical_with_perm_value_fn, - op_sort_and_swizzle_servo_with_perm_value_fn=op_sort_and_swizzle_servo_with_perm_value_fn, + swizzle_with_perm_fns=swizzle_with_perm_fns, safe_gather_value_fn=safe_gather_value_fn, guard_cfg=guard_cfg, arena_root_hash_fn=arena_root_hash_fn, @@ -697,14 +695,8 @@ def cycle_value( def cycle_cfg( arena, root_ptr, - do_sort=True, - use_morton=False, - block_size=None, - morton=None, - l2_block_size=None, - l1_block_size=None, - do_global=False, *, + sort_cfg: ArenaSortConfig = DEFAULT_ARENA_SORT_CONFIG, cfg: ArenaCycleConfig = DEFAULT_ARENA_CYCLE_CONFIG, ): """Interface/Control wrapper for cycle_core with DI bundle.""" @@ -732,6 +724,20 @@ def cycle_cfg( cfg.op_sort_and_swizzle_servo_with_perm_fn or op_sort_and_swizzle_servo_with_perm ) + if cfg.swizzle_with_perm_fns is not None: + swizzle_bundle = cfg.swizzle_with_perm_fns + if swizzle_bundle.with_perm is not None: + op_sort_and_swizzle_with_perm_fn = swizzle_bundle.with_perm + if swizzle_bundle.morton_with_perm is not None: + op_sort_and_swizzle_morton_with_perm_fn = swizzle_bundle.morton_with_perm + if swizzle_bundle.blocked_with_perm is not None: + op_sort_and_swizzle_blocked_with_perm_fn = swizzle_bundle.blocked_with_perm + if swizzle_bundle.hierarchical_with_perm is not None: + op_sort_and_swizzle_hierarchical_with_perm_fn = ( + swizzle_bundle.hierarchical_with_perm + ) + if swizzle_bundle.servo_with_perm is not None: + op_sort_and_swizzle_servo_with_perm_fn = swizzle_bundle.servo_with_perm safe_gather_policy = cfg.safe_gather_policy safe_gather_policy_value = cfg.safe_gather_policy_value if cfg.policy_binding is not None: @@ -768,21 +774,35 @@ def cycle_cfg( if safe_gather_policy_value is not None: if safe_gather_value_fn is None: safe_gather_value_fn = _jax_safe.safe_gather_1d_value - if cfg.op_sort_and_swizzle_with_perm_fn is None: + if cfg.swizzle_with_perm_value_fns is not None: + swizzle_value_bundle = cfg.swizzle_with_perm_value_fns + if swizzle_value_bundle.with_perm is not None: + op_sort_and_swizzle_with_perm_fn = swizzle_value_bundle.with_perm + if swizzle_value_bundle.morton_with_perm is not None: + op_sort_and_swizzle_morton_with_perm_fn = swizzle_value_bundle.morton_with_perm + if swizzle_value_bundle.blocked_with_perm is not None: + op_sort_and_swizzle_blocked_with_perm_fn = swizzle_value_bundle.blocked_with_perm + if swizzle_value_bundle.hierarchical_with_perm is not None: + op_sort_and_swizzle_hierarchical_with_perm_fn = ( + swizzle_value_bundle.hierarchical_with_perm + ) + if swizzle_value_bundle.servo_with_perm is not None: + op_sort_and_swizzle_servo_with_perm_fn = swizzle_value_bundle.servo_with_perm + if cfg.op_sort_and_swizzle_with_perm_fn is None and cfg.swizzle_with_perm_fns is None: op_sort_and_swizzle_with_perm_fn = op_sort_and_swizzle_with_perm_value - if cfg.op_sort_and_swizzle_morton_with_perm_fn is None: + if cfg.op_sort_and_swizzle_morton_with_perm_fn is None and cfg.swizzle_with_perm_fns is None: op_sort_and_swizzle_morton_with_perm_fn = ( op_sort_and_swizzle_morton_with_perm_value ) - if cfg.op_sort_and_swizzle_blocked_with_perm_fn is None: + if cfg.op_sort_and_swizzle_blocked_with_perm_fn is None and cfg.swizzle_with_perm_fns is None: op_sort_and_swizzle_blocked_with_perm_fn = ( op_sort_and_swizzle_blocked_with_perm_value ) - if cfg.op_sort_and_swizzle_hierarchical_with_perm_fn is None: + if cfg.op_sort_and_swizzle_hierarchical_with_perm_fn is None and cfg.swizzle_with_perm_fns is None: op_sort_and_swizzle_hierarchical_with_perm_fn = ( op_sort_and_swizzle_hierarchical_with_perm_value ) - if cfg.op_sort_and_swizzle_servo_with_perm_fn is None: + if cfg.op_sort_and_swizzle_servo_with_perm_fn is None and cfg.swizzle_with_perm_fns is None: op_sort_and_swizzle_servo_with_perm_fn = ( op_sort_and_swizzle_servo_with_perm_value ) @@ -797,27 +817,25 @@ def cycle_cfg( op_interact_fn = op_interact_value else: op_interact_fn = lambda a: op_interact(a, safe_gather_fn=safe_gather_fn) + swizzle_bundle = SwizzleWithPermFnsBundle( + with_perm=op_sort_and_swizzle_with_perm_fn, + morton_with_perm=op_sort_and_swizzle_morton_with_perm_fn, + blocked_with_perm=op_sort_and_swizzle_blocked_with_perm_fn, + hierarchical_with_perm=op_sort_and_swizzle_hierarchical_with_perm_fn, + servo_with_perm=op_sort_and_swizzle_servo_with_perm_fn, + ) + swizzle_with_perm_fns = SwizzleWithPermFnsBound(**asdict(swizzle_bundle)) if safe_gather_policy_value is not None and safe_gather_value_fn is not None: return cycle_value( arena, root_ptr, safe_gather_policy_value, - do_sort=do_sort, - use_morton=use_morton, - block_size=block_size, - morton=morton, - l2_block_size=l2_block_size, - l1_block_size=l1_block_size, - do_global=do_global, + sort_cfg=sort_cfg, op_rank_fn=op_rank_fn, servo_enabled_fn=servo_enabled_fn, servo_update_fn=servo_update_fn, op_morton_fn=op_morton_fn, - op_sort_and_swizzle_with_perm_value_fn=op_sort_and_swizzle_with_perm_fn, - op_sort_and_swizzle_morton_with_perm_value_fn=op_sort_and_swizzle_morton_with_perm_fn, - op_sort_and_swizzle_blocked_with_perm_value_fn=op_sort_and_swizzle_blocked_with_perm_fn, - op_sort_and_swizzle_hierarchical_with_perm_value_fn=op_sort_and_swizzle_hierarchical_with_perm_fn, - op_sort_and_swizzle_servo_with_perm_value_fn=op_sort_and_swizzle_servo_with_perm_fn, + swizzle_with_perm_fns=swizzle_with_perm_fns, safe_gather_value_fn=safe_gather_value_fn, guard_cfg=cfg.guard_cfg, arena_root_hash_fn=arena_root_hash_fn, @@ -828,22 +846,12 @@ def cycle_cfg( return cycle_core( arena, root_ptr, - do_sort=do_sort, - use_morton=use_morton, - block_size=block_size, - morton=morton, - l2_block_size=l2_block_size, - l1_block_size=l1_block_size, - do_global=do_global, + sort_cfg=sort_cfg, op_rank_fn=op_rank_fn, servo_enabled_fn=servo_enabled_fn, servo_update_fn=servo_update_fn, op_morton_fn=op_morton_fn, - op_sort_and_swizzle_with_perm_fn=op_sort_and_swizzle_with_perm_fn, - op_sort_and_swizzle_morton_with_perm_fn=op_sort_and_swizzle_morton_with_perm_fn, - op_sort_and_swizzle_blocked_with_perm_fn=op_sort_and_swizzle_blocked_with_perm_fn, - op_sort_and_swizzle_hierarchical_with_perm_fn=op_sort_and_swizzle_hierarchical_with_perm_fn, - op_sort_and_swizzle_servo_with_perm_fn=op_sort_and_swizzle_servo_with_perm_fn, + swizzle_with_perm_fns=swizzle_with_perm_fns, safe_gather_fn=safe_gather_fn, arena_root_hash_fn=arena_root_hash_fn, damage_tile_size_fn=damage_tile_size_fn, @@ -856,14 +864,8 @@ def cycle_bound_cfg( arena, root_ptr, policy_binding: PolicyBinding, - do_sort=True, - use_morton=False, - block_size=None, - morton=None, - l2_block_size=None, - l1_block_size=None, - do_global=False, *, + sort_cfg: ArenaSortConfig = DEFAULT_ARENA_SORT_CONFIG, cfg: ArenaCycleConfig | None = None, ): """PolicyBinding-required wrapper for cycle_cfg.""" @@ -888,13 +890,7 @@ def cycle_bound_cfg( return cycle_cfg( arena, root_ptr, - do_sort=do_sort, - use_morton=use_morton, - block_size=block_size, - morton=morton, - l2_block_size=l2_block_size, - l1_block_size=l1_block_size, - do_global=do_global, + sort_cfg=sort_cfg, cfg=cfg, ) diff --git a/src/prism_bsp/cnf2.py b/src/prism_bsp/cnf2.py index ad4c3ef..d929562 100644 --- a/src/prism_bsp/cnf2.py +++ b/src/prism_bsp/cnf2.py @@ -1,8 +1,10 @@ +from dataclasses import dataclass, replace +from functools import partial +from typing import Callable + import jax import jax.numpy as jnp from jax import lax -from functools import partial -from dataclasses import replace from prism_core import jax_safe as _jax_safe from prism_core.di import call_with_optional_kwargs @@ -12,25 +14,29 @@ ) from prism_core.compact import scatter_compacted_ids from prism_core.safety import ( - PolicyBinding, PolicyMode, DEFAULT_SAFETY_POLICY, POLICY_VALUE_DEFAULT, PolicyValue, SafetyPolicy, - resolve_policy_binding, require_static_policy, require_value_policy, ) from prism_core.errors import PrismPolicyBindingError from prism_core.modes import ValidateMode from prism_coord.coord import coord_xor_batch -from prism_ledger.intern import intern_nodes -from prism_ledger.config import InternConfig -from prism_metrics.metrics import _cnf2_metrics_enabled, _cnf2_metrics_update +from prism_ledger.intern import intern_nodes, intern_nodes_state +from prism_ledger.config import InternConfig, DEFAULT_INTERN_CONFIG +from prism_ledger.index import LedgerState, derive_ledger_state from prism_bsp.config import ( Cnf2Config, Cnf2BoundConfig, + Cnf2StaticBoundConfig, + Cnf2ValueBoundConfig, + Cnf2CommitInputs, + Cnf2InternInputs, + Cnf2RuntimeFns, + DEFAULT_CNF2_RUNTIME_FNS, resolve_cnf2_inputs, resolve_cnf2_candidate_inputs, resolve_cnf2_intern_inputs, @@ -41,6 +47,7 @@ from prism_semantics.commit import ( _identity_q, apply_q, + apply_q_ok, commit_stratum, commit_stratum_bound, commit_stratum_static, @@ -53,7 +60,6 @@ _host_int_value, _provisional_ids, ) -from prism_vm_core.gating import _cnf2_enabled, _cnf2_slot1_enabled from prism_vm_core.guards import _guards_enabled from prism_vm_core.hashes import _ledger_roots_hash_host from prism_vm_core.ontology import ( @@ -66,7 +72,7 @@ OP_ZERO, ZERO_PTR, ) -from prism_vm_core.structures import CandidateBuffer, Stratum, NodeBatch +from prism_vm_core.structures import CandidateBuffer, Ledger, Stratum, NodeBatch from prism_vm_core.protocols import ( ApplyQFn, CandidateIndicesFn, @@ -78,15 +84,38 @@ HostIntValueFn, IdentityQFn, InternFn, + InternStateFn, LedgerRootsHashFn, NodeBatchFn, ScatterDropFn, + SafeGatherOkFn, + SafeGatherOkValueFn, ) EMPTY_COMMIT_OPTIONAL: dict = {} +# dataflow-bundle: commit_stratum_fn, intern_fn +# CNF-2 commit/intern pair forwarded through DI resolution. +# dataflow-bundle: _frontier, _ledger, _post_ids +# root-assertion guard hook bundle (debug-only) +# dataflow-bundle: next_frontier, post_ids +# root-assertion bundle (debug-only) +@dataclass(frozen=True) +class _RootAssertNoopArgs: + ledger: object + frontier: object + post_ids: object + + +@dataclass(frozen=True) +class _RootAssertArgs: + next_frontier: object + post_ids: object + + def _assert_roots_noop(_ledger, _frontier, _post_ids): + _ = _RootAssertNoopArgs(_ledger, _frontier, _post_ids) return None @@ -98,6 +127,80 @@ def _node_batch(op, a1, a2) -> NodeBatch: return NodeBatch(op=op, a1=a1, a2=a2) +def _resolve_intern_cfg( + cfg: Cnf2Config | None, intern_cfg: InternConfig | None +) -> InternConfig: + if intern_cfg is None and cfg is not None and cfg.intern_cfg is not None: + intern_cfg = cfg.intern_cfg + if intern_cfg is None: + intern_cfg = DEFAULT_INTERN_CONFIG + return intern_cfg + + +def _state_with_ledger(state: LedgerState, ledger) -> LedgerState: + if ledger is state.ledger: + return state + return LedgerState( + ledger=ledger, + index=state.index, + op_buckets_full_range=state.op_buckets_full_range, + ) + + +def _coord_xor_batch_state( + state: LedgerState, + left_ids, + right_ids, + *, + coord_xor_batch_fn: CoordXorBatchFn, + intern_cfg: InternConfig, +) -> tuple[jnp.ndarray, LedgerState]: + ids, ledger = coord_xor_batch_fn(state.ledger, left_ids, right_ids) + if ledger is state.ledger: + return ids, state + return ids, derive_ledger_state( + ledger, op_buckets_full_range=intern_cfg.op_buckets_full_range + ) + + +def _commit_stratum_state( + state: LedgerState, + stratum: Stratum, + *, + commit_fns: Cnf2CommitInputs, + commit_optional: dict, + intern_cfg: InternConfig, + **kwargs, +): + commit_stratum_fn = commit_fns.commit_stratum_fn + intern_fn = commit_fns.intern_fn + + def _intern_with_index(ledger, batch_or_ops, a1=None, a2=None): + return call_with_optional_kwargs( + intern_fn, + {"ledger_index": state.index}, + ledger, + batch_or_ops, + a1, + a2, + ) + + ledger, canon_ids, q_map = call_with_optional_kwargs( + commit_stratum_fn, + commit_optional, + state.ledger, + stratum, + intern_fn=_intern_with_index, + **kwargs, + ) + if ledger is state.ledger: + return state, canon_ids, q_map + new_state = derive_ledger_state( + ledger, op_buckets_full_range=intern_cfg.op_buckets_full_range + ) + return new_state, canon_ids, q_map + + def emit_candidates(ledger, frontier_ids): num_frontier = frontier_ids.shape[0] size = num_frontier * 2 @@ -302,28 +405,64 @@ def scatter_compacted_ids_cfg( ) +def _ledger_index_is_bound(fn: Callable[..., object]) -> bool: + if getattr(fn, "_prism_ledger_index_bound", False): + return True + if isinstance(fn, partial): + keywords = fn.keywords or {} + return "ledger_index" in keywords + return False + + +def _bind_intern_with_index( + ledger: Ledger, + intern_inputs: Cnf2InternInputs, + *, + intern_cfg: InternConfig | None, +) -> Cnf2InternInputs: + if _ledger_index_is_bound(intern_inputs.intern_fn): + return intern_inputs + cfg = intern_cfg or DEFAULT_INTERN_CONFIG + ledger_index = derive_ledger_state( + ledger, op_buckets_full_range=cfg.op_buckets_full_range + ).index + + def _intern_with_index(ledger, batch_or_ops, a1=None, a2=None): + return call_with_optional_kwargs( + intern_inputs.intern_fn, + {"ledger_index": ledger_index}, + ledger, + batch_or_ops, + a1, + a2, + ) + + setattr(_intern_with_index, "_prism_ledger_index_bound", True) + return replace(intern_inputs, intern_fn=_intern_with_index) + + def _intern_candidates_core( ledger, candidates, *, - compact_candidates_fn, - intern_fn: InternFn, - node_batch_fn: NodeBatchFn, + intern_inputs: Cnf2InternInputs, ): - compacted, count = compact_candidates_fn(candidates) + compacted, count = intern_inputs.compact_candidates_fn(candidates) enabled = compacted.enabled.astype(jnp.int32) ops = jnp.where(enabled, compacted.opcode, jnp.int32(0)) a1 = jnp.where(enabled, compacted.arg1, jnp.int32(0)) a2 = jnp.where(enabled, compacted.arg2, jnp.int32(0)) - ids, new_ledger = intern_fn(ledger, node_batch_fn(ops, a1, a2)) + ids, new_ledger = intern_inputs.intern_fn( + ledger, intern_inputs.node_batch_fn(ops, a1, a2) + ) return ids, new_ledger, count def intern_candidates( - ledger, + ledger: Ledger, candidates, *, - compact_candidates_fn=compact_candidates, + compact_candidates_fn: Callable[..., tuple] = compact_candidates, intern_fn: InternFn = intern_nodes, intern_cfg: InternConfig | None = None, node_batch_fn: NodeBatchFn = _node_batch, @@ -339,26 +478,31 @@ def intern_candidates( node_batch_fn=node_batch_fn, node_batch_default=_node_batch, ) + resolved = _bind_intern_with_index( + ledger, + resolved, + intern_cfg=intern_cfg, + ) return _intern_candidates_core( ledger, candidates, - compact_candidates_fn=resolved.compact_candidates_fn, - intern_fn=resolved.intern_fn, - node_batch_fn=resolved.node_batch_fn, + intern_inputs=resolved, ) def intern_candidates_cfg( - ledger, + ledger: Ledger, candidates, *, cfg: Cnf2Config | None = None, - compact_candidates_fn=compact_candidates, + compact_candidates_fn: Callable[..., tuple] = compact_candidates, intern_fn: InternFn = intern_nodes, intern_cfg: InternConfig | None = None, node_batch_fn: NodeBatchFn = _node_batch, ): """Interface/Control wrapper for intern_candidates with DI bundle.""" + if cfg is not None and intern_cfg is None: + intern_cfg = cfg.intern_cfg resolved = resolve_cnf2_intern_inputs( cfg, compact_candidates_fn=compact_candidates_fn, @@ -370,16 +514,19 @@ def intern_candidates_cfg( node_batch_fn=node_batch_fn, node_batch_default=_node_batch, ) + resolved = _bind_intern_with_index( + ledger, + resolved, + intern_cfg=intern_cfg, + ) return _intern_candidates_core( ledger, candidates, - compact_candidates_fn=resolved.compact_candidates_fn, - intern_fn=resolved.intern_fn, - node_batch_fn=resolved.node_batch_fn, + intern_inputs=resolved, ) -def _cycle_candidates_core_impl( - ledger, +def _cycle_candidates_core_impl_state( + state: LedgerState, frontier_ids, validate_mode: ValidateMode = ValidateMode.NONE, *, @@ -387,37 +534,33 @@ def _cycle_candidates_core_impl( commit_optional: dict = EMPTY_COMMIT_OPTIONAL, post_q_handler, guard_cfg: GuardConfig | None = None, - intern_fn: InternFn = intern_nodes, + commit_fns: Cnf2CommitInputs, + intern_state_fn: InternStateFn = intern_nodes_state, intern_cfg: InternConfig | None = None, node_batch_fn: NodeBatchFn = _node_batch, coord_xor_batch_fn: CoordXorBatchFn = coord_xor_batch, emit_candidates_fn: EmitCandidatesFn = emit_candidates, candidate_indices_fn: CandidateIndicesFn = _candidate_indices, scatter_drop_fn: ScatterDropFn = _scatter_drop, - commit_stratum_fn: CommitStratumFn = commit_stratum, apply_q_fn: ApplyQFn = apply_q, identity_q_fn: IdentityQFn = _identity_q, assert_roots_fn=_assert_roots_noop, - safe_gather_ok_fn=_jax_safe.safe_gather_1d_ok, - safe_gather_ok_value_fn=_jax_safe.safe_gather_1d_ok_value, + safe_gather_ok_fn: SafeGatherOkFn = _jax_safe.safe_gather_1d_ok, + safe_gather_ok_value_fn: SafeGatherOkValueFn = _jax_safe.safe_gather_1d_ok_value, host_bool_value_fn: HostBoolValueFn = _host_bool_value, host_int_value_fn: HostIntValueFn = _host_int_value, - cnf2_enabled_fn=_cnf2_enabled, - cnf2_slot1_enabled_fn=_cnf2_slot1_enabled, - cnf2_metrics_enabled_fn=_cnf2_metrics_enabled, - cnf2_metrics_update_fn=_cnf2_metrics_update, + runtime_fns: Cnf2RuntimeFns = DEFAULT_CNF2_RUNTIME_FNS, ): resolved = resolve_cnf2_inputs( cfg, guard_cfg=guard_cfg, intern_cfg=intern_cfg, - intern_fn=intern_fn, + commit_fns=commit_fns, node_batch_fn=node_batch_fn, coord_xor_batch_fn=coord_xor_batch_fn, emit_candidates_fn=emit_candidates_fn, candidate_indices_fn=candidate_indices_fn, scatter_drop_fn=scatter_drop_fn, - commit_stratum_fn=commit_stratum_fn, apply_q_fn=apply_q_fn, identity_q_fn=identity_q_fn, safe_gather_ok_fn=safe_gather_ok_fn, @@ -426,26 +569,63 @@ def _cycle_candidates_core_impl( host_int_value_fn=host_int_value_fn, guards_enabled_fn=_guards_enabled, ledger_roots_hash_host_fn=_ledger_roots_hash_host, - cnf2_enabled_fn=cnf2_enabled_fn, - cnf2_slot1_enabled_fn=cnf2_slot1_enabled_fn, - cnf2_metrics_enabled_fn=cnf2_metrics_enabled_fn, - cnf2_metrics_update_fn=cnf2_metrics_update_fn, + runtime_fns=runtime_fns, ) guard_cfg = resolved.guard_cfg intern_cfg = resolved.intern_cfg - intern_fn = resolved.intern_fn + commit_fns = resolved.commit_fns + safe_gather_ok_fn = resolved.safe_gather_ok_fn + safe_gather_ok_value_fn = resolved.safe_gather_ok_value_fn node_batch_fn = resolved.node_batch_fn coord_xor_batch_fn = resolved.coord_xor_batch_fn emit_candidates_fn = resolved.emit_candidates_fn candidate_indices_fn = resolved.candidate_indices_fn scatter_drop_fn = resolved.scatter_drop_fn - commit_stratum_fn = resolved.commit_stratum_fn apply_q_fn = resolved.apply_q_fn identity_q_fn = resolved.identity_q_fn host_bool_value_fn = resolved.host_bool_value_fn host_int_value_fn = resolved.host_int_value_fn - cnf2_slot1_enabled_fn = resolved.cnf2_slot1_enabled_fn - cnf2_metrics_update_fn = resolved.cnf2_metrics_update_fn + runtime_fns = resolved.runtime_fns + if commit_optional: + commit_optional = dict(commit_optional) + if "safe_gather_ok_fn" in commit_optional: + commit_optional["safe_gather_ok_fn"] = safe_gather_ok_fn + if "safe_gather_ok_value_fn" in commit_optional: + commit_optional["safe_gather_ok_value_fn"] = safe_gather_ok_value_fn + if "guard_cfg" in commit_optional: + commit_optional["guard_cfg"] = guard_cfg + intern_cfg = _resolve_intern_cfg(cfg, intern_cfg) + if commit_fns.intern_fn is intern_nodes: + commit_fns = Cnf2CommitInputs( + intern_fn=partial(intern_nodes, cfg=intern_cfg), + commit_stratum_fn=commit_fns.commit_stratum_fn, + ) + if intern_state_fn is intern_nodes_state: + def _intern_state_from_commit_fn(state_in, batch_or_ops, a1=None, a2=None, *, cfg=None): + optional = {"cfg": cfg, "ledger_index": state_in.index} + ids, new_ledger = call_with_optional_kwargs( + commit_fns.intern_fn, + optional, + state_in.ledger, + batch_or_ops, + a1, + a2, + ) + if new_ledger is state_in.ledger: + return ids, state_in + op_buckets_full_range = ( + cfg.op_buckets_full_range + if cfg is not None + else DEFAULT_INTERN_CONFIG.op_buckets_full_range + ) + new_state = derive_ledger_state( + new_ledger, op_buckets_full_range=op_buckets_full_range + ) + return ids, new_state + + intern_state_fn = _intern_state_from_commit_fn + cnf2_metrics_update_fn = runtime_fns.cnf2_metrics_update_fn + ledger = state.ledger # BSPᵗ: temporal superstep / barrier semantics. frontier_ids = _committed_ids(frontier_ids) frontier_arr = jnp.atleast_1d(frontier_ids.a) @@ -494,9 +674,14 @@ def body(state): coord_idx_safe = jnp.where(coord_valid, coord_idx, 0) coord_left = r_a1[coord_idx_safe][:coord_count_i] coord_right = r_a2[coord_idx_safe][:coord_count_i] - coord_ids_compact, ledger = coord_xor_batch_fn( - ledger, coord_left, coord_right + coord_ids_compact, state = _coord_xor_batch_state( + state, + coord_left, + coord_right, + coord_xor_batch_fn=coord_xor_batch_fn, + intern_cfg=intern_cfg, ) + ledger = state.ledger coord_ids_full = jnp.zeros_like(coord_idx_safe) coord_ids_full = coord_ids_full.at[:coord_count_i].set(coord_ids_compact) coord_ids = _scatter_compacted_ids( @@ -516,7 +701,13 @@ def body(state): ops0 = jnp.where(enabled0, compacted0.opcode, jnp.int32(0)) a1_0 = jnp.where(enabled0, compacted0.arg1, jnp.int32(0)) a2_0 = jnp.where(enabled0, compacted0.arg2, jnp.int32(0)) - ids_compact, ledger0 = intern_fn(ledger, node_batch_fn(ops0, a1_0, a2_0)) + ids_compact, state0 = call_with_optional_kwargs( + intern_state_fn, + {"cfg": intern_cfg}, + state, + node_batch_fn(ops0, a1_0, a2_0), + ) + ledger0 = state0.ledger size0 = candidates.enabled.shape[0] ids_full0 = _scatter_compacted_ids( comp_idx0, @@ -552,9 +743,9 @@ def body(state): # test guards (m3 normative), hyperstrata visibility is enforced so slot1 # reads only from slot0 + pre-step. # See IMPLEMENTATION_PLAN.md (CNF-2 continuation slot). - slot1_gate = cnf2_slot1_enabled_fn() - slot1_add = is_add_suc & slot1_gate - slot1_mul = is_mul_suc & slot1_gate + # M2 commit: slot1 is always enabled; no runtime gate. + slot1_add = is_add_suc + slot1_mul = is_mul_suc slot1_enabled = slot1_add | slot1_mul slot1_ops = jnp.zeros_like(r_ops) slot1_a1 = jnp.zeros_like(r_a1) @@ -568,9 +759,13 @@ def body(state): slot1_ops = jnp.where(slot1_enabled, slot1_ops, jnp.int32(0)) slot1_a1 = jnp.where(slot1_enabled, slot1_a1, jnp.int32(0)) slot1_a2 = jnp.where(slot1_enabled, slot1_a2, jnp.int32(0)) - slot1_ids, ledger1 = intern_fn( - ledger0, node_batch_fn(slot1_ops, slot1_a1, slot1_a2) + slot1_ids, state1 = call_with_optional_kwargs( + intern_state_fn, + {"cfg": intern_cfg}, + state0, + node_batch_fn(slot1_ops, slot1_a1, slot1_a2), ) + ledger1 = state1.ledger zero_on_a1 = is_zero_a1 zero_on_a2 = (~is_zero_a1) & is_zero_a2 zero_other = jnp.where(zero_on_a1, r_a2, r_a1) @@ -585,14 +780,21 @@ def body(state): wrap_strata = [(host_int_value_fn(ledger1.count), 0)] wrap_depths = depths next_frontier = base_next - ledger2 = ledger1 + state2 = state1 + ledger2 = state2.ledger while host_bool_value_fn(jnp.any((wrap_depths > 0) & (~ledger2.oom))): to_wrap = (wrap_depths > 0) & (~ledger2.oom) ops = jnp.where(to_wrap, jnp.int32(OP_SUC), jnp.int32(0)) a1 = jnp.where(to_wrap, next_frontier, jnp.int32(0)) a2 = jnp.zeros_like(a1) start = host_int_value_fn(ledger2.count) - new_ids, ledger2 = intern_fn(ledger2, node_batch_fn(ops, a1, a2)) + new_ids, state2 = call_with_optional_kwargs( + intern_state_fn, + {"cfg": intern_cfg}, + state2, + node_batch_fn(ops, a1, a2), + ) + ledger2 = state2.ledger end = host_int_value_fn(ledger2.count) wrap_strata.append((start, end - start)) next_frontier = jnp.where(to_wrap, new_ids, next_frontier) @@ -615,41 +817,42 @@ def body(state): rewrite_child = host_int_value_fn(count0) changed_count = host_int_value_fn(jnp.sum(changed_mask.astype(jnp.int32))) cnf2_metrics_update_fn(rewrite_child, changed_count, int(count2_i)) - ledger2, _, q_map = call_with_optional_kwargs( - commit_stratum_fn, - commit_optional, - ledger2, + state2, _, q_map = _commit_stratum_state( + state2, stratum0, + commit_fns=commit_fns, + commit_optional=commit_optional, + intern_cfg=intern_cfg, validate_mode=validate_mode, - intern_fn=intern_fn, ) - ledger2, _, q_map = call_with_optional_kwargs( - commit_stratum_fn, - commit_optional, - ledger2, + state2, _, q_map = _commit_stratum_state( + state2, stratum1, + commit_fns=commit_fns, + commit_optional=commit_optional, + intern_cfg=intern_cfg, prior_q=q_map, validate_mode=validate_mode, - intern_fn=intern_fn, ) # Wrapper strata are micro-strata in s=2; commit in order for hyperstrata visibility. for start_i, count_i in wrap_strata: micro_stratum = Stratum( start=jnp.int32(start_i), count=jnp.int32(count_i) ) - ledger2, _, q_map = call_with_optional_kwargs( - commit_stratum_fn, - commit_optional, - ledger2, + state2, _, q_map = _commit_stratum_state( + state2, micro_stratum, + commit_fns=commit_fns, + commit_optional=commit_optional, + intern_cfg=intern_cfg, prior_q=q_map, validate_mode=validate_mode, - intern_fn=intern_fn, ) next_frontier = _provisional_ids(next_frontier) - ledger2, post_ids = post_q_handler(ledger2, q_map, next_frontier) - assert_roots_fn(ledger2, next_frontier, post_ids) - return ledger2, next_frontier, (stratum0, stratum1, stratum2), q_map + ledger2, post_ids = post_q_handler(state2.ledger, q_map, next_frontier) + state2 = _state_with_ledger(state2, ledger2) + assert_roots_fn(state2.ledger, next_frontier, post_ids) + return state2, next_frontier, (stratum0, stratum1, stratum2), q_map def _cycle_candidates_core_static_bound( @@ -660,42 +863,45 @@ def _cycle_candidates_core_static_bound( cfg: Cnf2Config | None = None, safe_gather_policy: SafetyPolicy, guard_cfg: GuardConfig | None = None, - intern_fn: InternFn = intern_nodes, + commit_fns: Cnf2CommitInputs, + intern_state_fn: InternStateFn = intern_nodes_state, intern_cfg: InternConfig | None = None, node_batch_fn: NodeBatchFn = _node_batch, coord_xor_batch_fn: CoordXorBatchFn = coord_xor_batch, emit_candidates_fn: EmitCandidatesFn = emit_candidates, candidate_indices_fn: CandidateIndicesFn = _candidate_indices, scatter_drop_fn: ScatterDropFn = _scatter_drop, - commit_stratum_fn: CommitStratumFn = commit_stratum_static, apply_q_fn: ApplyQFn = apply_q, identity_q_fn: IdentityQFn = _identity_q, - safe_gather_ok_fn=_jax_safe.safe_gather_1d_ok, + safe_gather_ok_fn: SafeGatherOkFn = _jax_safe.safe_gather_1d_ok, host_bool_value_fn: HostBoolValueFn = _host_bool_value, host_int_value_fn: HostIntValueFn = _host_int_value, guards_enabled_fn: GuardsEnabledFn = _guards_enabled, ledger_roots_hash_host_fn: LedgerRootsHashFn = _ledger_roots_hash_host, - cnf2_enabled_fn=_cnf2_enabled, - cnf2_slot1_enabled_fn=_cnf2_slot1_enabled, - cnf2_metrics_enabled_fn=_cnf2_metrics_enabled, - cnf2_metrics_update_fn=_cnf2_metrics_update, + runtime_fns: Cnf2RuntimeFns = DEFAULT_CNF2_RUNTIME_FNS, ): """CNF-2 core (static policy) with policy decisions pre-bound at the edge. This entrypoint assumes: - safe_gather_policy is already resolved (non-optional), - safe_gather_ok_fn is already bound if policy-bound behavior is required, - - commit_stratum_fn is already the correct bound/static variant. + - commit_fns.commit_stratum_fn is already the correct bound/static variant. All policy binding / guard binding / config resolution must happen outside this function so the core remains branch-free with respect to policy composition. Only algorithmic guards remain below. """ mode = resolve_validate_mode( - validate_mode, guards_enabled_fn=guards_enabled_fn, context="cycle_candidates" + validate_mode, guards_enabled_fn=guards_enabled_fn ) - if commit_stratum_fn is commit_stratum: - commit_stratum_fn = commit_stratum_static + intern_cfg = _resolve_intern_cfg(cfg, intern_cfg) + if isinstance(ledger, LedgerState): + state = ledger + ledger = state.ledger + else: + state = derive_ledger_state( + ledger, op_buckets_full_range=intern_cfg.op_buckets_full_range + ) commit_optional = { "safe_gather_policy": safe_gather_policy, "safe_gather_ok_fn": safe_gather_ok_fn, @@ -704,17 +910,12 @@ def _cycle_candidates_core_static_bound( frontier_ids = _committed_ids(frontier_ids) # --- Algorithmic guards (explicit, non-policy) --- - # CNF-2 pipeline gate: explicitly staged by milestone/flags. - if not cnf2_enabled_fn(): - # CNF-2 candidate pipeline is staged for m2+ (plan); guard at entry. - # See IMPLEMENTATION_PLAN.md (m2 CNF-2 pipeline). - raise RuntimeError("cycle_candidates disabled until m2 (set PRISM_ENABLE_CNF2=1)") # Corrupt ledger short-circuit: no rewrite on invalid state. # SYNC: host read to short-circuit on corrupt ledgers (m1). if host_bool_value_fn(ledger.corrupt): empty = Stratum(start=ledger.count.astype(jnp.int32), count=jnp.int32(0)) return ( - ledger, + state, _provisional_ids(frontier_ids.a), (empty, empty, empty), identity_q_fn, @@ -725,7 +926,7 @@ def _cycle_candidates_core_static_bound( if frontier_arr.shape[0] == 0: empty = Stratum(start=ledger.count.astype(jnp.int32), count=jnp.int32(0)) return ( - ledger, + state, _provisional_ids(frontier_ids.a), (empty, empty, empty), identity_q_fn, @@ -735,29 +936,30 @@ def _assert_roots(ledger2, next_frontier, post_ids): # Test guard: denotation invariance under q projection. if not _TEST_GUARDS: return - pre_hash = ledger_roots_hash_host_fn(ledger2, next_frontier.a) - post_hash = ledger_roots_hash_host_fn(ledger2, post_ids.a) + args = _RootAssertArgs(next_frontier, post_ids) + pre_hash = ledger_roots_hash_host_fn(ledger2, args.next_frontier.a) + post_hash = ledger_roots_hash_host_fn(ledger2, args.post_ids.a) if pre_hash != post_hash: raise RuntimeError("BSPᵗ projection changed root structure") _post_q_handler = make_cnf2_post_q_handler_static(apply_q_fn) - return _cycle_candidates_core_impl( - ledger, + return _cycle_candidates_core_impl_state( + state, frontier_ids, validate_mode=mode, cfg=cfg, commit_optional=commit_optional, post_q_handler=_post_q_handler, guard_cfg=guard_cfg, - intern_fn=intern_fn, + commit_fns=commit_fns, + intern_state_fn=intern_state_fn, intern_cfg=intern_cfg, node_batch_fn=node_batch_fn, coord_xor_batch_fn=coord_xor_batch_fn, emit_candidates_fn=emit_candidates_fn, candidate_indices_fn=candidate_indices_fn, scatter_drop_fn=scatter_drop_fn, - commit_stratum_fn=commit_stratum_fn, apply_q_fn=apply_q_fn, identity_q_fn=identity_q_fn, assert_roots_fn=_assert_roots, @@ -765,10 +967,7 @@ def _assert_roots(ledger2, next_frontier, post_ids): safe_gather_ok_value_fn=_jax_safe.safe_gather_1d_ok_value, host_bool_value_fn=host_bool_value_fn, host_int_value_fn=host_int_value_fn, - cnf2_enabled_fn=cnf2_enabled_fn, - cnf2_slot1_enabled_fn=cnf2_slot1_enabled_fn, - cnf2_metrics_enabled_fn=cnf2_metrics_enabled_fn, - cnf2_metrics_update_fn=cnf2_metrics_update_fn, + runtime_fns=runtime_fns, ) @@ -780,61 +979,58 @@ def _cycle_candidates_core_value_bound( cfg: Cnf2Config | None = None, safe_gather_policy_value: PolicyValue, guard_cfg: GuardConfig | None = None, - intern_fn: InternFn = intern_nodes, + commit_fns: Cnf2CommitInputs, + intern_state_fn: InternStateFn = intern_nodes_state, intern_cfg: InternConfig | None = None, node_batch_fn: NodeBatchFn = _node_batch, coord_xor_batch_fn: CoordXorBatchFn = coord_xor_batch, emit_candidates_fn: EmitCandidatesFn = emit_candidates, candidate_indices_fn: CandidateIndicesFn = _candidate_indices, scatter_drop_fn: ScatterDropFn = _scatter_drop, - commit_stratum_fn: CommitStratumFn = commit_stratum_value, apply_q_fn: ApplyQFn = apply_q, identity_q_fn: IdentityQFn = _identity_q, - safe_gather_ok_value_fn=_jax_safe.safe_gather_1d_ok_value, + safe_gather_ok_value_fn: SafeGatherOkValueFn = _jax_safe.safe_gather_1d_ok_value, host_bool_value_fn: HostBoolValueFn = _host_bool_value, host_int_value_fn: HostIntValueFn = _host_int_value, guards_enabled_fn: GuardsEnabledFn = _guards_enabled, ledger_roots_hash_host_fn: LedgerRootsHashFn = _ledger_roots_hash_host, - cnf2_enabled_fn=_cnf2_enabled, - cnf2_slot1_enabled_fn=_cnf2_slot1_enabled, - cnf2_metrics_enabled_fn=_cnf2_metrics_enabled, - cnf2_metrics_update_fn=_cnf2_metrics_update, + runtime_fns: Cnf2RuntimeFns = DEFAULT_CNF2_RUNTIME_FNS, ): """CNF-2 core (policy as JAX value) with policy decisions pre-bound at the edge. This entrypoint assumes: - safe_gather_policy_value is already resolved (non-optional), - safe_gather_ok_value_fn is already guard-bound, - - commit_stratum_fn is already the correct value-policy variant. + - commit_fns.commit_stratum_fn is already the correct value-policy variant. All policy binding / guard binding / config resolution must happen outside this function so the core remains branch-free with respect to policy composition. Only algorithmic guards remain below. """ mode = resolve_validate_mode( - validate_mode, guards_enabled_fn=guards_enabled_fn, context="cycle_candidates" + validate_mode, guards_enabled_fn=guards_enabled_fn ) - if commit_stratum_fn is commit_stratum: - commit_stratum_fn = commit_stratum_value + intern_cfg = _resolve_intern_cfg(cfg, intern_cfg) + if isinstance(ledger, LedgerState): + state = ledger + ledger = state.ledger + else: + state = derive_ledger_state( + ledger, op_buckets_full_range=intern_cfg.op_buckets_full_range + ) commit_optional = { "safe_gather_policy_value": safe_gather_policy_value, "safe_gather_ok_value_fn": safe_gather_ok_value_fn, "guard_cfg": guard_cfg, } - frontier_ids = _committed_ids(frontier_ids) # --- Algorithmic guards (explicit, non-policy) --- - # CNF-2 pipeline gate: explicitly staged by milestone/flags. - if not cnf2_enabled_fn(): - # CNF-2 candidate pipeline is staged for m2+ (plan); guard at entry. - # See IMPLEMENTATION_PLAN.md (m2 CNF-2 pipeline). - raise RuntimeError("cycle_candidates disabled until m2 (set PRISM_ENABLE_CNF2=1)") # Corrupt ledger short-circuit: no rewrite on invalid state. # SYNC: host read to short-circuit on corrupt ledgers (m1). if host_bool_value_fn(ledger.corrupt): empty = Stratum(start=ledger.count.astype(jnp.int32), count=jnp.int32(0)) return ( - ledger, + state, _provisional_ids(frontier_ids.a), (empty, empty, empty), identity_q_fn, @@ -845,7 +1041,7 @@ def _cycle_candidates_core_value_bound( if frontier_arr.shape[0] == 0: empty = Stratum(start=ledger.count.astype(jnp.int32), count=jnp.int32(0)) return ( - ledger, + state, _provisional_ids(frontier_ids.a), (empty, empty, empty), identity_q_fn, @@ -855,29 +1051,30 @@ def _assert_roots(ledger2, next_frontier, post_ids): # Test guard: denotation invariance under q projection. if not _TEST_GUARDS: return - pre_hash = ledger_roots_hash_host_fn(ledger2, next_frontier.a) - post_hash = ledger_roots_hash_host_fn(ledger2, post_ids.a) + args = _RootAssertArgs(next_frontier, post_ids) + pre_hash = ledger_roots_hash_host_fn(ledger2, args.next_frontier.a) + post_hash = ledger_roots_hash_host_fn(ledger2, args.post_ids.a) if pre_hash != post_hash: raise RuntimeError("BSPᵗ projection changed root structure") _post_q_handler = make_cnf2_post_q_handler_value(apply_q_fn) - return _cycle_candidates_core_impl( - ledger, + return _cycle_candidates_core_impl_state( + state, frontier_ids, validate_mode=mode, cfg=cfg, commit_optional=commit_optional, post_q_handler=_post_q_handler, guard_cfg=guard_cfg, - intern_fn=intern_fn, + commit_fns=commit_fns, + intern_state_fn=intern_state_fn, intern_cfg=intern_cfg, node_batch_fn=node_batch_fn, coord_xor_batch_fn=coord_xor_batch_fn, emit_candidates_fn=emit_candidates_fn, candidate_indices_fn=candidate_indices_fn, scatter_drop_fn=scatter_drop_fn, - commit_stratum_fn=commit_stratum_fn, apply_q_fn=apply_q_fn, identity_q_fn=identity_q_fn, assert_roots_fn=_assert_roots, @@ -885,14 +1082,15 @@ def _assert_roots(ledger2, next_frontier, post_ids): safe_gather_ok_value_fn=safe_gather_ok_value_fn, host_bool_value_fn=host_bool_value_fn, host_int_value_fn=host_int_value_fn, - cnf2_enabled_fn=cnf2_enabled_fn, - cnf2_slot1_enabled_fn=cnf2_slot1_enabled_fn, - cnf2_metrics_enabled_fn=cnf2_metrics_enabled_fn, - cnf2_metrics_update_fn=cnf2_metrics_update_fn, + runtime_fns=runtime_fns, ) -def _apply_q_optional_ok(apply_q_fn, q_map, ids): +def _apply_q_optional_ok(apply_q_fn: ApplyQFn, q_map, ids): + if apply_q_fn is apply_q: + return apply_q_ok(q_map, ids) + if apply_q_fn is apply_q_ok: + return apply_q_fn(q_map, ids) result = call_with_optional_kwargs( apply_q_fn, {"return_ok": True}, q_map, ids ) @@ -925,17 +1123,16 @@ def cycle_candidates_static( commit_stratum_fn: CommitStratumFn = commit_stratum_static, apply_q_fn: ApplyQFn = apply_q, identity_q_fn: IdentityQFn = _identity_q, - safe_gather_ok_fn=_jax_safe.safe_gather_1d_ok, + safe_gather_ok_fn: SafeGatherOkFn = _jax_safe.safe_gather_1d_ok, safe_gather_ok_bound_fn=None, host_bool_value_fn: HostBoolValueFn = _host_bool_value, host_int_value_fn: HostIntValueFn = _host_int_value, guards_enabled_fn: GuardsEnabledFn = _guards_enabled, ledger_roots_hash_host_fn: LedgerRootsHashFn = _ledger_roots_hash_host, - cnf2_enabled_fn=_cnf2_enabled, - cnf2_slot1_enabled_fn=_cnf2_slot1_enabled, - cnf2_metrics_enabled_fn=_cnf2_metrics_enabled, - cnf2_metrics_update_fn=_cnf2_metrics_update, + runtime_fns: Cnf2RuntimeFns = DEFAULT_CNF2_RUNTIME_FNS, ): + if cfg is not None and runtime_fns is DEFAULT_CNF2_RUNTIME_FNS: + runtime_fns = cfg.runtime_fns if cfg is not None and cfg.policy_binding is not None: if cfg.policy_binding.mode == PolicyMode.VALUE: raise PrismPolicyBindingError( @@ -998,21 +1195,149 @@ def cycle_candidates_static( ) safe_gather_ok_fn = safe_gather_ok_bound_fn guard_cfg = _resolve_guard_cfg(guard_cfg, cfg) - return _cycle_candidates_core_static_bound( + if commit_stratum_fn is commit_stratum: + commit_stratum_fn = commit_stratum_static + commit_fns = Cnf2CommitInputs( + intern_fn=intern_fn, + commit_stratum_fn=commit_stratum_fn, + ) + state, frontier, strata, q_map = _cycle_candidates_core_static_bound( ledger, frontier_ids, validate_mode=validate_mode, cfg=cfg, safe_gather_policy=safe_gather_policy, guard_cfg=guard_cfg, - intern_fn=intern_fn, + commit_fns=commit_fns, intern_cfg=intern_cfg, node_batch_fn=node_batch_fn, coord_xor_batch_fn=coord_xor_batch_fn, emit_candidates_fn=emit_candidates_fn, candidate_indices_fn=candidate_indices_fn, scatter_drop_fn=scatter_drop_fn, + apply_q_fn=apply_q_fn, + identity_q_fn=identity_q_fn, + safe_gather_ok_fn=safe_gather_ok_fn, + host_bool_value_fn=host_bool_value_fn, + host_int_value_fn=host_int_value_fn, + guards_enabled_fn=guards_enabled_fn, + ledger_roots_hash_host_fn=ledger_roots_hash_host_fn, + runtime_fns=runtime_fns, + ) + return state.ledger, frontier, strata, q_map + + +def cycle_candidates_static_state( + state: LedgerState, + frontier_ids, + validate_mode: ValidateMode = ValidateMode.NONE, + *, + cfg: Cnf2Config | None = None, + safe_gather_policy: SafetyPolicy | None = None, + guard_cfg: GuardConfig | None = None, + intern_fn: InternFn = intern_nodes, + intern_cfg: InternConfig | None = None, + node_batch_fn: NodeBatchFn = _node_batch, + coord_xor_batch_fn: CoordXorBatchFn = coord_xor_batch, + emit_candidates_fn: EmitCandidatesFn = emit_candidates, + candidate_indices_fn: CandidateIndicesFn = _candidate_indices, + scatter_drop_fn: ScatterDropFn = _scatter_drop, + commit_stratum_fn: CommitStratumFn = commit_stratum_static, + apply_q_fn: ApplyQFn = apply_q, + identity_q_fn: IdentityQFn = _identity_q, + safe_gather_ok_fn: SafeGatherOkFn = _jax_safe.safe_gather_1d_ok, + safe_gather_ok_bound_fn=None, + host_bool_value_fn: HostBoolValueFn = _host_bool_value, + host_int_value_fn: HostIntValueFn = _host_int_value, + guards_enabled_fn: GuardsEnabledFn = _guards_enabled, + ledger_roots_hash_host_fn: LedgerRootsHashFn = _ledger_roots_hash_host, + runtime_fns: Cnf2RuntimeFns = DEFAULT_CNF2_RUNTIME_FNS, +): + """CNF-2 evaluation (static policy) that preserves LedgerState.""" + if cfg is not None and runtime_fns is DEFAULT_CNF2_RUNTIME_FNS: + runtime_fns = cfg.runtime_fns + if cfg is not None and cfg.policy_binding is not None: + if cfg.policy_binding.mode == PolicyMode.VALUE: + raise PrismPolicyBindingError( + "cycle_candidates_static_state received cfg.policy_binding value-mode; " + "use cycle_candidates_value_state", + context="cycle_candidates_static_state", + policy_mode="static", + ) + if safe_gather_policy is None: + safe_gather_policy = require_static_policy( + cfg.policy_binding, context="cycle_candidates_static_state" + ) + if cfg is not None and cfg.safe_gather_policy_value is not None: + raise PrismPolicyBindingError( + "cycle_candidates_static_state received cfg.safe_gather_policy_value; " + "use cycle_candidates_value_state", + context="cycle_candidates_static_state", + policy_mode="static", + ) + if safe_gather_policy is None and cfg is not None and cfg.safe_gather_policy is not None: + safe_gather_policy = cfg.safe_gather_policy + if cfg is not None and cfg.safe_gather_ok_bound_fn is not None: + if safe_gather_ok_bound_fn is not None: + raise PrismPolicyBindingError( + "cycle_candidates_static_state received safe_gather_ok_bound_fn twice", + context="cycle_candidates_static_state", + policy_mode="static", + ) + safe_gather_ok_bound_fn = cfg.safe_gather_ok_bound_fn + if ( + cfg is not None + and cfg.safe_gather_ok_fn is not None + and safe_gather_ok_fn is _jax_safe.safe_gather_1d_ok + ): + safe_gather_ok_fn = cfg.safe_gather_ok_fn + if safe_gather_policy is None: + safe_gather_policy = DEFAULT_SAFETY_POLICY + if safe_gather_ok_bound_fn is not None: + if safe_gather_ok_fn is not _jax_safe.safe_gather_1d_ok: + raise PrismPolicyBindingError( + "cycle_candidates_static_state received both safe_gather_ok_fn and " + "safe_gather_ok_bound_fn", + context="cycle_candidates_static_state", + policy_mode="static", + ) + if guard_cfg is not None: + raise PrismPolicyBindingError( + "cycle_candidates_static_state received guard_cfg with " + "safe_gather_ok_bound_fn; bind guard config into safe_gather_ok_fn", + context="cycle_candidates_static_state", + policy_mode="static", + ) + if commit_stratum_fn in (commit_stratum, commit_stratum_static): + commit_stratum_fn = commit_stratum_bound + if commit_stratum_fn is not commit_stratum_bound: + raise PrismPolicyBindingError( + "cycle_candidates_static_state received safe_gather_ok_bound_fn without commit_stratum_bound", + context="cycle_candidates_static_state", + policy_mode="static", + ) + safe_gather_ok_fn = safe_gather_ok_bound_fn + guard_cfg = _resolve_guard_cfg(guard_cfg, cfg) + if commit_stratum_fn is commit_stratum: + commit_stratum_fn = commit_stratum_static + commit_fns = Cnf2CommitInputs( + intern_fn=intern_fn, commit_stratum_fn=commit_stratum_fn, + ) + return _cycle_candidates_core_static_bound( + state, + frontier_ids, + validate_mode=validate_mode, + cfg=cfg, + safe_gather_policy=safe_gather_policy, + guard_cfg=guard_cfg, + commit_fns=commit_fns, + intern_cfg=intern_cfg, + node_batch_fn=node_batch_fn, + coord_xor_batch_fn=coord_xor_batch_fn, + emit_candidates_fn=emit_candidates_fn, + candidate_indices_fn=candidate_indices_fn, + scatter_drop_fn=scatter_drop_fn, apply_q_fn=apply_q_fn, identity_q_fn=identity_q_fn, safe_gather_ok_fn=safe_gather_ok_fn, @@ -1020,10 +1345,7 @@ def cycle_candidates_static( host_int_value_fn=host_int_value_fn, guards_enabled_fn=guards_enabled_fn, ledger_roots_hash_host_fn=ledger_roots_hash_host_fn, - cnf2_enabled_fn=cnf2_enabled_fn, - cnf2_slot1_enabled_fn=cnf2_slot1_enabled_fn, - cnf2_metrics_enabled_fn=cnf2_metrics_enabled_fn, - cnf2_metrics_update_fn=cnf2_metrics_update_fn, + runtime_fns=runtime_fns, ) @@ -1045,17 +1367,16 @@ def cycle_candidates_value( commit_stratum_fn: CommitStratumFn = commit_stratum_value, apply_q_fn: ApplyQFn = apply_q, identity_q_fn: IdentityQFn = _identity_q, - safe_gather_ok_value_fn=_jax_safe.safe_gather_1d_ok_value, + safe_gather_ok_value_fn: SafeGatherOkValueFn = _jax_safe.safe_gather_1d_ok_value, safe_gather_ok_bound_fn=None, host_bool_value_fn: HostBoolValueFn = _host_bool_value, host_int_value_fn: HostIntValueFn = _host_int_value, guards_enabled_fn: GuardsEnabledFn = _guards_enabled, ledger_roots_hash_host_fn: LedgerRootsHashFn = _ledger_roots_hash_host, - cnf2_enabled_fn=_cnf2_enabled, - cnf2_slot1_enabled_fn=_cnf2_slot1_enabled, - cnf2_metrics_enabled_fn=_cnf2_metrics_enabled, - cnf2_metrics_update_fn=_cnf2_metrics_update, + runtime_fns: Cnf2RuntimeFns = DEFAULT_CNF2_RUNTIME_FNS, ): + if cfg is not None and runtime_fns is DEFAULT_CNF2_RUNTIME_FNS: + runtime_fns = cfg.runtime_fns if cfg is not None and cfg.policy_binding is not None: if cfg.policy_binding.mode == PolicyMode.STATIC: raise PrismPolicyBindingError( @@ -1097,21 +1418,129 @@ def cycle_candidates_value( safe_gather_ok_value_fn=safe_gather_ok_value_fn, guard_cfg=guard_cfg, ) - return _cycle_candidates_core_value_bound( + if commit_stratum_fn is commit_stratum: + commit_stratum_fn = commit_stratum_value + commit_fns = Cnf2CommitInputs( + intern_fn=intern_fn, + commit_stratum_fn=commit_stratum_fn, + ) + state, frontier, strata, q_map = _cycle_candidates_core_value_bound( ledger, frontier_ids, validate_mode=validate_mode, cfg=cfg, safe_gather_policy_value=safe_gather_policy_value, guard_cfg=guard_cfg, - intern_fn=intern_fn, + commit_fns=commit_fns, intern_cfg=intern_cfg, node_batch_fn=node_batch_fn, coord_xor_batch_fn=coord_xor_batch_fn, emit_candidates_fn=emit_candidates_fn, candidate_indices_fn=candidate_indices_fn, scatter_drop_fn=scatter_drop_fn, + apply_q_fn=apply_q_fn, + identity_q_fn=identity_q_fn, + safe_gather_ok_fn=None, + safe_gather_ok_value_fn=safe_gather_ok_value_fn, + host_bool_value_fn=host_bool_value_fn, + host_int_value_fn=host_int_value_fn, + guards_enabled_fn=guards_enabled_fn, + ledger_roots_hash_host_fn=ledger_roots_hash_host_fn, + runtime_fns=runtime_fns, + ) + return state.ledger, frontier, strata, q_map + + +def cycle_candidates_value_state( + state: LedgerState, + frontier_ids, + validate_mode: ValidateMode = ValidateMode.NONE, + *, + cfg: Cnf2Config | None = None, + safe_gather_policy_value: PolicyValue | None = None, + guard_cfg: GuardConfig | None = None, + intern_fn: InternFn = intern_nodes, + intern_cfg: InternConfig | None = None, + node_batch_fn: NodeBatchFn = _node_batch, + coord_xor_batch_fn: CoordXorBatchFn = coord_xor_batch, + emit_candidates_fn: EmitCandidatesFn = emit_candidates, + candidate_indices_fn: CandidateIndicesFn = _candidate_indices, + scatter_drop_fn: ScatterDropFn = _scatter_drop, + commit_stratum_fn: CommitStratumFn = commit_stratum_value, + apply_q_fn: ApplyQFn = apply_q, + identity_q_fn: IdentityQFn = _identity_q, + safe_gather_ok_value_fn: SafeGatherOkValueFn = _jax_safe.safe_gather_1d_ok_value, + safe_gather_ok_bound_fn=None, + host_bool_value_fn: HostBoolValueFn = _host_bool_value, + host_int_value_fn: HostIntValueFn = _host_int_value, + guards_enabled_fn: GuardsEnabledFn = _guards_enabled, + ledger_roots_hash_host_fn: LedgerRootsHashFn = _ledger_roots_hash_host, + runtime_fns: Cnf2RuntimeFns = DEFAULT_CNF2_RUNTIME_FNS, +): + """CNF-2 evaluation (policy as JAX value) that preserves LedgerState.""" + if cfg is not None and runtime_fns is DEFAULT_CNF2_RUNTIME_FNS: + runtime_fns = cfg.runtime_fns + if cfg is not None and cfg.policy_binding is not None: + if cfg.policy_binding.mode == PolicyMode.STATIC: + raise PrismPolicyBindingError( + "cycle_candidates_value_state received cfg.policy_binding static-mode; " + "use cycle_candidates_static_state", + context="cycle_candidates_value_state", + policy_mode="value", + ) + if safe_gather_policy_value is None: + safe_gather_policy_value = require_value_policy( + cfg.policy_binding, context="cycle_candidates_value_state" + ) + if cfg is not None and cfg.safe_gather_policy is not None: + raise PrismPolicyBindingError( + "cycle_candidates_value_state received cfg.safe_gather_policy; " + "use cycle_candidates_static_state", + context="cycle_candidates_value_state", + policy_mode="value", + ) + if safe_gather_ok_bound_fn is not None or ( + cfg is not None and cfg.safe_gather_ok_bound_fn is not None + ): + raise PrismPolicyBindingError( + "cycle_candidates_value_state received safe_gather_ok_bound_fn; " + "use cycle_candidates_static_state", + context="cycle_candidates_value_state", + policy_mode="value", + ) + if ( + safe_gather_policy_value is None + and cfg is not None + and cfg.safe_gather_policy_value is not None + ): + safe_gather_policy_value = cfg.safe_gather_policy_value + if safe_gather_policy_value is None: + safe_gather_policy_value = POLICY_VALUE_DEFAULT + guard_cfg = _resolve_guard_cfg(guard_cfg, cfg) + safe_gather_ok_value_fn = resolve_safe_gather_ok_value_fn( + safe_gather_ok_value_fn=safe_gather_ok_value_fn, + guard_cfg=guard_cfg, + ) + if commit_stratum_fn is commit_stratum: + commit_stratum_fn = commit_stratum_value + commit_fns = Cnf2CommitInputs( + intern_fn=intern_fn, commit_stratum_fn=commit_stratum_fn, + ) + return _cycle_candidates_core_value_bound( + state, + frontier_ids, + validate_mode=validate_mode, + cfg=cfg, + safe_gather_policy_value=safe_gather_policy_value, + guard_cfg=guard_cfg, + commit_fns=commit_fns, + intern_cfg=intern_cfg, + node_batch_fn=node_batch_fn, + coord_xor_batch_fn=coord_xor_batch_fn, + emit_candidates_fn=emit_candidates_fn, + candidate_indices_fn=candidate_indices_fn, + scatter_drop_fn=scatter_drop_fn, apply_q_fn=apply_q_fn, identity_q_fn=identity_q_fn, safe_gather_ok_fn=None, @@ -1120,10 +1549,7 @@ def cycle_candidates_value( host_int_value_fn=host_int_value_fn, guards_enabled_fn=guards_enabled_fn, ledger_roots_hash_host_fn=ledger_roots_hash_host_fn, - cnf2_enabled_fn=cnf2_enabled_fn, - cnf2_slot1_enabled_fn=cnf2_slot1_enabled_fn, - cnf2_metrics_enabled_fn=cnf2_metrics_enabled_fn, - cnf2_metrics_update_fn=cnf2_metrics_update_fn, + runtime_fns=runtime_fns, ) @@ -1146,18 +1572,17 @@ def cycle_candidates( commit_stratum_fn: CommitStratumFn = commit_stratum, apply_q_fn: ApplyQFn = apply_q, identity_q_fn: IdentityQFn = _identity_q, - safe_gather_ok_fn=_jax_safe.safe_gather_1d_ok, + safe_gather_ok_fn: SafeGatherOkFn = _jax_safe.safe_gather_1d_ok, safe_gather_ok_bound_fn=None, - safe_gather_ok_value_fn=_jax_safe.safe_gather_1d_ok_value, + safe_gather_ok_value_fn: SafeGatherOkValueFn = _jax_safe.safe_gather_1d_ok_value, host_bool_value_fn: HostBoolValueFn = _host_bool_value, host_int_value_fn: HostIntValueFn = _host_int_value, guards_enabled_fn: GuardsEnabledFn = _guards_enabled, ledger_roots_hash_host_fn: LedgerRootsHashFn = _ledger_roots_hash_host, - cnf2_enabled_fn=_cnf2_enabled, - cnf2_slot1_enabled_fn=_cnf2_slot1_enabled, - cnf2_metrics_enabled_fn=_cnf2_metrics_enabled, - cnf2_metrics_update_fn=_cnf2_metrics_update, + runtime_fns: Cnf2RuntimeFns = DEFAULT_CNF2_RUNTIME_FNS, ): + if cfg is not None and runtime_fns is DEFAULT_CNF2_RUNTIME_FNS: + runtime_fns = cfg.runtime_fns if cfg is not None and cfg.safe_gather_policy_value is not None: safe_gather_policy_value = cfg.safe_gather_policy_value if safe_gather_policy_value is not None: @@ -1190,10 +1615,7 @@ def cycle_candidates( host_int_value_fn=host_int_value_fn, guards_enabled_fn=guards_enabled_fn, ledger_roots_hash_host_fn=ledger_roots_hash_host_fn, - cnf2_enabled_fn=cnf2_enabled_fn, - cnf2_slot1_enabled_fn=cnf2_slot1_enabled_fn, - cnf2_metrics_enabled_fn=cnf2_metrics_enabled_fn, - cnf2_metrics_update_fn=cnf2_metrics_update_fn, + runtime_fns=runtime_fns, ) return cycle_candidates_static( ledger, @@ -1218,19 +1640,18 @@ def cycle_candidates( host_int_value_fn=host_int_value_fn, guards_enabled_fn=guards_enabled_fn, ledger_roots_hash_host_fn=ledger_roots_hash_host_fn, - cnf2_enabled_fn=cnf2_enabled_fn, - cnf2_slot1_enabled_fn=cnf2_slot1_enabled_fn, - cnf2_metrics_enabled_fn=cnf2_metrics_enabled_fn, - cnf2_metrics_update_fn=cnf2_metrics_update_fn, + runtime_fns=runtime_fns, ) -def cycle_candidates_bound( - ledger, +def cycle_candidates_state( + state: LedgerState, frontier_ids, - cfg: Cnf2BoundConfig, - *, validate_mode: ValidateMode = ValidateMode.NONE, + *, + cfg: Cnf2Config | None = None, + safe_gather_policy: SafetyPolicy | None = None, + safe_gather_policy_value: PolicyValue | None = None, guard_cfg: GuardConfig | None = None, intern_fn: InternFn = intern_nodes, intern_cfg: InternConfig | None = None, @@ -1242,29 +1663,33 @@ def cycle_candidates_bound( commit_stratum_fn: CommitStratumFn = commit_stratum, apply_q_fn: ApplyQFn = apply_q, identity_q_fn: IdentityQFn = _identity_q, - safe_gather_ok_fn=_jax_safe.safe_gather_1d_ok, - safe_gather_ok_value_fn=_jax_safe.safe_gather_1d_ok_value, + safe_gather_ok_fn: SafeGatherOkFn = _jax_safe.safe_gather_1d_ok, + safe_gather_ok_bound_fn=None, + safe_gather_ok_value_fn: SafeGatherOkValueFn = _jax_safe.safe_gather_1d_ok_value, host_bool_value_fn: HostBoolValueFn = _host_bool_value, host_int_value_fn: HostIntValueFn = _host_int_value, guards_enabled_fn: GuardsEnabledFn = _guards_enabled, ledger_roots_hash_host_fn: LedgerRootsHashFn = _ledger_roots_hash_host, - cnf2_enabled_fn=_cnf2_enabled, - cnf2_slot1_enabled_fn=_cnf2_slot1_enabled, - cnf2_metrics_enabled_fn=_cnf2_metrics_enabled, - cnf2_metrics_update_fn=_cnf2_metrics_update, + runtime_fns: Cnf2RuntimeFns = DEFAULT_CNF2_RUNTIME_FNS, ): - """PolicyBinding-required wrapper for cycle_candidates.""" - policy_binding = cfg.policy_binding - cfg = cfg.as_cfg() - if policy_binding.mode == PolicyMode.VALUE: - return cycle_candidates_value( - ledger, + if cfg is not None and runtime_fns is DEFAULT_CNF2_RUNTIME_FNS: + runtime_fns = cfg.runtime_fns + if cfg is not None and cfg.safe_gather_policy_value is not None: + safe_gather_policy_value = cfg.safe_gather_policy_value + if safe_gather_policy_value is not None: + if safe_gather_policy is not None: + raise PrismPolicyBindingError( + "cycle_candidates_state received both safe_gather_policy and " + "safe_gather_policy_value", + context="cycle_candidates_state", + policy_mode="ambiguous", + ) + return cycle_candidates_value_state( + state, frontier_ids, validate_mode=validate_mode, cfg=cfg, - safe_gather_policy_value=require_value_policy( - policy_binding, context="cycle_candidates_bound" - ), + safe_gather_policy_value=safe_gather_policy_value, guard_cfg=guard_cfg, intern_fn=intern_fn, intern_cfg=intern_cfg, @@ -1281,19 +1706,14 @@ def cycle_candidates_bound( host_int_value_fn=host_int_value_fn, guards_enabled_fn=guards_enabled_fn, ledger_roots_hash_host_fn=ledger_roots_hash_host_fn, - cnf2_enabled_fn=cnf2_enabled_fn, - cnf2_slot1_enabled_fn=cnf2_slot1_enabled_fn, - cnf2_metrics_enabled_fn=cnf2_metrics_enabled_fn, - cnf2_metrics_update_fn=cnf2_metrics_update_fn, + runtime_fns=runtime_fns, ) - return cycle_candidates_static( - ledger, + return cycle_candidates_static_state( + state, frontier_ids, validate_mode=validate_mode, cfg=cfg, - safe_gather_policy=require_static_policy( - policy_binding, context="cycle_candidates_bound" - ), + safe_gather_policy=safe_gather_policy, guard_cfg=guard_cfg, intern_fn=intern_fn, intern_cfg=intern_cfg, @@ -1306,15 +1726,238 @@ def cycle_candidates_bound( apply_q_fn=apply_q_fn, identity_q_fn=identity_q_fn, safe_gather_ok_fn=safe_gather_ok_fn, + safe_gather_ok_bound_fn=safe_gather_ok_bound_fn, + host_bool_value_fn=host_bool_value_fn, + host_int_value_fn=host_int_value_fn, + guards_enabled_fn=guards_enabled_fn, + ledger_roots_hash_host_fn=ledger_roots_hash_host_fn, + runtime_fns=runtime_fns, + ) + + +def cycle_candidates_bound( + ledger, + frontier_ids, + cfg: Cnf2BoundConfig, + *, + validate_mode: ValidateMode = ValidateMode.NONE, + guard_cfg: GuardConfig | None = None, + intern_fn: InternFn = intern_nodes, + intern_cfg: InternConfig | None = None, + node_batch_fn: NodeBatchFn = _node_batch, + coord_xor_batch_fn: CoordXorBatchFn = coord_xor_batch, + emit_candidates_fn: EmitCandidatesFn = emit_candidates, + candidate_indices_fn: CandidateIndicesFn = _candidate_indices, + scatter_drop_fn: ScatterDropFn = _scatter_drop, + commit_stratum_fn: CommitStratumFn | None = None, + apply_q_fn: ApplyQFn = apply_q, + identity_q_fn: IdentityQFn = _identity_q, + safe_gather_ok_fn: SafeGatherOkFn = _jax_safe.safe_gather_1d_ok, + safe_gather_ok_value_fn: SafeGatherOkValueFn = _jax_safe.safe_gather_1d_ok_value, + host_bool_value_fn: HostBoolValueFn = _host_bool_value, + host_int_value_fn: HostIntValueFn = _host_int_value, + guards_enabled_fn: GuardsEnabledFn = _guards_enabled, + ledger_roots_hash_host_fn: LedgerRootsHashFn = _ledger_roots_hash_host, + runtime_fns: Cnf2RuntimeFns = DEFAULT_CNF2_RUNTIME_FNS, +): + """PolicyBinding-required wrapper for cycle_candidates. + + Binds policy + guard exactly once, then delegates to branch-free core. + """ + if runtime_fns is DEFAULT_CNF2_RUNTIME_FNS: + runtime_fns = cfg.cfg.runtime_fns + if isinstance(cfg, Cnf2ValueBoundConfig): + if commit_stratum_fn is None: + commit_stratum_fn = commit_stratum_value + cfg_resolved, policy_value = cfg.bind_cfg( + safe_gather_ok_value_fn=safe_gather_ok_value_fn, + guard_cfg=guard_cfg, + commit_stratum_fn=commit_stratum_fn, + ) + commit_stratum_value_fn = ( + cfg_resolved.commit_stratum_fn or commit_stratum_value + ) + commit_fns = Cnf2CommitInputs( + intern_fn=intern_fn, + commit_stratum_fn=commit_stratum_value_fn, + ) + state, frontier, strata, q_map = _cycle_candidates_core_value_bound( + ledger, + frontier_ids, + validate_mode=validate_mode, + cfg=cfg_resolved, + safe_gather_policy_value=policy_value, + guard_cfg=None, + commit_fns=commit_fns, + intern_cfg=intern_cfg, + node_batch_fn=node_batch_fn, + coord_xor_batch_fn=coord_xor_batch_fn, + emit_candidates_fn=emit_candidates_fn, + candidate_indices_fn=candidate_indices_fn, + scatter_drop_fn=scatter_drop_fn, + apply_q_fn=apply_q_fn, + identity_q_fn=identity_q_fn, + safe_gather_ok_value_fn=cfg_resolved.safe_gather_ok_value_fn + or safe_gather_ok_value_fn, + host_bool_value_fn=host_bool_value_fn, + host_int_value_fn=host_int_value_fn, + guards_enabled_fn=guards_enabled_fn, + ledger_roots_hash_host_fn=ledger_roots_hash_host_fn, + runtime_fns=runtime_fns, + ) + return state.ledger, frontier, strata, q_map + if not isinstance(cfg, Cnf2StaticBoundConfig): + raise PrismPolicyBindingError( + "cycle_candidates_bound expected Cnf2StaticBoundConfig or Cnf2ValueBoundConfig", + context="cycle_candidates_bound", + policy_mode="ambiguous", + ) + if commit_stratum_fn is None: + commit_stratum_fn = commit_stratum_bound + cfg_resolved, policy = cfg.bind_cfg( + safe_gather_ok_fn=safe_gather_ok_fn, + guard_cfg=guard_cfg, + commit_stratum_fn=commit_stratum_fn, + ) + commit_stratum_static_fn = ( + cfg_resolved.commit_stratum_fn or commit_stratum_bound + ) + commit_fns = Cnf2CommitInputs( + intern_fn=intern_fn, + commit_stratum_fn=commit_stratum_static_fn, + ) + state, frontier, strata, q_map = _cycle_candidates_core_static_bound( + ledger, + frontier_ids, + validate_mode=validate_mode, + cfg=cfg_resolved, + safe_gather_policy=policy, + guard_cfg=None, + commit_fns=commit_fns, + intern_cfg=intern_cfg, + node_batch_fn=node_batch_fn, + coord_xor_batch_fn=coord_xor_batch_fn, + emit_candidates_fn=emit_candidates_fn, + candidate_indices_fn=candidate_indices_fn, + scatter_drop_fn=scatter_drop_fn, + apply_q_fn=apply_q_fn, + identity_q_fn=identity_q_fn, + safe_gather_ok_fn=cfg_resolved.safe_gather_ok_bound_fn or safe_gather_ok_fn, + host_bool_value_fn=host_bool_value_fn, + host_int_value_fn=host_int_value_fn, + guards_enabled_fn=guards_enabled_fn, + ledger_roots_hash_host_fn=ledger_roots_hash_host_fn, + runtime_fns=runtime_fns, + ) + return state.ledger, frontier, strata, q_map + + +def cycle_candidates_bound_state( + state: LedgerState, + frontier_ids, + cfg: Cnf2BoundConfig, + *, + validate_mode: ValidateMode = ValidateMode.NONE, + guard_cfg: GuardConfig | None = None, + intern_fn: InternFn = intern_nodes, + intern_cfg: InternConfig | None = None, + node_batch_fn: NodeBatchFn = _node_batch, + coord_xor_batch_fn: CoordXorBatchFn = coord_xor_batch, + emit_candidates_fn: EmitCandidatesFn = emit_candidates, + candidate_indices_fn: CandidateIndicesFn = _candidate_indices, + scatter_drop_fn: ScatterDropFn = _scatter_drop, + commit_stratum_fn: CommitStratumFn | None = None, + apply_q_fn: ApplyQFn = apply_q, + identity_q_fn: IdentityQFn = _identity_q, + safe_gather_ok_fn: SafeGatherOkFn = _jax_safe.safe_gather_1d_ok, + safe_gather_ok_value_fn: SafeGatherOkValueFn = _jax_safe.safe_gather_1d_ok_value, + host_bool_value_fn: HostBoolValueFn = _host_bool_value, + host_int_value_fn: HostIntValueFn = _host_int_value, + guards_enabled_fn: GuardsEnabledFn = _guards_enabled, + ledger_roots_hash_host_fn: LedgerRootsHashFn = _ledger_roots_hash_host, + runtime_fns: Cnf2RuntimeFns = DEFAULT_CNF2_RUNTIME_FNS, +): + """PolicyBinding-required wrapper for cycle_candidates returning LedgerState.""" + if runtime_fns is DEFAULT_CNF2_RUNTIME_FNS: + runtime_fns = cfg.cfg.runtime_fns + if isinstance(cfg, Cnf2ValueBoundConfig): + if commit_stratum_fn is None: + commit_stratum_fn = commit_stratum_value + cfg_resolved, policy_value = cfg.bind_cfg( + safe_gather_ok_value_fn=safe_gather_ok_value_fn, + guard_cfg=guard_cfg, + commit_stratum_fn=commit_stratum_fn, + ) + commit_stratum_value_fn = ( + cfg_resolved.commit_stratum_fn or commit_stratum_value + ) + commit_fns = Cnf2CommitInputs( + intern_fn=intern_fn, + commit_stratum_fn=commit_stratum_value_fn, + ) + state, frontier, strata, q_map = _cycle_candidates_core_value_bound( + state, + frontier_ids, + validate_mode=validate_mode, + cfg=cfg_resolved, + safe_gather_policy_value=policy_value, + guard_cfg=None, + commit_fns=commit_fns, + intern_cfg=intern_cfg, + node_batch_fn=node_batch_fn, + coord_xor_batch_fn=coord_xor_batch_fn, + emit_candidates_fn=emit_candidates_fn, + candidate_indices_fn=candidate_indices_fn, + scatter_drop_fn=scatter_drop_fn, + apply_q_fn=apply_q_fn, + identity_q_fn=identity_q_fn, + safe_gather_ok_value_fn=cfg_resolved.safe_gather_ok_value_fn + or safe_gather_ok_value_fn, + host_bool_value_fn=host_bool_value_fn, + host_int_value_fn=host_int_value_fn, + guards_enabled_fn=guards_enabled_fn, + ledger_roots_hash_host_fn=ledger_roots_hash_host_fn, + runtime_fns=runtime_fns, + ) + return state, frontier, strata, q_map + if commit_stratum_fn is None: + commit_stratum_fn = commit_stratum_bound + cfg_resolved, policy = cfg.bind_cfg( + safe_gather_ok_fn=safe_gather_ok_fn, + guard_cfg=guard_cfg, + commit_stratum_fn=commit_stratum_fn, + ) + commit_stratum_static_fn = ( + cfg_resolved.commit_stratum_fn or commit_stratum_bound + ) + commit_fns = Cnf2CommitInputs( + intern_fn=intern_fn, + commit_stratum_fn=commit_stratum_static_fn, + ) + state, frontier, strata, q_map = _cycle_candidates_core_static_bound( + state, + frontier_ids, + validate_mode=validate_mode, + cfg=cfg_resolved, + safe_gather_policy=policy, + guard_cfg=None, + commit_fns=commit_fns, + intern_cfg=intern_cfg, + node_batch_fn=node_batch_fn, + coord_xor_batch_fn=coord_xor_batch_fn, + emit_candidates_fn=emit_candidates_fn, + candidate_indices_fn=candidate_indices_fn, + scatter_drop_fn=scatter_drop_fn, + apply_q_fn=apply_q_fn, + identity_q_fn=identity_q_fn, + safe_gather_ok_fn=cfg_resolved.safe_gather_ok_fn or safe_gather_ok_fn, host_bool_value_fn=host_bool_value_fn, host_int_value_fn=host_int_value_fn, guards_enabled_fn=guards_enabled_fn, ledger_roots_hash_host_fn=ledger_roots_hash_host_fn, - cnf2_enabled_fn=cnf2_enabled_fn, - cnf2_slot1_enabled_fn=cnf2_slot1_enabled_fn, - cnf2_metrics_enabled_fn=cnf2_metrics_enabled_fn, - cnf2_metrics_update_fn=cnf2_metrics_update_fn, + runtime_fns=runtime_fns, ) + return state, frontier, strata, q_map __all__ = [ @@ -1330,7 +1973,11 @@ def cycle_candidates_bound( "intern_candidates", "intern_candidates_cfg", "cycle_candidates", + "cycle_candidates_state", "cycle_candidates_static", + "cycle_candidates_static_state", "cycle_candidates_value", + "cycle_candidates_value_state", "cycle_candidates_bound", + "cycle_candidates_bound_state", ] diff --git a/src/prism_bsp/config.py b/src/prism_bsp/config.py index ee4a8ba..ef6eb0e 100644 --- a/src/prism_bsp/config.py +++ b/src/prism_bsp/config.py @@ -1,8 +1,8 @@ from __future__ import annotations -from dataclasses import dataclass, replace +from dataclasses import dataclass, replace, field from functools import partial -from typing import Callable, TypeAlias +from typing import Callable, TypeAlias, Any from prism_coord.config import CoordConfig from prism_core.compact import CompactConfig @@ -16,8 +16,8 @@ oob_any, oob_any_value, ) -from prism_core.modes import Cnf2Mode, coerce_cnf2_mode, ValidateMode, require_validate_mode -from prism_core.errors import PrismCnf2ModeConflictError, PrismPolicyBindingError +from prism_core.modes import ValidateMode, require_validate_mode +from prism_core.errors import PrismPolicyBindingError from prism_core.di import call_with_optional_kwargs from prism_ledger.config import InternConfig from prism_core.protocols import ( @@ -57,26 +57,17 @@ from prism_vm_core.candidates import candidate_indices_cfg from prism_coord.coord import coord_xor_batch from prism_ledger.intern import intern_nodes -from prism_vm_core.gating import _cnf2_enabled, _cnf2_slot1_enabled -from prism_metrics.metrics import _cnf2_metrics_enabled, _cnf2_metrics_update +from prism_metrics.metrics import _cnf2_metrics_update from prism_vm_core.guards import _guards_enabled from prism_vm_core.domains import _host_bool_value, _host_int_value from prism_vm_core.hashes import _ledger_roots_hash_host -from prism_semantics.commit import commit_stratum_bound, commit_stratum_value - - -@dataclass(frozen=True, slots=True) -class Cnf2Flags: - """CNF-2 gate toggles for DI. - - None means "defer to default gating". - """ - - enabled: bool | None = None - slot1_enabled: bool | None = None - +from prism_semantics.commit import ( + apply_q, + apply_q_ok, + commit_stratum_bound, + commit_stratum_value, +) -DEFAULT_CNF2_FLAGS = Cnf2Flags() @dataclass(frozen=True, slots=True) class Cnf2Config: @@ -86,8 +77,6 @@ class Cnf2Config: arguments override config values (DI precedence). """ - cnf2_mode: Cnf2Mode | None = None - flags: Cnf2Flags | None = None intern_cfg: InternConfig | None = None coord_cfg: CoordConfig | None = None intern_fn: InternFn | None = None @@ -95,11 +84,17 @@ class Cnf2Config: coord_xor_batch_fn: CoordXorBatchFn | None = None emit_candidates_fn: EmitCandidatesFn | None = None candidate_indices_fn: CandidateIndicesFn | None = None + candidate_fns: "Cnf2CandidateFns" | None = None compact_cfg: CompactConfig | None = None scatter_drop_fn: ScatterDropFn | None = None + commit_fns: "Cnf2CommitFns" | None = None commit_stratum_fn: CommitStratumFn | None = None apply_q_fn: ApplyQFn | None = None identity_q_fn: IdentityQFn | None = None + policy_fns: "Cnf2PolicyFns" | None = None + runtime_fns: "Cnf2RuntimeFns" = field( + default_factory=lambda: DEFAULT_CNF2_RUNTIME_FNS + ) safe_gather_ok_fn: SafeGatherOkFn | None = None safe_gather_ok_bound_fn: SafeGatherOkBoundFn | None = None safe_gather_ok_value_fn: SafeGatherOkValueFn | None = None @@ -111,29 +106,21 @@ class Cnf2Config: safe_gather_policy: SafetyPolicy | None = None safe_gather_policy_value: PolicyValue | None = None policy_binding: PolicyBinding | None = None - cnf2_enabled_fn: Callable[[], bool] | None = None - cnf2_slot1_enabled_fn: Callable[[], bool] | None = None - cnf2_metrics_enabled_fn: Callable[[], bool] | None = None - cnf2_metrics_update_fn: Callable[[int, int, int], None] | None = None @dataclass(frozen=True, slots=True) class Cnf2ResolvedInputs: """Resolved CNF-2 inputs with all config overrides applied.""" - cnf2_enabled_fn: Callable[[], bool] - cnf2_slot1_enabled_fn: Callable[[], bool] - cnf2_metrics_enabled_fn: Callable[[], bool] - cnf2_metrics_update_fn: Callable[[int, int, int], None] + runtime_fns: "Cnf2RuntimeFns" guard_cfg: GuardConfig | None intern_cfg: InternConfig | None - intern_fn: InternFn + commit_fns: "Cnf2CommitInputs" node_batch_fn: NodeBatchFn coord_xor_batch_fn: CoordXorBatchFn emit_candidates_fn: EmitCandidatesFn candidate_indices_fn: CandidateIndicesFn scatter_drop_fn: ScatterDropFn - commit_stratum_fn: CommitStratumFn apply_q_fn: ApplyQFn identity_q_fn: IdentityQFn safe_gather_ok_fn: SafeGatherOkFn @@ -142,7 +129,6 @@ class Cnf2ResolvedInputs: host_int_value_fn: HostIntValueFn guards_enabled_fn: GuardsEnabledFn ledger_roots_hash_host_fn: LedgerRootsHashFn - cnf2_mode: Cnf2Mode | None @dataclass(frozen=True, slots=True) @@ -154,6 +140,53 @@ class Cnf2CandidateInputs: scatter_drop_fn: ScatterDropFn +@dataclass(frozen=True, slots=True) +class Cnf2CandidateFns: + """Bundle of candidate helper functions observed as a forwarding group.""" + + emit_candidates_fn: EmitCandidatesFn | None = None + candidate_indices_fn: CandidateIndicesFn | None = None + scatter_drop_fn: ScatterDropFn | None = None + + +@dataclass(frozen=True, slots=True) +class Cnf2CommitInputs: + """Resolved commit/intern inputs for CNF-2.""" + + intern_fn: InternFn + commit_stratum_fn: CommitStratumFn + + +@dataclass(frozen=True, slots=True) +class Cnf2CommitFns: + """Bundle of commit/intern functions observed as a forwarding group.""" + + intern_fn: InternFn | None = None + commit_stratum_fn: CommitStratumFn | None = None + + +@dataclass(frozen=True, slots=True) +class Cnf2RuntimeFns: + """Bundle of runtime control hooks observed as a forwarding group.""" + + cnf2_metrics_update_fn: Callable[[int, int, int], None] = _cnf2_metrics_update + + +DEFAULT_CNF2_RUNTIME_FNS = Cnf2RuntimeFns() + + +@dataclass(frozen=True, slots=True) +class Cnf2PolicyFns: + """Bundle of policy-sensitive core functions observed as a forwarding group.""" + + commit_stratum_fn: CommitStratumFn | None = None + apply_q_fn: ApplyQFn | None = None + identity_q_fn: IdentityQFn | None = None + safe_gather_ok_fn: SafeGatherOkFn | None = None + safe_gather_ok_bound_fn: SafeGatherOkBoundFn | None = None + safe_gather_ok_value_fn: SafeGatherOkValueFn | None = None + + def resolve_cnf2_candidate_inputs( cfg: Cnf2Config | None, *, @@ -163,6 +196,14 @@ def resolve_cnf2_candidate_inputs( scatter_drop_fn: ScatterDropFn, ) -> Cnf2CandidateInputs: if cfg is not None: + if cfg.candidate_fns is not None: + bundle = cfg.candidate_fns + if bundle.emit_candidates_fn is not None: + emit_candidates_fn = bundle.emit_candidates_fn + if bundle.candidate_indices_fn is not None: + candidate_indices_fn = bundle.candidate_indices_fn + if bundle.scatter_drop_fn is not None: + scatter_drop_fn = bundle.scatter_drop_fn if cfg.emit_candidates_fn is not None: emit_candidates_fn = cfg.emit_candidates_fn if cfg.candidate_indices_fn is not None: @@ -233,13 +274,12 @@ def resolve_cnf2_inputs( *, guard_cfg: GuardConfig | None, intern_cfg: InternConfig | None, - intern_fn: InternFn, + commit_fns: Cnf2CommitInputs, node_batch_fn: NodeBatchFn, coord_xor_batch_fn: CoordXorBatchFn, emit_candidates_fn: EmitCandidatesFn, candidate_indices_fn: CandidateIndicesFn, scatter_drop_fn: ScatterDropFn, - commit_stratum_fn: CommitStratumFn, apply_q_fn: ApplyQFn, identity_q_fn: IdentityQFn, safe_gather_ok_fn: SafeGatherOkFn, @@ -248,13 +288,13 @@ def resolve_cnf2_inputs( host_int_value_fn: HostIntValueFn, guards_enabled_fn: GuardsEnabledFn, ledger_roots_hash_host_fn: LedgerRootsHashFn, - cnf2_enabled_fn: Callable[[], bool], - cnf2_slot1_enabled_fn: Callable[[], bool], - cnf2_metrics_enabled_fn: Callable[[], bool], - cnf2_metrics_update_fn: Callable[[int, int, int], None], + runtime_fns: Cnf2RuntimeFns, ) -> Cnf2ResolvedInputs: """Resolve CNF-2 config overrides into concrete inputs.""" + intern_fn = commit_fns.intern_fn + commit_stratum_fn = commit_fns.commit_stratum_fn + def _maybe_override(current, default, override): if override is None: return current @@ -277,20 +317,48 @@ def _maybe_override(current, default, override): host_int_default = host_int_value_fn guards_enabled_default = guards_enabled_fn ledger_roots_hash_default = ledger_roots_hash_host_fn - cnf2_enabled_default = cnf2_enabled_fn - cnf2_slot1_default = cnf2_slot1_enabled_fn - cnf2_metrics_enabled_default = cnf2_metrics_enabled_fn - cnf2_metrics_update_default = cnf2_metrics_update_fn + runtime_default = runtime_fns - cnf2_mode = None if cfg is not None: + if cfg.commit_fns is not None: + commit_bundle = cfg.commit_fns + if commit_bundle.intern_fn is not None: + intern_fn = commit_bundle.intern_fn + if commit_bundle.commit_stratum_fn is not None: + commit_stratum_fn = commit_bundle.commit_stratum_fn + if cfg.policy_fns is not None: + policy_bundle = cfg.policy_fns + if policy_bundle.commit_stratum_fn is not None: + commit_stratum_fn = policy_bundle.commit_stratum_fn + if policy_bundle.apply_q_fn is not None: + apply_q_fn = policy_bundle.apply_q_fn + if policy_bundle.identity_q_fn is not None: + identity_q_fn = policy_bundle.identity_q_fn + if policy_bundle.safe_gather_ok_fn is not None: + safe_gather_ok_fn = policy_bundle.safe_gather_ok_fn + if policy_bundle.safe_gather_ok_bound_fn is not None: + if safe_gather_ok_fn is safe_gather_ok_default: + safe_gather_ok_fn = policy_bundle.safe_gather_ok_bound_fn + if policy_bundle.safe_gather_ok_value_fn is not None: + safe_gather_ok_value_fn = policy_bundle.safe_gather_ok_value_fn + if cfg.safe_gather_ok_bound_fn is not None: + if safe_gather_ok_fn is safe_gather_ok_default: + safe_gather_ok_fn = cfg.safe_gather_ok_bound_fn + else: + raise PrismPolicyBindingError( + "cycle_candidates_core received cfg.safe_gather_ok_bound_fn with safe_gather_ok_fn override", + context="cycle_candidates_core", + policy_mode="static", + ) + runtime_bundle = cfg.runtime_fns + if runtime_fns is runtime_default: + runtime_fns = runtime_bundle if cfg.policy_binding is not None: raise PrismPolicyBindingError( "cycle_candidates_core received cfg.policy_binding; bind at wrapper", context="cycle_candidates_core", policy_mode="ambiguous", ) - cnf2_mode = cfg.cnf2_mode guard_cfg = cfg.guard_cfg if guard_cfg is None else guard_cfg intern_cfg = intern_cfg if intern_cfg is not None else cfg.intern_cfg if cfg.coord_cfg is not None and coord_xor_batch_fn is coord_xor_batch: @@ -326,6 +394,14 @@ def _maybe_override(current, default, override): context="cycle_candidates_core", policy_mode="static", ) + if cfg.commit_fns is not None and cfg.commit_fns.commit_stratum_fn is not None: + if getattr(safe_gather_ok_fn, "_prism_policy_bound", False): + if cfg.commit_fns.commit_stratum_fn is not commit_stratum_fn: + raise PrismPolicyBindingError( + "cycle_candidates_core received cfg.commit_fns.commit_stratum_fn with policy-bound safe_gather_ok_fn", + context="cycle_candidates_core", + policy_mode="static", + ) commit_stratum_fn = _maybe_override( commit_stratum_fn, commit_stratum_default, cfg.commit_stratum_fn ) @@ -345,40 +421,12 @@ def _maybe_override(current, default, override): ledger_roots_hash_default, cfg.ledger_roots_hash_host_fn, ) - if cfg.cnf2_metrics_enabled_fn is not None and cnf2_metrics_enabled_fn is cnf2_metrics_enabled_default: - cnf2_metrics_enabled_fn = cfg.cnf2_metrics_enabled_fn - if cfg.cnf2_metrics_update_fn is not None and cnf2_metrics_update_fn is cnf2_metrics_update_default: - cnf2_metrics_update_fn = cfg.cnf2_metrics_update_fn - if cfg.cnf2_enabled_fn is not None and cnf2_enabled_fn is cnf2_enabled_default: - cnf2_enabled_fn = cfg.cnf2_enabled_fn - if cfg.cnf2_slot1_enabled_fn is not None and cnf2_slot1_enabled_fn is cnf2_slot1_default: - cnf2_slot1_enabled_fn = cfg.cnf2_slot1_enabled_fn - if cfg.flags is not None: - if cfg.flags.enabled is not None and cnf2_enabled_fn is cnf2_enabled_default: - cnf2_enabled_fn = lambda: bool(cfg.flags.enabled) - if cfg.flags.slot1_enabled is not None and cnf2_slot1_enabled_fn is cnf2_slot1_default: - cnf2_slot1_enabled_fn = lambda: bool(cfg.flags.slot1_enabled) if cfg.safe_gather_ok_value_fn is not None and getattr(safe_gather_ok_fn, "_prism_policy_bound", False): raise PrismPolicyBindingError( "cycle_candidates_core received cfg.safe_gather_ok_value_fn with policy-bound safe_gather_ok_fn", context="cycle_candidates_core", policy_mode="ambiguous", ) - if cnf2_mode is not None: - mode = coerce_cnf2_mode(cnf2_mode, context="cycle_candidates_core") - if mode != Cnf2Mode.AUTO: - if ( - cfg is not None - and cfg.flags is not None - ) or cnf2_enabled_fn is not cnf2_enabled_default or cnf2_slot1_enabled_fn is not cnf2_slot1_default: - raise PrismCnf2ModeConflictError( - "cycle_candidates_core received cnf2_mode alongside cnf2_flags or cnf2_*_enabled_fn", - context="cycle_candidates_core", - ) - enabled_value = mode in (Cnf2Mode.BASE, Cnf2Mode.SLOT1) - slot1_value = mode == Cnf2Mode.SLOT1 - cnf2_enabled_fn = lambda: enabled_value - cnf2_slot1_enabled_fn = lambda: slot1_value if intern_cfg is not None and intern_fn is intern_nodes: intern_fn = partial(intern_nodes, cfg=intern_cfg) if intern_cfg is not None and coord_xor_batch_fn is coord_xor_batch: @@ -391,8 +439,6 @@ def _maybe_override(current, default, override): context="cycle_candidates_core", policy_mode="static", ) - if cnf2_metrics_enabled_fn is not None and not cnf2_metrics_enabled_fn(): - cnf2_metrics_update_fn = lambda *_: None if node_batch_fn is None: raise ValueError("node_batch_fn is required") if emit_candidates_fn is None: @@ -407,20 +453,20 @@ def _maybe_override(current, default, override): raise ValueError("apply_q_fn is required") if identity_q_fn is None: raise ValueError("identity_q_fn is required") + commit_fns = Cnf2CommitInputs( + intern_fn=intern_fn, + commit_stratum_fn=commit_stratum_fn, + ) return Cnf2ResolvedInputs( - cnf2_enabled_fn=cnf2_enabled_fn, - cnf2_slot1_enabled_fn=cnf2_slot1_enabled_fn, - cnf2_metrics_enabled_fn=cnf2_metrics_enabled_fn, - cnf2_metrics_update_fn=cnf2_metrics_update_fn, + runtime_fns=runtime_fns, guard_cfg=guard_cfg, intern_cfg=intern_cfg, - intern_fn=intern_fn, + commit_fns=commit_fns, node_batch_fn=node_batch_fn, coord_xor_batch_fn=coord_xor_batch_fn, emit_candidates_fn=emit_candidates_fn, candidate_indices_fn=candidate_indices_fn, scatter_drop_fn=scatter_drop_fn, - commit_stratum_fn=commit_stratum_fn, apply_q_fn=apply_q_fn, identity_q_fn=identity_q_fn, safe_gather_ok_fn=safe_gather_ok_fn, @@ -429,7 +475,6 @@ def _maybe_override(current, default, override): host_int_value_fn=host_int_value_fn, guards_enabled_fn=guards_enabled_fn, ledger_roots_hash_host_fn=ledger_roots_hash_host_fn, - cnf2_mode=cnf2_mode, ) @@ -601,6 +646,28 @@ class ArenaInteractConfig: DEFAULT_ARENA_INTERACT_CONFIG = ArenaInteractConfig() +@dataclass(frozen=True, slots=True) +class SwizzleWithPermFns: + """Bundle of swizzle-with-perm functions observed as a forwarding group.""" + + with_perm: OpSortWithPermFn | None = None + morton_with_perm: OpSortWithPermFn | None = None + blocked_with_perm: OpSortWithPermFn | None = None + hierarchical_with_perm: OpSortWithPermFn | None = None + servo_with_perm: OpSortWithPermFn | None = None + + +@dataclass(frozen=True, slots=True) +class SwizzleWithPermFnsBound: + """Required swizzle-with-perm bundle (no None fields).""" + + with_perm: OpSortWithPermFn + morton_with_perm: OpSortWithPermFn + blocked_with_perm: OpSortWithPermFn + hierarchical_with_perm: OpSortWithPermFn + servo_with_perm: OpSortWithPermFn + + @dataclass(frozen=True, slots=True) class ArenaCycleConfig: """Arena cycle DI bundle.""" @@ -609,6 +676,8 @@ class ArenaCycleConfig: servo_enabled_fn: ServoEnabledFn | None = None servo_update_fn: ServoUpdateFn | None = None op_morton_fn: OpMortonFn | None = None + swizzle_with_perm_fns: SwizzleWithPermFns | None = None + swizzle_with_perm_value_fns: SwizzleWithPermFns | None = None op_sort_and_swizzle_with_perm_fn: OpSortWithPermFn | None = None op_sort_and_swizzle_morton_with_perm_fn: OpSortWithPermFn | None = None op_sort_and_swizzle_blocked_with_perm_fn: OpSortWithPermFn | None = None @@ -630,6 +699,26 @@ class ArenaCycleConfig: DEFAULT_ARENA_CYCLE_CONFIG = ArenaCycleConfig() +@dataclass(frozen=True, slots=True) +class ArenaSortConfig: + """Arena sort/schedule parameters bundled as data. + + morton is an optional precomputed array; type left as Any to avoid + importing jax into config modules. + """ + + do_sort: bool = True + use_morton: bool = False + block_size: int | None = None + morton: Any | None = None + l2_block_size: int | None = None + l1_block_size: int | None = None + do_global: bool = False + + +DEFAULT_ARENA_SORT_CONFIG = ArenaSortConfig() + + @dataclass(frozen=True, slots=True) class IntrinsicConfig: """Intrinsic cycle DI bundle.""" @@ -643,8 +732,6 @@ class IntrinsicConfig: DEFAULT_INTRINSIC_CONFIG = IntrinsicConfig() __all__ = [ - "Cnf2Flags", - "DEFAULT_CNF2_FLAGS", "Cnf2Config", "DEFAULT_CNF2_CONFIG", "Cnf2StaticBoundConfig", @@ -652,13 +739,23 @@ class IntrinsicConfig: "Cnf2BoundConfig", "ArenaInteractConfig", "DEFAULT_ARENA_INTERACT_CONFIG", + "SwizzleWithPermFns", + "SwizzleWithPermFnsBound", "ArenaCycleConfig", "DEFAULT_ARENA_CYCLE_CONFIG", + "ArenaSortConfig", + "DEFAULT_ARENA_SORT_CONFIG", "IntrinsicConfig", "DEFAULT_INTRINSIC_CONFIG", "Cnf2ResolvedInputs", "resolve_cnf2_inputs", "Cnf2CandidateInputs", + "Cnf2CandidateFns", + "Cnf2CommitInputs", + "Cnf2CommitFns", + "Cnf2RuntimeFns", + "DEFAULT_CNF2_RUNTIME_FNS", + "Cnf2PolicyFns", "resolve_cnf2_candidate_inputs", "Cnf2InternInputs", "resolve_cnf2_intern_inputs", @@ -672,7 +769,7 @@ def resolve_validate_mode( validate_mode: ValidateMode, *, guards_enabled_fn, - context: str, + context: str = "cycle_candidates", ) -> ValidateMode: mode = require_validate_mode(validate_mode, context=context) if guards_enabled_fn() and mode == ValidateMode.NONE: @@ -681,6 +778,10 @@ def resolve_validate_mode( def _apply_q_optional_ok(apply_q_fn: ApplyQFn, q_map, ids): + if apply_q_fn is apply_q: + return apply_q_ok(q_map, ids) + if apply_q_fn is apply_q_ok: + return apply_q_fn(q_map, ids) result = call_with_optional_kwargs( apply_q_fn, {"return_ok": True}, q_map, ids ) diff --git a/src/prism_bsp/intrinsic.py b/src/prism_bsp/intrinsic.py index 07b10b2..5b0a436 100644 --- a/src/prism_bsp/intrinsic.py +++ b/src/prism_bsp/intrinsic.py @@ -4,11 +4,13 @@ from functools import lru_cache, partial from prism_ledger.intern import intern_nodes -from prism_ledger.config import InternConfig +from prism_ledger.config import InternConfig, DEFAULT_INTERN_CONFIG +from prism_ledger.index import LedgerState, derive_ledger_state +from prism_core.di import call_with_optional_kwargs from prism_vm_core.domains import _host_raise_if_bad from prism_vm_core.ontology import OP_ADD, OP_MUL, OP_SUC, OP_ZERO, ZERO_PTR -from prism_vm_core.structures import NodeBatch -from prism_vm_core.protocols import HostRaiseFn, InternFn, NodeBatchFn +from prism_vm_core.structures import Ledger, NodeBatch +from prism_vm_core.protocols import HostRaiseFn, InternFn, InternStateFn, NodeBatchFn from prism_bsp.config import IntrinsicConfig, DEFAULT_INTRINSIC_CONFIG @@ -17,19 +19,20 @@ def _node_batch(op, a1, a2) -> NodeBatch: def _cycle_intrinsic_impl( - ledger, + state: LedgerState, frontier_ids, *, - intern_fn=intern_nodes, + intern_state_fn: InternStateFn, node_batch_fn=_node_batch, ): # m1 evaluator: intrinsic rewrite steps on Ledger (CNF-2 gated off). # See IMPLEMENTATION_PLAN.md (m1 intrinsic evaluator). def _skip(_): - return ledger, frontier_ids + return state, frontier_ids def _do(_): - ledger_local = ledger + state_local = state + ledger_local = state_local.ledger def _peel_one(ptr): def cond(state): @@ -81,9 +84,10 @@ def body(state): l1_a1 = jnp.where(is_mul_suc, val_x, l1_a1) l1_a2 = jnp.where(is_mul_suc, val_y, l1_a2) - l1_ids, ledger_local = intern_fn( - ledger_local, node_batch_fn(l1_ops, l1_a1, l1_a2) + l1_ids, state_local = intern_state_fn( + state_local, node_batch_fn(l1_ops, l1_a1, l1_a2) ) + ledger_local = state_local.ledger l2_ops = jnp.zeros_like(t_ops) l2_a1 = jnp.zeros_like(t_a1) @@ -94,9 +98,10 @@ def body(state): l2_a1 = jnp.where(is_mul_suc, val_y, l2_a1) l2_a2 = jnp.where(is_mul_suc, l1_ids, l2_a2) - l2_ids, ledger_local = intern_fn( - ledger_local, node_batch_fn(l2_ops, l2_a1, l2_a2) + l2_ids, state_local = intern_state_fn( + state_local, node_batch_fn(l2_ops, l2_a1, l2_a2) ) + ledger_local = state_local.ledger base_next = base_ids base_next = jnp.where(is_add_zero, zero_other, base_next) @@ -108,36 +113,38 @@ def body(state): wrap_child = jnp.where(changed, base_next, frontier_ids) def wrap_cond(state): - depth, _, led = state - return jnp.any((depth > 0) & (~led.oom)) + depth, _, led_state = state + return jnp.any((depth > 0) & (~led_state.ledger.oom)) def wrap_body(state): - depth, child, led = state - to_wrap = (depth > 0) & (~led.oom) + depth, child, led_state = state + to_wrap = (depth > 0) & (~led_state.ledger.oom) ops = jnp.where(to_wrap, jnp.int32(OP_SUC), jnp.int32(0)) a1 = jnp.where(to_wrap, child, jnp.int32(0)) a2 = jnp.zeros_like(a1) - new_ids, led = intern_fn(led, node_batch_fn(ops, a1, a2)) + new_ids, led_state = intern_state_fn( + led_state, node_batch_fn(ops, a1, a2) + ) child = jnp.where(to_wrap, new_ids, child) depth = depth - to_wrap.astype(jnp.int32) - return depth, child, led + return depth, child, led_state - _, wrap_child, ledger_local = lax.while_loop( - wrap_cond, wrap_body, (wrap_depth, wrap_child, ledger_local) + _, wrap_child, state_local = lax.while_loop( + wrap_cond, wrap_body, (wrap_depth, wrap_child, state_local) ) - return ledger_local, wrap_child + return state_local, wrap_child - return lax.cond(ledger.corrupt, _skip, _do, operand=None) + return lax.cond(state.ledger.corrupt, _skip, _do, operand=None) @lru_cache -def _cycle_intrinsic_jit(intern_fn: InternFn, node_batch_fn: NodeBatchFn): +def _cycle_intrinsic_jit(intern_state_fn: InternStateFn, node_batch_fn: NodeBatchFn): @jax.jit - def _impl(ledger, frontier_ids): + def _impl(state: LedgerState, frontier_ids): return _cycle_intrinsic_impl( - ledger, + state, frontier_ids, - intern_fn=intern_fn, + intern_state_fn=intern_state_fn, node_batch_fn=node_batch_fn, ) @@ -145,7 +152,7 @@ def _impl(ledger, frontier_ids): def cycle_intrinsic( - ledger, + ledger: Ledger | LedgerState, frontier_ids, *, intern_fn: InternFn = intern_nodes, @@ -156,15 +163,40 @@ def cycle_intrinsic( # BSPᵗ: temporal superstep / barrier semantics. if intern_cfg is not None and intern_fn is intern_nodes: intern_fn = partial(intern_nodes, cfg=intern_cfg) - ledger, frontier_ids = _cycle_intrinsic_jit(intern_fn, node_batch_fn)( - ledger, frontier_ids + if isinstance(ledger, LedgerState): + state = ledger + else: + if intern_cfg is None: + intern_cfg = DEFAULT_INTERN_CONFIG + state = derive_ledger_state( + ledger, op_buckets_full_range=intern_cfg.op_buckets_full_range + ) + + def _intern_state(state_in: LedgerState, batch_or_ops, a1=None, a2=None): + ids, new_ledger = call_with_optional_kwargs( + intern_fn, + {"ledger_index": state_in.index}, + state_in.ledger, + batch_or_ops, + a1, + a2, + ) + new_state = derive_ledger_state( + new_ledger, op_buckets_full_range=state_in.op_buckets_full_range + ) + return ids, new_state + + state_out, frontier_ids = _cycle_intrinsic_jit(_intern_state, node_batch_fn)( + state, frontier_ids ) - host_raise_fn(ledger, "Ledger capacity exceeded during cycle") - return ledger, frontier_ids + host_raise_fn(state_out.ledger, "Ledger capacity exceeded during cycle") + if isinstance(ledger, LedgerState): + return state_out, frontier_ids + return state_out.ledger, frontier_ids def cycle_intrinsic_cfg( - ledger, + ledger: Ledger | LedgerState, frontier_ids, *, cfg: IntrinsicConfig = DEFAULT_INTRINSIC_CONFIG, diff --git a/src/prism_bsp/space.py b/src/prism_bsp/space.py index 5788980..5101e37 100644 --- a/src/prism_bsp/space.py +++ b/src/prism_bsp/space.py @@ -181,16 +181,13 @@ def _apply_perm_and_swizzle( # IMPLEMENTATION_PLAN.md. # Swizzle is renormalization only; denotation must not change (plan). # See IMPLEMENTATION_PLAN.md (m3 denotation invariance). - guard_slot0_perm_cfg(perm, inv_perm, "swizzle.perm", cfg=guard_cfg) - guard_null_row_cfg( - new_ops, swizzled_arg1, swizzled_arg2, "swizzle.row0", cfg=guard_cfg - ) + guard_slot0_perm_cfg(perm, inv_perm, cfg=guard_cfg) + guard_null_row_cfg(new_ops, swizzled_arg1, swizzled_arg2, cfg=guard_cfg) guard_swizzle_args_cfg( swizzled_arg1, swizzled_arg2, live, arena.count, - "swizzle.args", cfg=guard_cfg, ) return ( @@ -235,16 +232,13 @@ def _apply_perm_and_swizzle_value( g2 = safe_gather_value_fn(inv_perm, idx2, "swizzle.arg2", policy_value=policy_value) swizzled_arg1 = jnp.where(live & (new_arg1 != 0), g1, 0) swizzled_arg2 = jnp.where(live & (new_arg2 != 0), g2, 0) - guard_slot0_perm_cfg(perm, inv_perm, "swizzle.perm", cfg=guard_cfg) - guard_null_row_cfg( - new_ops, swizzled_arg1, swizzled_arg2, "swizzle.row0", cfg=guard_cfg - ) + guard_slot0_perm_cfg(perm, inv_perm, cfg=guard_cfg) + guard_null_row_cfg(new_ops, swizzled_arg1, swizzled_arg2, cfg=guard_cfg) guard_swizzle_args_cfg( swizzled_arg1, swizzled_arg2, live, arena.count, - "swizzle.args", cfg=guard_cfg, ) return ( diff --git a/src/prism_cli/repl.py b/src/prism_cli/repl.py index 1acdf0e..5e04be4 100644 --- a/src/prism_cli/repl.py +++ b/src/prism_cli/repl.py @@ -1,5 +1,36 @@ import re import time +from dataclasses import dataclass + +# dataflow-bundle: block_size, do_global, do_sort, l1_block_size, l2_block_size, use_morton +# CLI sort/schedule bundle intentionally kept at the host interface. +# dataflow-bundle: block_size, use_morton +# Minimal CLI sort bundle for BSP entrypoints. +# dataflow-bundle: a1, a2 +# Host-side pointer pair bundle in baseline/arena allocators. + + +@dataclass(frozen=True) +class CliSortBundle: + block_size: int | None + do_global: bool + do_sort: bool + l1_block_size: int | None + l2_block_size: int | None + use_morton: bool + + +@dataclass(frozen=True) +class CliSortMiniBundle: + block_size: int | None + use_morton: bool + + +@dataclass(frozen=True) +class HostPtrPair: + a1: object + a2: object + from typing import Dict, Tuple import jax @@ -10,6 +41,7 @@ from prism_core.safety import oob_mask from prism_baseline.kernels import dispatch_kernel, kernel_add, kernel_mul, optimize_ptr from prism_bsp.arena_step import cycle +from prism_bsp.config import ArenaSortConfig, DEFAULT_ARENA_SORT_CONFIG from prism_bsp.intrinsic import cycle_intrinsic from prism_metrics.gpu import _gpu_watchdog_create from prism_metrics.metrics import ( @@ -21,7 +53,7 @@ damage_metrics_get, damage_metrics_reset, ) -from prism_semantics.commit import apply_q +from prism_semantics.commit import apply_q_ok from prism_vm_core.types import ( _arena_ptr, _committed_ids, @@ -46,17 +78,20 @@ ) from prism_vm_core.facade import ( _key_order_commutative_host, - _cnf2_enabled, _normalize_bsp_mode, cycle_candidates, + derive_ledger_state, init_arena, init_ledger, + init_ledger_state, init_manifest, - intern_nodes, + intern_nodes_state, node_batch, + LedgerState, + DEFAULT_INTERN_CONFIG, ) from prism_core.modes import BspMode -from prism_vm_core.guards import _expect_token, _pop_token +from prism_vm_core.guards import _expect_rparen, _pop_token _TEST_GUARDS = _jax_safe.TEST_GUARDS @@ -78,9 +113,10 @@ def __init__(self): self.kernels = {OP_ADD: kernel_add, OP_MUL: kernel_mul} def _cons_raw(self, op: int, a1: ManifestPtr, a2: ManifestPtr) -> ManifestPtr: - _require_manifest_ptr(a1, "PrismVM._cons_raw a1") - _require_manifest_ptr(a2, "PrismVM._cons_raw a2") - a1_i, a2_i = _key_order_commutative_host(op, int(a1), int(a2)) + pair = HostPtrPair(a1=a1, a2=a2) + _require_manifest_ptr(pair.a1, "PrismVM._cons_raw a1") + _require_manifest_ptr(pair.a2, "PrismVM._cons_raw a2") + a1_i, a2_i = _key_order_commutative_host(op, int(pair.a1), int(pair.a2)) cap = int(self.manifest.opcode.shape[0]) if self.active_count_host >= cap: self.manifest = self.manifest._replace( @@ -123,10 +159,11 @@ def cons( a1: ManifestPtr = ManifestPtr(0), a2: ManifestPtr = ManifestPtr(0), ) -> ManifestPtr: - _require_manifest_ptr(a1, "PrismVM.cons a1") - _require_manifest_ptr(a2, "PrismVM.cons a2") - a1_i = int(self._canonical_ptr(a1)) - a2_i = int(self._canonical_ptr(a2)) + pair = HostPtrPair(a1=a1, a2=a2) + _require_manifest_ptr(pair.a1, "PrismVM.cons a1") + _require_manifest_ptr(pair.a2, "PrismVM.cons a2") + a1_i = int(self._canonical_ptr(pair.a1)) + a2_i = int(self._canonical_ptr(pair.a2)) a1_i, a2_i = _key_order_commutative_host(op, a1_i, a2_i) signature = (op, _manifest_ptr(a1_i), _manifest_ptr(a2_i)) if signature in self.trace_cache: @@ -182,7 +219,7 @@ def parse(self, tokens) -> ManifestPtr: return self.cons(op, a1, a2) if token == "(": val = self.parse(tokens) - _expect_token(tokens, ")") + _expect_rparen(tokens) return val raise ValueError(f"Unknown: {token}") @@ -215,14 +252,15 @@ def __init__(self): def _alloc( self, op: int, a1: ArenaPtr = ArenaPtr(0), a2: ArenaPtr = ArenaPtr(0) ) -> ArenaPtr: + pair = HostPtrPair(a1=a1, a2=a2) cap = int(self.arena.opcode.shape[0]) idx = _host_int_value(self.arena.count) if idx >= cap: self.arena = self.arena._replace(oom=jnp.array(True, dtype=jnp.bool_)) raise ValueError("Arena capacity exceeded") - _require_arena_ptr(a1, "PrismVM_BSP_Legacy._alloc a1") - _require_arena_ptr(a2, "PrismVM_BSP_Legacy._alloc a2") - a1_i, a2_i = _key_order_commutative_host(op, int(a1), int(a2)) + _require_arena_ptr(pair.a1, "PrismVM_BSP_Legacy._alloc a1") + _require_arena_ptr(pair.a2, "PrismVM_BSP_Legacy._alloc a2") + a1_i, a2_i = _key_order_commutative_host(op, int(pair.a1), int(pair.a2)) self.arena = self.arena._replace( opcode=self.arena.opcode.at[idx].set(op), arg1=self.arena.arg1.at[idx].set(a1_i), @@ -244,7 +282,7 @@ def parse(self, tokens) -> ArenaPtr: return self._alloc(op, a1, a2) if token == "(": val = self.parse(tokens) - _expect_token(tokens, ")") + _expect_rparen(tokens) return val raise ValueError(f"Unknown: {token}") @@ -267,24 +305,37 @@ def decode(self, ptr: ArenaPtr, show_ids: bool = False) -> str: class PrismVM_BSP: def __init__(self): print("⚡ Prism IR: Initializing BSP Ledger...") - self.ledger = init_ledger() + self.intern_cfg = DEFAULT_INTERN_CONFIG + self.ledger = init_ledger_state(cfg=self.intern_cfg) def _intern( self, op: int, a1: LedgerId = LedgerId(0), a2: LedgerId = LedgerId(0) ) -> LedgerId: - _require_ledger_id(a1, "PrismVM_BSP._intern a1") - _require_ledger_id(a2, "PrismVM_BSP._intern a2") - a1_i, a2_i = _key_order_commutative_host(op, int(a1), int(a2)) - ids, self.ledger = intern_nodes( - self.ledger, - node_batch( - jnp.array([op], dtype=jnp.int32), - jnp.array([a1_i], dtype=jnp.int32), - jnp.array([a2_i], dtype=jnp.int32), - ), + pair = HostPtrPair(a1=a1, a2=a2) + _require_ledger_id(pair.a1, "PrismVM_BSP._intern a1") + _require_ledger_id(pair.a2, "PrismVM_BSP._intern a2") + a1_i, a2_i = _key_order_commutative_host(op, int(pair.a1), int(pair.a2)) + batch = node_batch( + jnp.array([op], dtype=jnp.int32), + jnp.array([a1_i], dtype=jnp.int32), + jnp.array([a2_i], dtype=jnp.int32), ) + if isinstance(self.ledger, LedgerState): + ids, self.ledger = intern_nodes_state( + self.ledger, batch, cfg=self.intern_cfg + ) + ledger_obj = self.ledger.ledger + else: + self.ledger = derive_ledger_state( + self.ledger, + op_buckets_full_range=self.intern_cfg.op_buckets_full_range, + ) + ids, self.ledger = intern_nodes_state( + self.ledger, batch, cfg=self.intern_cfg + ) + ledger_obj = self.ledger.ledger _host_raise_if_bad( - self.ledger, + ledger_obj, "Ledger capacity exceeded during interning", oom_exc=ValueError, ) @@ -303,27 +354,28 @@ def parse(self, tokens) -> LedgerId: return self._intern(op, a1, a2) if token == "(": val = self.parse(tokens) - _expect_token(tokens, ")") + _expect_rparen(tokens) return val raise ValueError(f"Unknown: {token}") def decode(self, ptr: LedgerId) -> str: _require_ledger_id(ptr, "PrismVM_BSP.decode ptr") + ledger_obj = self.ledger.ledger if isinstance(self.ledger, LedgerState) else self.ledger ptr_i = int(ptr) - op = _host_int_value(self.ledger.opcode[ptr_i]) + op = _host_int_value(ledger_obj.opcode[ptr_i]) if op == OP_ZERO: return "zero" if op == OP_SUC: - return f"(suc {self.decode(_ledger_id(self.ledger.arg1[ptr_i]))})" + return f"(suc {self.decode(_ledger_id(ledger_obj.arg1[ptr_i]))})" if op == OP_ADD: return ( - f"(add {self.decode(_ledger_id(self.ledger.arg1[ptr_i]))} " - f"{self.decode(_ledger_id(self.ledger.arg2[ptr_i]))})" + f"(add {self.decode(_ledger_id(ledger_obj.arg1[ptr_i]))} " + f"{self.decode(_ledger_id(ledger_obj.arg2[ptr_i]))})" ) if op == OP_MUL: return ( - f"(mul {self.decode(_ledger_id(self.ledger.arg1[ptr_i]))} " - f"{self.decode(_ledger_id(self.ledger.arg2[ptr_i]))})" + f"(mul {self.decode(_ledger_id(ledger_obj.arg1[ptr_i]))} " + f"{self.decode(_ledger_id(ledger_obj.arg2[ptr_i]))})" ) return f"<{OP_NAMES.get(op, '?')}:{ptr}>" @@ -376,16 +428,14 @@ def run_program_lines_arena( lines, vm=None, cycles=1, - do_sort=True, - use_morton=False, - block_size=None, - l2_block_size=None, - l1_block_size=None, - do_global=False, + *, + sort_cfg: ArenaSortConfig = DEFAULT_ARENA_SORT_CONFIG, ): if vm is None: vm = PrismVM_BSP_Legacy() - tile_size = _damage_tile_size(block_size, l2_block_size, l1_block_size) + tile_size = _damage_tile_size( + sort_cfg.block_size, sort_cfg.l2_block_size, sort_cfg.l1_block_size + ) watchdog = _gpu_watchdog_create() try: for inp in lines: @@ -400,12 +450,7 @@ def run_program_lines_arena( vm.arena, root_ptr = cycle( vm.arena, root_ptr, - do_sort=do_sort, - use_morton=use_morton, - block_size=block_size, - l2_block_size=l2_block_size, - l1_block_size=l1_block_size, - do_global=do_global, + sort_cfg=sort_cfg, ) root_ptr = _arena_ptr(_host_int_value(root_ptr)) if _host_bool_value(vm.arena.oom): @@ -459,17 +504,24 @@ def run_program_lines_bsp( l2_block_size=None, l1_block_size=None, do_global=False, - bsp_mode=BspMode.AUTO, + bsp_mode: BspMode | str | None = BspMode.AUTO, validate_mode: ValidateMode = ValidateMode.NONE, ): + sort_bundle = CliSortBundle( + block_size=block_size, + do_global=do_global, + do_sort=do_sort, + l1_block_size=l1_block_size, + l2_block_size=l2_block_size, + use_morton=use_morton, + ) + sort_kwargs = sort_bundle.__dict__ validate_mode = coerce_validate_mode( validate_mode, context="run_program_lines_bsp" ) if vm is None: vm = PrismVM_BSP() bsp_mode = _normalize_bsp_mode(bsp_mode) - if bsp_mode == BspMode.CNF2 and not _cnf2_enabled(): - raise ValueError("bsp_mode='cnf2' disabled until m2") for inp in lines: inp = inp.strip() if not inp or inp.startswith("#"): @@ -479,25 +531,53 @@ def run_program_lines_bsp( frontier = _committed_ids(jnp.array([int(root_ptr)], dtype=jnp.int32)) for _ in range(max(1, cycles)): if bsp_mode == BspMode.INTRINSIC: - vm.ledger, frontier_arr = cycle_intrinsic(vm.ledger, frontier.a) + ledger_obj = ( + vm.ledger.ledger + if isinstance(vm.ledger, LedgerState) + else vm.ledger + ) + ledger_obj, frontier_arr = cycle_intrinsic(ledger_obj, frontier.a) + if isinstance(vm.ledger, LedgerState): + vm.ledger = derive_ledger_state( + ledger_obj, + op_buckets_full_range=vm.intern_cfg.op_buckets_full_range, + ) + else: + vm.ledger = ledger_obj frontier = _committed_ids(frontier_arr) else: vm.ledger, frontier_prov, _, q_map = cycle_candidates( vm.ledger, frontier, validate_mode=validate_mode ) - frontier, ok = apply_q(q_map, frontier_prov, return_ok=True) + frontier, ok = apply_q_ok(q_map, frontier_prov) meta = getattr(q_map, "_prism_meta", None) if meta is not None and meta.safe_gather_policy is not None: corrupt = jnp.any( oob_mask(ok, policy=meta.safe_gather_policy) ) - vm.ledger = vm.ledger._replace( - corrupt=vm.ledger.corrupt | corrupt - ) - _host_raise_if_bad(vm.ledger, "Ledger capacity exceeded during cycle") + if isinstance(vm.ledger, LedgerState): + ledger_obj = vm.ledger.ledger._replace( + corrupt=vm.ledger.ledger.corrupt | corrupt + ) + vm.ledger = LedgerState( + ledger=ledger_obj, + index=vm.ledger.index, + op_buckets_full_range=vm.ledger.op_buckets_full_range, + ) + else: + vm.ledger = vm.ledger._replace( + corrupt=vm.ledger.corrupt | corrupt + ) + ledger_obj = ( + vm.ledger.ledger if isinstance(vm.ledger, LedgerState) else vm.ledger + ) + _host_raise_if_bad(ledger_obj, "Ledger capacity exceeded during cycle") root_ptr = frontier.a[0] root_ptr_int = _host_int_value(root_ptr) - print(f" ├─ Ledger : {_host_int_value(vm.ledger.count)} nodes") + ledger_obj = ( + vm.ledger.ledger if isinstance(vm.ledger, LedgerState) else vm.ledger + ) + print(f" ├─ Ledger : {_host_int_value(ledger_obj.count)} nodes") print(f" └─ Result : \033[92m{vm.decode(_ledger_id(root_ptr_int))}\033[0m") return vm @@ -506,9 +586,10 @@ def repl( mode="baseline", use_morton=False, block_size=None, - bsp_mode=BspMode.AUTO, + bsp_mode: BspMode | str | None = BspMode.AUTO, validate_mode: ValidateMode = ValidateMode.NONE, ): + cli_bundle = CliSortMiniBundle(block_size=block_size, use_morton=use_morton) validate_mode = coerce_validate_mode(validate_mode, context="repl") if mode == "bsp": vm = PrismVM_BSP() @@ -536,17 +617,23 @@ def repl( run_program_lines_bsp( [inp], vm, - use_morton=use_morton, - block_size=block_size, + **cli_bundle.__dict__, bsp_mode=bsp_mode, validate_mode=validate_mode, ) elif mode == "arena": + sort_cfg = ArenaSortConfig( + do_sort=do_sort, + use_morton=use_morton, + block_size=block_size, + l2_block_size=l2_block_size, + l1_block_size=l1_block_size, + do_global=do_global, + ) run_program_lines_arena( [inp], vm, - use_morton=use_morton, - block_size=block_size, + sort_cfg=sort_cfg, ) else: run_program_lines([inp], vm) @@ -565,6 +652,9 @@ def main(): do_sort = True use_morton = False block_size = None + l2_block_size = None + l1_block_size = None + do_global = False path = None i = 0 while i < len(args): @@ -650,12 +740,18 @@ def main(): validate_mode=validate_mode, ) elif mode == "arena": - run_program_lines_arena( - lines, - cycles=cycles, + sort_cfg = ArenaSortConfig( do_sort=do_sort, use_morton=use_morton, block_size=block_size, + l2_block_size=l2_block_size, + l1_block_size=l1_block_size, + do_global=do_global, + ) + run_program_lines_arena( + lines, + cycles=cycles, + sort_cfg=sort_cfg, ) else: run_program_lines(lines) diff --git a/src/prism_coord/coord.py b/src/prism_coord/coord.py index a9c2307..78464c5 100644 --- a/src/prism_coord/coord.py +++ b/src/prism_coord/coord.py @@ -1,19 +1,66 @@ +from dataclasses import dataclass +from functools import lru_cache, partial + import jax import jax.numpy as jnp from jax import lax -from functools import lru_cache, partial -from prism_ledger.intern import _coord_norm_id_jax, intern_nodes -from prism_ledger.config import InternConfig +from prism_ledger.intern import _coord_norm_id_jax, intern_nodes, intern_nodes_state +from prism_ledger.config import InternConfig, DEFAULT_INTERN_CONFIG +from prism_ledger.index import LedgerState, derive_ledger_index +from prism_core.di import call_with_optional_kwargs from prism_coord.config import CoordConfig, DEFAULT_COORD_CONFIG from prism_vm_core.domains import _host_int_value from prism_vm_core.ontology import OP_COORD_ONE, OP_COORD_PAIR, OP_COORD_ZERO from prism_vm_core.structures import NodeBatch from prism_vm_core.protocols import InternFn, NodeBatchFn +# dataflow-bundle: a1, a2, op +# dataflow-bundle: left_id, right_id + + +@dataclass(frozen=True) +class _CoordNodeArgs: + op: object + a1: object + a2: object + + +@dataclass(frozen=True) +class _CoordBinaryArgs: + left_id: object + right_id: object + def _node_batch(op, a1, a2) -> NodeBatch: - return NodeBatch(op=op, a1=a1, a2=a2) + bundle = _CoordNodeArgs(op=op, a1=a1, a2=a2) + return NodeBatch(op=bundle.op, a1=bundle.a1, a2=bundle.a2) + + +def _intern_cfg_for_fn(intern_fn): + cfg = None + if isinstance(intern_fn, partial) and intern_fn.func is intern_nodes: + cfg = intern_fn.keywords.get("cfg") + return cfg or DEFAULT_INTERN_CONFIG + + +def _call_intern(ledger, batch, *, intern_fn): + if isinstance(ledger, LedgerState) and ( + intern_fn is intern_nodes + or (isinstance(intern_fn, partial) and intern_fn.func is intern_nodes) + ): + kwargs = {} + if isinstance(intern_fn, partial): + kwargs.update(intern_fn.keywords or {}) + kwargs.pop("ledger_index", None) + return intern_nodes_state(ledger, batch, **kwargs) + cfg = _intern_cfg_for_fn(intern_fn) + ledger_index = derive_ledger_index( + ledger, op_buckets_full_range=cfg.op_buckets_full_range + ) + return call_with_optional_kwargs( + intern_fn, {"ledger_index": ledger_index}, ledger, batch + ) @jax.jit(static_argnames=("coord_norm_id_jax_fn",)) @@ -31,13 +78,14 @@ def _coord_leaf_id( node_batch_fn=_node_batch, host_int_value_fn=_host_int_value, ): - ids, ledger = intern_fn( + ids, ledger = _call_intern( ledger, node_batch_fn( jnp.array([op], dtype=jnp.int32), jnp.array([0], dtype=jnp.int32), jnp.array([0], dtype=jnp.int32), ), + intern_fn=intern_fn, ) # SYNC: host reads device id for coord leaf (m1). return host_int_value_fn(ids[0]), ledger @@ -59,13 +107,14 @@ def _coord_promote_leaf( node_batch_fn=node_batch_fn, host_int_value_fn=host_int_value_fn, ) - ids, ledger = intern_fn( + ids, ledger = _call_intern( ledger, node_batch_fn( jnp.array([OP_COORD_PAIR], dtype=jnp.int32), jnp.array([leaf_id], dtype=jnp.int32), jnp.array([zero_id], dtype=jnp.int32), ), + intern_fn=intern_fn, ) # SYNC: host reads device id for coord promotion (m1). return host_int_value_fn(ids[0]), ledger @@ -79,6 +128,8 @@ def _coord_cache_init(ledger, *, host_int_value_fn=_host_int_value): return ops, a1s, a2s, count +# dataflow-bundle: a1, a2, op +# Host-only coord cache payload (kept local to coord helpers). def _coord_cache_update(cache, idx, op, a1, a2): ops, a1s, a2s, count = cache if idx == count: @@ -102,13 +153,14 @@ def _coord_leaf_id_cached( cached = leaf_cache.get(int(op)) if cached is not None: return cached, ledger, cache - ids, ledger = intern_fn( + ids, ledger = _call_intern( ledger, node_batch_fn( jnp.array([op], dtype=jnp.int32), jnp.array([0], dtype=jnp.int32), jnp.array([0], dtype=jnp.int32), ), + intern_fn=intern_fn, ) new_id = host_int_value_fn(ids[0]) cache = _coord_cache_update(cache, new_id, op, 0, 0) @@ -136,13 +188,14 @@ def _coord_promote_leaf_cached( node_batch_fn=node_batch_fn, host_int_value_fn=host_int_value_fn, ) - ids, ledger = intern_fn( + ids, ledger = _call_intern( ledger, node_batch_fn( jnp.array([OP_COORD_PAIR], dtype=jnp.int32), jnp.array([leaf_id], dtype=jnp.int32), jnp.array([zero_id], dtype=jnp.int32), ), + intern_fn=intern_fn, ) new_id = host_int_value_fn(ids[0]) cache = _coord_cache_update(cache, new_id, OP_COORD_PAIR, leaf_id, zero_id) @@ -266,13 +319,14 @@ def _coord_xor_cached(ledger, left_id, right_id, cache, leaf_cache): new_right, ledger, cache, leaf_cache = _coord_xor_cached( ledger, left_a2, right_a2, cache, leaf_cache ) - ids, ledger = intern_fn( + ids, ledger = _call_intern( ledger, node_batch_fn( jnp.array([OP_COORD_PAIR], dtype=jnp.int32), jnp.array([new_left], dtype=jnp.int32), jnp.array([new_right], dtype=jnp.int32), ), + intern_fn=intern_fn, ) new_id = host_int_value_fn(ids[0]) cache = _coord_cache_update(cache, new_id, OP_COORD_PAIR, new_left, new_right) @@ -326,8 +380,9 @@ def cd_lift_binary( If both inputs are OP_COORD_PAIR, apply op componentwise and re-pair. Otherwise, fall back to op(left, right). """ - left_id = int(left_id) - right_id = int(right_id) + bundle = _CoordBinaryArgs(left_id=left_id, right_id=right_id) + left_id = int(bundle.left_id) + right_id = int(bundle.right_id) left_op = host_int_value_fn(ledger.opcode[left_id]) right_op = host_int_value_fn(ledger.opcode[right_id]) if left_op == OP_COORD_PAIR and right_op == OP_COORD_PAIR: @@ -353,22 +408,24 @@ def cd_lift_binary( node_batch_fn=node_batch_fn, host_int_value_fn=host_int_value_fn, ) - ids, ledger = intern_fn( + ids, ledger = _call_intern( ledger, node_batch_fn( jnp.array([OP_COORD_PAIR], dtype=jnp.int32), jnp.array([new_left], dtype=jnp.int32), jnp.array([new_right], dtype=jnp.int32), ), + intern_fn=intern_fn, ) return host_int_value_fn(ids[0]), ledger - ids, ledger = intern_fn( + ids, ledger = _call_intern( ledger, node_batch_fn( jnp.array([op], dtype=jnp.int32), jnp.array([left_id], dtype=jnp.int32), jnp.array([right_id], dtype=jnp.int32), ), + intern_fn=intern_fn, ) return host_int_value_fn(ids[0]), ledger @@ -386,6 +443,7 @@ def cd_lift_binary_cfg( host_int_value_fn=_host_int_value, ): """Interface/Control wrapper for cd_lift_binary with DI bundle.""" + bundle = _CoordBinaryArgs(left_id=left_id, right_id=right_id) if cfg.intern_cfg is not None and intern_cfg is not None: raise ValueError("Pass either cfg.intern_cfg or intern_cfg, not both.") intern_cfg = intern_cfg if intern_cfg is not None else cfg.intern_cfg @@ -394,8 +452,8 @@ def cd_lift_binary_cfg( return cd_lift_binary( ledger, op, - left_id, - right_id, + bundle.left_id, + bundle.right_id, intern_fn=intern_fn, node_batch_fn=node_batch_fn, host_int_value_fn=host_int_value_fn, @@ -527,7 +585,6 @@ def coord_xor_batch_cfg( right_ids, coord_xor_fn=coord_xor_fn, intern_fn=intern_fn, - intern_cfg=None, node_batch_fn=node_batch_fn, host_int_value_fn=host_int_value_fn, ) diff --git a/src/prism_core/di.py b/src/prism_core/di.py index 289c15d..befb9ee 100644 --- a/src/prism_core/di.py +++ b/src/prism_core/di.py @@ -1,5 +1,6 @@ from __future__ import annotations +from dataclasses import dataclass from functools import lru_cache from inspect import Parameter, signature from typing import Callable, TypeVar @@ -8,14 +9,46 @@ from prism_core.errors import PrismPolicyBindingError +# dataflow-bundle: args, kwargs +# dataflow-bundle: args, kwargs, name, value +# dataflow-bundle: arr, idx +# dataflow-bundle: idx, size + T = TypeVar("T") +TCallable = TypeVar("TCallable", bound=Callable[..., object]) + + +@dataclass(frozen=True) +class _CallOptionalKwargsArgs: + args: tuple + kwargs: dict + + +@dataclass(frozen=True) +class _CallOptionalKwArgs: + name: str + value: object + args: tuple + kwargs: dict + + +@dataclass(frozen=True) +class _ArrayIndexArgs: + arr: object + idx: object + + +@dataclass(frozen=True) +class _IndexSizeArgs: + idx: object + size: object def resolve(value: T | None, default: T) -> T: return default if value is None else value -def wrap_policy(safe_gather_fn, policy): +def wrap_policy(safe_gather_fn: TCallable, policy) -> TCallable: """Wrap a safe_gather_fn with a fixed SafetyPolicy (if provided).""" if policy is None: return safe_gather_fn @@ -28,10 +61,13 @@ def wrap_policy(safe_gather_fn, policy): wrapped = bind_optional_kwargs(safe_gather_fn, policy=policy) def _safe_gather(arr, idx, label, *, guard=None, return_ok=None): + bundle = _ArrayIndexArgs(arr=arr, idx=idx) optional = {"guard": guard} if return_ok is not None: optional["return_ok"] = return_ok - return call_with_optional_kwargs(wrapped, optional, arr, idx, label) + return call_with_optional_kwargs( + wrapped, optional, bundle.arr, bundle.idx, label + ) try: setattr(_safe_gather, "_prism_policy_bound", True) @@ -41,7 +77,7 @@ def _safe_gather(arr, idx, label, *, guard=None, return_ok=None): return _safe_gather -def wrap_index_policy(safe_index_fn, policy): +def wrap_index_policy(safe_index_fn: TCallable, policy) -> TCallable: """Wrap a safe_index_fn with a fixed SafetyPolicy (if provided).""" if policy is None: return safe_index_fn @@ -54,8 +90,9 @@ def wrap_index_policy(safe_index_fn, policy): wrapped = bind_optional_kwargs(safe_index_fn, policy=policy) def _safe_index(idx, size, label, *, guard=None): + bundle = _IndexSizeArgs(idx=idx, size=size) return call_with_optional_kwargs( - wrapped, {"guard": guard}, idx, size, label + wrapped, {"guard": guard}, bundle.idx, bundle.size, label ) try: @@ -87,29 +124,33 @@ def call_with_optional_kw(fn: Callable[..., T], name: str, value, *args, **kwarg This is a host-side helper to keep DI-compatible call sites tolerant of callables that have not yet adopted a new keyword parameter. """ - return call_with_optional_kwargs(fn, {name: value}, *args, **kwargs) + bundle = _CallOptionalKwArgs(name=name, value=value, args=args, kwargs=kwargs) + return call_with_optional_kwargs( + fn, {bundle.name: bundle.value}, *bundle.args, **bundle.kwargs + ) def call_with_optional_kwargs( fn: Callable[..., T], optional: dict, *args, **kwargs ) -> T: """Call fn with optional keyword arguments filtered to accepted names.""" + bundle = _CallOptionalKwargsArgs(args=args, kwargs=kwargs) optional = {k: v for k, v in optional.items() if v is not None} if not optional: - return fn(*args, **kwargs) + return fn(*bundle.args, **bundle.kwargs) try: sig = signature(fn) except (TypeError, ValueError): - return fn(*args, **kwargs, **optional) + return fn(*bundle.args, **bundle.kwargs, **optional) if any(p.kind == Parameter.VAR_KEYWORD for p in sig.parameters.values()): - return fn(*args, **kwargs, **optional) + return fn(*bundle.args, **bundle.kwargs, **optional) allowed = { name for name, p in sig.parameters.items() if p.kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY) } filtered = {k: v for k, v in optional.items() if k in allowed} - return fn(*args, **kwargs, **filtered) + return fn(*bundle.args, **bundle.kwargs, **filtered) def bind_optional_kwargs(fn: Callable[..., T], **optional): diff --git a/src/prism_core/guards.py b/src/prism_core/guards.py index eac9cf6..46c5881 100644 --- a/src/prism_core/guards.py +++ b/src/prism_core/guards.py @@ -9,10 +9,19 @@ from prism_core.errors import PrismPolicyBindingError from prism_core import jax_safe as _jax_safe from prism_core.protocols import ( + SafeGatherFn, + SafeGatherOkFn, SafeGatherOkValueFn, SafeGatherValueFn, + SafeIndexFn, SafeIndexValueFn, ) +from prism_core.safety import ( + DEFAULT_SAFETY_POLICY, + SafetyMode, + SafetyPolicy, + oob_mask, +) @dataclass(frozen=True, slots=True) @@ -22,7 +31,7 @@ class GuardConfig: guards_enabled_fn: Optional[Callable[[], bool]] = None guard_max_fn: Optional[Callable[..., None]] = None guard_gather_index_fn: Optional[Callable[..., None]] = None - safe_index_fn: Optional[Callable[..., tuple]] = None + safe_index_fn: SafeIndexFn | None = None guard_slot0_perm_fn: Optional[Callable[..., None]] = None guard_null_row_fn: Optional[Callable[..., None]] = None guard_zero_row_fn: Optional[Callable[..., None]] = None @@ -53,7 +62,7 @@ def safe_index_1d_cfg( label="safe_index_1d", *, guard=None, - policy=None, + policy: SafetyPolicy | None = None, cfg: GuardConfig = DEFAULT_GUARD_CONFIG, ): """Guard-configured wrapper for safe_index_1d (shared).""" @@ -66,7 +75,17 @@ def safe_index_1d_cfg( label, ) guard_gather_index_cfg(idx, size, label, guard=guard, cfg=cfg) - return _jax_safe.safe_index_1d(idx, size, label, guard=False, policy=policy) + if policy is None: + policy = DEFAULT_SAFETY_POLICY + size_i = jnp.asarray(size, dtype=jnp.int32) + idx_i = jnp.asarray(idx, dtype=jnp.int32) + ok = (idx_i >= 0) & (idx_i < size_i) + idx_safe = jnp.clip(idx_i, 0, size_i - 1) + if policy.mode == SafetyMode.DROP: + idx_safe = jnp.where(ok, idx_safe, jnp.int32(0)) + if policy.mode == SafetyMode.CLAMP: + ok = jnp.ones_like(ok, dtype=jnp.bool_) + return idx_safe, ok def safe_gather_1d_cfg( @@ -75,16 +94,28 @@ def safe_gather_1d_cfg( label="safe_gather_1d", *, guard=None, - policy=None, + policy: SafetyPolicy | None = None, return_ok: bool = False, cfg: GuardConfig = DEFAULT_GUARD_CONFIG, ): """Guard-configured wrapper for safe_gather_1d (shared).""" size = jnp.asarray(arr.shape[0], dtype=jnp.int32) guard_gather_index_cfg(idx, size, label, guard=guard, cfg=cfg) - return _jax_safe.safe_gather_1d( - arr, idx, label, guard=False, policy=policy, return_ok=return_ok - ) + if policy is None: + policy = DEFAULT_SAFETY_POLICY + idx_i = jnp.asarray(idx, dtype=jnp.int32) + ok = (idx_i >= 0) & (idx_i < size) + idx_safe = jnp.clip(idx_i, 0, size - 1) + if policy.mode == SafetyMode.DROP: + idx_safe = jnp.where(ok, idx_safe, jnp.int32(0)) + values = arr[idx_safe] + if policy.mode == SafetyMode.DROP: + values = jnp.where(ok, values, jnp.zeros_like(values)) + if policy.mode == SafetyMode.CLAMP: + ok = jnp.ones_like(ok, dtype=jnp.bool_) + if return_ok: + return values, ok + return values def safe_gather_1d_ok_cfg( @@ -93,22 +124,30 @@ def safe_gather_1d_ok_cfg( label="safe_gather_1d_ok", *, guard=None, - policy=None, + policy: SafetyPolicy | None = None, cfg: GuardConfig = DEFAULT_GUARD_CONFIG, ): """Guard-configured wrapper for safe_gather_1d_ok (shared).""" - size = jnp.asarray(arr.shape[0], dtype=jnp.int32) - guard_gather_index_cfg(idx, size, label, guard=guard, cfg=cfg) - return _jax_safe.safe_gather_1d_ok( - arr, idx, label, guard=False, policy=policy + values, ok = safe_gather_1d_cfg( + arr, + idx, + label, + guard=guard, + policy=policy, + return_ok=True, + cfg=cfg, ) + if policy is None: + policy = DEFAULT_SAFETY_POLICY + corrupt = oob_mask(ok, policy=policy) + return values, ok, corrupt def make_safe_gather_fn( *, cfg: GuardConfig = DEFAULT_GUARD_CONFIG, - policy=None, - safe_gather_fn=None, + policy: SafetyPolicy | None = None, + safe_gather_fn: SafeGatherFn | None = None, ): """Return a SafeGatherFn wired to the provided GuardConfig.""" if safe_gather_fn is None: @@ -131,8 +170,8 @@ def _safe_gather(arr, idx, label, *, policy=policy, return_ok: bool = False): def make_safe_gather_ok_fn( *, cfg: GuardConfig = DEFAULT_GUARD_CONFIG, - policy=None, - safe_gather_ok_fn=None, + policy: SafetyPolicy | None = None, + safe_gather_ok_fn: SafeGatherOkFn | None = None, ): """Return a SafeGatherOkFn wired to the provided GuardConfig.""" if safe_gather_ok_fn is None: @@ -155,8 +194,8 @@ def _safe_gather_ok(arr, idx, label, *, policy=policy): def make_safe_index_fn( *, cfg: GuardConfig = DEFAULT_GUARD_CONFIG, - policy=None, - safe_index_fn=None, + policy: SafetyPolicy | None = None, + safe_index_fn: SafeIndexFn | None = None, ): """Return a SafeIndexFn wired to the provided GuardConfig.""" if safe_index_fn is None: @@ -248,8 +287,8 @@ def _safe_index(idx, size, label, *, policy_value): def resolve_safe_gather_fn( *, - safe_gather_fn=None, - policy=None, + safe_gather_fn: SafeGatherFn | None = None, + policy: SafetyPolicy | None = None, guard_cfg: GuardConfig | None = None, ): """Return a SafeGatherFn with optional SafetyPolicy + GuardConfig wiring.""" @@ -283,8 +322,8 @@ def resolve_safe_gather_fn( def resolve_safe_gather_ok_fn( *, - safe_gather_ok_fn=None, - policy=None, + safe_gather_ok_fn: SafeGatherOkFn | None = None, + policy: SafetyPolicy | None = None, guard_cfg: GuardConfig | None = None, ): """Return a SafeGatherOkFn with optional SafetyPolicy + GuardConfig wiring.""" @@ -318,8 +357,8 @@ def resolve_safe_gather_ok_fn( def resolve_safe_index_fn( *, - safe_index_fn=None, - policy=None, + safe_index_fn: SafeIndexFn | None = None, + policy: SafetyPolicy | None = None, guard_cfg: GuardConfig | None = None, ): """Return a SafeIndexFn with optional SafetyPolicy + GuardConfig wiring.""" diff --git a/src/prism_core/jax_safe.py b/src/prism_core/jax_safe.py index 6e99aa9..c6fa504 100644 --- a/src/prism_core/jax_safe.py +++ b/src/prism_core/jax_safe.py @@ -1,4 +1,5 @@ import os +from dataclasses import dataclass import jax import jax.numpy as jnp @@ -13,6 +14,12 @@ oob_mask, ) +# dataflow-bundle: idx, policy_value +# dataflow-bundle: idx, policy_value, size +# dataflow-bundle: idx, size +# dataflow-bundle: max_allowed, max_val, min_val +# dataflow-bundle: max_val, min_val, size_val + TEST_GUARDS = os.environ.get("PRISM_TEST_GUARDS", "").strip().lower() in ( "1", "true", @@ -38,6 +45,32 @@ HAS_DEBUG_CALLBACK = hasattr(jax, "debug") and hasattr(jax.debug, "callback") +@dataclass(frozen=True) +class _IndexPolicyValue: + idx: object + policy_value: PolicyValue + + +@dataclass(frozen=True) +class _IndexPolicyValueSize: + idx: object + policy_value: PolicyValue + size: object + + +@dataclass(frozen=True) +class _IndexSizeArgs: + idx: object + size: object + + +@dataclass(frozen=True) +class _BoundsArgs: + max_val: object + min_val: object + size_val: object + + def scatter_guard(indices, max_index, label): if not SCATTER_GUARD or not HAS_DEBUG_CALLBACK: return @@ -108,9 +141,13 @@ def _raise(bad_val, min_val, max_val, size_val): f"{label} (min={int(min_val)}, max={int(max_val)}, size={int(size_val)})" ) - jax.debug.callback(_raise, bad, min_idx, max_idx, size) + bounds = _BoundsArgs(max_val=max_idx, min_val=min_idx, size_val=size) + jax.debug.callback( + _raise, bad, bounds.min_val, bounds.max_val, bounds.size_val + ) +# dataflow-bundle: arr, idx, label, policy, return_ok def safe_gather_1d( arr, idx, @@ -140,6 +177,7 @@ def safe_gather_1d( return values +# dataflow-bundle: arr, idx, label, policy def safe_gather_1d_ok( arr, idx, @@ -173,18 +211,13 @@ def safe_gather_1d_value( return_ok: bool = False, ): """Guarded gather that accepts policy as a JAX value.""" - size = jnp.asarray(arr.shape[0], dtype=jnp.int32) - idx_i = jnp.asarray(idx, dtype=jnp.int32) - policy_val = jnp.asarray(policy_value, dtype=jnp.int32) - guard_gather_index(idx_i, size, label, guard=guard) - ok = (idx_i >= 0) & (idx_i < size) - idx_safe = jnp.clip(idx_i, 0, size - 1) - drop_mask = policy_val == POLICY_VALUE_DROP - clamp_mask = policy_val == POLICY_VALUE_CLAMP - idx_safe = jnp.where(drop_mask, jnp.where(ok, idx_safe, jnp.int32(0)), idx_safe) - values = arr[idx_safe] - values = jnp.where(drop_mask & (~ok), jnp.zeros_like(values), values) - ok = jnp.where(clamp_mask, jnp.ones_like(ok, dtype=jnp.bool_), ok) + values, ok = _safe_gather_1d_value_ok( + arr, + idx, + label, + guard=guard, + policy_value=policy_value, + ) if return_ok: return values, ok return values @@ -199,19 +232,43 @@ def safe_gather_1d_ok_value( policy_value: PolicyValue, ): """Guarded gather that returns ok + corruption flag using policy value.""" - values, ok = safe_gather_1d_value( + values, ok = _safe_gather_1d_value_ok( arr, idx, label, guard=guard, policy_value=policy_value, - return_ok=True, ) - policy_val = jnp.asarray(policy_value, dtype=jnp.int32) + bundle = _IndexPolicyValue(idx=idx, policy_value=policy_value) + policy_val = jnp.asarray(bundle.policy_value, dtype=jnp.int32) corrupt = jnp.where(policy_val == POLICY_VALUE_CORRUPT, ~ok, False) return values, ok, corrupt +def _safe_gather_1d_value_ok( + arr, + idx, + label, + *, + guard=None, + policy_value: PolicyValue, +): + bundle = _IndexPolicyValue(idx=idx, policy_value=policy_value) + size = jnp.asarray(arr.shape[0], dtype=jnp.int32) + idx_i = jnp.asarray(bundle.idx, dtype=jnp.int32) + policy_val = jnp.asarray(bundle.policy_value, dtype=jnp.int32) + guard_gather_index(idx_i, size, label, guard=guard) + ok = (idx_i >= 0) & (idx_i < size) + idx_safe = jnp.clip(idx_i, 0, size - 1) + drop_mask = policy_val == POLICY_VALUE_DROP + clamp_mask = policy_val == POLICY_VALUE_CLAMP + idx_safe = jnp.where(drop_mask, jnp.where(ok, idx_safe, jnp.int32(0)), idx_safe) + values = arr[idx_safe] + values = jnp.where(drop_mask & (~ok), jnp.zeros_like(values), values) + ok = jnp.where(clamp_mask, jnp.ones_like(ok, dtype=jnp.bool_), ok) + return values, ok + + def safe_index_1d( idx, size, @@ -223,8 +280,9 @@ def safe_index_1d( """Return a policy-aware safe index and in-bounds mask.""" if policy is None: policy = DEFAULT_SAFETY_POLICY - size_i = jnp.asarray(size, dtype=jnp.int32) - idx_i = jnp.asarray(idx, dtype=jnp.int32) + bundle = _IndexSizeArgs(idx=idx, size=size) + size_i = jnp.asarray(bundle.size, dtype=jnp.int32) + idx_i = jnp.asarray(bundle.idx, dtype=jnp.int32) guard_gather_index(idx_i, size_i, label, guard=guard) ok = (idx_i >= 0) & (idx_i < size_i) idx_safe = jnp.clip(idx_i, 0, size_i - 1) @@ -244,9 +302,10 @@ def safe_index_1d_value( policy_value: PolicyValue, ): """Return a policy-aware safe index using policy value.""" - size_i = jnp.asarray(size, dtype=jnp.int32) - idx_i = jnp.asarray(idx, dtype=jnp.int32) - policy_val = jnp.asarray(policy_value, dtype=jnp.int32) + bundle = _IndexPolicyValueSize(idx=idx, policy_value=policy_value, size=size) + size_i = jnp.asarray(bundle.size, dtype=jnp.int32) + idx_i = jnp.asarray(bundle.idx, dtype=jnp.int32) + policy_val = jnp.asarray(bundle.policy_value, dtype=jnp.int32) guard_gather_index(idx_i, size_i, label, guard=guard) ok = (idx_i >= 0) & (idx_i < size_i) idx_safe = jnp.clip(idx_i, 0, size_i - 1) diff --git a/src/prism_core/modes.py b/src/prism_core/modes.py index 977c836..2cf4774 100644 --- a/src/prism_core/modes.py +++ b/src/prism_core/modes.py @@ -66,7 +66,7 @@ def coerce_bsp_mode( mode: BspMode | str | None, *, default_fn=None, - context: str | None = None, + context: str | None = "bsp_mode", ) -> BspMode: if mode is None or mode == "" or mode == BspMode.AUTO: return default_fn() if default_fn is not None else BspMode.AUTO diff --git a/src/prism_core/protocols.py b/src/prism_core/protocols.py index 2ca070b..333467c 100644 --- a/src/prism_core/protocols.py +++ b/src/prism_core/protocols.py @@ -1,14 +1,26 @@ from __future__ import annotations +from dataclasses import dataclass from typing import Protocol, TypeAlias, runtime_checkable import jax.numpy as jnp +# dataflow-bundle: idx, label, policy_value, size + PolicyValue: TypeAlias = jnp.ndarray +@dataclass(frozen=True) +class SafeGatherPolicyArgs: + idx: object + label: str + policy_value: PolicyValue + size: object + + @runtime_checkable class SafeGatherFn(Protocol): + # dataflow-bundle: arr, idx, label, policy, return_ok def __call__( self, arr, idx, label: str, *, policy=None, return_ok: bool = False ): @@ -17,6 +29,7 @@ def __call__( @runtime_checkable class SafeGatherOkFn(Protocol): + # dataflow-bundle: arr, idx, label, policy def __call__(self, arr, idx, label: str, *, policy=None): ... diff --git a/src/prism_ledger/index.py b/src/prism_ledger/index.py new file mode 100644 index 0000000..88ec9eb --- /dev/null +++ b/src/prism_ledger/index.py @@ -0,0 +1,100 @@ +from __future__ import annotations + +from dataclasses import dataclass + +import jax.numpy as jnp +import jax + +from prism_vm_core.structures import Ledger + + +@jax.tree_util.register_pytree_node_class +@dataclass(frozen=True, slots=True) +class LedgerIndex: + """Derived index data for fast ledger lookups (data-level cache).""" + + op_start: jnp.ndarray + op_end: jnp.ndarray + + def tree_flatten(self): + return (self.op_start, self.op_end), None + + @classmethod + def tree_unflatten(cls, aux_data, children): + op_start, op_end = children + return cls(op_start=op_start, op_end=op_end) + + +@jax.tree_util.register_pytree_node_class +@dataclass(frozen=True, slots=True) +class LedgerState: + """Ledger plus derived index bundle (canonical interning state).""" + + ledger: Ledger + index: LedgerIndex + op_buckets_full_range: bool + + def tree_flatten(self): + return (self.ledger, self.index), self.op_buckets_full_range + + @classmethod + def tree_unflatten(cls, aux_data, children): + ledger, index = children + return cls(ledger=ledger, index=index, op_buckets_full_range=aux_data) + + def __getattr__(self, name: str): + return getattr(self.ledger, name) + + def _replace(self, **kwargs): + ledger = kwargs.pop("ledger", self.ledger) + if kwargs: + ledger = ledger._replace(**kwargs) + return LedgerState( + ledger=ledger, + index=derive_ledger_index( + ledger, + op_buckets_full_range=self.op_buckets_full_range, + ), + op_buckets_full_range=self.op_buckets_full_range, + ) + + +def derive_ledger_index( + ledger: Ledger, + *, + op_buckets_full_range: bool, +) -> LedgerIndex: + """Derive opcode buckets from the ledger bundle (pure).""" + count = ledger.count.astype(jnp.int32) + if op_buckets_full_range: + op_start = jnp.zeros(256, dtype=jnp.int32) + op_end = jnp.full((256,), count, dtype=jnp.int32) + else: + op_values = jnp.arange(256, dtype=jnp.uint8) + op_start = jnp.searchsorted( + ledger.keys_b0_sorted, op_values, side="left" + ).astype(jnp.int32) + op_end = jnp.searchsorted( + ledger.keys_b0_sorted, op_values, side="right" + ).astype(jnp.int32) + op_start = jnp.minimum(op_start, count) + op_end = jnp.minimum(op_end, count) + return LedgerIndex(op_start=op_start, op_end=op_end) + + +def derive_ledger_state( + ledger: Ledger, + *, + op_buckets_full_range: bool, +) -> LedgerState: + """Derive a LedgerState from a ledger bundle (pure).""" + return LedgerState( + ledger=ledger, + index=derive_ledger_index( + ledger, op_buckets_full_range=op_buckets_full_range + ), + op_buckets_full_range=op_buckets_full_range, + ) + + +__all__ = ["LedgerIndex", "LedgerState", "derive_ledger_index", "derive_ledger_state"] diff --git a/src/prism_ledger/intern.py b/src/prism_ledger/intern.py index 451696a..879d243 100644 --- a/src/prism_ledger/intern.py +++ b/src/prism_ledger/intern.py @@ -1,3 +1,46 @@ +from dataclasses import dataclass + +# dataflow-bundle: a0, a1, a2, a3, b0, b1, b2, b3 +# dataflow-bundle: a4, b4 +# dataflow-bundle: coord_norm_probe_assert_fn, coord_norm_probe_reset_cb_fn +# dataflow-bundle: new_ids, new_keys +# dataflow-bundle: t_b0, t_b1, t_b2, t_b3 + + +@dataclass(frozen=True) +class _LexLessArgs: + a0: object + a1: object + a2: object + a3: object + a4: object + b0: object + b1: object + b2: object + b3: object + b4: object + + +@dataclass(frozen=True) +class _SearchKeyArgs: + t_b0: object + t_b1: object + t_b2: object + t_b3: object + t_b4: object + + +@dataclass(frozen=True) +class _NewKeysIds: + new_keys: object + new_ids: object + + +@dataclass(frozen=True) +class _CoordNormProbeFns: + coord_norm_probe_assert_fn: object + coord_norm_probe_reset_cb_fn: object + import jax import jax.numpy as jnp from jax import lax @@ -5,6 +48,12 @@ from prism_core import jax_safe as _jax_safe from prism_core.permutation import _invert_perm from prism_ledger.config import DEFAULT_INTERN_CONFIG, InternConfig +from prism_ledger.index import ( + LedgerIndex, + LedgerState, + derive_ledger_index, + derive_ledger_state, +) from prism_metrics.probes import ( _coord_norm_probe_assert, _coord_norm_probe_enabled, @@ -32,6 +81,17 @@ _scatter_drop = _jax_safe.scatter_drop + +@dataclass(frozen=True, slots=True) +class KeyColumns: + """Bundle of sorted key columns.""" + + b0: jnp.ndarray + b1: jnp.ndarray + b2: jnp.ndarray + b3: jnp.ndarray + b4: jnp.ndarray + def _coord_norm_id_jax_core(ledger, coord_id, *, lookup_node_id_fn=None): # CDᵣ + Normalize𝚌 (core, no probe) # NOTE: repeated lookups per step are an m1/m4 tradeoff; batching is @@ -112,7 +172,17 @@ def _coord_norm_id_jax_noprobe( ) -def _lookup_node_id(ledger, op, a1, a2, *, pack_key_fn=_pack_key): +def _lookup_node_id_bound( + ledger: Ledger, + op, + a1, + a2, + *, + pack_key_fn=_pack_key, + ledger_index: LedgerIndex, +): + op_start = ledger_index.op_start + op_end = ledger_index.op_end k0, k1, k2, k3, k4 = pack_key_fn(op, a1, a2) L_b0 = ledger.keys_b0_sorted L_b1 = ledger.keys_b1_sorted @@ -123,20 +193,36 @@ def _lookup_node_id(ledger, op, a1, a2, *, pack_key_fn=_pack_key): count = ledger.count.astype(jnp.int32) def _lex_less(a0, a1, a2, a3, a4, b0, b1, b2, b3, b4): + bundle = _LexLessArgs( + a0=a0, + a1=a1, + a2=a2, + a3=a3, + a4=a4, + b0=b0, + b1=b1, + b2=b2, + b3=b3, + b4=b4, + ) return jnp.logical_or( - a0 < b0, + bundle.a0 < bundle.b0, jnp.logical_and( - a0 == b0, + bundle.a0 == bundle.b0, jnp.logical_or( - a1 < b1, + bundle.a1 < bundle.b1, jnp.logical_and( - a1 == b1, + bundle.a1 == bundle.b1, jnp.logical_or( - a2 < b2, + bundle.a2 < bundle.b2, jnp.logical_and( - a2 == b2, + bundle.a2 == bundle.b2, jnp.logical_or( - a3 < b3, jnp.logical_and(a3 == b3, a4 < b4) + bundle.a3 < bundle.b3, + jnp.logical_and( + bundle.a3 == bundle.b3, + bundle.a4 < bundle.b4, + ), ), ), ), @@ -145,9 +231,13 @@ def _lex_less(a0, a1, a2, a3, a4, b0, b1, b2, b3, b4): ), ) + op_idx = k0.astype(jnp.int32) + lo_init = op_start[op_idx] + hi_init = op_end[op_idx] + def _do_search(_): - lo = jnp.int32(0) - hi = count + lo = lo_init + hi = hi_init def cond(state): lo_i, hi_i = state @@ -197,13 +287,32 @@ def body(state): return out_id, found return lax.cond( - count > 0, + (count > 0) & (lo_init < hi_init), _do_search, lambda _: (jnp.int32(0), jnp.bool_(False)), operand=None, ) +def _lookup_node_id( + ledger: Ledger, + op, + a1, + a2, + *, + pack_key_fn=_pack_key, + ledger_index: LedgerIndex | None = None, + cfg: InternConfig = DEFAULT_INTERN_CONFIG, +): + if ledger_index is None: + ledger_index = derive_ledger_index( + ledger, op_buckets_full_range=cfg.op_buckets_full_range + ) + return _lookup_node_id_bound( + ledger, op, a1, a2, pack_key_fn=pack_key_fn, ledger_index=ledger_index + ) + + def _key_safe_normalize_nodes( ops, a1, a2, *, guard_zero_args_fn=_guard_zero_args ): @@ -224,12 +333,13 @@ def _key_safe_normalize_nodes( def _intern_nodes_impl_core( - ledger, + ledger: Ledger, proposed_ops, proposed_a1, proposed_a2, *, cfg: InternConfig, + ledger_index: LedgerIndex | None = None, key_safe_normalize_fn=_key_safe_normalize_nodes, pack_key_fn=_pack_key, candidate_indices_fn=_candidate_indices, @@ -242,6 +352,10 @@ def _intern_nodes_impl_core( coord_norm_probe_assert_fn=_coord_norm_probe_assert, scatter_drop_fn=_scatter_drop, ): + probe_bundle = _CoordNormProbeFns( + coord_norm_probe_assert_fn=coord_norm_probe_assert_fn, + coord_norm_probe_reset_cb_fn=coord_norm_probe_reset_cb_fn, + ) max_key = jnp.uint8(0xFF) # Interning pipeline (vectorized): # - Key-safe normalization only (coord pairs); no semantic rewrites. @@ -259,8 +373,29 @@ def _intern_nodes_impl_core( is_coord_pair = proposed_ops == OP_COORD_PAIR has_coord = jnp.any(is_coord_pair) + L_b0 = ledger.keys_b0_sorted + count = ledger.count.astype(jnp.int32) + if ledger_index is None: + ledger_index = derive_ledger_index( + ledger, op_buckets_full_range=cfg.op_buckets_full_range + ) + op_start = ledger_index.op_start + op_end = ledger_index.op_end + + def _lookup_node_id_bucketed(ledger_in, op, a1, a2): + return _lookup_node_id_bound( + ledger_in, + op, + a1, + a2, + ledger_index=ledger_index, + ) if guards_enabled_fn() and coord_norm_probe_enabled_fn(): - jax.debug.callback(coord_norm_probe_reset_cb_fn, jnp.int32(0), ordered=True) + jax.debug.callback( + probe_bundle.coord_norm_probe_reset_cb_fn, + jnp.int32(0), + ordered=True, + ) # CD_r/CD_a: normalize coord pairs before packing keys for stable lookup. def _norm(args): @@ -271,8 +406,12 @@ def _norm(args): def _norm_body(i, state): a1_arr, a2_arr = state idx = coord_idx[i] - a1_norm = coord_norm_id_jax_fn(ledger, a1_arr[idx]) - a2_norm = coord_norm_id_jax_noprobe_fn(ledger, a2_arr[idx]) + a1_norm = coord_norm_id_jax_fn( + ledger, a1_arr[idx], lookup_node_id_fn=_lookup_node_id_bucketed + ) + a2_norm = coord_norm_id_jax_noprobe_fn( + ledger, a2_arr[idx], lookup_node_id_fn=_lookup_node_id_bucketed + ) a1_arr = a1_arr.at[idx].set(a1_norm) a2_arr = a2_arr.at[idx].set(a2_norm) return a1_arr, a2_arr @@ -289,7 +428,9 @@ def _no_norm(args): has_coord, _norm, _no_norm, (proposed_a1, proposed_a2) ) if guards_enabled_fn() and coord_norm_probe_enabled_fn(): - jax.debug.callback(coord_norm_probe_assert_fn, has_coord, ordered=True) + jax.debug.callback( + probe_bundle.coord_norm_probe_assert_fn, has_coord, ordered=True + ) # Key-safety: Normalize𝚌 before packing. # Sort proposals by packed key so duplicates collapse deterministically. @@ -329,46 +470,49 @@ def scan_fn(carry, x): _, leader_idx = lax.scan(scan_fn, jnp.int32(0), (is_diff, idx)) - L_b0 = ledger.keys_b0_sorted L_b1 = ledger.keys_b1_sorted L_b2 = ledger.keys_b2_sorted L_b3 = ledger.keys_b3_sorted L_b4 = ledger.keys_b4_sorted L_ids = ledger.ids_sorted - count = ledger.count.astype(jnp.int32) max_count = jnp.int32(MAX_COUNT) available = jnp.maximum(max_count - count, 0) available = jnp.where(base_oom | base_corrupt, jnp.int32(0), available) - if cfg.op_buckets_full_range: - op_start = jnp.zeros(256, dtype=jnp.int32) - op_end = jnp.full((256,), count, dtype=jnp.int32) - else: - # Bucket existing keys by opcode byte to narrow search ranges. - # Use searchsorted on the sorted opcode column to avoid full scans. - op_values = jnp.arange(256, dtype=jnp.uint8) - op_start = jnp.searchsorted(L_b0, op_values, side="left").astype(jnp.int32) - op_end = jnp.searchsorted(L_b0, op_values, side="right").astype(jnp.int32) - op_start = jnp.minimum(op_start, count) - op_end = jnp.minimum(op_end, count) # NOTE: opcode buckets are a precursor to per-op merges; full-array merge # remains an m1 tradeoff (see IMPLEMENTATION_PLAN.md). def _lex_less(a0, a1, a2, a3, a4, b0, b1, b2, b3, b4): + bundle = _LexLessArgs( + a0=a0, + a1=a1, + a2=a2, + a3=a3, + a4=a4, + b0=b0, + b1=b1, + b2=b2, + b3=b3, + b4=b4, + ) return jnp.logical_or( - a0 < b0, + bundle.a0 < bundle.b0, jnp.logical_and( - a0 == b0, + bundle.a0 == bundle.b0, jnp.logical_or( - a1 < b1, + bundle.a1 < bundle.b1, jnp.logical_and( - a1 == b1, + bundle.a1 == bundle.b1, jnp.logical_or( - a2 < b2, + bundle.a2 < bundle.b2, jnp.logical_and( - a2 == b2, + bundle.a2 == bundle.b2, jnp.logical_or( - a3 < b3, jnp.logical_and(a3 == b3, a4 < b4) + bundle.a3 < bundle.b3, + jnp.logical_and( + bundle.a3 == bundle.b3, + bundle.a4 < bundle.b4, + ), ), ), ), @@ -378,6 +522,9 @@ def _lex_less(a0, a1, a2, a3, a4, b0, b1, b2, b3, b4): ) def _search_one(t_b0, t_b1, t_b2, t_b3, t_b4, start, end): + bundle = _SearchKeyArgs( + t_b0=t_b0, t_b1=t_b1, t_b2=t_b2, t_b3=t_b3, t_b4=t_b4 + ) lo = start hi = end @@ -399,11 +546,11 @@ def body(state): mid_b2, mid_b3, mid_b4, - t_b0, - t_b1, - t_b2, - t_b3, - t_b4, + bundle.t_b0, + bundle.t_b1, + bundle.t_b2, + bundle.t_b3, + bundle.t_b4, ) lo_i = jnp.where(go_right, mid + 1, lo_i) hi_i = jnp.where(go_right, hi_i, mid) @@ -452,29 +599,24 @@ def _overflow(_): # NOTE: global merge is an m1 tradeoff; performance roadmap is tracked in # IMPLEMENTATION_PLAN.md. def _merge_sorted_keys( - old_b0, - old_b1, - old_b2, - old_b3, - old_b4, + old_keys: KeyColumns, old_ids, old_count, - new_b0, - new_b1, - new_b2, - new_b3, - new_b4, + new_keys: KeyColumns, new_ids, new_items, ): - out_b0 = jnp.full_like(old_b0, max_key) - out_b1 = jnp.full_like(old_b1, max_key) - out_b2 = jnp.full_like(old_b2, max_key) - out_b3 = jnp.full_like(old_b3, max_key) - out_b4 = jnp.full_like(old_b4, max_key) + bundle = _NewKeysIds(new_keys=new_keys, new_ids=new_ids) + new_keys = bundle.new_keys + new_ids = bundle.new_ids + out_b0 = jnp.full_like(old_keys.b0, max_key) + out_b1 = jnp.full_like(old_keys.b1, max_key) + out_b2 = jnp.full_like(old_keys.b2, max_key) + out_b3 = jnp.full_like(old_keys.b3, max_key) + out_b4 = jnp.full_like(old_keys.b4, max_key) out_ids = jnp.zeros_like(old_ids) total = old_count + new_items - guard_max_fn(total, jnp.int32(old_b0.shape[0]), "merge.total") + guard_max_fn(total, jnp.int32(old_keys.b0.shape[0]), "merge.total") def body(k, state): i, j, b0, b1, b2, b3, b4, ids = state @@ -483,17 +625,17 @@ def body(k, state): safe_i = jnp.where(old_valid, i, 0) safe_j = jnp.where(new_valid, j, 0) - old0 = jnp.where(old_valid, old_b0[safe_i], max_key) - old1 = jnp.where(old_valid, old_b1[safe_i], max_key) - old2 = jnp.where(old_valid, old_b2[safe_i], max_key) - old3 = jnp.where(old_valid, old_b3[safe_i], max_key) - old4 = jnp.where(old_valid, old_b4[safe_i], max_key) + old0 = jnp.where(old_valid, old_keys.b0[safe_i], max_key) + old1 = jnp.where(old_valid, old_keys.b1[safe_i], max_key) + old2 = jnp.where(old_valid, old_keys.b2[safe_i], max_key) + old3 = jnp.where(old_valid, old_keys.b3[safe_i], max_key) + old4 = jnp.where(old_valid, old_keys.b4[safe_i], max_key) - new0 = jnp.where(new_valid, new_b0[safe_j], max_key) - new1 = jnp.where(new_valid, new_b1[safe_j], max_key) - new2 = jnp.where(new_valid, new_b2[safe_j], max_key) - new3 = jnp.where(new_valid, new_b3[safe_j], max_key) - new4 = jnp.where(new_valid, new_b4[safe_j], max_key) + new0 = jnp.where(new_valid, new_keys.b0[safe_j], max_key) + new1 = jnp.where(new_valid, new_keys.b1[safe_j], max_key) + new2 = jnp.where(new_valid, new_keys.b2[safe_j], max_key) + new3 = jnp.where(new_valid, new_keys.b3[safe_j], max_key) + new4 = jnp.where(new_valid, new_keys.b4[safe_j], max_key) new_less = _lex_less( new0, @@ -546,37 +688,32 @@ def body(k, state): return out_b0, out_b1, out_b2, out_b3, out_b4, out_ids def _merge_sorted_keys_bucketed( - old_b0, - old_b1, - old_b2, - old_b3, - old_b4, + old_keys: KeyColumns, old_ids, old_count, - new_b0, - new_b1, - new_b2, - new_b3, - new_b4, + new_keys: KeyColumns, new_ids, new_items, op_start, op_end, ): - out_b0 = jnp.full_like(old_b0, max_key) - out_b1 = jnp.full_like(old_b1, max_key) - out_b2 = jnp.full_like(old_b2, max_key) - out_b3 = jnp.full_like(old_b3, max_key) - out_b4 = jnp.full_like(old_b4, max_key) + bundle = _NewKeysIds(new_keys=new_keys, new_ids=new_ids) + new_keys = bundle.new_keys + new_ids = bundle.new_ids + out_b0 = jnp.full_like(old_keys.b0, max_key) + out_b1 = jnp.full_like(old_keys.b1, max_key) + out_b2 = jnp.full_like(old_keys.b2, max_key) + out_b3 = jnp.full_like(old_keys.b3, max_key) + out_b4 = jnp.full_like(old_keys.b4, max_key) out_ids = jnp.zeros_like(old_ids) total = old_count + new_items - guard_max_fn(total, jnp.int32(old_b0.shape[0]), "merge.total") + guard_max_fn(total, jnp.int32(old_keys.b0.shape[0]), "merge.total") op_values = jnp.arange(256, dtype=jnp.uint8) - new_op_start = jnp.searchsorted(new_b0, op_values, side="left").astype( + new_op_start = jnp.searchsorted(new_keys.b0, op_values, side="left").astype( jnp.int32 ) - new_op_end = jnp.searchsorted(new_b0, op_values, side="right").astype( + new_op_end = jnp.searchsorted(new_keys.b0, op_values, side="right").astype( jnp.int32 ) new_op_start = jnp.minimum(new_op_start, new_items) @@ -602,17 +739,17 @@ def _merge_body(k, carry): old_idx = old_lo + i new_idx = new_lo + j - old0 = jnp.where(old_valid, old_b0[old_idx], max_key) - old1 = jnp.where(old_valid, old_b1[old_idx], max_key) - old2 = jnp.where(old_valid, old_b2[old_idx], max_key) - old3 = jnp.where(old_valid, old_b3[old_idx], max_key) - old4 = jnp.where(old_valid, old_b4[old_idx], max_key) + old0 = jnp.where(old_valid, old_keys.b0[old_idx], max_key) + old1 = jnp.where(old_valid, old_keys.b1[old_idx], max_key) + old2 = jnp.where(old_valid, old_keys.b2[old_idx], max_key) + old3 = jnp.where(old_valid, old_keys.b3[old_idx], max_key) + old4 = jnp.where(old_valid, old_keys.b4[old_idx], max_key) - new0 = jnp.where(new_valid, new_b0[new_idx], max_key) - new1 = jnp.where(new_valid, new_b1[new_idx], max_key) - new2 = jnp.where(new_valid, new_b2[new_idx], max_key) - new3 = jnp.where(new_valid, new_b3[new_idx], max_key) - new4 = jnp.where(new_valid, new_b4[new_idx], max_key) + new0 = jnp.where(new_valid, new_keys.b0[new_idx], max_key) + new1 = jnp.where(new_valid, new_keys.b1[new_idx], max_key) + new2 = jnp.where(new_valid, new_keys.b2[new_idx], max_key) + new3 = jnp.where(new_valid, new_keys.b3[new_idx], max_key) + new4 = jnp.where(new_valid, new_keys.b4[new_idx], max_key) new_less = _lex_less( new0, @@ -790,6 +927,14 @@ def _write_alloc(_): ) # Merge sorted new keys into the ledger's sorted key arrays. + old_keys = KeyColumns(L_b0, L_b1, L_b2, L_b3, L_b4) + new_keys = KeyColumns( + new_entry_b0_sorted, + new_entry_b1_sorted, + new_entry_b2_sorted, + new_entry_b3_sorted, + new_entry_b4_sorted, + ) ( new_keys_b0_sorted, new_keys_b1_sorted, @@ -798,18 +943,10 @@ def _write_alloc(_): new_keys_b4_sorted, new_ids_sorted, ) = _merge_sorted_keys_bucketed( - L_b0, - L_b1, - L_b2, - L_b3, - L_b4, + old_keys, L_ids, count, - new_entry_b0_sorted, - new_entry_b1_sorted, - new_entry_b2_sorted, - new_entry_b3_sorted, - new_entry_b4_sorted, + new_keys, new_entry_ids_sorted, num_new, op_start, @@ -842,6 +979,7 @@ def _intern_nodes_impl( batch: NodeBatch, *, cfg: InternConfig, + ledger_index: LedgerIndex | None = None, intern_core_fn=_intern_nodes_impl_core, key_safe_normalize_fn=_key_safe_normalize_nodes, guard_zero_args_fn=_guard_zero_args, @@ -888,6 +1026,7 @@ def _do(_): proposed_a1, proposed_a2, cfg=cfg, + ledger_index=ledger_index, key_safe_normalize_fn=key_safe_normalize_fn, pack_key_fn=pack_key_fn, candidate_indices_fn=candidate_indices_fn, @@ -907,12 +1046,13 @@ def _do(_): def intern_nodes( - ledger, + ledger: Ledger, batch_or_ops, a1=None, a2=None, *, cfg: InternConfig = DEFAULT_INTERN_CONFIG, + ledger_index: LedgerIndex, intern_impl_fn=_intern_nodes_impl, lookup_node_id_fn=_lookup_node_id, key_safe_normalize_fn=_key_safe_normalize_nodes, @@ -954,6 +1094,8 @@ def intern_nodes( proposed_ops, proposed_a1, proposed_a2 = batch if proposed_ops.shape[0] == 0: return jnp.zeros_like(proposed_ops), ledger + if ledger_index is None: + raise ValueError("intern_nodes requires a bound ledger_index") stop = ledger.oom | ledger.corrupt # NOTE: stop path returns zeros today; read-only lookup fallback is deferred. @@ -973,8 +1115,14 @@ def _lookup_existing(_): a1 = jnp.where(bad_key, jnp.int32(0), a1) a2 = jnp.where(bad_key, jnp.int32(0), a2) - def _lookup_one(op, a1_val, a2_val): - return lookup_node_id_fn(ledger, op, a1_val, a2_val) + if lookup_node_id_fn is _lookup_node_id: + def _lookup_one(op, a1_val, a2_val): + return _lookup_node_id( + ledger, op, a1_val, a2_val, ledger_index=ledger_index + ) + else: + def _lookup_one(op, a1_val, a2_val): + return lookup_node_id_fn(ledger, op, a1_val, a2_val) ids, found = jax.vmap(_lookup_one)(ops, a1, a2) ids = jnp.where(found & (~bad_key), ids, jnp.int32(0)) @@ -986,6 +1134,7 @@ def _do(_): ledger, batch, cfg=cfg, + ledger_index=ledger_index, key_safe_normalize_fn=key_safe_normalize_fn, guard_zero_args_fn=guard_zero_args_fn, checked_pack_key_fn=checked_pack_key_fn, @@ -1008,11 +1157,68 @@ def _do(_): return ids, new_ledger +def intern_nodes_state( + state: LedgerState, + batch_or_ops, + a1=None, + a2=None, + *, + cfg: InternConfig = DEFAULT_INTERN_CONFIG, + intern_impl_fn=_intern_nodes_impl, + lookup_node_id_fn=_lookup_node_id, + key_safe_normalize_fn=_key_safe_normalize_nodes, + guard_zero_args_fn=_guard_zero_args, + checked_pack_key_fn=_checked_pack_key, + guard_zero_row_fn=_guard_zero_row, + pack_key_fn=_pack_key, + candidate_indices_fn=_candidate_indices, + guard_max_fn=_guard_max, + guards_enabled_fn=_guards_enabled, + coord_norm_id_jax_fn=_coord_norm_id_jax, + coord_norm_id_jax_noprobe_fn=_coord_norm_id_jax_noprobe, + coord_norm_probe_enabled_fn=_coord_norm_probe_enabled, + coord_norm_probe_reset_cb_fn=_coord_norm_probe_reset_cb, + coord_norm_probe_assert_fn=_coord_norm_probe_assert, + scatter_drop_fn=_scatter_drop, +): + """Intern nodes against a LedgerState, returning updated state.""" + ids, new_ledger = intern_nodes( + state.ledger, + batch_or_ops, + a1, + a2, + cfg=cfg, + ledger_index=state.index, + intern_impl_fn=intern_impl_fn, + lookup_node_id_fn=lookup_node_id_fn, + key_safe_normalize_fn=key_safe_normalize_fn, + guard_zero_args_fn=guard_zero_args_fn, + checked_pack_key_fn=checked_pack_key_fn, + guard_zero_row_fn=guard_zero_row_fn, + pack_key_fn=pack_key_fn, + candidate_indices_fn=candidate_indices_fn, + guard_max_fn=guard_max_fn, + guards_enabled_fn=guards_enabled_fn, + coord_norm_id_jax_fn=coord_norm_id_jax_fn, + coord_norm_id_jax_noprobe_fn=coord_norm_id_jax_noprobe_fn, + coord_norm_probe_enabled_fn=coord_norm_probe_enabled_fn, + coord_norm_probe_reset_cb_fn=coord_norm_probe_reset_cb_fn, + coord_norm_probe_assert_fn=coord_norm_probe_assert_fn, + scatter_drop_fn=scatter_drop_fn, + ) + new_state = derive_ledger_state( + new_ledger, op_buckets_full_range=cfg.op_buckets_full_range + ) + return ids, new_state + + __all__ = [ "_coord_norm_id_jax", "_coord_norm_id_jax_noprobe", "_lookup_node_id", + "KeyColumns", "InternConfig", "DEFAULT_INTERN_CONFIG", "intern_nodes", + "intern_nodes_state", ] diff --git a/src/prism_metrics/metrics.py b/src/prism_metrics/metrics.py index 658fe59..05eec0e 100644 --- a/src/prism_metrics/metrics.py +++ b/src/prism_metrics/metrics.py @@ -1,10 +1,13 @@ import os +from dataclasses import dataclass import jax import numpy as np from prism_vm_core.domains import _host_int_value +# dataflow-bundle: changed, rewrite_child, wrap_emit + _damage_metrics_cycles = 0 _damage_metrics_hot_nodes = 0 _damage_metrics_edge_total = 0 @@ -15,6 +18,13 @@ _cnf2_metrics_wrap_emit = 0 +@dataclass(frozen=True) +class _Cnf2MetricsArgs: + rewrite_child: object + changed: object + wrap_emit: object + + def _damage_metrics_enabled(): value = os.environ.get("PRISM_DAMAGE_METRICS", "").strip().lower() return value in ("1", "true", "yes", "on") @@ -103,10 +113,13 @@ def _cnf2_metrics_update(rewrite_child, changed, wrap_emit): global _cnf2_metrics_wrap_emit if not _cnf2_metrics_enabled(): return + bundle = _Cnf2MetricsArgs( + rewrite_child=rewrite_child, changed=changed, wrap_emit=wrap_emit + ) _cnf2_metrics_cycles += 1 - _cnf2_metrics_rewrite_child += int(rewrite_child) - _cnf2_metrics_changed += int(changed) - _cnf2_metrics_wrap_emit += int(wrap_emit) + _cnf2_metrics_rewrite_child += int(bundle.rewrite_child) + _cnf2_metrics_changed += int(bundle.changed) + _cnf2_metrics_wrap_emit += int(bundle.wrap_emit) def _damage_metrics_update(arena, tile_size, rank_hot=0): diff --git a/src/prism_semantics/commit.py b/src/prism_semantics/commit.py index 9d44d3a..62f585d 100644 --- a/src/prism_semantics/commit.py +++ b/src/prism_semantics/commit.py @@ -1,4 +1,6 @@ from dataclasses import dataclass +from functools import partial +from typing import Callable, TypeVar import jax import jax.numpy as jnp @@ -25,6 +27,17 @@ resolve_safe_gather_ok_fn, resolve_safe_gather_ok_value_fn, ) + +# dataflow-bundle: act_val, exp_val + + +@dataclass(frozen=True) +class _ExpectedActualArgs: + exp_val: object + act_val: object + +from prism_ledger.config import InternConfig, DEFAULT_INTERN_CONFIG +from prism_ledger.index import LedgerIndex, derive_ledger_index from prism_ledger.intern import intern_nodes from prism_vm_core.constants import _PREFIX_SCAN_CHUNK from prism_vm_core.domains import ( @@ -35,8 +48,9 @@ _host_raise_if_bad, _provisional_ids, ) +from prism_vm_core.ontology import ProvisionalIds from prism_vm_core.guards import _guards_enabled -from prism_vm_core.structures import NodeBatch, Stratum +from prism_vm_core.structures import Ledger, NodeBatch, Stratum from prism_core.protocols import SafeGatherOkBoundFn, SafeGatherOkFn, SafeGatherOkValueFn from prism_vm_core.protocols import HostRaiseFn, InternFn, NodeBatchFn @@ -44,6 +58,33 @@ safe_gather_1d_ok = _jax_safe.safe_gather_1d_ok safe_gather_1d_ok_value = _jax_safe.safe_gather_1d_ok_value +T = TypeVar("T") + + +def _ledger_index_is_bound(fn: Callable[..., object]) -> bool: + if getattr(fn, "_prism_ledger_index_bound", False): + return True + if isinstance(fn, partial): + keywords = fn.keywords or {} + return "ledger_index" in keywords + return False + + +def _bind_ledger_index( + fn: Callable[..., T], ledger_index: LedgerIndex | None +) -> Callable[..., T]: + if ledger_index is None or _ledger_index_is_bound(fn): + return fn + + # dataflow-bundle: args, kwargs + def _wrapped(*args, **kwargs): + return call_with_optional_kwargs( + fn, {"ledger_index": ledger_index}, *args, **kwargs + ) + + setattr(_wrapped, "_prism_ledger_index_bound", True) + return _wrapped + def _node_batch(op, a1, a2): return NodeBatch(op=op, a1=a1, a2=a2) @@ -207,13 +248,29 @@ def apply_q( return out, ok +def apply_q_ok( + q: QMap, + ids, + *, + provisional_ids_fn=_provisional_ids, +): + """Collapseʰ: homomorphic projection q with ok mask.""" + ids_in = provisional_ids_fn(ids) + out = q(ids_in) + meta = getattr(q, "_prism_meta", None) + if meta is None: + ok = jnp.ones_like(out.a, dtype=jnp.bool_) + return out, ok + ok = _q_map_ok(ids_in, meta) + return out, ok + + def _apply_stratum_q_core( ids, stratum: Stratum, canon_ids, label: str, *, - safe_gather_fn=safe_gather_1d, safe_gather_ok_fn: SafeGatherOkFn = safe_gather_1d_ok, guards_enabled_fn=_guards_enabled, provisional_ids_fn=_provisional_ids, @@ -231,7 +288,8 @@ def _raise(bad_val, exp_val, act_val): f"guard failed: {label} count={int(exp_val)} canon_ids={int(act_val)}" ) - jax.debug.callback(_raise, mismatch, expected, actual) + bundle = _ExpectedActualArgs(exp_val=expected, act_val=actual) + jax.debug.callback(_raise, mismatch, bundle.exp_val, bundle.act_val) if canon_ids.a.shape[0] == 0: return ids start = jnp.asarray(stratum.start, dtype=jnp.int32) @@ -257,7 +315,6 @@ def _apply_stratum_q_static( canon_ids, label: str, *, - safe_gather_fn=safe_gather_1d, safe_gather_ok_fn: SafeGatherOkBoundFn = safe_gather_1d_ok, guards_enabled_fn=_guards_enabled, provisional_ids_fn=_provisional_ids, @@ -270,7 +327,6 @@ def _apply_stratum_q_static( stratum, canon_ids, label, - safe_gather_fn=safe_gather_fn, safe_gather_ok_fn=safe_gather_ok_fn, guards_enabled_fn=guards_enabled_fn, provisional_ids_fn=provisional_ids_fn, @@ -285,7 +341,6 @@ def _apply_stratum_q_dynamic( canon_ids, label: str, *, - safe_gather_fn=safe_gather_1d, safe_gather_ok_fn: SafeGatherOkFn = safe_gather_1d_ok, guards_enabled_fn=_guards_enabled, provisional_ids_fn=_provisional_ids, @@ -304,7 +359,6 @@ def _apply_stratum_q_dynamic( stratum, canon_ids, label, - safe_gather_fn=safe_gather_fn, safe_gather_ok_fn=safe_gather_ok_fn, guards_enabled_fn=guards_enabled_fn, provisional_ids_fn=provisional_ids_fn, @@ -348,7 +402,8 @@ def _raise(bad_val, exp_val, act_val): f"guard failed: {label} count={int(exp_val)} canon_ids={int(act_val)}" ) - jax.debug.callback(_raise, mismatch, expected, actual) + bundle = _ExpectedActualArgs(exp_val=expected, act_val=actual) + jax.debug.callback(_raise, mismatch, bundle.exp_val, bundle.act_val) if canon_ids.a.shape[0] == 0: return ids start = jnp.asarray(stratum.start, dtype=jnp.int32) @@ -371,15 +426,17 @@ def _raise(bad_val, exp_val, act_val): def _commit_stratum_core( - ledger, + ledger: Ledger, stratum: Stratum, prior_q: QMap | None = None, validate_mode: ValidateMode = ValidateMode.NONE, *, intern_fn: InternFn = intern_nodes, + ledger_index: LedgerIndex | None = None, + intern_cfg: InternConfig | None = None, node_batch_fn: NodeBatchFn = _node_batch, identity_q_fn=_identity_q, - apply_stratum_q_fn=_apply_stratum_q_static, + apply_stratum_q_fn: Callable[..., ProvisionalIds] = _apply_stratum_q_static, validate_within_fn=validate_stratum_no_within_refs, validate_future_fn=validate_stratum_no_future_refs, guards_enabled_fn=_guards_enabled, @@ -420,8 +477,31 @@ def _commit_stratum_core( ops = ledger.opcode[ids] a1 = q_prev(provisional_ids_fn(ledger.arg1[ids])).a a2 = q_prev(provisional_ids_fn(ledger.arg2[ids])).a + pre_intern_count = ledger.count.astype(jnp.int32) + if ledger_index is None: + cfg = intern_cfg or DEFAULT_INTERN_CONFIG + ledger_index = derive_ledger_index( + ledger, op_buckets_full_range=cfg.op_buckets_full_range + ) + intern_fn = _bind_ledger_index(intern_fn, ledger_index) canon_ids_raw, ledger = intern_fn(ledger, node_batch_fn(ops, a1, a2)) canon_ids = committed_ids_fn(canon_ids_raw) + post_intern_count = ledger.count.astype(jnp.int32) + if mode != ValidateMode.NONE: + intern_delta = post_intern_count - pre_intern_count + if host_int_value_fn(jnp.maximum(intern_delta, 0)) > 0: + intern_stratum = Stratum( + start=pre_intern_count, + count=jnp.maximum(intern_delta, 0), + ) + if mode == ValidateMode.STRICT: + ok = validate_within_fn(ledger, intern_stratum) + else: + ok = validate_future_fn(ledger, intern_stratum) + if not ok: + if mode == ValidateMode.STRICT: + raise ValueError("Stratum contains within-tier references") + raise ValueError("Stratum contains future references") if (mode != ValidateMode.NONE or guards_enabled_fn()) and canon_ids.a.shape[0] != count: raise ValueError("Stratum count mismatch in commit_stratum") @@ -459,15 +539,17 @@ def q_map(ids_in): def _commit_stratum_common_static( - ledger, + ledger: Ledger, stratum: Stratum, prior_q: QMap | None = None, validate_mode: ValidateMode = ValidateMode.NONE, *, intern_fn: InternFn = intern_nodes, + ledger_index: LedgerIndex | None = None, + intern_cfg: InternConfig | None = None, node_batch_fn: NodeBatchFn = _node_batch, identity_q_fn=_identity_q, - apply_stratum_q_fn=_apply_stratum_q_static, + apply_stratum_q_fn: Callable[..., ProvisionalIds] = _apply_stratum_q_static, validate_within_fn=validate_stratum_no_within_refs, validate_future_fn=validate_stratum_no_future_refs, guards_enabled_fn=_guards_enabled, @@ -499,6 +581,8 @@ def _commit_stratum_common_static( prior_q=prior_q, validate_mode=validate_mode, intern_fn=intern_fn, + ledger_index=ledger_index, + intern_cfg=intern_cfg, node_batch_fn=node_batch_fn, identity_q_fn=identity_q_fn, apply_stratum_q_fn=apply_stratum_q_fn, @@ -519,15 +603,17 @@ def _commit_stratum_common_static( def _commit_stratum_common_value( - ledger, + ledger: Ledger, stratum: Stratum, prior_q: QMap | None = None, validate_mode: ValidateMode = ValidateMode.NONE, *, intern_fn: InternFn = intern_nodes, + ledger_index: LedgerIndex | None = None, + intern_cfg: InternConfig | None = None, node_batch_fn: NodeBatchFn = _node_batch, identity_q_fn=_identity_q, - apply_stratum_q_fn=_apply_stratum_q_static, + apply_stratum_q_fn: Callable[..., ProvisionalIds] = _apply_stratum_q_static, validate_within_fn=validate_stratum_no_within_refs, validate_future_fn=validate_stratum_no_future_refs, guards_enabled_fn=_guards_enabled, @@ -553,6 +639,8 @@ def _commit_stratum_common_value( prior_q=prior_q, validate_mode=validate_mode, intern_fn=intern_fn, + ledger_index=ledger_index, + intern_cfg=intern_cfg, node_batch_fn=node_batch_fn, identity_q_fn=identity_q_fn, apply_stratum_q_fn=apply_stratum_q_fn, @@ -573,15 +661,17 @@ def _commit_stratum_common_value( def _commit_stratum_common_bound( - ledger, + ledger: Ledger, stratum: Stratum, prior_q: QMap | None = None, validate_mode: ValidateMode = ValidateMode.NONE, *, intern_fn: InternFn = intern_nodes, + ledger_index: LedgerIndex | None = None, + intern_cfg: InternConfig | None = None, node_batch_fn: NodeBatchFn = _node_batch, identity_q_fn=_identity_q, - apply_stratum_q_fn=_apply_stratum_q_static, + apply_stratum_q_fn: Callable[..., ProvisionalIds] = _apply_stratum_q_static, validate_within_fn=validate_stratum_no_within_refs, validate_future_fn=validate_stratum_no_future_refs, guards_enabled_fn=_guards_enabled, @@ -613,6 +703,8 @@ def _commit_stratum_common_bound( prior_q=prior_q, validate_mode=validate_mode, intern_fn=intern_fn, + ledger_index=ledger_index, + intern_cfg=intern_cfg, node_batch_fn=node_batch_fn, identity_q_fn=identity_q_fn, apply_stratum_q_fn=apply_stratum_q_fn, @@ -633,15 +725,17 @@ def _commit_stratum_common_bound( def commit_stratum_static( - ledger, + ledger: Ledger, stratum: Stratum, prior_q: QMap | None = None, validate_mode: ValidateMode = ValidateMode.NONE, *, intern_fn: InternFn = intern_nodes, + ledger_index: LedgerIndex | None = None, + intern_cfg: InternConfig | None = None, node_batch_fn: NodeBatchFn = _node_batch, identity_q_fn=_identity_q, - apply_stratum_q_fn=_apply_stratum_q_static, + apply_stratum_q_fn: Callable[..., ProvisionalIds] = _apply_stratum_q_static, validate_within_fn=validate_stratum_no_within_refs, validate_future_fn=validate_stratum_no_future_refs, guards_enabled_fn=_guards_enabled, @@ -679,6 +773,8 @@ def commit_stratum_static( prior_q=prior_q, validate_mode=validate_mode, intern_fn=intern_fn, + ledger_index=ledger_index, + intern_cfg=intern_cfg, node_batch_fn=node_batch_fn, identity_q_fn=identity_q_fn, apply_stratum_q_fn=apply_stratum_q_fn, @@ -698,15 +794,17 @@ def commit_stratum_static( def commit_stratum_bound( - ledger, + ledger: Ledger, stratum: Stratum, prior_q: QMap | None = None, validate_mode: ValidateMode = ValidateMode.NONE, *, intern_fn: InternFn = intern_nodes, + ledger_index: LedgerIndex | None = None, + intern_cfg: InternConfig | None = None, node_batch_fn: NodeBatchFn = _node_batch, identity_q_fn=_identity_q, - apply_stratum_q_fn=_apply_stratum_q_static, + apply_stratum_q_fn: Callable[..., ProvisionalIds] = _apply_stratum_q_static, validate_within_fn=validate_stratum_no_within_refs, validate_future_fn=validate_stratum_no_future_refs, guards_enabled_fn=_guards_enabled, @@ -735,6 +833,8 @@ def commit_stratum_bound( prior_q=prior_q, validate_mode=validate_mode, intern_fn=intern_fn, + ledger_index=ledger_index, + intern_cfg=intern_cfg, node_batch_fn=node_batch_fn, identity_q_fn=identity_q_fn, apply_stratum_q_fn=apply_stratum_q_fn, @@ -751,15 +851,17 @@ def commit_stratum_bound( def commit_stratum_value( - ledger, + ledger: Ledger, stratum: Stratum, prior_q: QMap | None = None, validate_mode: ValidateMode = ValidateMode.NONE, *, intern_fn: InternFn = intern_nodes, + ledger_index: LedgerIndex | None = None, + intern_cfg: InternConfig | None = None, node_batch_fn: NodeBatchFn = _node_batch, identity_q_fn=_identity_q, - apply_stratum_q_fn=_apply_stratum_q_static, + apply_stratum_q_fn: Callable[..., ProvisionalIds] = _apply_stratum_q_static, validate_within_fn=validate_stratum_no_within_refs, validate_future_fn=validate_stratum_no_future_refs, guards_enabled_fn=_guards_enabled, @@ -789,6 +891,8 @@ def commit_stratum_value( prior_q=prior_q, validate_mode=validate_mode, intern_fn=intern_fn, + ledger_index=ledger_index, + intern_cfg=intern_cfg, node_batch_fn=node_batch_fn, identity_q_fn=identity_q_fn, apply_stratum_q_fn=apply_stratum_q_fn, @@ -806,15 +910,17 @@ def commit_stratum_value( def commit_stratum( - ledger, + ledger: Ledger, stratum: Stratum, prior_q: QMap | None = None, validate_mode: ValidateMode = ValidateMode.NONE, *, intern_fn: InternFn = intern_nodes, + ledger_index: LedgerIndex | None = None, + intern_cfg: InternConfig | None = None, node_batch_fn: NodeBatchFn = _node_batch, identity_q_fn=_identity_q, - apply_stratum_q_fn=_apply_stratum_q_static, + apply_stratum_q_fn: Callable[..., ProvisionalIds] = _apply_stratum_q_static, validate_within_fn=validate_stratum_no_within_refs, validate_future_fn=validate_stratum_no_future_refs, guards_enabled_fn=_guards_enabled, @@ -854,6 +960,8 @@ def commit_stratum( prior_q=prior_q, validate_mode=validate_mode, intern_fn=intern_fn, + ledger_index=ledger_index, + intern_cfg=intern_cfg, node_batch_fn=node_batch_fn, identity_q_fn=identity_q_fn, apply_stratum_q_fn=apply_stratum_q_fn, @@ -874,6 +982,8 @@ def commit_stratum( prior_q=prior_q, validate_mode=validate_mode, intern_fn=intern_fn, + ledger_index=ledger_index, + intern_cfg=intern_cfg, node_batch_fn=node_batch_fn, identity_q_fn=identity_q_fn, apply_stratum_q_fn=apply_stratum_q_fn, @@ -895,6 +1005,8 @@ def commit_stratum( prior_q=prior_q, validate_mode=validate_mode, intern_fn=intern_fn, + ledger_index=ledger_index, + intern_cfg=intern_cfg, node_batch_fn=node_batch_fn, identity_q_fn=identity_q_fn, apply_stratum_q_fn=apply_stratum_q_fn, @@ -915,6 +1027,8 @@ def commit_stratum( prior_q=prior_q, validate_mode=validate_mode, intern_fn=intern_fn, + ledger_index=ledger_index, + intern_cfg=intern_cfg, node_batch_fn=node_batch_fn, identity_q_fn=identity_q_fn, apply_stratum_q_fn=apply_stratum_q_fn, @@ -934,6 +1048,7 @@ def commit_stratum( __all__ = [ "apply_q", + "apply_q_ok", "commit_stratum", "commit_stratum_static", "commit_stratum_bound", diff --git a/src/prism_semantics/project.py b/src/prism_semantics/project.py index 5a731fd..a37fa77 100644 --- a/src/prism_semantics/project.py +++ b/src/prism_semantics/project.py @@ -1,10 +1,22 @@ +from dataclasses import dataclass import jax import jax.numpy as jnp -from prism_ledger.intern import intern_nodes +from prism_ledger.config import DEFAULT_INTERN_CONFIG +from prism_ledger.intern import intern_nodes_state +from prism_ledger.index import LedgerState, derive_ledger_state from prism_vm_core.domains import _host_int_value, _host_raise_if_bad, _ledger_id from prism_vm_core.ontology import OP_NULL -from prism_vm_core.structures import NodeBatch +from prism_vm_core.structures import Ledger, NodeBatch + +# dataflow-bundle: arg1, arg2, opcode + + +@dataclass(frozen=True) +class _ProjectArgs: + opcode: object + arg1: object + arg2: object def _node_batch(op, a1, a2): @@ -17,18 +29,26 @@ def _project_graph_to_ledger( arg2, count, root_idx, - ledger, + ledger: Ledger | LedgerState, label, limit=None, ): - ops = jax.device_get(opcode[:count]) - a1s = jax.device_get(arg1[:count]) - a2s = jax.device_get(arg2[:count]) + if isinstance(ledger, LedgerState): + ledger_state = ledger + else: + ledger_state = derive_ledger_state( + ledger, + op_buckets_full_range=DEFAULT_INTERN_CONFIG.op_buckets_full_range, + ) + bundle = _ProjectArgs(opcode=opcode, arg1=arg1, arg2=arg2) + ops = jax.device_get(bundle.opcode[:count]) + a1s = jax.device_get(bundle.arg1[:count]) + a2s = jax.device_get(bundle.arg2[:count]) mapping = {0: 0} visiting = set() def _project(idx): - nonlocal ledger + nonlocal ledger_state idx_i = int(idx) if idx_i in mapping: return mapping[idx_i] @@ -50,8 +70,8 @@ def _project(idx): return 0 child1 = _project(int(a1s[idx_i])) child2 = _project(int(a2s[idx_i])) - ids, ledger = intern_nodes( - ledger, + ids, ledger_state = intern_nodes_state( + ledger_state, _node_batch( jnp.array([op], dtype=jnp.int32), jnp.array([child1], dtype=jnp.int32), @@ -63,8 +83,10 @@ def _project(idx): return mapping[idx_i] root_out = _project(int(root_idx)) - _host_raise_if_bad(ledger, f"{label}: projection exceeded ledger capacity") - return ledger, _ledger_id(root_out) + _host_raise_if_bad( + ledger_state.ledger, f"{label}: projection exceeded ledger capacity" + ) + return ledger_state.ledger, _ledger_id(root_out) def project_manifest_to_ledger(manifest, root_ptr, ledger=None, limit=None): diff --git a/src/prism_vm_core/candidates.py b/src/prism_vm_core/candidates.py index 2ea4cc8..41ce69c 100644 --- a/src/prism_vm_core/candidates.py +++ b/src/prism_vm_core/candidates.py @@ -1,3 +1,5 @@ +from typing import Callable + import jax.numpy as jnp from prism_core.compact import ( @@ -23,7 +25,7 @@ def _candidate_indices( def candidate_indices_cfg( enabled, *, - candidate_indices_fn=None, + candidate_indices_fn: Callable[..., CompactResult] | None = None, compact_cfg: CompactConfig | None = None, ): """Interface/Control wrapper for candidate index selection.""" diff --git a/src/prism_vm_core/exports.py b/src/prism_vm_core/exports.py index 6866607..301e39b 100644 --- a/src/prism_vm_core/exports.py +++ b/src/prism_vm_core/exports.py @@ -9,8 +9,6 @@ PrismPolicyModeError, PrismValidateModeError, PrismBspModeError, - PrismCnf2ModeError, - PrismCnf2ModeConflictError, PrismSafetyModeError, ) @@ -36,8 +34,6 @@ "PrismPolicyModeError", "PrismValidateModeError", "PrismBspModeError", - "PrismCnf2ModeError", - "PrismCnf2ModeConflictError", "PrismSafetyModeError", "ManifestPtr", "LedgerId", @@ -55,16 +51,26 @@ "MAX_COORD_STEPS", "InternConfig", "DEFAULT_INTERN_CONFIG", + "LedgerIndex", + "derive_ledger_index", + "LedgerState", + "derive_ledger_state", "AllocConfig", "DEFAULT_ALLOC_CONFIG", - "Cnf2Flags", - "DEFAULT_CNF2_FLAGS", "Cnf2Config", "DEFAULT_CNF2_CONFIG", + "Cnf2RuntimeFns", + "DEFAULT_CNF2_RUNTIME_FNS", + "Cnf2CandidateFns", + "Cnf2PolicyFns", "ArenaInteractConfig", "DEFAULT_ARENA_INTERACT_CONFIG", + "SwizzleWithPermFns", + "SwizzleWithPermFnsBound", "ArenaCycleConfig", "DEFAULT_ARENA_CYCLE_CONFIG", + "ArenaSortConfig", + "DEFAULT_ARENA_SORT_CONFIG", "arena_interact_config_with_policy", "arena_interact_config_with_policy_value", "arena_interact_config_with_guard", @@ -91,8 +97,6 @@ "require_validate_mode", "BspMode", "coerce_bsp_mode", - "Cnf2Mode", - "coerce_cnf2_mode", "PolicyValue", "POLICY_VALUE_CORRUPT", "POLICY_VALUE_CLAMP", @@ -133,8 +137,6 @@ "guard_zero_args_cfg", "guard_swizzle_args_cfg", "_key_order_commutative_host", - "_cnf2_enabled", - "_cnf2_slot1_enabled", "_default_bsp_mode", "_normalize_bsp_mode", "_parse_milestone_value", @@ -202,6 +204,7 @@ "init_manifest", "init_arena", "init_ledger", + "init_ledger_state", "ledger_has_corrupt", "project_manifest_to_ledger", "project_arena_to_ledger", @@ -222,8 +225,11 @@ "intern_candidates_jit", "intern_candidates_jit_cfg", "cycle_candidates_jit", + "cycle_candidates_state_jit", "cycle_candidates_static_jit", + "cycle_candidates_static_state_jit", "cycle_candidates_value_jit", + "cycle_candidates_value_state_jit", "compact_candidates", "compact_candidates_result", "compact_candidates_cfg", @@ -242,9 +248,13 @@ "commit_stratum_static", "commit_stratum_value", "cycle_candidates", + "cycle_candidates_state", "cycle_candidates_static", + "cycle_candidates_static_state", "cycle_candidates_value", + "cycle_candidates_value_state", "cycle_candidates_bound", + "cycle_candidates_bound_state", "op_rank", "coord_norm", "coord_xor", @@ -257,6 +267,9 @@ "coord_xor_batch_cfg", "intern_nodes", "intern_nodes_jit", + "intern_nodes_with_index", + "intern_nodes_with_index_jit", + "intern_nodes_state", "op_sort_and_swizzle_with_perm", "op_sort_and_swizzle_with_perm_value", "op_sort_with_perm_cfg", diff --git a/src/prism_vm_core/facade.py b/src/prism_vm_core/facade.py index 5bc3d03..7f9041a 100644 --- a/src/prism_vm_core/facade.py +++ b/src/prism_vm_core/facade.py @@ -7,9 +7,14 @@ shadowing or monkeypatching drift. """ +# dataflow-bundle: cfg, guard, policy +# dataflow-bundle: cfg, guard, policy, return_ok +# dataflow-bundle: emit_candidates_fn, intern_fn +# dataflow-bundle: guard_cfg, safe_gather_value_fn + from typing import Optional from functools import partial -from dataclasses import replace +from dataclasses import dataclass, replace import jax import jax.numpy as jnp @@ -37,34 +42,34 @@ require_static_policy, require_value_policy, ) -from prism_core.modes import ( - ValidateMode, - require_validate_mode, - BspMode, - coerce_bsp_mode, - Cnf2Mode, - coerce_cnf2_mode, -) -from prism_core.errors import ( - PrismPolicyModeError, - PrismPolicyBindingError, - PrismCnf2ModeError, - PrismCnf2ModeConflictError, -) +from prism_core.modes import ValidateMode, require_validate_mode, BspMode, coerce_bsp_mode +from prism_core.errors import PrismPolicyModeError, PrismPolicyBindingError from prism_ledger import intern as _ledger_intern from prism_ledger.config import InternConfig, DEFAULT_INTERN_CONFIG +from prism_ledger.index import ( + LedgerIndex, + LedgerState, + derive_ledger_index, + derive_ledger_state, +) from prism_bsp.config import ( Cnf2Config, Cnf2BoundConfig, Cnf2StaticBoundConfig, Cnf2ValueBoundConfig, - Cnf2Flags, DEFAULT_CNF2_CONFIG, - DEFAULT_CNF2_FLAGS, + Cnf2RuntimeFns, + DEFAULT_CNF2_RUNTIME_FNS, + Cnf2CandidateFns, + Cnf2PolicyFns, ArenaInteractConfig, DEFAULT_ARENA_INTERACT_CONFIG, + SwizzleWithPermFns, + SwizzleWithPermFnsBound, ArenaCycleConfig, DEFAULT_ARENA_CYCLE_CONFIG, + ArenaSortConfig, + DEFAULT_ARENA_SORT_CONFIG, IntrinsicConfig, DEFAULT_INTRINSIC_CONFIG, ) @@ -84,8 +89,8 @@ from prism_vm_core.candidates import _candidate_indices, candidate_indices_cfg from prism_bsp.cnf2 import _scatter_compacted_ids from prism_vm_core.ontology import OP_ADD, OP_MUL, OP_NULL, OP_ZERO, HostBool -from prism_vm_core.domains import _host_bool, _host_raise_if_bad -from prism_vm_core.structures import NodeBatch +from prism_vm_core.domains import QMap, _host_bool, _host_raise_if_bad +from prism_vm_core.structures import Ledger, NodeBatch, Stratum from prism_bsp.space import RANK_FREE from prism_bsp.cnf2 import ( emit_candidates as _emit_candidates_default, @@ -204,7 +209,7 @@ def arena_interact_config_with_policy( def arena_interact_config_with_policy_value( - policy_value, + policy_value: PolicyValue | None, *, cfg: ArenaInteractConfig = DEFAULT_ARENA_INTERACT_CONFIG, ) -> ArenaInteractConfig: @@ -263,7 +268,7 @@ def arena_cycle_config_with_policy( def arena_cycle_config_with_policy_value( - policy_value, + policy_value: PolicyValue | None, *, cfg: ArenaCycleConfig = DEFAULT_ARENA_CYCLE_CONFIG, include_interact: bool = True, @@ -318,10 +323,16 @@ def op_sort_with_perm_cfg( guard_cfg: GuardConfig | None = None, ): """Interface/Control wrapper for op_sort_and_swizzle_with_perm with guard cfg.""" + bundle = _GuardSafeGatherValueBundle( + guard_cfg=guard_cfg, safe_gather_value_fn=safe_gather_value_fn + ) if safe_gather_policy_value is not None: return call_with_optional_kwargs( op_sort_and_swizzle_with_perm_value, - {"guard_cfg": guard_cfg, "safe_gather_value_fn": safe_gather_value_fn}, + { + "guard_cfg": bundle.guard_cfg, + "safe_gather_value_fn": bundle.safe_gather_value_fn, + }, arena, safe_gather_policy_value, ) @@ -361,10 +372,16 @@ def op_sort_blocked_with_perm_cfg( guard_cfg: GuardConfig | None = None, ): """Interface/Control wrapper for op_sort_and_swizzle_blocked_with_perm with guard cfg.""" + bundle = _GuardSafeGatherValueBundle( + guard_cfg=guard_cfg, safe_gather_value_fn=safe_gather_value_fn + ) if safe_gather_policy_value is not None: return call_with_optional_kwargs( op_sort_and_swizzle_blocked_with_perm_value, - {"guard_cfg": guard_cfg, "safe_gather_value_fn": safe_gather_value_fn}, + { + "guard_cfg": bundle.guard_cfg, + "safe_gather_value_fn": bundle.safe_gather_value_fn, + }, arena, block_size, safe_gather_policy_value, @@ -412,10 +429,16 @@ def op_sort_hierarchical_with_perm_cfg( guard_cfg: GuardConfig | None = None, ): """Interface/Control wrapper for op_sort_and_swizzle_hierarchical_with_perm with guard cfg.""" + bundle = _GuardSafeGatherValueBundle( + guard_cfg=guard_cfg, safe_gather_value_fn=safe_gather_value_fn + ) if safe_gather_policy_value is not None: return call_with_optional_kwargs( op_sort_and_swizzle_hierarchical_with_perm_value, - {"guard_cfg": guard_cfg, "safe_gather_value_fn": safe_gather_value_fn}, + { + "guard_cfg": bundle.guard_cfg, + "safe_gather_value_fn": bundle.safe_gather_value_fn, + }, arena, l2_block_size, l1_block_size, @@ -466,10 +489,16 @@ def op_sort_morton_with_perm_cfg( guard_cfg: GuardConfig | None = None, ): """Interface/Control wrapper for op_sort_and_swizzle_morton_with_perm with guard cfg.""" + bundle = _GuardSafeGatherValueBundle( + guard_cfg=guard_cfg, safe_gather_value_fn=safe_gather_value_fn + ) if safe_gather_policy_value is not None: return call_with_optional_kwargs( op_sort_and_swizzle_morton_with_perm_value, - {"guard_cfg": guard_cfg, "safe_gather_value_fn": safe_gather_value_fn}, + { + "guard_cfg": bundle.guard_cfg, + "safe_gather_value_fn": bundle.safe_gather_value_fn, + }, arena, morton, safe_gather_policy_value, @@ -512,10 +541,16 @@ def op_sort_servo_with_perm_cfg( guard_cfg: GuardConfig | None = None, ): """Interface/Control wrapper for op_sort_and_swizzle_servo_with_perm with guard cfg.""" + bundle = _GuardSafeGatherValueBundle( + guard_cfg=guard_cfg, safe_gather_value_fn=safe_gather_value_fn + ) if safe_gather_policy_value is not None: return call_with_optional_kwargs( op_sort_and_swizzle_servo_with_perm_value, - {"guard_cfg": guard_cfg, "safe_gather_value_fn": safe_gather_value_fn}, + { + "guard_cfg": bundle.guard_cfg, + "safe_gather_value_fn": bundle.safe_gather_value_fn, + }, arena, morton, servo_mask, @@ -580,7 +615,7 @@ def cnf2_config_bound( def cnf2_config_with_policy_value( - policy_value, + policy_value: PolicyValue | None, *, cfg: Cnf2Config = DEFAULT_CNF2_CONFIG, ) -> Cnf2Config: @@ -606,8 +641,6 @@ def cnf2_config_with_guard( """Return a Cnf2Config with guard_cfg set.""" return replace(cfg, guard_cfg=guard_cfg) from prism_vm_core.gating import ( - _cnf2_enabled as _cnf2_enabled_default, - _cnf2_slot1_enabled as _cnf2_slot1_enabled_default, _default_bsp_mode, _gpu_metrics_device_index, _gpu_metrics_enabled, @@ -643,6 +676,33 @@ def cnf2_config_with_guard( guard_zero_args_cfg, guard_swizzle_args_cfg, ) + + +@dataclass(frozen=True) +class _SafeGatherCfgArgs: + cfg: GuardConfig + guard: object + policy: SafetyPolicy | None + + +@dataclass(frozen=True) +class _SafeGatherCfgReturnOkArgs: + cfg: GuardConfig + guard: object + policy: SafetyPolicy | None + return_ok: bool + + +@dataclass(frozen=True) +class _EmitInternFns: + emit_candidates_fn: object + intern_fn: object + + +@dataclass(frozen=True) +class _GuardSafeGatherValueBundle: + guard_cfg: GuardConfig + safe_gather_value_fn: object from prism_semantics.commit import ( commit_stratum as _commit_stratum_impl, commit_stratum_static as _commit_stratum_static_impl, @@ -650,14 +710,22 @@ def cnf2_config_with_guard( ) from prism_bsp.cnf2 import ( cycle_candidates as _cycle_candidates_impl, + cycle_candidates_bound as _cycle_candidates_bound, + cycle_candidates_bound_state as _cycle_candidates_bound_state, + cycle_candidates_state as _cycle_candidates_state, cycle_candidates_static as _cycle_candidates_static, + cycle_candidates_static_state as _cycle_candidates_static_state, cycle_candidates_value as _cycle_candidates_value, + cycle_candidates_value_state as _cycle_candidates_value_state, ) from prism_vm_core.jit_entrypoints import ( coord_norm_batch_jit, cycle_candidates_jit, + cycle_candidates_state_jit, cycle_candidates_static_jit, + cycle_candidates_static_state_jit, cycle_candidates_value_jit, + cycle_candidates_value_state_jit, cycle_intrinsic_jit, cycle_intrinsic_jit_cfg, cycle_jit as _cycle_jit_factory, @@ -676,6 +744,7 @@ def cnf2_config_with_guard( intern_candidates_jit, intern_candidates_jit_cfg, intern_nodes_jit, + intern_nodes_with_index_jit, op_interact_jit, op_interact_jit_cfg, op_interact_jit_bound_cfg, @@ -702,8 +771,6 @@ def cnf2_config_with_guard( require_validate_mode = require_validate_mode BspMode = BspMode coerce_bsp_mode = coerce_bsp_mode -Cnf2Mode = Cnf2Mode -coerce_cnf2_mode = coerce_cnf2_mode PolicyValue = PolicyValue POLICY_VALUE_CORRUPT = POLICY_VALUE_CORRUPT POLICY_VALUE_CLAMP = POLICY_VALUE_CLAMP @@ -808,9 +875,17 @@ def safe_gather_1d_cfg( return_ok: bool = False, ): """Interface/Control wrapper for safe_gather_1d with guard config.""" + bundle = _SafeGatherCfgReturnOkArgs( + cfg=cfg, guard=guard, policy=policy, return_ok=return_ok + ) return call_with_optional_kwargs( _safe_gather_1d_cfg, - {"guard": guard, "policy": policy, "cfg": cfg, "return_ok": return_ok}, + { + "guard": bundle.guard, + "policy": bundle.policy, + "cfg": bundle.cfg, + "return_ok": bundle.return_ok, + }, arr, idx, label, @@ -827,9 +902,10 @@ def safe_gather_1d_ok_cfg( cfg: GuardConfig = DEFAULT_GUARD_CONFIG, ): """Interface/Control wrapper for safe_gather_1d_ok with guard config.""" + bundle = _SafeGatherCfgArgs(cfg=cfg, guard=guard, policy=policy) return call_with_optional_kwargs( _safe_gather_1d_ok_cfg, - {"guard": guard, "policy": policy, "cfg": cfg}, + {"guard": bundle.guard, "policy": bundle.policy, "cfg": bundle.cfg}, arr, idx, label, @@ -869,9 +945,10 @@ def safe_index_1d_cfg( cfg: GuardConfig = DEFAULT_GUARD_CONFIG, ): """Interface/Control wrapper for safe_index_1d with guard config.""" + bundle = _SafeGatherCfgArgs(cfg=cfg, guard=guard, policy=policy) return call_with_optional_kwargs( _safe_index_1d_cfg, - {"guard": guard, "policy": policy, "cfg": cfg}, + {"guard": bundle.guard, "policy": bundle.policy, "cfg": bundle.cfg}, idx, size, label, @@ -903,6 +980,16 @@ def init_ledger(): return _init_ledger(_pack_key, LEDGER_CAPACITY, OP_NULL, OP_ZERO) +def init_ledger_state(*, cfg: InternConfig | None = None): + """Initialize a LedgerState with a derived index.""" + ledger = init_ledger() + if cfg is None: + cfg = DEFAULT_INTERN_CONFIG + return derive_ledger_state( + ledger, op_buckets_full_range=cfg.op_buckets_full_range + ) + + def ledger_has_corrupt(ledger) -> HostBool: """Interface/Control wrapper for host-visible corrupt checks. @@ -923,8 +1010,6 @@ def ledger_has_corrupt(ledger) -> HostBool: candidate_indices_cfg = candidate_indices_cfg _scatter_compacted_ids = _scatter_compacted_ids scatter_compacted_ids_cfg = _scatter_compacted_ids_cfg -_cnf2_enabled = _cnf2_enabled_default -_cnf2_slot1_enabled = _cnf2_slot1_enabled_default emit_candidates = _emit_candidates emit_candidates_cfg = _emit_candidates_cfg compact_candidates = _compact_candidates @@ -1001,10 +1086,12 @@ def ledger_has_corrupt(ledger) -> HostBool: _gpu_watchdog_create = _gpu_watchdog_create InternConfig = InternConfig DEFAULT_INTERN_CONFIG = DEFAULT_INTERN_CONFIG +LedgerIndex = LedgerIndex +derive_ledger_index = derive_ledger_index +LedgerState = LedgerState +derive_ledger_state = derive_ledger_state Cnf2Config = Cnf2Config -Cnf2Flags = Cnf2Flags DEFAULT_CNF2_CONFIG = DEFAULT_CNF2_CONFIG -DEFAULT_CNF2_FLAGS = DEFAULT_CNF2_FLAGS CoordConfig = CoordConfig DEFAULT_COORD_CONFIG = DEFAULT_COORD_CONFIG dispatch_kernel = dispatch_kernel @@ -1021,7 +1108,7 @@ def _key_order_commutative_host(op, a1, a2): def intern_nodes( - ledger, + ledger: Ledger | LedgerState, batch_or_ops, a1=None, a2=None, @@ -1029,12 +1116,40 @@ def intern_nodes( cfg: InternConfig | None = None, op_buckets_full_range: Optional[bool] = None, force_spawn_clip: Optional[bool] = None, + ledger_index: LedgerIndex | None = None, ): """Interface/Control wrapper for intern_nodes behavior flags. + If ledger_index is provided, the bound LedgerIndex path is used to avoid + recomputing opcode buckets for the same ledger state. + + If ledger is a LedgerState, the LedgerIndex path is used implicitly and the + updated LedgerState is returned. + Axis: Interface/Control. Commutes with q. Erased by q. Test: tests/test_m1_gate.py """ + if isinstance(ledger, LedgerState): + if ledger_index is not None: + raise ValueError("Pass either LedgerState or ledger_index, not both.") + if cfg is not None and (op_buckets_full_range is not None or force_spawn_clip is not None): + raise ValueError("Pass either cfg or individual flags, not both.") + if cfg is None and op_buckets_full_range is None and force_spawn_clip is None: + cfg = DEFAULT_INTERN_CONFIG + elif cfg is None: + cfg = InternConfig( + op_buckets_full_range=bool(op_buckets_full_range or False), + force_spawn_clip=bool(force_spawn_clip or False), + ) + if a1 is None and a2 is None: + if not isinstance(batch_or_ops, NodeBatch): + raise TypeError("intern_nodes expects a NodeBatch or (ops, a1, a2)") + batch = batch_or_ops + else: + if a1 is None or a2 is None: + raise TypeError("intern_nodes expects both a1 and a2 arrays") + batch = NodeBatch(batch_or_ops, a1, a2) + return intern_nodes_state(ledger, batch, cfg=cfg) if cfg is not None: if op_buckets_full_range is not None or force_spawn_clip is not None: raise ValueError("Pass either cfg or individual flags, not both.") @@ -1053,46 +1168,90 @@ def intern_nodes( if a1 is None or a2 is None: raise TypeError("intern_nodes expects both a1 and a2 arrays") batch = NodeBatch(batch_or_ops, a1, a2) - return intern_nodes_jit(cfg)(ledger, batch) + if ledger_index is None: + ledger_index = derive_ledger_index( + ledger, op_buckets_full_range=cfg.op_buckets_full_range + ) + return intern_nodes_jit(cfg)(ledger, ledger_index, batch) + + +def intern_nodes_with_index( + ledger: Ledger, + ledger_index: LedgerIndex, + batch_or_ops, + a1=None, + a2=None, + *, + cfg: InternConfig | None = None, +): + """Interface/Control wrapper for intern_nodes with a bound LedgerIndex.""" + if cfg is None: + cfg = DEFAULT_INTERN_CONFIG + if a1 is None and a2 is None: + if not isinstance(batch_or_ops, NodeBatch): + raise TypeError("intern_nodes_with_index expects a NodeBatch or (ops, a1, a2)") + batch = batch_or_ops + else: + if a1 is None or a2 is None: + raise TypeError("intern_nodes_with_index expects both a1 and a2 arrays") + batch = NodeBatch(batch_or_ops, a1, a2) + return intern_nodes_with_index_jit(cfg)(ledger, ledger_index, batch) + + +def intern_nodes_state( + state: LedgerState, + batch_or_ops, + a1=None, + a2=None, + *, + cfg: InternConfig | None = None, +): + """Interface/Control wrapper for intern_nodes on LedgerState.""" + if cfg is None: + cfg = DEFAULT_INTERN_CONFIG + if a1 is None and a2 is None: + if not isinstance(batch_or_ops, NodeBatch): + raise TypeError("intern_nodes_state expects a NodeBatch or (ops, a1, a2)") + batch = batch_or_ops + else: + if a1 is None or a2 is None: + raise TypeError("intern_nodes_state expects both a1 and a2 arrays") + batch = NodeBatch(batch_or_ops, a1, a2) + ids, new_state = _ledger_intern.intern_nodes_state( + state, + batch, + cfg=cfg, + ) + return ids, new_state def cycle_jit( *, - do_sort=True, - use_morton=False, - block_size=None, - l2_block_size=None, - l1_block_size=None, - do_global=False, + sort_cfg: ArenaSortConfig = DEFAULT_ARENA_SORT_CONFIG, op_rank_fn=op_rank, servo_enabled_fn=_servo_enabled, servo_update_fn=_servo_update, op_morton_fn=op_morton, - op_sort_and_swizzle_with_perm_fn=op_sort_and_swizzle_with_perm, - op_sort_and_swizzle_morton_with_perm_fn=op_sort_and_swizzle_morton_with_perm, - op_sort_and_swizzle_blocked_with_perm_fn=op_sort_and_swizzle_blocked_with_perm, - op_sort_and_swizzle_hierarchical_with_perm_fn=op_sort_and_swizzle_hierarchical_with_perm, - op_sort_and_swizzle_servo_with_perm_fn=op_sort_and_swizzle_servo_with_perm, + swizzle_with_perm_fns: SwizzleWithPermFnsBound | None = None, safe_gather_fn=_jax_safe.safe_gather_1d, op_interact_fn=_op_interact, ): """Return a jitted cycle entrypoint for fixed DI.""" + if swizzle_with_perm_fns is None: + swizzle_with_perm_fns = SwizzleWithPermFnsBound( + with_perm=op_sort_and_swizzle_with_perm, + morton_with_perm=op_sort_and_swizzle_morton_with_perm, + blocked_with_perm=op_sort_and_swizzle_blocked_with_perm, + hierarchical_with_perm=op_sort_and_swizzle_hierarchical_with_perm, + servo_with_perm=op_sort_and_swizzle_servo_with_perm, + ) return _cycle_jit_factory( - do_sort=do_sort, - use_morton=use_morton, - block_size=block_size, - l2_block_size=l2_block_size, - l1_block_size=l1_block_size, - do_global=do_global, + sort_cfg=sort_cfg, op_rank_fn=op_rank_fn, servo_enabled_fn=servo_enabled_fn, servo_update_fn=servo_update_fn, op_morton_fn=op_morton_fn, - op_sort_and_swizzle_with_perm_fn=op_sort_and_swizzle_with_perm_fn, - op_sort_and_swizzle_morton_with_perm_fn=op_sort_and_swizzle_morton_with_perm_fn, - op_sort_and_swizzle_blocked_with_perm_fn=op_sort_and_swizzle_blocked_with_perm_fn, - op_sort_and_swizzle_hierarchical_with_perm_fn=op_sort_and_swizzle_hierarchical_with_perm_fn, - op_sort_and_swizzle_servo_with_perm_fn=op_sort_and_swizzle_servo_with_perm_fn, + swizzle_with_perm_fns=swizzle_with_perm_fns, safe_gather_fn=safe_gather_fn, op_interact_fn=op_interact_fn, test_guards=_TEST_GUARDS, @@ -1100,9 +1259,9 @@ def cycle_jit( def commit_stratum_static( - ledger, - stratum, - prior_q=None, + ledger: Ledger, + stratum: Stratum, + prior_q: QMap | None = None, validate_mode: ValidateMode = ValidateMode.NONE, intern_fn: InternFn | None = None, *, @@ -1113,12 +1272,28 @@ def commit_stratum_static( """Static-policy wrapper for commit_stratum injection.""" if intern_fn is None: intern_fn = intern_nodes + ledger_index = derive_ledger_index( + ledger, op_buckets_full_range=DEFAULT_INTERN_CONFIG.op_buckets_full_range + ) + def _intern_with_index(ledger_in, batch_or_ops, a1=None, a2=None): + return call_with_optional_kwargs( + intern_fn, + {"ledger_index": ledger_index}, + ledger_in, + batch_or_ops, + a1, + a2, + ) + + setattr(_intern_with_index, "_prism_ledger_index_bound", True) return _commit_stratum_static_impl( ledger, stratum, prior_q=prior_q, validate_mode=validate_mode, - intern_fn=intern_fn, + intern_fn=_intern_with_index, + ledger_index=ledger_index, + intern_cfg=DEFAULT_INTERN_CONFIG, safe_gather_policy=safe_gather_policy, guard_cfg=guard_cfg, policy_binding=policy_binding, @@ -1126,9 +1301,9 @@ def commit_stratum_static( def commit_stratum_value( - ledger, - stratum, - prior_q=None, + ledger: Ledger, + stratum: Stratum, + prior_q: QMap | None = None, validate_mode: ValidateMode = ValidateMode.NONE, intern_fn: InternFn | None = None, *, @@ -1139,12 +1314,28 @@ def commit_stratum_value( """Policy-value wrapper for commit_stratum injection.""" if intern_fn is None: intern_fn = intern_nodes + ledger_index = derive_ledger_index( + ledger, op_buckets_full_range=DEFAULT_INTERN_CONFIG.op_buckets_full_range + ) + def _intern_with_index(ledger_in, batch_or_ops, a1=None, a2=None): + return call_with_optional_kwargs( + intern_fn, + {"ledger_index": ledger_index}, + ledger_in, + batch_or_ops, + a1, + a2, + ) + + setattr(_intern_with_index, "_prism_ledger_index_bound", True) return _commit_stratum_value_impl( ledger, stratum, prior_q=prior_q, validate_mode=validate_mode, - intern_fn=intern_fn, + intern_fn=_intern_with_index, + ledger_index=ledger_index, + intern_cfg=DEFAULT_INTERN_CONFIG, safe_gather_policy_value=safe_gather_policy_value, guard_cfg=guard_cfg, policy_binding=policy_binding, @@ -1152,9 +1343,9 @@ def commit_stratum_value( def commit_stratum( - ledger, - stratum, - prior_q=None, + ledger: Ledger, + stratum: Stratum, + prior_q: QMap | None = None, validate_mode: ValidateMode = ValidateMode.NONE, intern_fn: InternFn | None = None, *, @@ -1183,6 +1374,8 @@ def commit_stratum( policy_value=safe_gather_policy_value, context="commit_stratum", ) + if intern_fn is None: + intern_fn = intern_nodes if binding.mode == PolicyMode.VALUE: return commit_stratum_value( ledger, @@ -1218,33 +1411,14 @@ def _cycle_candidates_common( safe_gather_policy_value: PolicyValue | None, guard_cfg: GuardConfig | None, cnf2_cfg: Cnf2Config | None, - cnf2_flags: Cnf2Flags | None, - cnf2_mode: Cnf2Mode | None, - cnf2_enabled_fn, - cnf2_slot1_enabled_fn, + runtime_fns: Cnf2RuntimeFns = DEFAULT_CNF2_RUNTIME_FNS, ): """Shared wrapper for CNF-2 entrypoints with explicit policy mode.""" if not isinstance(policy_mode, PolicyMode): raise PrismPolicyModeError(mode=policy_mode, context="cycle_candidates") - if cnf2_mode is not None and not isinstance(cnf2_mode, Cnf2Mode): - raise PrismCnf2ModeError(mode=cnf2_mode) if intern_fn is None: intern_fn = intern_nodes - if cnf2_cfg is not None and cnf2_flags is not None: - cnf2_cfg = replace(cnf2_cfg, flags=cnf2_flags) - elif cnf2_cfg is None and cnf2_flags is not None: - cnf2_cfg = Cnf2Config(flags=cnf2_flags) if cnf2_cfg is not None: - if cnf2_mode is None and cnf2_cfg.cnf2_mode is not None: - cnf2_mode = cnf2_cfg.cnf2_mode - elif cnf2_mode is not None and cnf2_cfg.cnf2_mode is not None: - mode_a = coerce_cnf2_mode(cnf2_mode, context="cycle_candidates") - mode_b = coerce_cnf2_mode(cnf2_cfg.cnf2_mode, context="cycle_candidates") - if mode_a != mode_b: - raise PrismCnf2ModeConflictError( - "cycle_candidates received both cnf2_mode and cfg.cnf2_mode", - context="cycle_candidates", - ) if intern_cfg is None: intern_cfg = cnf2_cfg.intern_cfg if guard_cfg is None and cnf2_cfg.guard_cfg is not None: @@ -1253,11 +1427,8 @@ def _cycle_candidates_common( intern_fn = cnf2_cfg.intern_fn if emit_candidates_fn is None and cnf2_cfg.emit_candidates_fn is not None: emit_candidates_fn = cnf2_cfg.emit_candidates_fn - if cnf2_enabled_fn is None and cnf2_cfg.cnf2_enabled_fn is not None: - cnf2_enabled_fn = cnf2_cfg.cnf2_enabled_fn - if cnf2_slot1_enabled_fn is None and cnf2_cfg.cnf2_slot1_enabled_fn is not None: - cnf2_slot1_enabled_fn = cnf2_cfg.cnf2_slot1_enabled_fn - cnf2_flags = cnf2_cfg.flags if cnf2_flags is None else cnf2_flags + if runtime_fns is DEFAULT_CNF2_RUNTIME_FNS: + runtime_fns = cnf2_cfg.runtime_fns if policy_mode == PolicyMode.STATIC: if cnf2_cfg.policy_binding is not None: if cnf2_cfg.policy_binding.mode == PolicyMode.VALUE: @@ -1321,53 +1492,10 @@ def _cycle_candidates_common( context="cycle_candidates_value", policy_mode=PolicyMode.VALUE, ) - def _resolve_gate(flag_value, fn_value, default_fn): - if flag_value is not None: - return bool(flag_value) - if fn_value is not None: - return bool(fn_value()) - return bool(default_fn()) - - if cnf2_mode is not None: - mode = coerce_cnf2_mode(cnf2_mode, context="cycle_candidates") - if mode != Cnf2Mode.AUTO: - if cnf2_flags is not None or cnf2_enabled_fn is not None or cnf2_slot1_enabled_fn is not None: - raise PrismCnf2ModeConflictError( - "cycle_candidates received cnf2_mode alongside cnf2_flags or cnf2_*_enabled_fn", - context="cycle_candidates", - ) - enabled_value = mode in (Cnf2Mode.BASE, Cnf2Mode.SLOT1) - slot1_value = mode == Cnf2Mode.SLOT1 - cnf2_enabled_fn = lambda: enabled_value - cnf2_slot1_enabled_fn = lambda: slot1_value - cnf2_flags = None - if cnf2_flags is not None: - if cnf2_enabled_fn is not None or cnf2_slot1_enabled_fn is not None: - raise PrismPolicyBindingError( - "Pass either cnf2_flags or cnf2_*_enabled_fn, not both.", - context="cycle_candidates", - ) - enabled_value = _resolve_gate( - cnf2_flags.enabled, None, _cnf2_enabled_default - ) - slot1_value = _resolve_gate( - cnf2_flags.slot1_enabled, None, _cnf2_slot1_enabled_default - ) - cnf2_enabled_fn = lambda: enabled_value - cnf2_slot1_enabled_fn = lambda: slot1_value if emit_candidates_fn is None: emit_candidates_fn = _emit_candidates_default - if cnf2_flags is None: - enabled_value = _resolve_gate(None, cnf2_enabled_fn, _cnf2_enabled_default) - slot1_value = _resolve_gate( - None, cnf2_slot1_enabled_fn, _cnf2_slot1_enabled_default - ) - cnf2_enabled_fn = lambda: enabled_value - cnf2_slot1_enabled_fn = lambda: slot1_value if host_raise_if_bad_fn is None: host_raise_if_bad_fn = _host_raise_if_bad - if not cnf2_enabled_fn(): - raise RuntimeError("cycle_candidates disabled until m2 (set PRISM_ENABLE_CNF2=1)") if policy_mode == PolicyMode.STATIC: if safe_gather_policy is None: safe_gather_policy = DEFAULT_SAFETY_POLICY @@ -1381,8 +1509,7 @@ def _resolve_gate(flag_value, fn_value, default_fn): intern_fn=intern_fn, intern_cfg=intern_cfg, emit_candidates_fn=emit_candidates_fn, - cnf2_enabled_fn=cnf2_enabled_fn, - cnf2_slot1_enabled_fn=cnf2_slot1_enabled_fn, + runtime_fns=runtime_fns, ) else: if safe_gather_policy_value is None: @@ -1397,16 +1524,151 @@ def _resolve_gate(flag_value, fn_value, default_fn): intern_fn=intern_fn, intern_cfg=intern_cfg, emit_candidates_fn=emit_candidates_fn, - cnf2_enabled_fn=cnf2_enabled_fn, - cnf2_slot1_enabled_fn=cnf2_slot1_enabled_fn, + runtime_fns=runtime_fns, ) if not bool(jax.device_get(ledger.corrupt)): host_raise_if_bad_fn(ledger, "Ledger capacity exceeded during cycle") return ledger, frontier_ids, strata, q_map +def _cycle_candidates_common_state( + state: LedgerState, + frontier_ids, + validate_mode: ValidateMode, + *, + policy_mode: PolicyMode, + intern_fn: InternFn | None, + intern_cfg: InternConfig | None, + emit_candidates_fn: EmitCandidatesFn | None, + host_raise_if_bad_fn: HostRaiseFn | None, + safe_gather_policy: SafetyPolicy | None, + safe_gather_policy_value: PolicyValue | None, + guard_cfg: GuardConfig | None, + cnf2_cfg: Cnf2Config | None, + runtime_fns: Cnf2RuntimeFns = DEFAULT_CNF2_RUNTIME_FNS, +): + """Shared wrapper for CNF-2 entrypoints returning LedgerState.""" + if not isinstance(policy_mode, PolicyMode): + raise PrismPolicyModeError(mode=policy_mode, context="cycle_candidates_state") + if intern_fn is None: + intern_fn = intern_nodes + if cnf2_cfg is not None: + if intern_cfg is None: + intern_cfg = cnf2_cfg.intern_cfg + if guard_cfg is None and cnf2_cfg.guard_cfg is not None: + guard_cfg = cnf2_cfg.guard_cfg + if intern_fn is None and cnf2_cfg.intern_fn is not None: + intern_fn = cnf2_cfg.intern_fn + if emit_candidates_fn is None and cnf2_cfg.emit_candidates_fn is not None: + emit_candidates_fn = cnf2_cfg.emit_candidates_fn + if runtime_fns is DEFAULT_CNF2_RUNTIME_FNS: + runtime_fns = cnf2_cfg.runtime_fns + if policy_mode == PolicyMode.STATIC: + if cnf2_cfg.policy_binding is not None: + if cnf2_cfg.policy_binding.mode == PolicyMode.VALUE: + raise PrismPolicyBindingError( + "cycle_candidates_static_state received cfg.policy_binding value-mode; " + "use cycle_candidates_value_state", + context="cycle_candidates_static_state", + policy_mode=PolicyMode.STATIC, + ) + if safe_gather_policy is None: + safe_gather_policy = require_static_policy( + cnf2_cfg.policy_binding, + context="cycle_candidates_static_state", + ) + if cnf2_cfg.safe_gather_policy_value is not None: + raise PrismPolicyBindingError( + "cycle_candidates_static_state received cfg.safe_gather_policy_value; " + "use cycle_candidates_value_state", + context="cycle_candidates_static_state", + policy_mode=PolicyMode.STATIC, + ) + if safe_gather_policy is None and cnf2_cfg.safe_gather_policy is not None: + safe_gather_policy = cnf2_cfg.safe_gather_policy + else: + if cnf2_cfg.policy_binding is not None: + if cnf2_cfg.policy_binding.mode == PolicyMode.STATIC: + raise PrismPolicyBindingError( + "cycle_candidates_value_state received cfg.policy_binding static-mode; " + "use cycle_candidates_static_state", + context="cycle_candidates_value_state", + policy_mode=PolicyMode.VALUE, + ) + if safe_gather_policy_value is None: + safe_gather_policy_value = require_value_policy( + cnf2_cfg.policy_binding, + context="cycle_candidates_value_state", + ) + if cnf2_cfg.safe_gather_policy is not None: + raise PrismPolicyBindingError( + "cycle_candidates_value_state received cfg.safe_gather_policy; " + "use cycle_candidates_static_state", + context="cycle_candidates_value_state", + policy_mode=PolicyMode.VALUE, + ) + if ( + safe_gather_policy_value is None + and cnf2_cfg.safe_gather_policy_value is not None + ): + safe_gather_policy_value = cnf2_cfg.safe_gather_policy_value + if policy_mode == PolicyMode.STATIC: + if safe_gather_policy_value is not None: + raise PrismPolicyBindingError( + "cycle_candidates_static_state received safe_gather_policy_value; " + "use cycle_candidates_value_state", + context="cycle_candidates_static_state", + policy_mode=PolicyMode.STATIC, + ) + else: + if safe_gather_policy is not None: + raise PrismPolicyBindingError( + "cycle_candidates_value_state received safe_gather_policy; " + "use cycle_candidates_static_state", + context="cycle_candidates_value_state", + policy_mode=PolicyMode.VALUE, + ) + if emit_candidates_fn is None: + emit_candidates_fn = _emit_candidates_default + if host_raise_if_bad_fn is None: + host_raise_if_bad_fn = _host_raise_if_bad + if policy_mode == PolicyMode.STATIC: + if safe_gather_policy is None: + safe_gather_policy = DEFAULT_SAFETY_POLICY + state, frontier_ids, strata, q_map = _cycle_candidates_static_state( + state, + frontier_ids, + validate_mode=validate_mode, + cfg=cnf2_cfg, + safe_gather_policy=safe_gather_policy, + guard_cfg=guard_cfg, + intern_fn=intern_fn, + intern_cfg=intern_cfg, + emit_candidates_fn=emit_candidates_fn, + runtime_fns=runtime_fns, + ) + else: + if safe_gather_policy_value is None: + safe_gather_policy_value = POLICY_VALUE_DEFAULT + state, frontier_ids, strata, q_map = _cycle_candidates_value_state( + state, + frontier_ids, + validate_mode=validate_mode, + cfg=cnf2_cfg, + safe_gather_policy_value=safe_gather_policy_value, + guard_cfg=guard_cfg, + intern_fn=intern_fn, + intern_cfg=intern_cfg, + emit_candidates_fn=emit_candidates_fn, + runtime_fns=runtime_fns, + ) + if not bool(jax.device_get(state.ledger.corrupt)): + host_raise_if_bad_fn(state.ledger, "Ledger capacity exceeded during cycle") + return state, frontier_ids, strata, q_map + + def cycle_candidates_static( - ledger, + ledger: Ledger | LedgerState, frontier_ids, validate_mode: ValidateMode = ValidateMode.NONE, *, @@ -1417,12 +1679,27 @@ def cycle_candidates_static( safe_gather_policy: SafetyPolicy | None = None, guard_cfg: GuardConfig | None = None, cnf2_cfg: Cnf2Config | None = None, - cnf2_flags: Cnf2Flags | None = None, - cnf2_mode: Cnf2Mode | None = None, - cnf2_enabled_fn=None, - cnf2_slot1_enabled_fn=None, + runtime_fns: Cnf2RuntimeFns = DEFAULT_CNF2_RUNTIME_FNS, ): - """Interface/Control wrapper for CNF-2 evaluation (static policy).""" + """Interface/Control wrapper for CNF-2 evaluation (static policy). + + If ``ledger`` is a LedgerState, returns a LedgerState to preserve the index. + Otherwise returns a Ledger. + """ + if isinstance(ledger, LedgerState): + return cycle_candidates_static_state( + ledger, + frontier_ids, + validate_mode=validate_mode, + intern_fn=intern_fn, + intern_cfg=intern_cfg, + emit_candidates_fn=emit_candidates_fn, + host_raise_if_bad_fn=host_raise_if_bad_fn, + safe_gather_policy=safe_gather_policy, + guard_cfg=guard_cfg, + cnf2_cfg=cnf2_cfg, + runtime_fns=runtime_fns, + ) return _cycle_candidates_common( ledger, frontier_ids, @@ -1436,15 +1713,12 @@ def cycle_candidates_static( safe_gather_policy_value=None, guard_cfg=guard_cfg, cnf2_cfg=cnf2_cfg, - cnf2_flags=cnf2_flags, - cnf2_mode=cnf2_mode, - cnf2_enabled_fn=cnf2_enabled_fn, - cnf2_slot1_enabled_fn=cnf2_slot1_enabled_fn, + runtime_fns=runtime_fns, ) def cycle_candidates_value( - ledger, + ledger: Ledger | LedgerState, frontier_ids, validate_mode: ValidateMode = ValidateMode.NONE, *, @@ -1455,12 +1729,27 @@ def cycle_candidates_value( safe_gather_policy_value: PolicyValue | None = None, guard_cfg: GuardConfig | None = None, cnf2_cfg: Cnf2Config | None = None, - cnf2_flags: Cnf2Flags | None = None, - cnf2_mode: Cnf2Mode | None = None, - cnf2_enabled_fn=None, - cnf2_slot1_enabled_fn=None, + runtime_fns: Cnf2RuntimeFns = DEFAULT_CNF2_RUNTIME_FNS, ): - """Interface/Control wrapper for CNF-2 evaluation (policy as JAX value).""" + """Interface/Control wrapper for CNF-2 evaluation (policy as JAX value). + + If ``ledger`` is a LedgerState, returns a LedgerState to preserve the index. + Otherwise returns a Ledger. + """ + if isinstance(ledger, LedgerState): + return cycle_candidates_value_state( + ledger, + frontier_ids, + validate_mode=validate_mode, + intern_fn=intern_fn, + intern_cfg=intern_cfg, + emit_candidates_fn=emit_candidates_fn, + host_raise_if_bad_fn=host_raise_if_bad_fn, + safe_gather_policy_value=safe_gather_policy_value, + guard_cfg=guard_cfg, + cnf2_cfg=cnf2_cfg, + runtime_fns=runtime_fns, + ) return _cycle_candidates_common( ledger, frontier_ids, @@ -1474,15 +1763,130 @@ def cycle_candidates_value( safe_gather_policy_value=safe_gather_policy_value, guard_cfg=guard_cfg, cnf2_cfg=cnf2_cfg, - cnf2_flags=cnf2_flags, - cnf2_mode=cnf2_mode, - cnf2_enabled_fn=cnf2_enabled_fn, - cnf2_slot1_enabled_fn=cnf2_slot1_enabled_fn, + runtime_fns=runtime_fns, + ) + + +def cycle_candidates_static_state( + state: LedgerState, + frontier_ids, + validate_mode: ValidateMode = ValidateMode.NONE, + *, + intern_fn: InternFn | None = None, + intern_cfg: InternConfig | None = None, + emit_candidates_fn: EmitCandidatesFn | None = None, + host_raise_if_bad_fn: HostRaiseFn | None = None, + safe_gather_policy: SafetyPolicy | None = None, + guard_cfg: GuardConfig | None = None, + cnf2_cfg: Cnf2Config | None = None, + runtime_fns: Cnf2RuntimeFns = DEFAULT_CNF2_RUNTIME_FNS, +): + """Interface/Control wrapper for CNF-2 evaluation (static policy, state).""" + return _cycle_candidates_common_state( + state, + frontier_ids, + validate_mode, + policy_mode=PolicyMode.STATIC, + intern_fn=intern_fn, + intern_cfg=intern_cfg, + emit_candidates_fn=emit_candidates_fn, + host_raise_if_bad_fn=host_raise_if_bad_fn, + safe_gather_policy=safe_gather_policy, + safe_gather_policy_value=None, + guard_cfg=guard_cfg, + cnf2_cfg=cnf2_cfg, + runtime_fns=runtime_fns, + ) + + +def cycle_candidates_value_state( + state: LedgerState, + frontier_ids, + validate_mode: ValidateMode = ValidateMode.NONE, + *, + intern_fn: InternFn | None = None, + intern_cfg: InternConfig | None = None, + emit_candidates_fn: EmitCandidatesFn | None = None, + host_raise_if_bad_fn: HostRaiseFn | None = None, + safe_gather_policy_value: PolicyValue | None = None, + guard_cfg: GuardConfig | None = None, + cnf2_cfg: Cnf2Config | None = None, + runtime_fns: Cnf2RuntimeFns = DEFAULT_CNF2_RUNTIME_FNS, +): + """Interface/Control wrapper for CNF-2 evaluation (policy value, state).""" + return _cycle_candidates_common_state( + state, + frontier_ids, + validate_mode, + policy_mode=PolicyMode.VALUE, + intern_fn=intern_fn, + intern_cfg=intern_cfg, + emit_candidates_fn=emit_candidates_fn, + host_raise_if_bad_fn=host_raise_if_bad_fn, + safe_gather_policy=None, + safe_gather_policy_value=safe_gather_policy_value, + guard_cfg=guard_cfg, + cnf2_cfg=cnf2_cfg, + runtime_fns=runtime_fns, + ) + + +def cycle_candidates_state( + state: LedgerState, + frontier_ids, + validate_mode: ValidateMode = ValidateMode.NONE, + *, + intern_fn: InternFn | None = None, + intern_cfg: InternConfig | None = None, + emit_candidates_fn: EmitCandidatesFn | None = None, + host_raise_if_bad_fn: HostRaiseFn | None = None, + safe_gather_policy: SafetyPolicy | None = None, + safe_gather_policy_value: PolicyValue | None = None, + guard_cfg: GuardConfig | None = None, + cnf2_cfg: Cnf2Config | None = None, + runtime_fns: Cnf2RuntimeFns = DEFAULT_CNF2_RUNTIME_FNS, +): + """Interface/Control wrapper for CNF-2 evaluation on LedgerState.""" + binding = resolve_policy_binding( + policy=safe_gather_policy, + policy_value=safe_gather_policy_value, + context="cycle_candidates_state", + ) + if binding.mode == PolicyMode.VALUE: + return cycle_candidates_value_state( + state, + frontier_ids, + validate_mode=validate_mode, + intern_fn=intern_fn, + intern_cfg=intern_cfg, + emit_candidates_fn=emit_candidates_fn, + host_raise_if_bad_fn=host_raise_if_bad_fn, + safe_gather_policy_value=require_value_policy( + binding, context="cycle_candidates_state" + ), + guard_cfg=guard_cfg, + cnf2_cfg=cnf2_cfg, + runtime_fns=runtime_fns, + ) + return cycle_candidates_static_state( + state, + frontier_ids, + validate_mode=validate_mode, + intern_fn=intern_fn, + intern_cfg=intern_cfg, + emit_candidates_fn=emit_candidates_fn, + host_raise_if_bad_fn=host_raise_if_bad_fn, + safe_gather_policy=require_static_policy( + binding, context="cycle_candidates_state" + ), + guard_cfg=guard_cfg, + cnf2_cfg=cnf2_cfg, + runtime_fns=runtime_fns, ) def cycle_candidates( - ledger, + ledger: Ledger | LedgerState, frontier_ids, validate_mode: ValidateMode = ValidateMode.NONE, *, @@ -1494,16 +1898,31 @@ def cycle_candidates( safe_gather_policy_value: PolicyValue | None = None, guard_cfg: GuardConfig | None = None, cnf2_cfg: Cnf2Config | None = None, - cnf2_flags: Cnf2Flags | None = None, - cnf2_mode: Cnf2Mode | None = None, - cnf2_enabled_fn=None, - cnf2_slot1_enabled_fn=None, + runtime_fns: Cnf2RuntimeFns = DEFAULT_CNF2_RUNTIME_FNS, ): """Interface/Control wrapper for CNF-2 evaluation with DI hooks. Axis: Interface/Control. Commutes with q. Erased by q. Test: tests/test_candidate_cycle.py + + If ``ledger`` is a LedgerState, returns a LedgerState to preserve the index. + Otherwise returns a Ledger. """ + if isinstance(ledger, LedgerState): + return cycle_candidates_state( + ledger, + frontier_ids, + validate_mode=validate_mode, + intern_fn=intern_fn, + intern_cfg=intern_cfg, + emit_candidates_fn=emit_candidates_fn, + host_raise_if_bad_fn=host_raise_if_bad_fn, + safe_gather_policy=safe_gather_policy, + safe_gather_policy_value=safe_gather_policy_value, + guard_cfg=guard_cfg, + cnf2_cfg=cnf2_cfg, + runtime_fns=runtime_fns, + ) binding = resolve_policy_binding( policy=safe_gather_policy, policy_value=safe_gather_policy_value, @@ -1518,15 +1937,12 @@ def cycle_candidates( intern_cfg=intern_cfg, emit_candidates_fn=emit_candidates_fn, host_raise_if_bad_fn=host_raise_if_bad_fn, - safe_gather_policy_value=require_value_policy( - binding, context="cycle_candidates" - ), + safe_gather_policy_value=require_value_policy( + binding, context="cycle_candidates" + ), guard_cfg=guard_cfg, cnf2_cfg=cnf2_cfg, - cnf2_flags=cnf2_flags, - cnf2_mode=cnf2_mode, - cnf2_enabled_fn=cnf2_enabled_fn, - cnf2_slot1_enabled_fn=cnf2_slot1_enabled_fn, + runtime_fns=runtime_fns, ) return cycle_candidates_static( ledger, @@ -1541,15 +1957,12 @@ def cycle_candidates( ), guard_cfg=guard_cfg, cnf2_cfg=cnf2_cfg, - cnf2_flags=cnf2_flags, - cnf2_mode=cnf2_mode, - cnf2_enabled_fn=cnf2_enabled_fn, - cnf2_slot1_enabled_fn=cnf2_slot1_enabled_fn, + runtime_fns=runtime_fns, ) def cycle_candidates_bound( - ledger, + ledger: Ledger | LedgerState, frontier_ids, cfg: Cnf2BoundConfig, *, @@ -1559,33 +1972,95 @@ def cycle_candidates_bound( emit_candidates_fn: EmitCandidatesFn | None = None, host_raise_if_bad_fn: HostRaiseFn | None = None, guard_cfg: GuardConfig | None = None, - cnf2_flags: Cnf2Flags | None = None, - cnf2_mode: Cnf2Mode | None = None, - cnf2_enabled_fn=None, - cnf2_slot1_enabled_fn=None, + runtime_fns: Cnf2RuntimeFns = DEFAULT_CNF2_RUNTIME_FNS, ): - """Interface/Control wrapper for CNF-2 evaluation with required PolicyBinding.""" - policy_binding = cfg.policy_binding - cnf2_cfg = cfg.as_cfg() - return _cycle_candidates_common( + """Interface/Control wrapper for CNF-2 evaluation with required PolicyBinding. + + If ``ledger`` is a LedgerState, returns a LedgerState to preserve the index. + Otherwise returns a Ledger. + """ + if isinstance(ledger, LedgerState): + return cycle_candidates_bound_state( + ledger, + frontier_ids, + cfg, + validate_mode=validate_mode, + intern_fn=intern_fn, + intern_cfg=intern_cfg, + emit_candidates_fn=emit_candidates_fn, + host_raise_if_bad_fn=host_raise_if_bad_fn, + guard_cfg=guard_cfg, + runtime_fns=runtime_fns, + ) + if host_raise_if_bad_fn is None: + host_raise_if_bad_fn = _host_raise_if_bad + base_cfg = cfg.as_cfg() + if base_cfg.policy_binding is not None or base_cfg.safe_gather_policy is not None or base_cfg.safe_gather_policy_value is not None: + base_cfg = replace( + base_cfg, + policy_binding=None, + safe_gather_policy=None, + safe_gather_policy_value=None, + ) + cfg = cnf2_config_bound(cfg.policy_binding, cfg=base_cfg) + bundle = _EmitInternFns(emit_candidates_fn=emit_candidates_fn, intern_fn=intern_fn) + ledger, frontier_ids, strata, q_map = _cycle_candidates_bound( ledger, frontier_ids, - validate_mode, - policy_mode=policy_binding.mode, - intern_fn=intern_fn, + cfg, + validate_mode=validate_mode, + guard_cfg=guard_cfg, + intern_fn=bundle.intern_fn if bundle.intern_fn is not None else _ledger_intern.intern_nodes, intern_cfg=intern_cfg, - emit_candidates_fn=emit_candidates_fn, - host_raise_if_bad_fn=host_raise_if_bad_fn, - safe_gather_policy=require_static_policy( - policy_binding, context="cycle_candidates_bound" - ) if policy_binding.mode == PolicyMode.STATIC else None, - safe_gather_policy_value=require_value_policy( - policy_binding, context="cycle_candidates_bound" - ) if policy_binding.mode == PolicyMode.VALUE else None, + emit_candidates_fn=bundle.emit_candidates_fn if bundle.emit_candidates_fn is not None else _emit_candidates_default, + runtime_fns=runtime_fns, + ) + if not bool(jax.device_get(ledger.corrupt)): + host_raise_if_bad_fn(ledger, "Ledger capacity exceeded during cycle") + return ledger, frontier_ids, strata, q_map + + +def cycle_candidates_bound_state( + state: LedgerState, + frontier_ids, + cfg: Cnf2BoundConfig, + *, + validate_mode: ValidateMode = ValidateMode.NONE, + intern_fn: InternFn | None = None, + intern_cfg: InternConfig | None = None, + emit_candidates_fn: EmitCandidatesFn | None = None, + host_raise_if_bad_fn: HostRaiseFn | None = None, + guard_cfg: GuardConfig | None = None, + runtime_fns: Cnf2RuntimeFns = DEFAULT_CNF2_RUNTIME_FNS, +): + """Interface/Control wrapper for CNF-2 evaluation (PolicyBinding, state).""" + if host_raise_if_bad_fn is None: + host_raise_if_bad_fn = _host_raise_if_bad + base_cfg = cfg.as_cfg() + if ( + base_cfg.policy_binding is not None + or base_cfg.safe_gather_policy is not None + or base_cfg.safe_gather_policy_value is not None + ): + base_cfg = replace( + base_cfg, + policy_binding=None, + safe_gather_policy=None, + safe_gather_policy_value=None, + ) + cfg = cnf2_config_bound(cfg.policy_binding, cfg=base_cfg) + bundle = _EmitInternFns(emit_candidates_fn=emit_candidates_fn, intern_fn=intern_fn) + state, frontier_ids, strata, q_map = _cycle_candidates_bound_state( + state, + frontier_ids, + cfg, + validate_mode=validate_mode, guard_cfg=guard_cfg, - cnf2_cfg=cnf2_cfg, - cnf2_flags=cnf2_flags, - cnf2_mode=cnf2_mode, - cnf2_enabled_fn=cnf2_enabled_fn, - cnf2_slot1_enabled_fn=cnf2_slot1_enabled_fn, + intern_fn=bundle.intern_fn if bundle.intern_fn is not None else _ledger_intern.intern_nodes, + intern_cfg=intern_cfg, + emit_candidates_fn=bundle.emit_candidates_fn if bundle.emit_candidates_fn is not None else _emit_candidates_default, + runtime_fns=runtime_fns, ) + if not bool(jax.device_get(state.ledger.corrupt)): + host_raise_if_bad_fn(state.ledger, "Ledger capacity exceeded during cycle") + return state, frontier_ids, strata, q_map diff --git a/src/prism_vm_core/gating.py b/src/prism_vm_core/gating.py index 08035af..d0d7312 100644 --- a/src/prism_vm_core/gating.py +++ b/src/prism_vm_core/gating.py @@ -17,9 +17,7 @@ def _parse_milestone_value(value): return None -def _read_pytest_milestone(): - if not _TEST_GUARDS: - return None +def _read_pytest_milestone_path(): repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir)) path = os.path.join(repo_root, ".pytest-milestone") try: @@ -39,48 +37,40 @@ def _read_pytest_milestone(): return None -def _cnf2_enabled(): - # CNF-2 pipeline is staged for m2+; guard uses env/milestone in tests. - # See IMPLEMENTATION_PLAN.md (m2 CNF-2 enablement). - value = os.environ.get("PRISM_ENABLE_CNF2", "").strip().lower() - if value in ("1", "true", "yes", "on"): - return True - milestone = _parse_milestone_value(os.environ.get("PRISM_MILESTONE", "")) - if milestone is None: - milestone = _read_pytest_milestone() - return milestone is not None and milestone >= 2 +def _read_pytest_milestone(): + if not _TEST_GUARDS: + return None + return _read_pytest_milestone_path() -def _cnf2_slot1_enabled(): - # Slot1 continuation is staged for m2+; hyperstrata visibility is enforced - # under test guards (m3 normative) to justify continuation enablement. - # See IMPLEMENTATION_PLAN.md (CNF-2 continuation slot). - value = os.environ.get("PRISM_ENABLE_CNF2_SLOT1", "").strip().lower() - if value in ("1", "true", "yes", "on"): - return True - milestone = _parse_milestone_value(os.environ.get("PRISM_MILESTONE", "")) - if milestone is None: - milestone = _read_pytest_milestone() - return milestone is not None and milestone >= 2 +def _read_pytest_milestone_unprotected(): + return _read_pytest_milestone_path() + + +def _normalize_milestone(value): + milestone = _parse_milestone_value(value) + if milestone != 1: + return milestone + # m1-only mode is deprecated; treat m1 as baseline coverage when possible. + baseline = _read_pytest_milestone_unprotected() + return baseline or 2 def _default_bsp_mode() -> BspMode: # CNF-2 becomes the default at m2; intrinsic remains the oracle path. # See IMPLEMENTATION_PLAN.md (m1/m2 engine staging). - return BspMode.CNF2 if _cnf2_enabled() else BspMode.INTRINSIC + return BspMode.CNF2 -def _normalize_bsp_mode(bsp_mode): - return coerce_bsp_mode( - bsp_mode, default_fn=_default_bsp_mode, context="bsp_mode" - ) +def _normalize_bsp_mode(bsp_mode: BspMode | str | None): + return coerce_bsp_mode(bsp_mode, default_fn=_default_bsp_mode) def _servo_enabled(): value = os.environ.get("PRISM_ENABLE_SERVO", "").strip().lower() if value in ("1", "true", "yes", "on"): return True - milestone = _parse_milestone_value(os.environ.get("PRISM_MILESTONE", "")) + milestone = _normalize_milestone(os.environ.get("PRISM_MILESTONE", "")) if milestone is None: milestone = _read_pytest_milestone() return milestone is not None and milestone >= 5 @@ -103,8 +93,8 @@ def _gpu_metrics_device_index(): __all__ = [ "_parse_milestone_value", "_read_pytest_milestone", - "_cnf2_enabled", - "_cnf2_slot1_enabled", + "_read_pytest_milestone_unprotected", + "_normalize_milestone", "_default_bsp_mode", "_normalize_bsp_mode", "_servo_enabled", diff --git a/src/prism_vm_core/guards.py b/src/prism_vm_core/guards.py index f1ebfa2..3f45c55 100644 --- a/src/prism_vm_core/guards.py +++ b/src/prism_vm_core/guards.py @@ -1,9 +1,13 @@ from typing import Callable, Optional +from dataclasses import dataclass import jax import jax.numpy as jnp from prism_core import jax_safe as _jax_safe + +# dataflow-bundle: arg1, arg2 + from prism_core.guards import ( GuardConfig, guard_gather_index_cfg as _guard_gather_index_cfg, @@ -23,8 +27,22 @@ resolve_safe_gather_ok_value_fn as _resolve_safe_gather_ok_value_fn, resolve_safe_index_value_fn as _resolve_safe_index_value_fn, ) +from prism_core.protocols import ( + SafeGatherFn, + SafeGatherOkFn, + SafeGatherOkValueFn, + SafeGatherValueFn, + SafeIndexFn, + SafeIndexValueFn, +) +from prism_core.safety import SafetyPolicy from prism_vm_core.ontology import OP_NULL, OP_ZERO +@dataclass(frozen=True) +class _BinaryArgs: + arg1: object + arg2: object + _TEST_GUARDS = _jax_safe.TEST_GUARDS _HAS_DEBUG_CALLBACK = _jax_safe.HAS_DEBUG_CALLBACK @@ -77,7 +95,7 @@ def safe_gather_1d_cfg( label="safe_gather_1d", *, guard=None, - policy=None, + policy: SafetyPolicy | None = None, return_ok: bool = False, cfg: GuardConfig = DEFAULT_GUARD_CONFIG, ): @@ -92,7 +110,7 @@ def safe_gather_1d_ok_cfg( label="safe_gather_1d_ok", *, guard=None, - policy=None, + policy: SafetyPolicy | None = None, cfg: GuardConfig = DEFAULT_GUARD_CONFIG, ): return _safe_gather_1d_ok_cfg( @@ -106,22 +124,27 @@ def safe_index_1d_cfg( label="safe_index_1d", *, guard=None, - policy=None, + policy: SafetyPolicy | None = None, cfg: GuardConfig = DEFAULT_GUARD_CONFIG, ): return _safe_index_1d_cfg( idx, size, label, guard=guard, policy=policy, cfg=cfg ) -def make_safe_gather_fn(*, cfg: GuardConfig = DEFAULT_GUARD_CONFIG, policy=None, safe_gather_fn=None): +def make_safe_gather_fn( + *, + cfg: GuardConfig = DEFAULT_GUARD_CONFIG, + policy: SafetyPolicy | None = None, + safe_gather_fn: SafeGatherFn | None = None, +): return _make_safe_gather_fn(cfg=cfg, policy=policy, safe_gather_fn=safe_gather_fn) def make_safe_gather_ok_fn( *, cfg: GuardConfig = DEFAULT_GUARD_CONFIG, - policy=None, - safe_gather_ok_fn=None, + policy: SafetyPolicy | None = None, + safe_gather_ok_fn: SafeGatherOkFn | None = None, ): return _make_safe_gather_ok_fn( cfg=cfg, policy=policy, safe_gather_ok_fn=safe_gather_ok_fn @@ -131,8 +154,8 @@ def make_safe_gather_ok_fn( def make_safe_index_fn( *, cfg: GuardConfig = DEFAULT_GUARD_CONFIG, - policy=None, - safe_index_fn=None, + policy: SafetyPolicy | None = None, + safe_index_fn: SafeIndexFn | None = None, ): return _make_safe_index_fn( cfg=cfg, policy=policy, safe_index_fn=safe_index_fn @@ -142,7 +165,7 @@ def make_safe_index_fn( def make_safe_gather_value_fn( *, cfg: GuardConfig = DEFAULT_GUARD_CONFIG, - safe_gather_value_fn=None, + safe_gather_value_fn: SafeGatherValueFn | None = None, ): return _make_safe_gather_value_fn( cfg=cfg, safe_gather_value_fn=safe_gather_value_fn @@ -152,7 +175,7 @@ def make_safe_gather_value_fn( def make_safe_gather_ok_value_fn( *, cfg: GuardConfig = DEFAULT_GUARD_CONFIG, - safe_gather_ok_value_fn=None, + safe_gather_ok_value_fn: SafeGatherOkValueFn | None = None, ): return _make_safe_gather_ok_value_fn( cfg=cfg, safe_gather_ok_value_fn=safe_gather_ok_value_fn @@ -162,7 +185,7 @@ def make_safe_gather_ok_value_fn( def make_safe_index_value_fn( *, cfg: GuardConfig = DEFAULT_GUARD_CONFIG, - safe_index_value_fn=None, + safe_index_value_fn: SafeIndexValueFn | None = None, ): return _make_safe_index_value_fn( cfg=cfg, safe_index_value_fn=safe_index_value_fn @@ -171,8 +194,8 @@ def make_safe_index_value_fn( def resolve_safe_gather_fn( *, - safe_gather_fn=None, - policy=None, + safe_gather_fn: SafeGatherFn | None = None, + policy: SafetyPolicy | None = None, guard_cfg: GuardConfig | None = None, ): return _resolve_safe_gather_fn( @@ -182,8 +205,8 @@ def resolve_safe_gather_fn( def resolve_safe_gather_ok_fn( *, - safe_gather_ok_fn=None, - policy=None, + safe_gather_ok_fn: SafeGatherOkFn | None = None, + policy: SafetyPolicy | None = None, guard_cfg: GuardConfig | None = None, ): return _resolve_safe_gather_ok_fn( @@ -195,8 +218,8 @@ def resolve_safe_gather_ok_fn( def resolve_safe_index_fn( *, - safe_index_fn=None, - policy=None, + safe_index_fn: SafeIndexFn | None = None, + policy: SafetyPolicy | None = None, guard_cfg: GuardConfig | None = None, ): return _resolve_safe_index_fn( @@ -206,7 +229,7 @@ def resolve_safe_index_fn( def resolve_safe_gather_value_fn( *, - safe_gather_value_fn=None, + safe_gather_value_fn: SafeGatherValueFn | None = None, guard_cfg: GuardConfig | None = None, ): return _resolve_safe_gather_value_fn( @@ -216,7 +239,7 @@ def resolve_safe_gather_value_fn( def resolve_safe_gather_ok_value_fn( *, - safe_gather_ok_value_fn=None, + safe_gather_ok_value_fn: SafeGatherOkValueFn | None = None, guard_cfg: GuardConfig | None = None, ): return _resolve_safe_gather_ok_value_fn( @@ -226,7 +249,7 @@ def resolve_safe_gather_ok_value_fn( def resolve_safe_index_value_fn( *, - safe_index_value_fn=None, + safe_index_value_fn: SafeIndexValueFn | None = None, guard_cfg: GuardConfig | None = None, ): return _resolve_safe_index_value_fn( @@ -247,6 +270,13 @@ def _expect_token(tokens, expected): return token +def _expect_rparen(tokens): + token = _pop_token(tokens) + if token != ")": + raise ValueError(f"Expected ')', got {token!r}") + return token + + def _guard_slot0_perm(perm, inv_perm, label): if not _guards_enabled(): return @@ -263,7 +293,13 @@ def _raise(ok_val, p0_val, i0_val): jax.debug.callback(_raise, ok, p0, i0) -def guard_slot0_perm_cfg(perm, inv_perm, label, *, cfg: GuardConfig = DEFAULT_GUARD_CONFIG): +def guard_slot0_perm_cfg( + perm, + inv_perm, + label="swizzle.perm", + *, + cfg: GuardConfig = DEFAULT_GUARD_CONFIG, +): fn = cfg.guard_slot0_perm_fn or _guard_slot0_perm return fn(perm, inv_perm, label) @@ -285,7 +321,14 @@ def _raise(ok_val, op0_val, a10_val, a20_val): jax.debug.callback(_raise, ok, op0, a10, a20) -def guard_null_row_cfg(opcode, arg1, arg2, label, *, cfg: GuardConfig = DEFAULT_GUARD_CONFIG): +def guard_null_row_cfg( + opcode, + arg1, + arg2, + label="swizzle.row0", + *, + cfg: GuardConfig = DEFAULT_GUARD_CONFIG, +): fn = cfg.guard_null_row_fn or _guard_null_row return fn(opcode, arg1, arg2, label) @@ -317,7 +360,8 @@ def _guard_zero_args(mask, arg1, arg2, label): return if mask.size == 0: return - bad = jnp.any(mask & ((arg1 != 0) | (arg2 != 0))) + bundle = _BinaryArgs(arg1=arg1, arg2=arg2) + bad = jnp.any(mask & ((bundle.arg1 != 0) | (bundle.arg2 != 0))) def _raise(bad_val): if bad_val: @@ -351,7 +395,13 @@ def _raise(bad_val, count_val): def guard_swizzle_args_cfg( - arg1, arg2, live, count, label, *, cfg: GuardConfig = DEFAULT_GUARD_CONFIG + arg1, + arg2, + live, + count, + label="swizzle.args", + *, + cfg: GuardConfig = DEFAULT_GUARD_CONFIG, ): fn = cfg.guard_swizzle_args_fn or _guard_swizzle_args return fn(arg1, arg2, live, count, label) @@ -364,6 +414,7 @@ def guard_swizzle_args_cfg( "_guard_max", "_pop_token", "_expect_token", + "_expect_rparen", "_guard_slot0_perm", "_guard_null_row", "_guard_zero_row", diff --git a/src/prism_vm_core/hashes.py b/src/prism_vm_core/hashes.py index 9992e3c..d00225a 100644 --- a/src/prism_vm_core/hashes.py +++ b/src/prism_vm_core/hashes.py @@ -1,3 +1,4 @@ +from dataclasses import dataclass import jax import jax.numpy as jnp @@ -5,13 +6,23 @@ from prism_vm_core.domains import _host_int_value from prism_vm_core.ontology import OP_ADD, OP_MUL +# dataflow-bundle: a1, a2, ops + _TEST_GUARDS = _jax_safe.TEST_GUARDS +@dataclass(frozen=True) +class _RootStructArgs: + ops: object + a1: object + a2: object + + def _root_struct_hash_host(ops, a1, a2, root_i, count, limit): if root_i <= 0 or root_i >= count: return 0 + bundle = _RootStructArgs(ops=ops, a1=a1, a2=a2) cache = {} visiting = set() @@ -25,9 +36,9 @@ def _hash(idx): if len(cache) >= int(limit): return 0 visiting.add(idx) - op = int(ops[idx]) - h1 = _hash(int(a1[idx])) - h2 = _hash(int(a2[idx])) + op = int(bundle.ops[idx]) + h1 = _hash(int(bundle.a1[idx])) + h2 = _hash(int(bundle.a2[idx])) if op in (OP_ADD, OP_MUL) and h2 < h1: h1, h2 = h2, h1 h = (op * 1315423911) ^ (h1 + 0x9E3779B9) ^ ((h2 << 1) & 0xFFFFFFFF) diff --git a/src/prism_vm_core/jit_entrypoints.py b/src/prism_vm_core/jit_entrypoints.py index 9da0bed..a0cf94a 100644 --- a/src/prism_vm_core/jit_entrypoints.py +++ b/src/prism_vm_core/jit_entrypoints.py @@ -2,20 +2,20 @@ """JIT entrypoint factories with explicit DI and static args.""" -from dataclasses import replace +# dataflow-bundle: _arena, _root +# dataflow-bundle: _arena, _tile_size +# dataflow-bundle: _args, _kwargs + +from dataclasses import dataclass, replace from functools import partial -from typing import Optional +from typing import Callable, Optional import jax import jax.numpy as jnp from prism_core import jax_safe as _jax_safe -from prism_core.errors import ( - PrismPolicyBindingError, - PrismCnf2ModeError, - PrismCnf2ModeConflictError, -) -from prism_core.di import bind_optional_kwargs, cached_jit, resolve +from prism_core.errors import PrismPolicyBindingError +from prism_core.di import bind_optional_kwargs, cached_jit, resolve, call_with_optional_kwargs from prism_core.guards import ( GuardConfig, resolve_safe_gather_fn, @@ -31,27 +31,51 @@ require_static_policy, require_value_policy, ) -from prism_core.modes import ValidateMode, Cnf2Mode, coerce_cnf2_mode +from prism_core.modes import ValidateMode from prism_ledger import intern as _ledger_intern from prism_ledger.config import InternConfig, DEFAULT_INTERN_CONFIG +from prism_ledger.index import LedgerIndex, LedgerState, derive_ledger_state from prism_bsp.config import ( Cnf2Config, - Cnf2Flags, + Cnf2RuntimeFns, + DEFAULT_CNF2_RUNTIME_FNS, ArenaInteractConfig, ArenaCycleConfig, + ArenaSortConfig, IntrinsicConfig, DEFAULT_ARENA_INTERACT_CONFIG, DEFAULT_ARENA_CYCLE_CONFIG, + DEFAULT_ARENA_SORT_CONFIG, DEFAULT_INTRINSIC_CONFIG, + SwizzleWithPermFnsBound, ) -from prism_vm_core.protocols import EmitCandidatesFn, HostRaiseFn, InternFn +from prism_vm_core.protocols import EmitCandidatesFn, HostRaiseFn, InternFn, InternStateFn + + +@dataclass(frozen=True) +class _ArenaRootArgs: + arena: object + root: object + + +@dataclass(frozen=True) +class _ArenaTileArgs: + arena: object + tile_size: object + + +@dataclass(frozen=True) +class _ArgsKwargs: + args: tuple + kwargs: dict def _safe_gather_is_bound(safe_gather_fn) -> bool: if safe_gather_fn is None: return False return bool(getattr(safe_gather_fn, "_prism_policy_bound", False)) -from prism_vm_core.structures import NodeBatch + +from prism_vm_core.structures import Ledger, NodeBatch from prism_vm_core.candidates import _candidate_indices, candidate_indices_cfg from prism_bsp.cnf2 import ( emit_candidates as _emit_candidates_default, @@ -63,6 +87,9 @@ def _safe_gather_is_bound(safe_gather_fn) -> bool: cycle_candidates as _cycle_candidates_impl, cycle_candidates_static as _cycle_candidates_static, cycle_candidates_value as _cycle_candidates_value, + cycle_candidates_state as _cycle_candidates_state, + cycle_candidates_static_state as _cycle_candidates_static_state, + cycle_candidates_value_state as _cycle_candidates_value_state, ) from prism_bsp.arena_step import ( cycle_core as _cycle_core, @@ -86,42 +113,70 @@ def _safe_gather_is_bound(safe_gather_fn) -> bool: op_sort_and_swizzle_with_perm, op_sort_and_swizzle_with_perm_value, ) -from prism_vm_core.domains import _host_raise_if_bad -from prism_vm_core.gating import ( - _cnf2_enabled as _cnf2_enabled_default, - _cnf2_slot1_enabled as _cnf2_slot1_enabled_default, - _servo_enabled, +DEFAULT_SWIZZLE_WITH_PERM_FNS_BOUND = SwizzleWithPermFnsBound( + with_perm=op_sort_and_swizzle_with_perm, + morton_with_perm=op_sort_and_swizzle_morton_with_perm, + blocked_with_perm=op_sort_and_swizzle_blocked_with_perm, + hierarchical_with_perm=op_sort_and_swizzle_hierarchical_with_perm, + servo_with_perm=op_sort_and_swizzle_servo_with_perm, ) + +DEFAULT_SWIZZLE_WITH_PERM_VALUE_FNS_BOUND = SwizzleWithPermFnsBound( + with_perm=op_sort_and_swizzle_with_perm_value, + morton_with_perm=op_sort_and_swizzle_morton_with_perm_value, + blocked_with_perm=op_sort_and_swizzle_blocked_with_perm_value, + hierarchical_with_perm=op_sort_and_swizzle_hierarchical_with_perm_value, + servo_with_perm=op_sort_and_swizzle_servo_with_perm_value, +) +from prism_vm_core.domains import _host_raise_if_bad +from prism_vm_core.gating import _servo_enabled from prism_ledger.intern import _coord_norm_id_jax def _noop_root_hash(_arena, _root): + _ = _ArenaRootArgs(arena=_arena, root=_root) return jnp.int32(0) def _noop_tile_size(*_args, **_kwargs): + _ = _ArgsKwargs(args=_args, kwargs=_kwargs) return jnp.int32(0) def _noop_metrics(_arena, _tile_size): + _ = _ArenaTileArgs(arena=_arena, tile_size=_tile_size) return jnp.int32(0) @cached_jit def _intern_nodes_jit(cfg: InternConfig): - def _impl(ledger, batch: NodeBatch): - return _ledger_intern.intern_nodes(ledger, batch, cfg=cfg) + def _impl(ledger: Ledger, ledger_index: LedgerIndex, batch: NodeBatch): + return _ledger_intern.intern_nodes( + ledger, batch, cfg=cfg, ledger_index=ledger_index + ) return _impl +@cached_jit +def _intern_nodes_with_index_jit(cfg: InternConfig): + return _intern_nodes_jit(cfg) + + def intern_nodes_jit(cfg: InternConfig | None = None): - """Return a jitted intern_nodes entrypoint for a fixed config.""" + """Return a jitted intern_nodes entrypoint that requires a LedgerIndex.""" if cfg is None: cfg = DEFAULT_INTERN_CONFIG return _intern_nodes_jit(cfg) +def intern_nodes_with_index_jit(cfg: InternConfig | None = None): + """Return a jitted intern_nodes entrypoint that requires a LedgerIndex.""" + if cfg is None: + cfg = DEFAULT_INTERN_CONFIG + return _intern_nodes_with_index_jit(cfg) + + @cached_jit def _op_interact_jit(safe_gather_fn, scatter_drop_fn, guard_max_fn): def _impl(arena): @@ -262,7 +317,7 @@ def emit_candidates_jit(emit_candidates_fn: EmitCandidatesFn | None = None): emit_candidates_fn = _emit_candidates_default @jax.jit - def _impl(ledger, frontier_ids): + def _impl(ledger: Ledger, frontier_ids): return emit_candidates_fn(ledger, frontier_ids) return _impl @@ -364,7 +419,7 @@ def compact_candidates_with_index_result_jit_cfg(cfg: Cnf2Config | None = None): def intern_candidates_jit( *, - compact_candidates_fn=_compact_candidates, + compact_candidates_fn: Callable[..., tuple] = _compact_candidates, intern_fn: InternFn = _ledger_intern.intern_nodes, intern_cfg: InternConfig | None = None, node_batch_fn=None, @@ -376,13 +431,12 @@ def intern_candidates_jit( node_batch_fn = NodeBatch @jax.jit - def _impl(ledger, candidates): + def _impl(ledger: Ledger, candidates): return _intern_candidates( ledger, candidates, compact_candidates_fn=compact_candidates_fn, intern_fn=intern_fn, - intern_cfg=None, node_batch_fn=node_batch_fn, ) @@ -427,38 +481,12 @@ def cycle_candidates_static_jit( safe_gather_policy: SafetyPolicy | None = None, guard_cfg: GuardConfig | None = None, cnf2_cfg: Cnf2Config | None = None, - cnf2_flags: Cnf2Flags | None = None, - cnf2_mode: Cnf2Mode | None = None, - cnf2_enabled_fn=None, - cnf2_slot1_enabled_fn=None, + runtime_fns: Cnf2RuntimeFns = DEFAULT_CNF2_RUNTIME_FNS, ): """Return a jitted cycle_candidates entrypoint (static policy).""" - if cnf2_mode is not None and not isinstance(cnf2_mode, Cnf2Mode): - raise PrismCnf2ModeError(mode=cnf2_mode) - def _resolve_gate(flag_value, fn_value, default_fn): - if flag_value is not None: - return bool(flag_value) - if fn_value is not None: - return bool(fn_value()) - return bool(default_fn()) - if intern_fn is None: intern_fn = _ledger_intern.intern_nodes - if cnf2_cfg is not None and cnf2_flags is not None: - cnf2_cfg = replace(cnf2_cfg, flags=cnf2_flags) - elif cnf2_cfg is None and cnf2_flags is not None: - cnf2_cfg = Cnf2Config(flags=cnf2_flags) if cnf2_cfg is not None: - if cnf2_mode is None and cnf2_cfg.cnf2_mode is not None: - cnf2_mode = cnf2_cfg.cnf2_mode - elif cnf2_mode is not None and cnf2_cfg.cnf2_mode is not None: - mode_a = coerce_cnf2_mode(cnf2_mode, context="cycle_candidates_static_jit") - mode_b = coerce_cnf2_mode(cnf2_cfg.cnf2_mode, context="cycle_candidates_static_jit") - if mode_a != mode_b: - raise PrismCnf2ModeConflictError( - "cycle_candidates_static_jit received both cnf2_mode and cfg.cnf2_mode", - context="cycle_candidates_static_jit", - ) if intern_cfg is None: intern_cfg = cnf2_cfg.intern_cfg if intern_fn is None and cnf2_cfg.intern_fn is not None: @@ -490,53 +518,17 @@ def _resolve_gate(flag_value, fn_value, default_fn): ) if guard_cfg is None and cnf2_cfg.guard_cfg is not None: guard_cfg = cnf2_cfg.guard_cfg - if cnf2_enabled_fn is None and cnf2_cfg.cnf2_enabled_fn is not None: - cnf2_enabled_fn = cnf2_cfg.cnf2_enabled_fn - if cnf2_slot1_enabled_fn is None and cnf2_cfg.cnf2_slot1_enabled_fn is not None: - cnf2_slot1_enabled_fn = cnf2_cfg.cnf2_slot1_enabled_fn - cnf2_flags = cnf2_cfg.flags if cnf2_flags is None else cnf2_flags - if cnf2_mode is not None: - mode = coerce_cnf2_mode(cnf2_mode, context="cycle_candidates_static_jit") - if mode != Cnf2Mode.AUTO: - if cnf2_flags is not None or cnf2_enabled_fn is not None or cnf2_slot1_enabled_fn is not None: - raise PrismCnf2ModeConflictError( - "cycle_candidates_static_jit received cnf2_mode alongside cnf2_flags or cnf2_*_enabled_fn", - context="cycle_candidates_static_jit", - ) - enabled_value = mode in (Cnf2Mode.BASE, Cnf2Mode.SLOT1) - slot1_value = mode == Cnf2Mode.SLOT1 - cnf2_enabled_fn = lambda: enabled_value - cnf2_slot1_enabled_fn = lambda: slot1_value - cnf2_flags = None - if cnf2_flags is not None: - if cnf2_enabled_fn is not None or cnf2_slot1_enabled_fn is not None: - raise ValueError("Pass either cnf2_flags or cnf2_*_enabled_fn, not both.") - enabled_value = _resolve_gate( - cnf2_flags.enabled, None, _cnf2_enabled_default - ) - slot1_value = _resolve_gate( - cnf2_flags.slot1_enabled, None, _cnf2_slot1_enabled_default - ) - cnf2_enabled_fn = lambda: enabled_value - cnf2_slot1_enabled_fn = lambda: slot1_value + if runtime_fns is DEFAULT_CNF2_RUNTIME_FNS: + runtime_fns = cnf2_cfg.runtime_fns if emit_candidates_fn is None: emit_candidates_fn = _emit_candidates_default - if cnf2_flags is None: - enabled_value = _resolve_gate(None, cnf2_enabled_fn, _cnf2_enabled_default) - slot1_value = _resolve_gate( - None, cnf2_slot1_enabled_fn, _cnf2_slot1_enabled_default - ) - cnf2_enabled_fn = lambda: enabled_value - cnf2_slot1_enabled_fn = lambda: slot1_value if host_raise_if_bad_fn is None: host_raise_if_bad_fn = _host_raise_if_bad - if not cnf2_enabled_fn(): - raise RuntimeError("cycle_candidates disabled until m2 (set PRISM_ENABLE_CNF2=1)") if safe_gather_policy is None: safe_gather_policy = DEFAULT_SAFETY_POLICY @jax.jit - def _impl(ledger, frontier_ids): + def _impl(ledger: Ledger, frontier_ids): return _cycle_candidates_static( ledger, frontier_ids, @@ -547,8 +539,7 @@ def _impl(ledger, frontier_ids): intern_fn=intern_fn, intern_cfg=intern_cfg, emit_candidates_fn=emit_candidates_fn, - cnf2_enabled_fn=cnf2_enabled_fn, - cnf2_slot1_enabled_fn=cnf2_slot1_enabled_fn, + runtime_fns=runtime_fns, ) def _run(ledger, frontier_ids): @@ -571,38 +562,12 @@ def cycle_candidates_value_jit( safe_gather_policy_value: jnp.ndarray | None = None, guard_cfg: GuardConfig | None = None, cnf2_cfg: Cnf2Config | None = None, - cnf2_flags: Cnf2Flags | None = None, - cnf2_mode: Cnf2Mode | None = None, - cnf2_enabled_fn=None, - cnf2_slot1_enabled_fn=None, + runtime_fns: Cnf2RuntimeFns = DEFAULT_CNF2_RUNTIME_FNS, ): """Return a jitted cycle_candidates entrypoint (policy as JAX value).""" - if cnf2_mode is not None and not isinstance(cnf2_mode, Cnf2Mode): - raise PrismCnf2ModeError(mode=cnf2_mode) - def _resolve_gate(flag_value, fn_value, default_fn): - if flag_value is not None: - return bool(flag_value) - if fn_value is not None: - return bool(fn_value()) - return bool(default_fn()) - if intern_fn is None: intern_fn = _ledger_intern.intern_nodes - if cnf2_cfg is not None and cnf2_flags is not None: - cnf2_cfg = replace(cnf2_cfg, flags=cnf2_flags) - elif cnf2_cfg is None and cnf2_flags is not None: - cnf2_cfg = Cnf2Config(flags=cnf2_flags) if cnf2_cfg is not None: - if cnf2_mode is None and cnf2_cfg.cnf2_mode is not None: - cnf2_mode = cnf2_cfg.cnf2_mode - elif cnf2_mode is not None and cnf2_cfg.cnf2_mode is not None: - mode_a = coerce_cnf2_mode(cnf2_mode, context="cycle_candidates_value_jit") - mode_b = coerce_cnf2_mode(cnf2_cfg.cnf2_mode, context="cycle_candidates_value_jit") - if mode_a != mode_b: - raise PrismCnf2ModeConflictError( - "cycle_candidates_value_jit received both cnf2_mode and cfg.cnf2_mode", - context="cycle_candidates_value_jit", - ) if intern_cfg is None: intern_cfg = cnf2_cfg.intern_cfg if intern_fn is None and cnf2_cfg.intern_fn is not None: @@ -635,53 +600,17 @@ def _resolve_gate(flag_value, fn_value, default_fn): safe_gather_policy_value = cnf2_cfg.safe_gather_policy_value if guard_cfg is None and cnf2_cfg.guard_cfg is not None: guard_cfg = cnf2_cfg.guard_cfg - if cnf2_enabled_fn is None and cnf2_cfg.cnf2_enabled_fn is not None: - cnf2_enabled_fn = cnf2_cfg.cnf2_enabled_fn - if cnf2_slot1_enabled_fn is None and cnf2_cfg.cnf2_slot1_enabled_fn is not None: - cnf2_slot1_enabled_fn = cnf2_cfg.cnf2_slot1_enabled_fn - cnf2_flags = cnf2_cfg.flags if cnf2_flags is None else cnf2_flags - if cnf2_mode is not None: - mode = coerce_cnf2_mode(cnf2_mode, context="cycle_candidates_value_jit") - if mode != Cnf2Mode.AUTO: - if cnf2_flags is not None or cnf2_enabled_fn is not None or cnf2_slot1_enabled_fn is not None: - raise PrismCnf2ModeConflictError( - "cycle_candidates_value_jit received cnf2_mode alongside cnf2_flags or cnf2_*_enabled_fn", - context="cycle_candidates_value_jit", - ) - enabled_value = mode in (Cnf2Mode.BASE, Cnf2Mode.SLOT1) - slot1_value = mode == Cnf2Mode.SLOT1 - cnf2_enabled_fn = lambda: enabled_value - cnf2_slot1_enabled_fn = lambda: slot1_value - cnf2_flags = None - if cnf2_flags is not None: - if cnf2_enabled_fn is not None or cnf2_slot1_enabled_fn is not None: - raise ValueError("Pass either cnf2_flags or cnf2_*_enabled_fn, not both.") - enabled_value = _resolve_gate( - cnf2_flags.enabled, None, _cnf2_enabled_default - ) - slot1_value = _resolve_gate( - cnf2_flags.slot1_enabled, None, _cnf2_slot1_enabled_default - ) - cnf2_enabled_fn = lambda: enabled_value - cnf2_slot1_enabled_fn = lambda: slot1_value + if runtime_fns is DEFAULT_CNF2_RUNTIME_FNS: + runtime_fns = cnf2_cfg.runtime_fns if emit_candidates_fn is None: emit_candidates_fn = _emit_candidates_default - if cnf2_flags is None: - enabled_value = _resolve_gate(None, cnf2_enabled_fn, _cnf2_enabled_default) - slot1_value = _resolve_gate( - None, cnf2_slot1_enabled_fn, _cnf2_slot1_enabled_default - ) - cnf2_enabled_fn = lambda: enabled_value - cnf2_slot1_enabled_fn = lambda: slot1_value if host_raise_if_bad_fn is None: host_raise_if_bad_fn = _host_raise_if_bad - if not cnf2_enabled_fn(): - raise RuntimeError("cycle_candidates disabled until m2 (set PRISM_ENABLE_CNF2=1)") if safe_gather_policy_value is None: safe_gather_policy_value = POLICY_VALUE_DEFAULT @jax.jit - def _impl(ledger, frontier_ids): + def _impl(ledger: Ledger, frontier_ids): return _cycle_candidates_value( ledger, frontier_ids, @@ -692,8 +621,7 @@ def _impl(ledger, frontier_ids): intern_fn=intern_fn, intern_cfg=intern_cfg, emit_candidates_fn=emit_candidates_fn, - cnf2_enabled_fn=cnf2_enabled_fn, - cnf2_slot1_enabled_fn=cnf2_slot1_enabled_fn, + runtime_fns=runtime_fns, ) def _run(ledger, frontier_ids): @@ -706,6 +634,217 @@ def _run(ledger, frontier_ids): return _run +def cycle_candidates_static_state_jit( + *, + validate_mode: ValidateMode = ValidateMode.NONE, + intern_fn: InternFn | None = None, + intern_cfg: InternConfig | None = None, + emit_candidates_fn: EmitCandidatesFn | None = None, + host_raise_if_bad_fn: HostRaiseFn | None = None, + safe_gather_policy: SafetyPolicy | None = None, + guard_cfg: GuardConfig | None = None, + cnf2_cfg: Cnf2Config | None = None, + runtime_fns: Cnf2RuntimeFns = DEFAULT_CNF2_RUNTIME_FNS, +): + """Return a jitted cycle_candidates entrypoint (static policy, state).""" + if intern_fn is None: + intern_fn = _ledger_intern.intern_nodes + if cnf2_cfg is not None: + if intern_cfg is None: + intern_cfg = cnf2_cfg.intern_cfg + if intern_fn is None and cnf2_cfg.intern_fn is not None: + intern_fn = cnf2_cfg.intern_fn + if emit_candidates_fn is None and cnf2_cfg.emit_candidates_fn is not None: + emit_candidates_fn = cnf2_cfg.emit_candidates_fn + if cnf2_cfg.policy_binding is not None: + if cnf2_cfg.policy_binding.mode == PolicyMode.VALUE: + raise PrismPolicyBindingError( + "cycle_candidates_static_state_jit received cfg.policy_binding value-mode; " + "use cycle_candidates_value_state_jit", + context="cycle_candidates_static_state_jit", + policy_mode="static", + ) + if safe_gather_policy is None: + safe_gather_policy = require_static_policy( + cnf2_cfg.policy_binding, + context="cycle_candidates_static_state_jit", + ) + if safe_gather_policy is None and cnf2_cfg.safe_gather_policy is not None: + safe_gather_policy = cnf2_cfg.safe_gather_policy + if cnf2_cfg.safe_gather_policy_value is not None: + raise PrismPolicyBindingError( + "cycle_candidates_static_state_jit received cfg.safe_gather_policy_value; " + "use cycle_candidates_value_state_jit", + context="cycle_candidates_static_state_jit", + policy_mode="static", + ) + if guard_cfg is None and cnf2_cfg.guard_cfg is not None: + guard_cfg = cnf2_cfg.guard_cfg + if runtime_fns is DEFAULT_CNF2_RUNTIME_FNS: + runtime_fns = cnf2_cfg.runtime_fns + if emit_candidates_fn is None: + emit_candidates_fn = _emit_candidates_default + if host_raise_if_bad_fn is None: + host_raise_if_bad_fn = _host_raise_if_bad + if safe_gather_policy is None: + safe_gather_policy = DEFAULT_SAFETY_POLICY + + @jax.jit + def _impl(state: LedgerState, frontier_ids): + return _cycle_candidates_static_state( + state, + frontier_ids, + validate_mode=validate_mode, + cfg=cnf2_cfg, + safe_gather_policy=safe_gather_policy, + guard_cfg=guard_cfg, + intern_fn=intern_fn, + intern_cfg=intern_cfg, + emit_candidates_fn=emit_candidates_fn, + runtime_fns=runtime_fns, + ) + + def _run(state, frontier_ids): + out = _impl(state, frontier_ids) + out_state = out[0] + if not bool(jax.device_get(out_state.ledger.corrupt)): + host_raise_if_bad_fn(out_state.ledger, "Ledger capacity exceeded during cycle") + return out + + return _run + + +def cycle_candidates_value_state_jit( + *, + validate_mode: ValidateMode = ValidateMode.NONE, + intern_fn: InternFn | None = None, + intern_cfg: InternConfig | None = None, + emit_candidates_fn: EmitCandidatesFn | None = None, + host_raise_if_bad_fn: HostRaiseFn | None = None, + safe_gather_policy_value: jnp.ndarray | None = None, + guard_cfg: GuardConfig | None = None, + cnf2_cfg: Cnf2Config | None = None, + runtime_fns: Cnf2RuntimeFns = DEFAULT_CNF2_RUNTIME_FNS, +): + """Return a jitted cycle_candidates entrypoint (policy as JAX value, state).""" + if intern_fn is None: + intern_fn = _ledger_intern.intern_nodes + if cnf2_cfg is not None: + if intern_cfg is None: + intern_cfg = cnf2_cfg.intern_cfg + if intern_fn is None and cnf2_cfg.intern_fn is not None: + intern_fn = cnf2_cfg.intern_fn + if emit_candidates_fn is None and cnf2_cfg.emit_candidates_fn is not None: + emit_candidates_fn = cnf2_cfg.emit_candidates_fn + if cnf2_cfg.policy_binding is not None: + if cnf2_cfg.policy_binding.mode == PolicyMode.STATIC: + raise PrismPolicyBindingError( + "cycle_candidates_value_state_jit received cfg.policy_binding static-mode; " + "use cycle_candidates_static_state_jit", + context="cycle_candidates_value_state_jit", + policy_mode="value", + ) + if safe_gather_policy_value is None: + safe_gather_policy_value = require_value_policy( + cnf2_cfg.policy_binding, + context="cycle_candidates_value_state_jit", + ) + if cnf2_cfg.safe_gather_policy is not None: + raise PrismPolicyBindingError( + "cycle_candidates_value_state_jit received cfg.safe_gather_policy; " + "use cycle_candidates_static_state_jit", + context="cycle_candidates_value_state_jit", + policy_mode="value", + ) + if ( + safe_gather_policy_value is None + and cnf2_cfg.safe_gather_policy_value is not None + ): + safe_gather_policy_value = cnf2_cfg.safe_gather_policy_value + if guard_cfg is None and cnf2_cfg.guard_cfg is not None: + guard_cfg = cnf2_cfg.guard_cfg + if runtime_fns is DEFAULT_CNF2_RUNTIME_FNS: + runtime_fns = cnf2_cfg.runtime_fns + if emit_candidates_fn is None: + emit_candidates_fn = _emit_candidates_default + if host_raise_if_bad_fn is None: + host_raise_if_bad_fn = _host_raise_if_bad + if safe_gather_policy_value is None: + safe_gather_policy_value = POLICY_VALUE_DEFAULT + + @jax.jit + def _impl(state: LedgerState, frontier_ids): + return _cycle_candidates_value_state( + state, + frontier_ids, + validate_mode=validate_mode, + cfg=cnf2_cfg, + safe_gather_policy_value=safe_gather_policy_value, + guard_cfg=guard_cfg, + intern_fn=intern_fn, + intern_cfg=intern_cfg, + emit_candidates_fn=emit_candidates_fn, + runtime_fns=runtime_fns, + ) + + def _run(state, frontier_ids): + out = _impl(state, frontier_ids) + out_state = out[0] + if not bool(jax.device_get(out_state.ledger.corrupt)): + host_raise_if_bad_fn(out_state.ledger, "Ledger capacity exceeded during cycle") + return out + + return _run + + +def cycle_candidates_state_jit( + *, + validate_mode: ValidateMode = ValidateMode.NONE, + intern_fn: InternFn | None = None, + intern_cfg: InternConfig | None = None, + emit_candidates_fn: EmitCandidatesFn | None = None, + host_raise_if_bad_fn: HostRaiseFn | None = None, + safe_gather_policy: SafetyPolicy | None = None, + safe_gather_policy_value: jnp.ndarray | None = None, + guard_cfg: GuardConfig | None = None, + cnf2_cfg: Cnf2Config | None = None, + runtime_fns: Cnf2RuntimeFns = DEFAULT_CNF2_RUNTIME_FNS, +): + """Return a jitted cycle_candidates entrypoint for fixed DI (LedgerState).""" + binding = resolve_policy_binding( + policy=safe_gather_policy, + policy_value=safe_gather_policy_value, + context="cycle_candidates_state_jit", + ) + if binding.mode == PolicyMode.VALUE: + return cycle_candidates_value_state_jit( + validate_mode=validate_mode, + intern_fn=intern_fn, + intern_cfg=intern_cfg, + emit_candidates_fn=emit_candidates_fn, + host_raise_if_bad_fn=host_raise_if_bad_fn, + safe_gather_policy_value=require_value_policy( + binding, context="cycle_candidates_state_jit" + ), + guard_cfg=guard_cfg, + cnf2_cfg=cnf2_cfg, + runtime_fns=runtime_fns, + ) + return cycle_candidates_static_state_jit( + validate_mode=validate_mode, + intern_fn=intern_fn, + intern_cfg=intern_cfg, + emit_candidates_fn=emit_candidates_fn, + host_raise_if_bad_fn=host_raise_if_bad_fn, + safe_gather_policy=require_static_policy( + binding, context="cycle_candidates_state_jit" + ), + guard_cfg=guard_cfg, + cnf2_cfg=cnf2_cfg, + runtime_fns=runtime_fns, + ) + + def cycle_candidates_jit( *, validate_mode: ValidateMode = ValidateMode.NONE, @@ -717,14 +856,9 @@ def cycle_candidates_jit( safe_gather_policy_value: jnp.ndarray | None = None, guard_cfg: GuardConfig | None = None, cnf2_cfg: Cnf2Config | None = None, - cnf2_flags: Cnf2Flags | None = None, - cnf2_mode: Cnf2Mode | None = None, - cnf2_enabled_fn=None, - cnf2_slot1_enabled_fn=None, + runtime_fns: Cnf2RuntimeFns = DEFAULT_CNF2_RUNTIME_FNS, ): """Return a jitted cycle_candidates entrypoint for fixed DI.""" - if cnf2_mode is not None and not isinstance(cnf2_mode, Cnf2Mode): - raise PrismCnf2ModeError(mode=cnf2_mode) binding = resolve_policy_binding( policy=safe_gather_policy, policy_value=safe_gather_policy_value, @@ -742,10 +876,7 @@ def cycle_candidates_jit( ), guard_cfg=guard_cfg, cnf2_cfg=cnf2_cfg, - cnf2_flags=cnf2_flags, - cnf2_mode=cnf2_mode, - cnf2_enabled_fn=cnf2_enabled_fn, - cnf2_slot1_enabled_fn=cnf2_slot1_enabled_fn, + runtime_fns=runtime_fns, ) return cycle_candidates_static_jit( validate_mode=validate_mode, @@ -758,28 +889,16 @@ def cycle_candidates_jit( ), guard_cfg=guard_cfg, cnf2_cfg=cnf2_cfg, - cnf2_flags=cnf2_flags, - cnf2_mode=cnf2_mode, - cnf2_enabled_fn=cnf2_enabled_fn, - cnf2_slot1_enabled_fn=cnf2_slot1_enabled_fn, + runtime_fns=runtime_fns, ) @cached_jit def _cycle_jit( - do_sort, - use_morton, - block_size, - l2_block_size, - l1_block_size, - do_global, + sort_cfg: ArenaSortConfig, op_rank_fn, servo_enabled_value, servo_update_fn, op_morton_fn, - op_sort_and_swizzle_with_perm_fn, - op_sort_and_swizzle_morton_with_perm_fn, - op_sort_and_swizzle_blocked_with_perm_fn, - op_sort_and_swizzle_hierarchical_with_perm_fn, - op_sort_and_swizzle_servo_with_perm_fn, + swizzle_with_perm_fns: SwizzleWithPermFnsBound, safe_gather_fn, guard_cfg, op_interact_fn, @@ -790,22 +909,12 @@ def _impl(arena, root_ptr): return cycle_core_fn( arena, root_ptr, - do_sort=do_sort, - use_morton=use_morton, - block_size=block_size, - morton=None, - l2_block_size=l2_block_size, - l1_block_size=l1_block_size, - do_global=do_global, + sort_cfg=sort_cfg, op_rank_fn=op_rank_fn, servo_enabled_fn=lambda: servo_enabled_value, servo_update_fn=servo_update_fn, op_morton_fn=op_morton_fn, - op_sort_and_swizzle_with_perm_fn=op_sort_and_swizzle_with_perm_fn, - op_sort_and_swizzle_morton_with_perm_fn=op_sort_and_swizzle_morton_with_perm_fn, - op_sort_and_swizzle_blocked_with_perm_fn=op_sort_and_swizzle_blocked_with_perm_fn, - op_sort_and_swizzle_hierarchical_with_perm_fn=op_sort_and_swizzle_hierarchical_with_perm_fn, - op_sort_and_swizzle_servo_with_perm_fn=op_sort_and_swizzle_servo_with_perm_fn, + swizzle_with_perm_fns=swizzle_with_perm_fns, safe_gather_fn=safe_gather_fn, arena_root_hash_fn=_noop_root_hash, damage_tile_size_fn=_noop_tile_size, @@ -818,21 +927,12 @@ def _impl(arena, root_ptr): def cycle_jit( *, - do_sort=True, - use_morton=False, - block_size=None, - l2_block_size=None, - l1_block_size=None, - do_global=False, + sort_cfg: ArenaSortConfig = DEFAULT_ARENA_SORT_CONFIG, op_rank_fn=op_rank, servo_enabled_fn=_servo_enabled, servo_update_fn=_servo_update, op_morton_fn=op_morton, - op_sort_and_swizzle_with_perm_fn=op_sort_and_swizzle_with_perm, - op_sort_and_swizzle_morton_with_perm_fn=op_sort_and_swizzle_morton_with_perm, - op_sort_and_swizzle_blocked_with_perm_fn=op_sort_and_swizzle_blocked_with_perm, - op_sort_and_swizzle_hierarchical_with_perm_fn=op_sort_and_swizzle_hierarchical_with_perm, - op_sort_and_swizzle_servo_with_perm_fn=op_sort_and_swizzle_servo_with_perm, + swizzle_with_perm_fns: SwizzleWithPermFnsBound = DEFAULT_SWIZZLE_WITH_PERM_FNS_BOUND, safe_gather_fn=_jax_safe.safe_gather_1d, op_interact_fn=_op_interact, test_guards: bool = False, @@ -847,23 +947,16 @@ def cycle_jit( policy=safe_gather_policy, guard_cfg=guard_cfg, ) + if sort_cfg.morton is not None: + raise ValueError("cycle_jit requires sort_cfg.morton is None") servo_enabled_value = bool(servo_enabled_fn()) return _cycle_jit( - do_sort, - use_morton, - block_size, - l2_block_size, - l1_block_size, - do_global, + sort_cfg, op_rank_fn, servo_enabled_value, servo_update_fn, op_morton_fn, - op_sort_and_swizzle_with_perm_fn, - op_sort_and_swizzle_morton_with_perm_fn, - op_sort_and_swizzle_blocked_with_perm_fn, - op_sort_and_swizzle_hierarchical_with_perm_fn, - op_sort_and_swizzle_servo_with_perm_fn, + swizzle_with_perm_fns, safe_gather_fn, guard_cfg, op_interact_fn, @@ -872,21 +965,12 @@ def cycle_jit( @cached_jit def _cycle_value_jit( - do_sort, - use_morton, - block_size, - l2_block_size, - l1_block_size, - do_global, + sort_cfg: ArenaSortConfig, op_rank_fn, servo_enabled_value, servo_update_fn, op_morton_fn, - op_sort_and_swizzle_with_perm_fn, - op_sort_and_swizzle_morton_with_perm_fn, - op_sort_and_swizzle_blocked_with_perm_fn, - op_sort_and_swizzle_hierarchical_with_perm_fn, - op_sort_and_swizzle_servo_with_perm_fn, + swizzle_with_perm_fns: SwizzleWithPermFnsBound, safe_gather_value_fn, guard_cfg, ): @@ -897,22 +981,12 @@ def _impl(arena, root_ptr, policy_value): arena, root_ptr, policy_value, - do_sort=do_sort, - use_morton=use_morton, - block_size=block_size, - morton=None, - l2_block_size=l2_block_size, - l1_block_size=l1_block_size, - do_global=do_global, + sort_cfg=sort_cfg, op_rank_fn=op_rank_fn, servo_enabled_fn=lambda: servo_enabled_value, servo_update_fn=servo_update_fn, op_morton_fn=op_morton_fn, - op_sort_and_swizzle_with_perm_value_fn=op_sort_and_swizzle_with_perm_fn, - op_sort_and_swizzle_morton_with_perm_value_fn=op_sort_and_swizzle_morton_with_perm_fn, - op_sort_and_swizzle_blocked_with_perm_value_fn=op_sort_and_swizzle_blocked_with_perm_fn, - op_sort_and_swizzle_hierarchical_with_perm_value_fn=op_sort_and_swizzle_hierarchical_with_perm_fn, - op_sort_and_swizzle_servo_with_perm_value_fn=op_sort_and_swizzle_servo_with_perm_fn, + swizzle_with_perm_fns=swizzle_with_perm_fns, safe_gather_value_fn=safe_gather_value_fn, arena_root_hash_fn=_noop_root_hash, damage_tile_size_fn=_noop_tile_size, @@ -924,42 +998,26 @@ def _impl(arena, root_ptr, policy_value): def cycle_value_jit( *, - do_sort=True, - use_morton=False, - block_size=None, - l2_block_size=None, - l1_block_size=None, - do_global=False, + sort_cfg: ArenaSortConfig = DEFAULT_ARENA_SORT_CONFIG, op_rank_fn=op_rank, servo_enabled_fn=_servo_enabled, servo_update_fn=_servo_update, op_morton_fn=op_morton, - op_sort_and_swizzle_with_perm_fn=op_sort_and_swizzle_with_perm_value, - op_sort_and_swizzle_morton_with_perm_fn=op_sort_and_swizzle_morton_with_perm_value, - op_sort_and_swizzle_blocked_with_perm_fn=op_sort_and_swizzle_blocked_with_perm_value, - op_sort_and_swizzle_hierarchical_with_perm_fn=op_sort_and_swizzle_hierarchical_with_perm_value, - op_sort_and_swizzle_servo_with_perm_fn=op_sort_and_swizzle_servo_with_perm_value, + swizzle_with_perm_fns: SwizzleWithPermFnsBound = DEFAULT_SWIZZLE_WITH_PERM_VALUE_FNS_BOUND, safe_gather_value_fn=_jax_safe.safe_gather_1d_value, guard_cfg: GuardConfig | None = None, ): """Return a jitted cycle entrypoint that accepts policy_value.""" + if sort_cfg.morton is not None: + raise ValueError("cycle_value_jit requires sort_cfg.morton is None") servo_enabled_value = bool(servo_enabled_fn()) return _cycle_value_jit( - do_sort, - use_morton, - block_size, - l2_block_size, - l1_block_size, - do_global, + sort_cfg, op_rank_fn, servo_enabled_value, servo_update_fn, op_morton_fn, - op_sort_and_swizzle_with_perm_fn, - op_sort_and_swizzle_morton_with_perm_fn, - op_sort_and_swizzle_blocked_with_perm_fn, - op_sort_and_swizzle_hierarchical_with_perm_fn, - op_sort_and_swizzle_servo_with_perm_fn, + swizzle_with_perm_fns, safe_gather_value_fn, guard_cfg, ) @@ -967,12 +1025,7 @@ def cycle_value_jit( def cycle_jit_cfg( *, - do_sort=True, - use_morton=False, - block_size=None, - l2_block_size=None, - l1_block_size=None, - do_global=False, + sort_cfg: ArenaSortConfig = DEFAULT_ARENA_SORT_CONFIG, cfg: ArenaCycleConfig | None = None, test_guards: bool = False, ): @@ -998,32 +1051,20 @@ def cycle_jit_cfg( cfg.policy_binding, context="cycle_jit_cfg" ) if safe_gather_policy_value is not None: + swizzle_with_perm_fns = SwizzleWithPermFnsBound( + with_perm=op_sort_and_swizzle_with_perm_fn, + morton_with_perm=op_sort_and_swizzle_morton_with_perm_fn, + blocked_with_perm=op_sort_and_swizzle_blocked_with_perm_fn, + hierarchical_with_perm=op_sort_and_swizzle_hierarchical_with_perm_fn, + servo_with_perm=op_sort_and_swizzle_servo_with_perm_fn, + ) return cycle_value_jit( - do_sort=do_sort, - use_morton=use_morton, - block_size=block_size, - l2_block_size=l2_block_size, - l1_block_size=l1_block_size, - do_global=do_global, + sort_cfg=sort_cfg, op_rank_fn=cfg.op_rank_fn or op_rank, servo_enabled_fn=cfg.servo_enabled_fn or _servo_enabled, servo_update_fn=cfg.servo_update_fn or _servo_update, op_morton_fn=cfg.op_morton_fn or op_morton, - op_sort_and_swizzle_with_perm_fn=( - cfg.op_sort_and_swizzle_with_perm_fn or op_sort_and_swizzle_with_perm - ), - op_sort_and_swizzle_morton_with_perm_fn=( - cfg.op_sort_and_swizzle_morton_with_perm_fn or op_sort_and_swizzle_morton_with_perm - ), - op_sort_and_swizzle_blocked_with_perm_fn=( - cfg.op_sort_and_swizzle_blocked_with_perm_fn or op_sort_and_swizzle_blocked_with_perm - ), - op_sort_and_swizzle_hierarchical_with_perm_fn=( - cfg.op_sort_and_swizzle_hierarchical_with_perm_fn or op_sort_and_swizzle_hierarchical_with_perm - ), - op_sort_and_swizzle_servo_with_perm_fn=( - cfg.op_sort_and_swizzle_servo_with_perm_fn or op_sort_and_swizzle_servo_with_perm - ), + swizzle_with_perm_fns=swizzle_with_perm_fns, safe_gather_value_fn=resolve( cfg.safe_gather_value_fn, _jax_safe.safe_gather_1d_value ), @@ -1055,6 +1096,13 @@ def cycle_jit_cfg( cfg.op_sort_and_swizzle_servo_with_perm_fn or op_sort_and_swizzle_servo_with_perm ) + swizzle_with_perm_fns = SwizzleWithPermFnsBound( + with_perm=op_sort_and_swizzle_with_perm_fn, + morton_with_perm=op_sort_and_swizzle_morton_with_perm_fn, + blocked_with_perm=op_sort_and_swizzle_blocked_with_perm_fn, + hierarchical_with_perm=op_sort_and_swizzle_hierarchical_with_perm_fn, + servo_with_perm=op_sort_and_swizzle_servo_with_perm_fn, + ) safe_gather_fn = cfg.safe_gather_fn or _jax_safe.safe_gather_1d op_interact_fn = cfg.op_interact_fn if op_interact_fn is None and cfg.interact_cfg is not None: @@ -1069,21 +1117,12 @@ def cycle_jit_cfg( else: op_interact_fn = _op_interact return cycle_jit( - do_sort=do_sort, - use_morton=use_morton, - block_size=block_size, - l2_block_size=l2_block_size, - l1_block_size=l1_block_size, - do_global=do_global, + sort_cfg=sort_cfg, op_rank_fn=op_rank_fn, servo_enabled_fn=servo_enabled_fn, servo_update_fn=servo_update_fn, op_morton_fn=op_morton_fn, - op_sort_and_swizzle_with_perm_fn=op_sort_and_swizzle_with_perm_fn, - op_sort_and_swizzle_morton_with_perm_fn=op_sort_and_swizzle_morton_with_perm_fn, - op_sort_and_swizzle_blocked_with_perm_fn=op_sort_and_swizzle_blocked_with_perm_fn, - op_sort_and_swizzle_hierarchical_with_perm_fn=op_sort_and_swizzle_hierarchical_with_perm_fn, - op_sort_and_swizzle_servo_with_perm_fn=op_sort_and_swizzle_servo_with_perm_fn, + swizzle_with_perm_fns=swizzle_with_perm_fns, safe_gather_fn=safe_gather_fn, op_interact_fn=op_interact_fn, test_guards=test_guards, @@ -1095,12 +1134,7 @@ def cycle_jit_cfg( def cycle_jit_bound_cfg( policy_binding: PolicyBinding, *, - do_sort=True, - use_morton=False, - block_size=None, - l2_block_size=None, - l1_block_size=None, - do_global=False, + sort_cfg: ArenaSortConfig = DEFAULT_ARENA_SORT_CONFIG, cfg: ArenaCycleConfig | None = None, test_guards: bool = False, ): @@ -1124,12 +1158,7 @@ def cycle_jit_bound_cfg( interact_cfg=interact_cfg, ) return cycle_jit_cfg( - do_sort=do_sort, - use_morton=use_morton, - block_size=block_size, - l2_block_size=l2_block_size, - l1_block_size=l1_block_size, - do_global=do_global, + sort_cfg=sort_cfg, cfg=cfg, test_guards=test_guards, ) @@ -1137,23 +1166,58 @@ def cycle_jit_bound_cfg( def cycle_intrinsic_jit( *, + intern_state_fn: InternStateFn | None = None, intern_fn: InternFn | None = None, intern_cfg: InternConfig | None = None, node_batch_fn=None, ): """Return a jitted intrinsic cycle entrypoint for fixed DI.""" - if intern_fn is None: - intern_fn = _ledger_intern.intern_nodes - if intern_cfg is not None and intern_fn is _ledger_intern.intern_nodes: - intern_fn = partial(_ledger_intern.intern_nodes, cfg=intern_cfg) + if intern_state_fn is not None and intern_fn is not None: + raise ValueError("Pass either intern_state_fn or intern_fn, not both.") + if intern_state_fn is None: + if intern_fn is None: + intern_state_fn = _ledger_intern.intern_nodes_state + if intern_cfg is not None and intern_state_fn is _ledger_intern.intern_nodes_state: + intern_state_fn = partial(_ledger_intern.intern_nodes_state, cfg=intern_cfg) + else: + cfg = intern_cfg or DEFAULT_INTERN_CONFIG + + def _intern_state(state, batch): + ids, new_ledger = call_with_optional_kwargs( + intern_fn, + {"ledger_index": state.index}, + state.ledger, + batch, + ) + new_state = derive_ledger_state( + new_ledger, op_buckets_full_range=cfg.op_buckets_full_range + ) + return ids, new_state + + intern_state_fn = _intern_state if node_batch_fn is None: node_batch_fn = NodeBatch - return _cycle_intrinsic_jit_impl(intern_fn, node_batch_fn) + impl = _cycle_intrinsic_jit_impl(intern_state_fn, node_batch_fn) + cfg_local = intern_cfg or DEFAULT_INTERN_CONFIG + + def _entry(state_or_ledger, frontier_ids): + if isinstance(state_or_ledger, LedgerState): + state_out, frontier_out = impl(state_or_ledger, frontier_ids) + return state_out, frontier_out + state = derive_ledger_state( + state_or_ledger, + op_buckets_full_range=cfg_local.op_buckets_full_range, + ) + state_out, frontier_out = impl(state, frontier_ids) + return state_out.ledger, frontier_out + + return _entry def cycle_intrinsic_jit_cfg( cfg: IntrinsicConfig | None = None, *, + intern_state_fn: InternStateFn | None = None, intern_fn: InternFn | None = None, intern_cfg: InternConfig | None = None, node_batch_fn=None, @@ -1167,6 +1231,7 @@ def cycle_intrinsic_jit_cfg( raise ValueError("Pass either cfg.intern_cfg or intern_cfg, not both.") intern_cfg = intern_cfg if intern_cfg is not None else cfg.intern_cfg return cycle_intrinsic_jit( + intern_state_fn=intern_state_fn, intern_fn=intern_fn, intern_cfg=intern_cfg, node_batch_fn=node_batch_fn, @@ -1183,6 +1248,7 @@ def coord_norm_batch_jit(coord_norm_id_jax_fn=None): __all__ = [ "intern_nodes_jit", + "intern_nodes_with_index_jit", "op_interact_jit", "op_interact_jit_cfg", "op_interact_jit_bound_cfg", @@ -1200,8 +1266,11 @@ def coord_norm_batch_jit(coord_norm_id_jax_fn=None): "intern_candidates_jit", "intern_candidates_jit_cfg", "cycle_candidates_jit", + "cycle_candidates_state_jit", "cycle_candidates_static_jit", + "cycle_candidates_static_state_jit", "cycle_candidates_value_jit", + "cycle_candidates_value_state_jit", "cycle_jit", "cycle_jit_cfg", "cycle_jit_bound_cfg", diff --git a/src/prism_vm_core/protocols.py b/src/prism_vm_core/protocols.py index a3c13e4..a654a6c 100644 --- a/src/prism_vm_core/protocols.py +++ b/src/prism_vm_core/protocols.py @@ -15,17 +15,32 @@ SafeIndexFn, SafeIndexValueFn, ) +from prism_ledger.index import LedgerIndex, LedgerState from prism_vm_core.structures import Arena, CandidateBuffer, Ledger, NodeBatch, Stratum @runtime_checkable class InternFn(Protocol): def __call__( - self, ledger: Ledger, batch_or_ops, a1=None, a2=None + self, + ledger: Ledger, + batch_or_ops, + a1=None, + a2=None, + *, + ledger_index: LedgerIndex | None = None, ) -> Tuple[jnp.ndarray, Ledger]: ... +@runtime_checkable +class InternStateFn(Protocol): + def __call__( + self, state: LedgerState, batch_or_ops, a1=None, a2=None + ) -> Tuple[jnp.ndarray, LedgerState]: + ... + + @runtime_checkable class EmitCandidatesFn(Protocol): def __call__(self, ledger: Ledger, rewrite_ids) -> CandidateBuffer: @@ -179,6 +194,7 @@ def __call__(self, ids): __all__ = [ "PolicyValue", "InternFn", + "InternStateFn", "EmitCandidatesFn", "HostRaiseFn", "NodeBatchFn", diff --git a/tests/conftest.py b/tests/conftest.py index b0cebeb..90d11d9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,13 +1,80 @@ import os import sys +import subprocess from pathlib import Path -import jax import pytest + +def _gpu_total_mb() -> float | None: + try: + out = subprocess.check_output( + [ + "nvidia-smi", + "--query-gpu=memory.total", + "--format=csv,noheader,nounits", + ], + text=True, + ).strip() + except Exception: + return None + if not out: + return None + try: + return float(out.splitlines()[0]) + except Exception: + return None + + +def _set_jax_gpu_memory_limits() -> None: + # Disable preallocation unless explicitly set. + os.environ.setdefault("XLA_PYTHON_CLIENT_PREALLOCATE", "false") + if "XLA_PYTHON_CLIENT_MEM_FRACTION" in os.environ: + return + fraction = os.environ.get("PRISM_JAX_GPU_MEM_FRACTION", "").strip() + if fraction: + os.environ["XLA_PYTHON_CLIENT_MEM_FRACTION"] = fraction + return + cap_mb = os.environ.get("PRISM_JAX_GPU_MEM_CAP_MB", "").strip() + if not cap_mb: + return + try: + cap = float(cap_mb) + except Exception: + return + total = _gpu_total_mb() + if total is None or total <= 0: + return + frac = max(min(cap / total, 1.0), 0.0) + os.environ["XLA_PYTHON_CLIENT_MEM_FRACTION"] = str(frac) + + +def _set_jax_compilation_cache() -> None: + cache_dir = os.environ.get("PRISM_JAX_COMPILATION_CACHE_DIR", "").strip() + if not cache_dir: + cache_dir = os.environ.get("PRISM_JAX_CACHE_DIR", "").strip() + if not cache_dir: + return + cache_path = Path(cache_dir).expanduser() + cache_path.mkdir(parents=True, exist_ok=True) + try: + from jax.experimental import compilation_cache as cc + except Exception: + return + set_cache_dir = getattr(cc, "set_cache_dir", None) + if set_cache_dir is None and hasattr(cc, "compilation_cache"): + set_cache_dir = getattr(cc.compilation_cache, "set_cache_dir", None) + if set_cache_dir is not None: + set_cache_dir(str(cache_path)) + + # Enable strict scatter guard in tests unless explicitly overridden. os.environ.setdefault("PRISM_SCATTER_GUARD", "1") os.environ.setdefault("PRISM_TEST_GUARDS", "1") +_set_jax_gpu_memory_limits() + +import jax +_set_jax_compilation_cache() # Ensure repo root is importable when pytest uses importlib mode. ROOT = os.path.dirname(os.path.dirname(__file__)) @@ -112,7 +179,11 @@ def pytest_configure(config): selector = _parse_milestone_selector(config.getoption("--milestone-band")) if selector is not None: low, high = selector - milestone = high if high is not None else low + if low == 1: + # Treat m1-band runs as baseline coverage (avoid m1-only semantics). + milestone = _parse_milestone(config.getoption("--milestone")) + else: + milestone = high if high is not None else low else: milestone = _parse_milestone(config.getoption("--milestone")) if milestone is None: @@ -139,6 +210,21 @@ def pytest_generate_tests(metafunc): metafunc.parametrize("backend_device", backends, ids=ids, indirect=True) +@pytest.fixture(scope="session", autouse=True) +def _jax_warmup_session() -> None: + if os.environ.get("PRISM_JAX_WARMUP", "").strip().lower() not in { + "1", + "true", + "yes", + }: + return + from tests import harness + + # Warm up the BSP intrinsic pipeline to populate JAX compile caches. + harness.denote_bsp_intrinsic("(add (suc zero) (suc zero))", max_steps=64) + harness.denote_bsp_intrinsic("(add zero (suc (suc zero)))", max_steps=64) + + @pytest.fixture def backend_device(request): backend = getattr(request, "param", None) @@ -149,10 +235,10 @@ def backend_device(request): @pytest.fixture(autouse=True) def _set_default_device(request): - if not request.node.get_closest_marker("backend_matrix"): - yield - return - device = request.getfixturevalue("backend_device") + if request.node.get_closest_marker("backend_matrix"): + device = request.getfixturevalue("backend_device") + else: + device = jax.devices("cpu")[0] with jax.default_device(device): yield diff --git a/tests/harness.py b/tests/harness.py index e165633..25a0531 100644 --- a/tests/harness.py +++ b/tests/harness.py @@ -1,4 +1,6 @@ +import os import re +import time import jax.numpy as jnp @@ -9,6 +11,12 @@ STATUS_CONVERGED = "converged" STATUS_BUDGET_EXHAUSTED = "budget_exhausted" STATUS_ERROR = "error" +_PROFILE_BSP_INTRINSIC = os.environ.get("PRISM_PROFILE_BSP_INTRINSIC", "").lower() in ( + "1", + "true", + "yes", + "on", +) def i32(values): @@ -32,6 +40,11 @@ def intern1(ledger, op, a1, a2): return ids[0], ledger +def init_ledger_state(*, cfg=None): + """Return a LedgerState seeded with an index.""" + return pv.init_ledger_state(cfg=cfg) + + def committed_ids(*values): """Build CommittedIds from values or a single iterable.""" if len(values) == 1 and isinstance(values[0], (list, tuple, jnp.ndarray)): @@ -72,7 +85,7 @@ def make_cnf2_bound_static_cfg( guard_cfg=None, cfg=None, ): - """Return (cfg, policy) with a static policy bound at the edge.""" + """Return a Cnf2BoundConfig with a static policy bound at the edge.""" if cfg is None: cfg = pv.DEFAULT_CNF2_CONFIG if guard_cfg is not None: @@ -84,11 +97,10 @@ def make_cnf2_bound_static_cfg( policy_value=None, context="tests.harness.make_cnf2_bound_static_cfg", ) - bound = pv.cnf2_config_bound(binding, cfg=cfg) - return bound.bind_cfg() + return pv.cnf2_config_bound(binding, cfg=cfg) -_DEFAULT_CNF2_BOUND_CFG, _DEFAULT_CNF2_BOUND_POLICY = make_cnf2_bound_static_cfg() +_DEFAULT_CNF2_BOUND_CFG = make_cnf2_bound_static_cfg() def make_cnf2_bound_value_cfg( @@ -97,7 +109,7 @@ def make_cnf2_bound_value_cfg( guard_cfg=None, cfg=None, ): - """Return (cfg, policy_value) with a value policy bound at the edge.""" + """Return a Cnf2BoundConfig with a value policy bound at the edge.""" if cfg is None: cfg = pv.DEFAULT_CNF2_CONFIG if guard_cfg is not None: @@ -109,8 +121,7 @@ def make_cnf2_bound_value_cfg( policy_value=policy_value, context="tests.harness.make_cnf2_bound_value_cfg", ) - bound = pv.cnf2_config_bound(binding, cfg=cfg) - return bound.bind_cfg() + return pv.cnf2_config_bound(binding, cfg=cfg) def cycle_candidates_static_bound( @@ -124,21 +135,29 @@ def cycle_candidates_static_bound( **kwargs, ): """Run CNF-2 candidates with static policy bound at the edge.""" + if cfg is not None and hasattr(cfg, "cfg"): + intern_cfg = cfg.cfg.intern_cfg + elif isinstance(cfg, pv.Cnf2Config): + intern_cfg = cfg.intern_cfg + else: + intern_cfg = pv.DEFAULT_INTERN_CONFIG + if not isinstance(ledger, pv.LedgerState): + ledger = pv.derive_ledger_state( + ledger, op_buckets_full_range=intern_cfg.op_buckets_full_range + ) if cfg is None and safety_policy is None and guard_cfg is None: cfg = _DEFAULT_CNF2_BOUND_CFG - safety_policy = _DEFAULT_CNF2_BOUND_POLICY - elif cfg is None or safety_policy is None: - cfg, safety_policy = make_cnf2_bound_static_cfg( + elif cfg is None: + cfg = make_cnf2_bound_static_cfg( safety_policy=safety_policy, guard_cfg=guard_cfg, cfg=cfg, ) - return pv.cycle_candidates_static( + return pv.cycle_candidates_bound( ledger, frontier_ids, validate_mode=validate_mode, - cnf2_cfg=cfg, - safe_gather_policy=safety_policy, + cfg=cfg, **kwargs, ) @@ -154,18 +173,27 @@ def cycle_candidates_value_bound( **kwargs, ): """Run CNF-2 candidates with value policy bound at the edge.""" - if cfg is None or policy_value is None: - cfg, policy_value = make_cnf2_bound_value_cfg( + if cfg is not None and hasattr(cfg, "cfg"): + intern_cfg = cfg.cfg.intern_cfg + elif isinstance(cfg, pv.Cnf2Config): + intern_cfg = cfg.intern_cfg + else: + intern_cfg = pv.DEFAULT_INTERN_CONFIG + if not isinstance(ledger, pv.LedgerState): + ledger = pv.derive_ledger_state( + ledger, op_buckets_full_range=intern_cfg.op_buckets_full_range + ) + if cfg is None: + cfg = make_cnf2_bound_value_cfg( policy_value=policy_value, guard_cfg=guard_cfg, cfg=cfg, ) - return pv.cycle_candidates_value( + return pv.cycle_candidates_bound( ledger, frontier_ids, validate_mode=validate_mode, - cnf2_cfg=cfg, - safe_gather_policy_value=policy_value, + cfg=cfg, **kwargs, ) @@ -225,6 +253,11 @@ def make_ic_apply_active_pairs_jit_cfg(**kwargs): return ic.apply_active_pairs_jit_cfg(**kwargs) +def make_ic_apply_active_pairs_jit_runtime(**kwargs): + """Build a jitted IC apply_active_pairs entrypoint from a runtime bundle.""" + return ic.apply_active_pairs_jit_runtime(**kwargs) + + def make_ic_reduce_jit(**kwargs): """Build a jitted IC reduce entrypoint with fixed DI.""" return ic.reduce_jit(**kwargs) @@ -235,6 +268,11 @@ def make_ic_reduce_jit_cfg(**kwargs): return ic.reduce_jit_cfg(**kwargs) +def make_ic_reduce_jit_runtime(**kwargs): + """Build a jitted IC reduce entrypoint from a runtime bundle.""" + return ic.reduce_jit_runtime(**kwargs) + + def make_ic_find_active_pairs_jit(**kwargs): """Build a jitted IC active-pair finder entrypoint with fixed DI.""" return ic.find_active_pairs_jit(**kwargs) @@ -245,6 +283,11 @@ def make_ic_find_active_pairs_jit_cfg(**kwargs): return ic.find_active_pairs_jit_cfg(**kwargs) +def make_ic_find_active_pairs_jit_runtime(**kwargs): + """Build a jitted IC active-pair finder entrypoint from a runtime bundle.""" + return ic.find_active_pairs_jit_runtime(**kwargs) + + def make_ic_compact_active_pairs_jit(**kwargs): """Build a jitted IC compact active-pairs entrypoint with fixed DI.""" return ic.compact_active_pairs_jit(**kwargs) @@ -255,6 +298,11 @@ def make_ic_compact_active_pairs_jit_cfg(**kwargs): return ic.compact_active_pairs_jit_cfg(**kwargs) +def make_ic_compact_active_pairs_jit_runtime(**kwargs): + """Build a jitted IC compact active-pairs entrypoint from a runtime bundle.""" + return ic.compact_active_pairs_jit_runtime(**kwargs) + + def make_ic_compact_active_pairs_result_jit(**kwargs): """Build a jitted IC compact-result active-pairs entrypoint with fixed DI.""" return ic.compact_active_pairs_result_jit(**kwargs) @@ -265,6 +313,11 @@ def make_ic_compact_active_pairs_result_jit_cfg(**kwargs): return ic.compact_active_pairs_result_jit_cfg(**kwargs) +def make_ic_compact_active_pairs_result_jit_runtime(**kwargs): + """Build a jitted IC compact-result active-pairs entrypoint from a runtime bundle.""" + return ic.compact_active_pairs_result_jit_runtime(**kwargs) + + def make_ic_wire_jax_jit(**kwargs): """Build a jitted IC wire entrypoint with fixed DI.""" return ic.wire_jax_jit(**kwargs) @@ -275,6 +328,11 @@ def make_ic_wire_jax_jit_cfg(**kwargs): return ic.wire_jax_jit_cfg(**kwargs) +def make_ic_wire_jax_jit_runtime(**kwargs): + """Build a jitted IC wire entrypoint from a runtime bundle.""" + return ic.wire_jax_jit_runtime(**kwargs) + + def make_ic_wire_jax_safe_jit(**kwargs): """Build a jitted IC NULL-safe wire entrypoint with fixed DI.""" return ic.wire_jax_safe_jit(**kwargs) @@ -285,6 +343,11 @@ def make_ic_wire_jax_safe_jit_cfg(**kwargs): return ic.wire_jax_safe_jit_cfg(**kwargs) +def make_ic_wire_jax_safe_jit_runtime(**kwargs): + """Build a jitted IC NULL-safe wire entrypoint from a runtime bundle.""" + return ic.wire_jax_safe_jit_runtime(**kwargs) + + def make_ic_wire_ptrs_jit(**kwargs): """Build a jitted IC ptr wire entrypoint with fixed DI.""" return ic.wire_ptrs_jit(**kwargs) @@ -295,6 +358,11 @@ def make_ic_wire_ptrs_jit_cfg(**kwargs): return ic.wire_ptrs_jit_cfg(**kwargs) +def make_ic_wire_ptrs_jit_runtime(**kwargs): + """Build a jitted IC ptr wire entrypoint from a runtime bundle.""" + return ic.wire_ptrs_jit_runtime(**kwargs) + + def make_ic_wire_pairs_jit(**kwargs): """Build a jitted IC wire-pairs entrypoint with fixed DI.""" return ic.wire_pairs_jit(**kwargs) @@ -305,6 +373,11 @@ def make_ic_wire_pairs_jit_cfg(**kwargs): return ic.wire_pairs_jit_cfg(**kwargs) +def make_ic_wire_pairs_jit_runtime(**kwargs): + """Build a jitted IC wire-pairs entrypoint from a runtime bundle.""" + return ic.wire_pairs_jit_runtime(**kwargs) + + def make_ic_wire_ptr_pairs_jit(**kwargs): """Build a jitted IC ptr wire-pairs entrypoint with fixed DI.""" return ic.wire_ptr_pairs_jit(**kwargs) @@ -315,6 +388,11 @@ def make_ic_wire_ptr_pairs_jit_cfg(**kwargs): return ic.wire_ptr_pairs_jit_cfg(**kwargs) +def make_ic_wire_ptr_pairs_jit_runtime(**kwargs): + """Build a jitted IC ptr wire-pairs entrypoint from a runtime bundle.""" + return ic.wire_ptr_pairs_jit_runtime(**kwargs) + + def make_ic_wire_star_jit(**kwargs): """Build a jitted IC wire-star entrypoint with fixed DI.""" return ic.wire_star_jit(**kwargs) @@ -325,6 +403,11 @@ def make_ic_wire_star_jit_cfg(**kwargs): return ic.wire_star_jit_cfg(**kwargs) +def make_ic_wire_star_jit_runtime(**kwargs): + """Build a jitted IC wire-star entrypoint from a runtime bundle.""" + return ic.wire_star_jit_runtime(**kwargs) + + def tokenize(expr): return TOKEN_RE.findall(expr) @@ -443,6 +526,18 @@ def pretty_bsp_intrinsic(expr, max_steps=64, vm=None, **kwargs): def denote_pretty_bsp_intrinsic(expr, max_steps=64, vm=None, **kwargs): + if _PROFILE_BSP_INTRINSIC: + start = time.perf_counter() + vm, ptr = denote_bsp_intrinsic( + expr, max_steps=max_steps, vm=vm, **kwargs + ) + pretty = vm.decode(ptr) + elapsed = time.perf_counter() - start + print( + f"[profile:bsp_intrinsic] elapsed_s={elapsed:.3f} " + f"max_steps={max_steps} expr={expr!r}" + ) + return pretty vm, ptr = denote_bsp_intrinsic(expr, max_steps=max_steps, vm=vm, **kwargs) return vm.decode(ptr) @@ -568,13 +663,8 @@ def assert_baseline_equals_bsp_candidates(expr, max_steps=64, validate_mode=pv.V def run_arena( expr, steps=4, - do_sort=True, - use_morton=False, - block_size=None, - l2_block_size=None, - l1_block_size=None, - do_global=False, *, + sort_cfg: "pv.ArenaSortConfig" | None = None, cycle_fn=None, cycle_kwargs=None, ): @@ -583,13 +673,10 @@ def run_arena( arena = vm.arena if cycle_fn is None: cycle_fn = pv.cycle + if sort_cfg is None: + sort_cfg = pv.ArenaSortConfig() cycle_kwargs = { - "do_sort": do_sort, - "use_morton": use_morton, - "block_size": block_size, - "l2_block_size": l2_block_size, - "l1_block_size": l1_block_size, - "do_global": do_global, + "sort_cfg": sort_cfg, } if cycle_kwargs is None: cycle_kwargs = {} @@ -603,33 +690,29 @@ def run_arena( def denote_pretty_arena( expr, steps=4, - do_sort=True, - use_morton=False, - block_size=None, - l2_block_size=None, - l1_block_size=None, - do_global=False, *, + sort_cfg: "pv.ArenaSortConfig" | None = None, cycle_fn=None, cycle_kwargs=None, ): return run_arena( expr, steps=steps, - do_sort=do_sort, - use_morton=use_morton, - block_size=block_size, - l2_block_size=l2_block_size, - l1_block_size=l1_block_size, - do_global=do_global, + sort_cfg=sort_cfg, cycle_fn=cycle_fn, cycle_kwargs=cycle_kwargs, ) def assert_arena_schedule_invariance(expr, steps=4): - no_sort = denote_pretty_arena(expr, steps=steps, do_sort=False, use_morton=False) - rank_sort = denote_pretty_arena(expr, steps=steps, do_sort=True, use_morton=False) - morton_sort = denote_pretty_arena(expr, steps=steps, do_sort=True, use_morton=True) + no_sort = denote_pretty_arena( + expr, steps=steps, sort_cfg=pv.ArenaSortConfig(do_sort=False) + ) + rank_sort = denote_pretty_arena( + expr, steps=steps, sort_cfg=pv.ArenaSortConfig(do_sort=True, use_morton=False) + ) + morton_sort = denote_pretty_arena( + expr, steps=steps, sort_cfg=pv.ArenaSortConfig(do_sort=True, use_morton=True) + ) assert no_sort == rank_sort assert no_sort == morton_sort diff --git a/tests/min_prism/harness.py b/tests/min_prism/harness.py index 513864c..fd390f3 100644 --- a/tests/min_prism/harness.py +++ b/tests/min_prism/harness.py @@ -55,6 +55,7 @@ def make_cnf2_bound_static_cfg( guard_cfg=None, cfg=None, ): + """Return a Cnf2BoundConfig with a static policy bound at the edge.""" if cfg is None: cfg = pv.DEFAULT_CNF2_CONFIG if guard_cfg is not None: @@ -66,11 +67,10 @@ def make_cnf2_bound_static_cfg( policy_value=None, context="tests.min_prism.harness.make_cnf2_bound_static_cfg", ) - bound = pv.cnf2_config_bound(binding, cfg=cfg) - return bound.bind_cfg() + return pv.cnf2_config_bound(binding, cfg=cfg) -_DEFAULT_CNF2_BOUND_CFG, _DEFAULT_CNF2_BOUND_POLICY = make_cnf2_bound_static_cfg() +_DEFAULT_CNF2_BOUND_CFG = make_cnf2_bound_static_cfg() def cycle_candidates_static_bound( @@ -82,20 +82,28 @@ def cycle_candidates_static_bound( guard_cfg=None, **kwargs, ): + if cfg is not None and hasattr(cfg, "cfg"): + intern_cfg = cfg.cfg.intern_cfg + elif isinstance(cfg, pv.Cnf2Config): + intern_cfg = cfg.intern_cfg + else: + intern_cfg = pv.DEFAULT_INTERN_CONFIG + if not isinstance(ledger, pv.LedgerState): + ledger = pv.derive_ledger_state( + ledger, op_buckets_full_range=intern_cfg.op_buckets_full_range + ) if cfg is None and safety_policy is None and guard_cfg is None: cfg = _DEFAULT_CNF2_BOUND_CFG - safety_policy = _DEFAULT_CNF2_BOUND_POLICY - elif cfg is None or safety_policy is None: - cfg, safety_policy = make_cnf2_bound_static_cfg( + elif cfg is None: + cfg = make_cnf2_bound_static_cfg( safety_policy=safety_policy, guard_cfg=guard_cfg, cfg=cfg, ) - return pv.cycle_candidates_static( + return pv.cycle_candidates_bound( ledger, frontier_ids, - cnf2_cfg=cfg, - safe_gather_policy=safety_policy, + cfg=cfg, **kwargs, ) diff --git a/tests/test_arena_denotation_invariance.py b/tests/test_arena_denotation_invariance.py index e8c8560..74def1f 100644 --- a/tests/test_arena_denotation_invariance.py +++ b/tests/test_arena_denotation_invariance.py @@ -25,13 +25,13 @@ def test_arena_denotation_invariance_random_suite(): for _ in range(10): expr = _rand_expr(rng, 4) no_sort = harness.denote_pretty_arena( - expr, steps=4, do_sort=False, use_morton=False + expr, steps=4, sort_cfg=pv.ArenaSortConfig(do_sort=False) ) rank_sort = harness.denote_pretty_arena( - expr, steps=4, do_sort=True, use_morton=False + expr, steps=4, sort_cfg=pv.ArenaSortConfig(do_sort=True, use_morton=False) ) morton_sort = harness.denote_pretty_arena( - expr, steps=4, do_sort=True, use_morton=True + expr, steps=4, sort_cfg=pv.ArenaSortConfig(do_sort=True, use_morton=True) ) assert no_sort == rank_sort assert no_sort == morton_sort @@ -49,16 +49,18 @@ def test_arena_denotation_invariance_blocked_small_suite(): ] for expr in cases: no_sort = harness.denote_pretty_arena( - expr, steps=4, do_sort=False, use_morton=False + expr, steps=4, sort_cfg=pv.ArenaSortConfig(do_sort=False) ) rank_sort = harness.denote_pretty_arena( - expr, steps=4, do_sort=True, use_morton=False + expr, steps=4, sort_cfg=pv.ArenaSortConfig(do_sort=True, use_morton=False) ) morton_sort = harness.denote_pretty_arena( - expr, steps=4, do_sort=True, use_morton=True + expr, steps=4, sort_cfg=pv.ArenaSortConfig(do_sort=True, use_morton=True) ) blocked = harness.denote_pretty_arena( - expr, steps=4, do_sort=True, block_size=block_size + expr, + steps=4, + sort_cfg=pv.ArenaSortConfig(do_sort=True, block_size=block_size), ) assert no_sort == rank_sort assert no_sort == morton_sort diff --git a/tests/test_candidate_cycle.py b/tests/test_candidate_cycle.py index faf5992..8674700 100644 --- a/tests/test_candidate_cycle.py +++ b/tests/test_candidate_cycle.py @@ -74,14 +74,6 @@ def _assert_ledger_snapshot(ledger, snapshot): assert (field == expected).all() -def test_cycle_candidates_rejects_when_cnf2_disabled(monkeypatch): - _require_cycle_candidates() - ledger = pv.init_ledger() - frontier = committed_ids(pv.ZERO_PTR) - with pytest.raises(RuntimeError, match="cycle_candidates disabled until m2"): - cycle_candidates(ledger, frontier, cnf2_enabled_fn=lambda: False) - - def test_cycle_candidates_empty_frontier_no_mutation(): _require_cycle_candidates() ledger = pv.init_ledger() diff --git a/tests/test_cqrs_replay.py b/tests/test_cqrs_replay.py index 0c4d080..19fbcda 100644 --- a/tests/test_cqrs_replay.py +++ b/tests/test_cqrs_replay.py @@ -24,8 +24,6 @@ def test_cqrs_replay_matches_intrinsic_cycles(): def test_cqrs_replay_matches_cnf2_cycles(): - if not pv._cnf2_enabled(): - pytest.skip("CNF-2 disabled") vm = pv.PrismVM_BSP() root_ptr = harness.parse_expr(vm, "(mul (suc zero) (suc zero))") frontier = pv._committed_ids(jnp.array([int(root_ptr)], dtype=jnp.int32)) diff --git a/tests/test_cycle.py b/tests/test_cycle.py index 4307862..f02b2e4 100644 --- a/tests/test_cycle.py +++ b/tests/test_cycle.py @@ -8,6 +8,7 @@ def test_cycle_root_remap(): assert hasattr(pv, "cycle"), "cycle missing" + assert hasattr(pv, "ArenaSortConfig"), "ArenaSortConfig missing" arena = pv.init_arena() arena = arena._replace( opcode=arena.opcode.at[2].set(pv.OP_ADD).at[3].set(pv.OP_SUC), @@ -28,6 +29,7 @@ def test_cycle_root_remap(): def test_cycle_without_sort_keeps_root(): assert hasattr(pv, "cycle"), "cycle missing" + assert hasattr(pv, "ArenaSortConfig"), "ArenaSortConfig missing" arena = pv.init_arena() arena = arena._replace( opcode=arena.opcode.at[3].set(pv.OP_SUC), @@ -35,7 +37,8 @@ def test_cycle_without_sort_keeps_root(): arg2=arena.arg2.at[3].set(0), count=jnp.array(4, dtype=jnp.int32), ) - updated, new_root = pv.cycle(arena, 3, do_sort=False) + sort_cfg = pv.ArenaSortConfig(do_sort=False) + updated, new_root = pv.cycle(arena, 3, sort_cfg=sort_cfg) assert new_root.shape == () assert new_root.dtype == jnp.int32 assert int(new_root) == 3 diff --git a/tests/test_damage_metrics.py b/tests/test_damage_metrics.py index eb51d6f..ec6bf01 100644 --- a/tests/test_damage_metrics.py +++ b/tests/test_damage_metrics.py @@ -11,7 +11,9 @@ def test_damage_metrics_disabled_noop(monkeypatch): monkeypatch.delenv("PRISM_DAMAGE_TILE_SIZE", raising=False) pv.damage_metrics_reset() _ = harness.denote_pretty_arena( - "(add (suc zero) (suc zero))", steps=2, do_sort=True, use_morton=True + "(add (suc zero) (suc zero))", + steps=2, + sort_cfg=pv.ArenaSortConfig(do_sort=True, use_morton=True), ) metrics = pv.damage_metrics_get() assert metrics["cycles"] == 0 @@ -25,13 +27,13 @@ def test_damage_metrics_no_semantic_effect(monkeypatch): monkeypatch.delenv("PRISM_DAMAGE_TILE_SIZE", raising=False) pv.damage_metrics_reset() baseline = harness.denote_pretty_arena( - expr, steps=2, do_sort=True, use_morton=True + expr, steps=2, sort_cfg=pv.ArenaSortConfig(do_sort=True, use_morton=True) ) monkeypatch.setenv("PRISM_DAMAGE_METRICS", "1") monkeypatch.setenv("PRISM_DAMAGE_TILE_SIZE", "2") pv.damage_metrics_reset() with_metrics = harness.denote_pretty_arena( - expr, steps=2, do_sort=True, use_morton=True + expr, steps=2, sort_cfg=pv.ArenaSortConfig(do_sort=True, use_morton=True) ) assert baseline == with_metrics metrics = pv.damage_metrics_get() diff --git a/tests/test_harness_cnf2_cfg_smoke.py b/tests/test_harness_cnf2_cfg_smoke.py index c5b4676..aa3748f 100644 --- a/tests/test_harness_cnf2_cfg_smoke.py +++ b/tests/test_harness_cnf2_cfg_smoke.py @@ -6,6 +6,7 @@ def test_harness_cnf2_cfg_smoke(): ledger = pv.init_ledger() + ledger_state = harness.init_ledger_state() cfg = pv.Cnf2Config() frontier = jnp.zeros((0,), dtype=jnp.int32) @@ -17,6 +18,9 @@ def test_harness_cnf2_cfg_smoke(): ids, ledger2, count3 = pv.intern_candidates_cfg( ledger, candidates, cfg=cfg ) + ids_state, ledger_state2, count_state = pv.intern_candidates_cfg( + ledger_state, candidates, cfg=cfg + ) _ = pv.scatter_compacted_ids_cfg( idx, jnp.zeros_like(idx), jnp.int32(0), candidates.enabled.shape[0], cfg=cfg ) @@ -25,8 +29,12 @@ def test_harness_cnf2_cfg_smoke(): assert compacted2 is not None assert ids is not None assert ledger2 is not None + assert ids_state is not None + assert ledger_state2 is not None + assert isinstance(ledger_state2, pv.LedgerState) assert int(count) == int(count2) assert int(count3) == int(count) + assert int(count_state) == int(count) emit_jit = harness.make_emit_candidates_jit_cfg() compact_jit = harness.make_compact_candidates_jit_cfg() diff --git a/tests/test_harness_jit_cfg_smoke.py b/tests/test_harness_jit_cfg_smoke.py index cee68b0..de5c9c5 100644 --- a/tests/test_harness_jit_cfg_smoke.py +++ b/tests/test_harness_jit_cfg_smoke.py @@ -14,7 +14,9 @@ def test_harness_jit_cfg_smoke(): arena_out = op_interact(arena) assert arena_out is not None - cycle = harness.make_cycle_jit_cfg(do_sort=False) + cycle = harness.make_cycle_jit_cfg( + sort_cfg=pv.ArenaSortConfig(do_sort=False) + ) arena_out, root_out = cycle(arena, root) assert arena_out is not None assert root_out is not None diff --git a/tests/test_ic_guard_cfg_smoke.py b/tests/test_ic_guard_cfg_smoke.py index 919146c..7d4c7bf 100644 --- a/tests/test_ic_guard_cfg_smoke.py +++ b/tests/test_ic_guard_cfg_smoke.py @@ -8,15 +8,17 @@ def test_graph_config_with_guard_smoke(): state = ic.ic_init(4) guard_cfg = ic.ICGuardConfig() graph_cfg = ic.graph_config_with_guard(guard_cfg=guard_cfg) - state = ic.ic_wire_jax_cfg( - state, + endpoints = ic.WireEndpoints( jnp.uint32(1), jnp.uint32(ic.PORT_PRINCIPAL), jnp.uint32(2), jnp.uint32(ic.PORT_PRINCIPAL), + ) + state = ic.ic_wire_jax_cfg( + state, + endpoints, cfg=graph_cfg, ) state.ports.block_until_ready() ptr = state.ports[1, ic.PORT_PRINCIPAL] assert int(ptr) != 0 - diff --git a/tests/test_ic_vm.py b/tests/test_ic_vm.py index d407d0e..2aa7680 100644 --- a/tests/test_ic_vm.py +++ b/tests/test_ic_vm.py @@ -17,7 +17,10 @@ def test_ic_port_encode_decode_roundtrip(): def test_ic_wire_jax_roundtrip(): state = ic.ic_init(4) - state = ic.ic_wire_jax(state, jnp.uint32(1), jnp.uint32(0), jnp.uint32(2), jnp.uint32(0)) + endpoints = ic.WireEndpoints( + jnp.uint32(1), jnp.uint32(0), jnp.uint32(2), jnp.uint32(0) + ) + state = ic.ic_wire_jax(state, endpoints) n1, p1 = ic.decode_port(state.ports[1, 0]) n2, p2 = ic.decode_port(state.ports[2, 0]) assert int(n1) == 2 @@ -29,21 +32,22 @@ def test_ic_wire_jax_roundtrip(): def test_ic_wire_jax_safe_noop(): state = ic.ic_init(4) ports_before = state.ports - state = ic.ic_wire_jax_safe( - state, jnp.uint32(0), jnp.uint32(0), jnp.uint32(2), jnp.uint32(0) + endpoints = ic.WireEndpoints( + jnp.uint32(0), jnp.uint32(0), jnp.uint32(2), jnp.uint32(0) ) + state = ic.ic_wire_jax_safe(state, endpoints) assert jnp.array_equal(state.ports, ports_before) def test_ic_wire_pairs_jax_basic(): state = ic.ic_init(6) - state = ic.ic_wire_pairs_jax( - state, + endpoints = ic.WireEndpoints( jnp.array([1, 3], dtype=jnp.uint32), jnp.array([0, 1], dtype=jnp.uint32), jnp.array([2, 4], dtype=jnp.uint32), jnp.array([0, 2], dtype=jnp.uint32), ) + state = ic.ic_wire_pairs_jax(state, endpoints) n1, p1 = ic.decode_port(state.ports[1, 0]) n2, p2 = ic.decode_port(state.ports[2, 0]) assert int(n1) == 2 @@ -74,7 +78,7 @@ def test_ic_wire_ptr_pairs_jax_null_skip(): ], dtype=jnp.uint32, ) - state = ic.ic_wire_ptr_pairs_jax(state, ptr_a, ptr_b) + state = ic.ic_wire_ptr_pairs_jax(state, ic.WirePtrPair(ptr_a, ptr_b)) n1, p1 = ic.decode_port(state.ports[1, 0]) n2, p2 = ic.decode_port(state.ports[2, 0]) assert int(n1) == 2 @@ -88,9 +92,10 @@ def test_ic_wire_star_jax_leafs_connect(): state = ic.ic_init(5) leaf_nodes = jnp.array([2, 3], dtype=jnp.uint32) leaf_ports = jnp.array([0, 0], dtype=jnp.uint32) - state = ic.ic_wire_star_jax( - state, jnp.uint32(1), jnp.uint32(0), leaf_nodes, leaf_ports + endpoints = ic.WireStarEndpoints( + jnp.uint32(1), jnp.uint32(0), leaf_nodes, leaf_ports ) + state = ic.ic_wire_star_jax(state, endpoints) for leaf in (2, 3): node, port = ic.decode_port(state.ports[leaf, 0]) assert int(node) == 1 diff --git a/tests/test_q_projection.py b/tests/test_q_projection.py index 0813bc8..f86237c 100644 --- a/tests/test_q_projection.py +++ b/tests/test_q_projection.py @@ -20,7 +20,7 @@ def test_manifest_q_projection_matches_baseline(): def test_arena_q_projection_invariant_across_schedule(): expr = "(add (suc zero) (suc zero))" - def _project(do_sort, use_morton): + def _project(sort_cfg): vm = pv.PrismVM_BSP_Legacy() root_ptr = vm.parse(harness.tokenize(expr)) arena = vm.arena @@ -28,8 +28,7 @@ def _project(do_sort, use_morton): arena, root_ptr = pv.cycle( arena, root_ptr, - do_sort=do_sort, - use_morton=use_morton, + sort_cfg=sort_cfg, ) root_ptr = pv._arena_ptr(pv._host_int_value(root_ptr)) ledger, root_id = pv.project_arena_to_ledger(arena, root_ptr) @@ -37,6 +36,6 @@ def _project(do_sort, use_morton): vm_bsp.ledger = ledger return vm_bsp.decode(root_id) - no_sort = _project(do_sort=False, use_morton=False) - morton = _project(do_sort=True, use_morton=True) + no_sort = _project(pv.ArenaSortConfig(do_sort=False, use_morton=False)) + morton = _project(pv.ArenaSortConfig(do_sort=True, use_morton=True)) assert no_sort == morton diff --git a/tests/test_servo_invariance.py b/tests/test_servo_invariance.py index aaa2935..476ccac 100644 --- a/tests/test_servo_invariance.py +++ b/tests/test_servo_invariance.py @@ -18,11 +18,11 @@ def test_servo_denotation_invariance(monkeypatch): for expr in exprs: monkeypatch.setenv("PRISM_ENABLE_SERVO", "0") base = harness.denote_pretty_arena( - expr, steps=4, do_sort=True, use_morton=True + expr, steps=4, sort_cfg=pv.ArenaSortConfig(do_sort=True, use_morton=True) ) monkeypatch.setenv("PRISM_ENABLE_SERVO", "1") servo = harness.denote_pretty_arena( - expr, steps=4, do_sort=True, use_morton=True + expr, steps=4, sort_cfg=pv.ArenaSortConfig(do_sort=True, use_morton=True) ) assert base == servo