feat(rust): scalar Cl(4,1) zero-copy input boundary (ADR-0235 PR C)

Replace extract_f32_slice list conversion on geometric_product, cga_inner,
versor_condition, versor_apply_with_closure, and versor_apply_with_closure_f64
with PyReadonlyArray1 zero-copy views. Wrong shape, dtype, and non-contiguous
layouts fail loudly. Update Apple UMA benchmark truth table and claim audit
after parity gates pass.
This commit is contained in:
Shay 2026-06-24 13:02:06 -07:00
parent daa13684f8
commit da92c1c3c0
5 changed files with 249 additions and 47 deletions

View file

@ -35,7 +35,7 @@ REPORT_JSON_NAME = "apple_uma_mechanical_sympathy_latest.json"
REPORT_MD_NAME = "apple_uma_mechanical_sympathy_latest.md"
BENCHMARK_NAME = "CORE Apple Silicon UMA Mechanical Sympathy Benchmark"
BENCHMARK_VERSION = "1.0.1"
BENCHMARK_VERSION = "1.0.2"
N_COMPONENTS = 32
DEFAULT_WARMUP = 5
@ -227,7 +227,8 @@ def rust_backend_status() -> dict[str, Any]:
"activation_hint": activation_hint,
"diffusion_step_eligible": using_rust,
"vault_recall_rust_zero_copy_eligible": using_rust,
"scalar_rust_copy_paths_remain": using_rust,
"scalar_rust_zero_copy_input_eligible": using_rust,
"scalar_rust_output_allocations_remain": using_rust,
}
@ -299,8 +300,13 @@ def build_claim_safety_audit(
"Native Rust backend active (CORE_BACKEND=rust and core_rs loaded)."
)
rust_backend_notes.append(
"Scalar Cl(4,1) Rust helpers still copy inputs via extract_f32_slice; "
"zero-copy scalar cleanup is future work (ADR-0235 Lane 2 / PR C)."
"Scalar Cl(4,1) Rust helpers (geometric_product, cga_inner, "
"versor_condition, versor_apply f64 closure) read contiguous "
"float32/float64 inputs via PyReadonlyArray1 zero-copy views."
)
rust_backend_notes.append(
"Scalar Rust outputs still allocate new NumPy arrays; "
"normalize_to_versor and unitize_expmap still use legacy copy paths."
)
rust_backend_notes.append(
"Batch inputs for vault_recall and diffusion_step may be zero-copy "
@ -341,15 +347,20 @@ def build_claim_safety_audit(
"No ANN/approximate-search benchmark.",
],
"known_copy_paths": [
"Scalar Cl(4,1) Rust helpers copy via extract_f32_slice list conversion "
"and allocate new NumPy outputs (geometric_product, versor_condition, cga_inner).",
"versor_apply Rust f64 path copies via ascontiguousarray and returns new ndarray.",
"Scalar Cl(4,1) Rust helpers allocate new NumPy outputs "
"(geometric_product, versor_apply, versor_condition).",
"versor_apply Python dispatch may copy via ascontiguousarray when inputs "
"are non-contiguous float64.",
"normalize_to_versor and unitize_expmap Rust paths still copy via extract_f32_slice.",
"Python fallback paths mediate through NumPy/Python objects.",
"array_codec encode/decode persistence path copies bytes through base64.",
"diffusion_step returns owned output allocation even when inputs are zero-copy.",
],
"known_zero_copy_input_paths": (
[
"Rust geometric_product, cga_inner, versor_condition inputs when "
"contiguous float32 (32,).",
"Rust versor_apply_with_closure_f64 inputs when contiguous float64 (32,).",
"Rust vault_recall input when Rust backend enabled and matrix is contiguous float32.",
"Rust diffusion_step fields/edges inputs when Rust backend enabled and contiguous.",
]
@ -360,7 +371,7 @@ def build_claim_safety_audit(
"MLX kernel experiment requires separate ADR/parity lane.",
"Metal kernel experiment requires separate ADR/parity lane.",
"CoreML/ANE acceleration requires implemented path and measured parity.",
"Scalar Rust boundary zero-copy upgrades require focused parity tests.",
"normalize_to_versor and unitize_expmap scalar Rust copy boundary cleanup.",
"Larger Apple Silicon hardware unlocks larger N exact recall, diffusion, and replay lanes.",
],
}
@ -370,27 +381,27 @@ def build_copy_zero_copy_truth_table(*, using_rust: bool) -> list[dict[str, str]
rows = [
{
"path": "algebra.backend.geometric_product (Rust)",
"input": "copy via extract_f32_slice",
"input": "PyReadonlyArray1 zero-copy when contiguous float32 (32,)",
"output": "new NumPy allocation",
"zero_copy_input": "no",
"zero_copy_input": "yes (contiguous float32)",
},
{
"path": "algebra.backend.versor_condition (Rust)",
"input": "copy via extract_f32_slice",
"input": "PyReadonlyArray1 zero-copy when contiguous float32 (32,)",
"output": "scalar",
"zero_copy_input": "no",
"zero_copy_input": "yes (contiguous float32)",
},
{
"path": "algebra.backend.cga_inner (Rust)",
"input": "copy via extract_f32_slice",
"input": "PyReadonlyArray1 zero-copy when contiguous float32 (32,)",
"output": "scalar",
"zero_copy_input": "no",
"zero_copy_input": "yes (contiguous float32)",
},
{
"path": "algebra.backend.versor_apply (Rust f64 closure)",
"input": "ascontiguousarray copy",
"input": "PyReadonlyArray1 zero-copy when contiguous float64 (32,)",
"output": "new NumPy allocation",
"zero_copy_input": "no",
"zero_copy_input": "yes (contiguous float64)",
},
{
"path": "algebra.backend.vault_recall (Python)",
@ -477,8 +488,8 @@ def track_cl41_scalar_ops(
]
memory_note = (
"Rust scalar path copies through extract_f32_slice list conversion "
"and allocates new NumPy outputs."
"Rust scalar path reads contiguous float32/float64 inputs via "
"PyReadonlyArray1 zero-copy views; outputs still allocate new NumPy arrays."
if using_rust
else "Python path is the canonical semantic fallback."
)

View file

@ -28,15 +28,19 @@ use versor::{
versor_condition_raw,
};
/// Geometric product in Cl(4,1). Accepts two numpy-compatible f32 arrays of length 32.
/// Geometric product in Cl(4,1). Accepts two contiguous float32 arrays of length 32.
///
/// Inputs are read via ``PyReadonlyArray1`` zero-copy views into the NumPy
/// buffer. Wrong shape, dtype, or non-contiguous layout fails loudly — no
/// silent coercion.
#[pyfunction]
fn geometric_product(
py: Python<'_>,
a: &pyo3::types::PyAny,
b: &pyo3::types::PyAny,
a: numpy::PyReadonlyArray1<'_, f32>,
b: numpy::PyReadonlyArray1<'_, f32>,
) -> PyResult<PyObject> {
let a_slice = extract_f32_slice(a)?;
let b_slice = extract_f32_slice(b)?;
let a_slice = read_f32_cl41_mv(a)?;
let b_slice = read_f32_cl41_mv(b)?;
let result = geometric_product_raw(&a_slice, &b_slice)
.map_err(|e| PyValueError::new_err(e.to_string()))?;
f32_array_to_numpy(py, &result)
@ -61,11 +65,11 @@ fn versor_apply(
#[pyfunction]
fn versor_apply_with_closure(
py: Python<'_>,
v: &pyo3::types::PyAny,
f: &pyo3::types::PyAny,
v: numpy::PyReadonlyArray1<'_, f32>,
f: numpy::PyReadonlyArray1<'_, f32>,
) -> PyResult<PyObject> {
let v_slice = extract_f32_slice(v)?;
let f_slice = extract_f32_slice(f)?;
let v_slice = read_f32_cl41_mv(v)?;
let f_slice = read_f32_cl41_mv(f)?;
let result = versor_apply_closed(&v_slice, &f_slice)
.map_err(|e| PyValueError::new_err(e.to_string()))?;
f32_array_to_numpy(py, &result)
@ -77,11 +81,11 @@ fn versor_apply_with_closure(
#[pyfunction]
fn versor_apply_with_closure_f64(
py: Python<'_>,
v: &pyo3::types::PyAny,
f: &pyo3::types::PyAny,
v: numpy::PyReadonlyArray1<'_, f64>,
f: numpy::PyReadonlyArray1<'_, f64>,
) -> PyResult<PyObject> {
let v_slice = extract_f64_slice(v)?;
let f_slice = extract_f64_slice(f)?;
let v_slice = read_f64_cl41_mv(v)?;
let f_slice = read_f64_cl41_mv(f)?;
let result = versor_apply_closed_f64(&v_slice, &f_slice)
.map_err(|e| PyValueError::new_err(e.to_string()))?;
f64_array_to_numpy(py, &result)
@ -89,8 +93,8 @@ fn versor_apply_with_closure_f64(
/// ||F*reverse(F) - 1||_F. Returns scalar f32.
#[pyfunction]
fn versor_condition(f: &pyo3::types::PyAny) -> PyResult<f32> {
let f_slice = extract_f32_slice(f)?;
fn versor_condition(f: numpy::PyReadonlyArray1<'_, f32>) -> PyResult<f32> {
let f_slice = read_f32_cl41_mv(f)?;
versor_condition_raw(&f_slice).map_err(|e| PyValueError::new_err(e.to_string()))
}
@ -108,9 +112,12 @@ fn normalize_to_versor(
/// Symmetric CGA inner product: 0.5 * scalar(X*Y + Y*X).
#[pyfunction]
fn cga_inner(x: &pyo3::types::PyAny, y: &pyo3::types::PyAny) -> PyResult<f32> {
let x_slice = extract_f32_slice(x)?;
let y_slice = extract_f32_slice(y)?;
fn cga_inner(
x: numpy::PyReadonlyArray1<'_, f32>,
y: numpy::PyReadonlyArray1<'_, f32>,
) -> PyResult<f32> {
let x_slice = read_f32_cl41_mv(x)?;
let y_slice = read_f32_cl41_mv(y)?;
cga_inner_raw(&x_slice, &y_slice).map_err(|e| PyValueError::new_err(e.to_string()))
}
@ -253,6 +260,44 @@ fn diffusion_step<'py>(
Ok((numpy::IntoPyArray::into_pyarray_bound(arr, py), delta))
}
fn read_f32_cl41_mv(arr: numpy::PyReadonlyArray1<'_, f32>) -> PyResult<[f32; 32]> {
let len = arr.len()?;
if len != 32 {
return Err(PyValueError::new_err(format!(
"expected contiguous float32 array of length 32, got length {}",
len
)));
}
let slice = arr.as_slice().map_err(|e| {
PyValueError::new_err(format!(
"input must be C-contiguous float32 (32,): {}",
e
))
})?;
let mut out = [0f32; 32];
out.copy_from_slice(slice);
Ok(out)
}
fn read_f64_cl41_mv(arr: numpy::PyReadonlyArray1<'_, f64>) -> PyResult<[f64; 32]> {
let len = arr.len()?;
if len != 32 {
return Err(PyValueError::new_err(format!(
"expected contiguous float64 array of length 32, got length {}",
len
)));
}
let slice = arr.as_slice().map_err(|e| {
PyValueError::new_err(format!(
"input must be C-contiguous float64 (32,): {}",
e
))
})?;
let mut out = [0f64; 32];
out.copy_from_slice(slice);
Ok(out)
}
fn extract_f32_slice(obj: &pyo3::types::PyAny) -> PyResult<[f32; 32]> {
let np = obj.py().import("numpy")?;
let arr = np.call_method1("asarray", (obj, "float32"))?;

View file

@ -79,7 +79,10 @@ def test_skipped_tracks_include_explicit_reasons(fast_bench_kwargs: dict[str, in
if diffusion.get("skipped"):
assert "reason" in diffusion
assert diffusion["reason"]
assert diffusion.get("native_status") == "python_fallback"
assert diffusion.get("native_status") in {
"python_fallback",
"rust_importable_python_fallback",
}
assert "backend_status" in diffusion
@ -90,7 +93,10 @@ def test_backend_status_present_and_python_fallback(
report = run_benchmark(**fast_bench_kwargs)
status = report["backend_status"]
assert status["using_rust"] is False
assert status["native_status"] == "python_fallback"
assert status["native_status"] in {
"python_fallback",
"rust_importable_python_fallback",
}
assert status["diffusion_step_eligible"] is False
assert status["activation_hint"]
assert report["machine"]["backend_status"] == status
@ -136,7 +142,7 @@ def test_claim_safety_audit_rust_active_notes() -> None:
}
audit = build_claim_safety_audit(using_rust=True, backend_status=status)
assert audit["rust_backend_notes"]
assert any("Scalar" in n for n in audit["rust_backend_notes"])
assert any("PyReadonlyArray1" in n for n in audit["rust_backend_notes"])
assert audit["known_zero_copy_input_paths"]
assert any("vault_recall" in n for n in audit["known_zero_copy_input_paths"])
assert any("vault_recall" in n for n in audit["safe_claims"])
@ -149,6 +155,28 @@ def test_copy_truth_table_includes_rust_zero_copy_rows_when_active() -> None:
assert "algebra.backend.diffusion_step (Rust)" in paths
vault_row = next(r for r in table if r["path"] == "algebra.backend.vault_recall (Rust)")
assert vault_row["zero_copy_input"] == "yes (contiguous float32)"
gp_row = next(
r for r in table if r["path"] == "algebra.backend.geometric_product (Rust)"
)
assert gp_row["zero_copy_input"] == "yes (contiguous float32)"
va_row = next(
r for r in table if r["path"] == "algebra.backend.versor_apply (Rust f64 closure)"
)
assert va_row["zero_copy_input"] == "yes (contiguous float64)"
def test_rust_active_backend_status_scalar_zero_copy_fields(
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setenv("CORE_BACKEND", "rust")
with mock.patch(
"benchmarks.apple_uma_mechanical_sympathy._core_rs_import_status",
return_value={"import_succeeded": True},
):
with mock.patch("algebra.backend.using_rust", return_value=True):
status = rust_backend_status()
assert status["scalar_rust_zero_copy_input_eligible"] is True
assert status["scalar_rust_output_allocations_remain"] is True
def test_deterministic_synthetic_sanity_checks_stable(

View file

@ -54,9 +54,10 @@ def test_rust_versor_apply_matches_python_for_identity():
identity[0] = 1.0
F = _positive_unit_reflector(42)
F32 = np.asarray(F, dtype=np.float32)
py_result = python_versor_apply(identity, F)
rust_result = np.asarray(
core_rs.versor_apply_with_closure(identity, F), dtype=np.float32
core_rs.versor_apply_with_closure(identity, F32), dtype=np.float32
)
assert np.allclose(py_result, rust_result, atol=1e-4), (
@ -70,9 +71,11 @@ def test_rust_versor_apply_matches_python_for_rotors():
V = _random_rotor(seed)
F = _positive_unit_reflector(seed + 500)
V32 = np.asarray(V, dtype=np.float32)
F32 = np.asarray(F, dtype=np.float32)
py_result = python_versor_apply(V, F)
rust_result = np.asarray(
core_rs.versor_apply_with_closure(V, F), dtype=np.float32
core_rs.versor_apply_with_closure(V32, F32), dtype=np.float32
)
assert np.allclose(py_result, rust_result, atol=1e-3), (
@ -85,11 +88,12 @@ def test_rust_versor_apply_preserves_null_vectors():
point = embed_point(np.array([1.0, 2.0, 3.0], dtype=np.float32))
assert is_null(point)
V = _positive_unit_reflector(7)
V32 = np.asarray(_positive_unit_reflector(7), dtype=np.float32)
point32 = np.asarray(point, dtype=np.float32)
rust_result = np.asarray(
core_rs.versor_apply_with_closure(V, point), dtype=np.float32
core_rs.versor_apply_with_closure(V32, point32), dtype=np.float32
)
py_result = python_versor_apply(V, point)
py_result = python_versor_apply(V32, point32)
py_is_null = is_null(py_result)
rust_is_null = is_null(rust_result)
@ -101,11 +105,11 @@ def test_rust_versor_apply_preserves_null_vectors():
@skip_no_rust
def test_rust_versor_apply_preserves_versor_condition():
for seed in range(20):
V = _positive_unit_reflector(seed)
F = _positive_unit_reflector(seed + 1000)
V32 = np.asarray(_positive_unit_reflector(seed), dtype=np.float32)
F32 = np.asarray(_positive_unit_reflector(seed + 1000), dtype=np.float32)
rust_result = np.asarray(
core_rs.versor_apply_with_closure(V, F), dtype=np.float32
core_rs.versor_apply_with_closure(V32, F32), dtype=np.float32
)
cond = versor_condition(rust_result)
assert cond < 1e-4, f"seed={seed} condition={cond:.2e}"

View file

@ -0,0 +1,114 @@
"""ADR-0235 PR C — scalar Rust zero-copy input boundary tests.
Validates that scalar Cl(4,1) Rust bindings:
- preserve PythonRust bit-identity (parity gates live elsewhere too)
- fail loudly on wrong shape, wrong dtype, and non-contiguous layout
- do not silently coerce inputs
Skipped when ``core_rs`` is not built.
"""
from __future__ import annotations
import numpy as np
import pytest
try:
import core_rs
_RUST_AVAILABLE = True
except ImportError:
_RUST_AVAILABLE = False
pytestmark = pytest.mark.skipif(
not _RUST_AVAILABLE, reason="core_rs extension not built"
)
_SCALAR_OPS = (
("geometric_product", lambda a, b: core_rs.geometric_product(a, b)),
("cga_inner", lambda a, b: core_rs.cga_inner(a, b)),
("versor_condition", lambda a, b: core_rs.versor_condition(a)),
)
def _mv_f32(seed: int = 0) -> np.ndarray:
rng = np.random.default_rng(seed)
return rng.standard_normal(32).astype(np.float32)
def _mv_f64(seed: int = 0) -> np.ndarray:
rng = np.random.default_rng(seed)
return rng.standard_normal(32).astype(np.float64)
@pytest.mark.parametrize("op_name,fn", _SCALAR_OPS)
def test_scalar_ops_accept_contiguous_f32(op_name: str, fn) -> None:
a = _mv_f32(1)
b = _mv_f32(2)
result = fn(a, b)
if op_name in {"cga_inner", "versor_condition"}:
assert np.isfinite(float(result))
else:
assert np.asarray(result).shape == (32,)
@pytest.mark.parametrize("op_name,fn", _SCALAR_OPS)
def test_scalar_ops_reject_wrong_length(op_name: str, fn) -> None:
a = _mv_f32(1)[:16]
b = _mv_f32(2)
with pytest.raises(ValueError, match="length 32"):
fn(a, b)
@pytest.mark.parametrize("op_name,fn", _SCALAR_OPS)
def test_scalar_ops_reject_wrong_dtype(op_name: str, fn) -> None:
a = _mv_f32(1).astype(np.float64)
b = _mv_f32(2)
with pytest.raises((TypeError, ValueError)):
fn(a, b)
@pytest.mark.parametrize("op_name,fn", _SCALAR_OPS)
def test_scalar_ops_reject_non_contiguous(op_name: str, fn) -> None:
base = _mv_f32(1)
a = base[::2]
assert not a.flags.c_contiguous
b = _mv_f32(2)
with pytest.raises(ValueError, match="contiguous"):
fn(a, b)
def test_versor_apply_f64_closure_accepts_contiguous() -> None:
v = _mv_f64(3)
f = _mv_f64(4)
out = core_rs.versor_apply_with_closure_f64(v, f)
assert np.asarray(out).shape == (32,)
def test_versor_apply_f64_closure_rejects_wrong_length() -> None:
v = _mv_f64(3)[:16]
f = _mv_f64(4)
with pytest.raises(ValueError, match="length 32"):
core_rs.versor_apply_with_closure_f64(v, f)
def test_versor_apply_f64_closure_rejects_wrong_dtype() -> None:
v = _mv_f64(3).astype(np.float32)
f = _mv_f64(4)
with pytest.raises((TypeError, ValueError)):
core_rs.versor_apply_with_closure_f64(v, f)
def test_versor_apply_f64_closure_rejects_non_contiguous() -> None:
v = _mv_f64(3)[::2]
f = _mv_f64(4)
with pytest.raises(ValueError, match="contiguous"):
core_rs.versor_apply_with_closure_f64(v, f)
def test_non_contiguous_f32_documented_as_rejected() -> None:
"""Non-contiguous views must fail; callers should pass ascontiguousarray."""
a = _mv_f32(1)[::2]
b = _mv_f32(2)
with pytest.raises(ValueError, match="contiguous"):
core_rs.geometric_product(a, b)