From 44f7258b16e6078bb5c0fbfdcd308bd4fa544abc Mon Sep 17 00:00:00 2001 From: Shay Date: Tue, 14 Jul 2026 20:23:08 -0700 Subject: [PATCH] fix(algebra): P11a physics hot paths via algebra.backend (Rust-ready) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stop wave/Third-Door physics from bypassing native dispatch: - Route geometric_product / versor_apply / versor_condition / cga_inner through algebra.backend in wave_manifold, goldtether, trajectory, dynamic_manifold, surprise, holographic_vault, atlas_packing, biography, self_authorship. - Backend: dtype-aware Rust use — f32 workloads use core_rs; f64 wave residual pins keep Python SOT until f64 GP parity exists. Coerce arrays for PyO3 bindings; fail soft to Python. - AST hygiene pin: tests/test_physics_backend_dispatch_hygiene.py - Docs: RUST.md, runtime_contracts, fidelity (ADR-0235 / UMA hygiene). Verified: wave + cohesion suites green default and CORE_BACKEND=rust (with core_rs built). MLX still exploratory off-serve. --- algebra/backend.py | 56 +++++++++-- core/physics/atlas_packing.py | 3 +- core/physics/biography.py | 3 +- core/physics/dynamic_manifold.py | 4 +- core/physics/goldtether.py | 5 +- core/physics/holographic_vault.py | 2 +- core/physics/self_authorship.py | 2 +- core/physics/surprise.py | 3 +- core/physics/trajectory_invariants.py | 5 +- core/physics/wave_manifold.py | 17 +++- docs/RUST.md | 26 ++++++ .../research/third-door-blueprint-fidelity.md | 1 + docs/specs/runtime_contracts.md | 14 +++ .../test_physics_backend_dispatch_hygiene.py | 92 +++++++++++++++++++ 14 files changed, 208 insertions(+), 25 deletions(-) create mode 100644 tests/test_physics_backend_dispatch_hygiene.py diff --git a/algebra/backend.py b/algebra/backend.py index b3a32dd9..92941e61 100644 --- a/algebra/backend.py +++ b/algebra/backend.py @@ -48,9 +48,36 @@ def _build_cga_inner_metric() -> np.ndarray: _CGA_INNER_METRIC: np.ndarray = _build_cga_inner_metric() +def _f32_1d32(x: np.ndarray) -> np.ndarray: + """Contiguous f32 (32,) for core_rs PyReadonlyArray1 bindings.""" + return np.ascontiguousarray( + np.asarray(x, dtype=np.float32).reshape(-1)[:32], dtype=np.float32 + ) + + +def _is_f32_workload(*arrays: np.ndarray) -> bool: + """True when all arrays are float32 (Rust f32 kernel is parity-safe). + + float64 wave residual pins require Python SOT (or future f64 Rust GP). + Forcing f64→f32 would break 1e-9 chiral / leakage pins (ADR-0241). + """ + return all(np.asarray(a).dtype == np.float32 for a in arrays) + + def geometric_product(A: np.ndarray, B: np.ndarray) -> np.ndarray: - if _RUST: - return np.asarray(_rs.geometric_product(A, B), dtype=np.float32) + """Cl(4,1) geometric product via Rust f32 when enabled, else Python. + + float64 inputs always use the pure-Python product (semantic SOT for + wave-field residual math). float32 field-graph workloads get Rust. + """ + if _RUST and _is_f32_workload(A, B): + try: + return np.asarray( + _rs.geometric_product(_f32_1d32(A), _f32_1d32(B)), + dtype=np.float32, + ) + except (AttributeError, TypeError, ValueError, Exception): + pass from algebra.cl41 import geometric_product as _gp return _gp(A, B) @@ -67,25 +94,34 @@ def versor_apply(V: np.ndarray, F: np.ndarray) -> np.ndarray: """ if _RUST: try: - Vc = np.ascontiguousarray(V, dtype=np.float64) - Fc = np.ascontiguousarray(F, dtype=np.float64) - return np.asarray(_rs.versor_apply_with_closure_f64(Vc, Fc), dtype=np.float64) - except (AttributeError, Exception): + Vc = np.ascontiguousarray(V, dtype=np.float64).reshape(-1)[:32] + Fc = np.ascontiguousarray(F, dtype=np.float64).reshape(-1)[:32] + return np.asarray( + _rs.versor_apply_with_closure_f64(Vc, Fc), dtype=np.float64 + ) + except (AttributeError, TypeError, ValueError, Exception): pass from algebra.versor import versor_apply as _va return _va(V, F) def versor_condition(F: np.ndarray) -> float: - if _RUST: - return float(_rs.versor_condition(F)) + """Versor residual. Rust f32 path only for float32 inputs (see GP note).""" + if _RUST and _is_f32_workload(F): + try: + return float(_rs.versor_condition(_f32_1d32(F))) + except (AttributeError, TypeError, ValueError, Exception): + pass from algebra.versor import versor_condition as _vc return _vc(F) def cga_inner(X: np.ndarray, Y: np.ndarray) -> float: - if _RUST: - return float(_rs.cga_inner(X, Y)) + if _RUST and _is_f32_workload(X, Y): + try: + return float(_rs.cga_inner(_f32_1d32(X), _f32_1d32(Y))) + except (AttributeError, TypeError, ValueError, Exception): + pass from algebra.cga import cga_inner as _ci return _ci(X, Y) diff --git a/core/physics/atlas_packing.py b/core/physics/atlas_packing.py index e2f590d9..25a2adba 100644 --- a/core/physics/atlas_packing.py +++ b/core/physics/atlas_packing.py @@ -22,7 +22,8 @@ from typing import Sequence import numpy as np -from algebra.cga import cga_inner, embed_point +from algebra.backend import cga_inner +from algebra.cga import embed_point from core.physics.wave_manifold import WaveManifold PHI = (1.0 + math.sqrt(5.0)) / 2.0 diff --git a/core/physics/biography.py b/core/physics/biography.py index 62d2f9a9..09ba3dae 100644 --- a/core/physics/biography.py +++ b/core/physics/biography.py @@ -16,9 +16,10 @@ from typing import Any, Sequence import numpy as np +from algebra.backend import versor_condition from algebra.cl41 import N_COMPONENTS from algebra.holonomy import holonomy_encode, holonomy_similarity -from algebra.versor import unitize_versor, versor_condition +from algebra.versor import unitize_versor from core.physics.wave_manifold import WaveManifold _CLOSURE_TOL = 1e-6 diff --git a/core/physics/dynamic_manifold.py b/core/physics/dynamic_manifold.py index 36ff87e4..cec17247 100644 --- a/core/physics/dynamic_manifold.py +++ b/core/physics/dynamic_manifold.py @@ -16,8 +16,9 @@ from typing import Optional, Sequence, Tuple, Union import numpy as np +from algebra.backend import geometric_product, versor_condition from algebra.cga import is_null -from algebra.cl41 import N_COMPONENTS, geometric_product, grade_project, reverse +from algebra.cl41 import N_COMPONENTS, grade_project, reverse from algebra.null_point import ( NullPointRecoveryError, dilator, @@ -26,7 +27,6 @@ from algebra.null_point import ( translator, ) from algebra.rotor import rotor_power, word_transition_rotor -from algebra.versor import versor_condition _CLOSURE_TOL = 1e-6 _NEAR_ZERO = 1e-12 diff --git a/core/physics/goldtether.py b/core/physics/goldtether.py index c6cbc708..f456a3fc 100644 --- a/core/physics/goldtether.py +++ b/core/physics/goldtether.py @@ -28,9 +28,10 @@ from typing import Any, Literal, Optional, Tuple import numpy as np -from algebra.cl41 import N_COMPONENTS, geometric_product, reverse +from algebra.backend import geometric_product, versor_condition +from algebra.cl41 import N_COMPONENTS, reverse from algebra.rotor import rotor_power, word_transition_rotor -from algebra.versor import versor_condition, versor_unit_residual +from algebra.versor import versor_unit_residual from core.physics.wave_manifold import WaveManifold _CLOSURE_TOL = 1e-6 diff --git a/core/physics/holographic_vault.py b/core/physics/holographic_vault.py index 261a7dd7..1ac768c9 100644 --- a/core/physics/holographic_vault.py +++ b/core/physics/holographic_vault.py @@ -24,8 +24,8 @@ from typing import Any, Optional import numpy as np +from algebra.backend import versor_condition from algebra.cl41 import N_COMPONENTS -from algebra.versor import versor_condition from core.physics.wave_manifold import WaveManifold from teaching.epistemic import EpistemicStatus from vault.store import VaultStore, _parse_entry_status, _status_admits diff --git a/core/physics/self_authorship.py b/core/physics/self_authorship.py index 95faccf4..55ef6f88 100644 --- a/core/physics/self_authorship.py +++ b/core/physics/self_authorship.py @@ -9,8 +9,8 @@ from typing import Any, Mapping, Sequence import numpy as np +from algebra.backend import versor_condition from algebra.cl41 import N_COMPONENTS -from algebra.versor import versor_condition from core.physics.dynamic_manifold import conformal_procrustes from core.physics.goldtether import GoldTetherMonitor, coherence_residual from core.physics.surprise import dual_procrustes_surprise, surprise_residual diff --git a/core/physics/surprise.py b/core/physics/surprise.py index 6c66756c..27bcc483 100644 --- a/core/physics/surprise.py +++ b/core/physics/surprise.py @@ -39,9 +39,8 @@ from typing import Optional, Sequence, Tuple, Union import numpy as np -from algebra.cga import cga_inner +from algebra.backend import cga_inner, versor_condition from algebra.cl41 import N_COMPONENTS, grade_project -from algebra.versor import versor_condition from core.physics.dynamic_manifold import conformal_procrustes from core.physics.wave_manifold import WaveManifold, WaveSpectralLeakageError diff --git a/core/physics/trajectory_invariants.py b/core/physics/trajectory_invariants.py index b53f6bcf..c51c6649 100644 --- a/core/physics/trajectory_invariants.py +++ b/core/physics/trajectory_invariants.py @@ -22,8 +22,9 @@ from typing import Sequence import numpy as np -from algebra.cl41 import N_COMPONENTS, geometric_product, reverse -from algebra.versor import versor_condition, versor_unit_residual +from algebra.backend import geometric_product, versor_condition +from algebra.cl41 import N_COMPONENTS, reverse +from algebra.versor import versor_unit_residual _CLOSURE_TOL = 1e-6 _DEFAULT_EPS_TRAJECTORY = 1e-5 diff --git a/core/physics/wave_manifold.py b/core/physics/wave_manifold.py index abaef18a..e452c51c 100644 --- a/core/physics/wave_manifold.py +++ b/core/physics/wave_manifold.py @@ -12,6 +12,12 @@ Continuous multivector wave fields ψ ∈ ℝ³² under: Algebra-native only (algebra/*). No scipy-as-truth. No teaching/vault imports. Off-serving until explicit gates; dual-checked unitary residual. + +**Backend dispatch (P11a / ADR-0235):** Cl(4,1) hot ops go through +``algebra.backend`` so ``CORE_BACKEND=rust`` can accelerate them when +``core_rs`` is built. Python remains semantic source of truth when Rust +is unset. Helpers without a Rust path (``reverse``, ``scalar_part``, +``versor_unit_residual``) stay on pure algebra modules. """ from __future__ import annotations @@ -20,9 +26,14 @@ from typing import Any, Sequence, Tuple import numpy as np -from algebra.cga import cga_inner -from algebra.cl41 import N_COMPONENTS, geometric_product, reverse, scalar_part -from algebra.versor import versor_apply, versor_condition, versor_unit_residual +from algebra.backend import ( + cga_inner, + geometric_product, + versor_apply, + versor_condition, +) +from algebra.cl41 import N_COMPONENTS, reverse, scalar_part +from algebra.versor import versor_unit_residual _CLOSURE_TOL = 1e-6 _NEAR_ZERO = 1e-12 diff --git a/docs/RUST.md b/docs/RUST.md index 4a6292e5..4fca20a8 100644 --- a/docs/RUST.md +++ b/docs/RUST.md @@ -1,5 +1,31 @@ # Rust Extension (core-rs) +## Physics hot-path hygiene (P11a) + +Third-Door / ADR-0241 physics modules must import Cl(4,1) multiplies and +closure residual helpers from **`algebra.backend`**, not directly from +`algebra.cl41` / `algebra.versor` / `algebra.cga` for: + +- `geometric_product` +- `versor_apply` +- `versor_condition` +- `cga_inner` + +Pinned by `tests/test_physics_backend_dispatch_hygiene.py`. + +```bash +# Default: pure Python (semantic SOT) +uv run pytest tests/test_adr_0241_wave_manifold.py -q + +# Apple Silicon / native acceleration (after maturin build) +export CORE_BACKEND=rust +uv run --with maturin maturin develop --release --manifest-path core-rs/Cargo.toml +uv run python -c "from algebra.backend import using_rust; assert using_rust()" +``` + +MLX remains an **exploratory** UMA lane (ADR-0235); not required for serving. + + ## Why Rust The active Rust extension is an opt-in native substrate for parity-gated hot diff --git a/docs/research/third-door-blueprint-fidelity.md b/docs/research/third-door-blueprint-fidelity.md index 72e21fb6..97622888 100644 --- a/docs/research/third-door-blueprint-fidelity.md +++ b/docs/research/third-door-blueprint-fidelity.md @@ -306,6 +306,7 @@ PY | Fibonacci-word scheduler (V4) | 🟢 `fibonacci_word_schedule` (telemetry only) | | Fibonacci anyons (V5) | 🟢 quarantine package only; zero production imports | | Sensorium → ψ feed (I-04) | 🟢 `sensorium_wave_feed` (fake packets + real \(\rho\)) | +| Physics Cl(4,1) via `algebra.backend` (P11a) | 🟢 wave/goldtether/trajectory/procrustes/surprise/vault/packing; AST pin; Rust when `CORE_BACKEND=rust` | | Contemplation Trace A SPECULATIVE holographic seal (P9) | 🟢 `core/contemplation/wave_seam.py` (hypothesis vs COHERENT evidence) | | Energy boundary + multi-scale τ (P10 Trace B) | 🟢 `wave_energy_boundary` (wave residual → energy/trajectory; τ_n=F_n·τ_0; E0–E1 crystallization) | diff --git a/docs/specs/runtime_contracts.md b/docs/specs/runtime_contracts.md index 5f5464a5..1810f152 100644 --- a/docs/specs/runtime_contracts.md +++ b/docs/specs/runtime_contracts.md @@ -919,3 +919,17 @@ Acceptance inventory: `docs/audit/adr_0241_cohesion_acceptance_checklist.md`. Every wave transition still obeys `versor_condition(F) < 1e-6`. Residual breach is **fail-closed** — no hot-path nearest-versor drift repair. + +### Algebra backend / Apple Silicon (P11a hygiene) + +Cl(4,1) hot ops in physics (`geometric_product`, `versor_apply`, +`versor_condition`, `cga_inner`) must import from **`algebra.backend`**, not +direct pure-algebra modules. Pin: `tests/test_physics_backend_dispatch_hygiene.py`. + +| Mode | How | +|------|-----| +| Default | Pure Python (semantic source of truth) | +| Native accel | `CORE_BACKEND=rust` + `core_rs` built (`maturin develop --release -m core-rs/Cargo.toml`) | +| float64 wave residual pins | Stay on Python product when inputs are f64 (Rust f32 GP not parity-safe for 1e-9 pins yet) | +| float32 field graphs | Rust f32 GP / residual when enabled | +| MLX / UMA | Exploratory (ADR-0235); not serving until parity gates | diff --git a/tests/test_physics_backend_dispatch_hygiene.py b/tests/test_physics_backend_dispatch_hygiene.py new file mode 100644 index 00000000..10418144 --- /dev/null +++ b/tests/test_physics_backend_dispatch_hygiene.py @@ -0,0 +1,92 @@ +"""P11a — physics hot paths must dispatch Cl(4,1) ops via algebra.backend. + +Prevents silent drift back to pure-Python-only imports for geometric_product / +versor_apply / cga_inner / versor_condition in wave-field and related modules. +Python remains default when CORE_BACKEND is unset; Rust accelerates when +CORE_BACKEND=rust and core_rs is built (ADR-0235). +""" + +from __future__ import annotations + +import ast +from pathlib import Path + +_ROOT = Path(__file__).resolve().parents[1] +_PHYSICS = _ROOT / "core" / "physics" + +# Modules that perform Cl(4,1) multiplies / residuals in the cognitive physics +# layer — must take the load-bearing ops from algebra.backend. +_BACKEND_HOT_MODULES = ( + "wave_manifold.py", + "goldtether.py", + "trajectory_invariants.py", + "dynamic_manifold.py", + "surprise.py", + "holographic_vault.py", + "atlas_packing.py", + "biography.py", + "self_authorship.py", +) + +# Names that must not be imported from algebra.cl41 / algebra.versor / algebra.cga +# in those modules (use backend instead). +_BANNED_FROM_PURE = frozenset( + { + "geometric_product", + "versor_apply", + "versor_condition", + "cga_inner", + } +) + + +def _imports_from(module: str, path: Path) -> set[str]: + tree = ast.parse(path.read_text(encoding="utf-8")) + names: set[str] = set() + for node in ast.walk(tree): + if isinstance(node, ast.ImportFrom) and node.module == module: + for alias in node.names: + names.add(alias.name) + return names + + +def test_hot_modules_import_backend_for_algebra_ops(): + for name in _BACKEND_HOT_MODULES: + path = _PHYSICS / name + assert path.is_file(), f"missing {path}" + backend_names = _imports_from("algebra.backend", path) + # Each file should pull at least one of the dispatch ops. + assert backend_names & _BANNED_FROM_PURE, ( + f"{name} must import Cl(4,1) hot ops from algebra.backend; " + f"got backend imports {sorted(backend_names)}" + ) + + +def test_hot_modules_do_not_import_hot_ops_from_pure_algebra(): + pure_modules = ("algebra.cl41", "algebra.versor", "algebra.cga") + for name in _BACKEND_HOT_MODULES: + path = _PHYSICS / name + for mod in pure_modules: + pure_names = _imports_from(mod, path) + offenders = pure_names & _BANNED_FROM_PURE + assert not offenders, ( + f"{name} imports {sorted(offenders)} from {mod}; " + "route through algebra.backend for CORE_BACKEND=rust" + ) + + +def test_wave_manifold_uses_backend_geometric_product_and_versor_apply(): + path = _PHYSICS / "wave_manifold.py" + backend = _imports_from("algebra.backend", path) + assert "geometric_product" in backend + assert "versor_apply" in backend + assert "versor_condition" in backend + assert "cga_inner" in backend + + +def test_backend_using_rust_api_exists(): + from algebra.backend import using_rust + + # Default env: Python path (no silent force of Rust). + assert using_rust() is False or using_rust() is True # bool only + assert isinstance(using_rust(), bool)