From 7aa8626ec465c1b5656b24efb50cb19d72154cc8 Mon Sep 17 00:00:00 2001 From: Shay Date: Fri, 15 May 2026 23:12:26 -0700 Subject: [PATCH] fix(algebra): make versor_apply close runtime field results Remove the implicit null-vector bypass from the runtime-facing versor_apply closure boundary. FieldState.F is treated throughout the runtime and cognitive pipeline as a unit versor field. Returning null-like raw sandwich results from versor_apply created a contract mismatch and allowed multi-turn closure drift to escape into session state. - make _close_applied_versor always close runtime field results - keep unitize-first semantics and construction-seed fallback - add regression proving null-like sandwich output is closed for the runtime contract Null-vector preservation should return later behind an explicit geometry API, not the generic runtime field propagation path. No GitHub CI/status checks were exposed for this PR. --- algebra/versor.py | 18 +++++------------- tests/test_versor_closure.py | 12 ++++++++++++ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/algebra/versor.py b/algebra/versor.py index 51f92df3..98f31035 100644 --- a/algebra/versor.py +++ b/algebra/versor.py @@ -14,7 +14,6 @@ _CONSTRUCTION_RESIDUE_TOLERANCE = 1e-2 _NEAR_ZERO_TOLERANCE = 1e-12 _DENSE_SEED_MIN_COMPONENTS = 8 _SEED_BIVECTORS = (6, 7, 8, 10, 11, 13) -_NULL_SCALAR_TOLERANCE = 1e-9 def _array_dtype(v: np.ndarray) -> np.dtype: @@ -95,22 +94,15 @@ def construction_seed_versor(v: np.ndarray) -> np.ndarray: return _seed_to_rotor(v, _array_dtype(v)) - -def _is_null_vector(v: np.ndarray) -> bool: - product = geometric_product(v, v).astype(np.float64) - return float(np.linalg.norm(product)) < _NULL_SCALAR_TOLERANCE - - def _close_applied_versor(v: np.ndarray, dtype: np.dtype) -> np.ndarray: - """Close algebra-produced sandwich results without breaking null vectors. + """Close algebra-produced sandwich results for runtime field states. - CGA sandwiching must preserve null vectors as null vectors. Unit-versor - closure only applies when the result is meant to remain a versor field; - null vectors are geometric points and must pass through unchanged. + ``versor_apply`` is the field-propagation API: callers expect the returned + multivector to satisfy ``versor_condition(result)``. CGA point/null-vector + preservation belongs behind an explicit geometry API, not this runtime + manifold boundary. """ arr = np.asarray(v, dtype=dtype) - if _is_null_vector(arr): - return arr.astype(dtype) try: return unitize_versor(arr).astype(dtype) except ValueError: diff --git a/tests/test_versor_closure.py b/tests/test_versor_closure.py index 10077775..423db1a4 100644 --- a/tests/test_versor_closure.py +++ b/tests/test_versor_closure.py @@ -103,6 +103,18 @@ def test_composition_closed(): assert versor_condition(F3) < 1e-4 +def test_versor_apply_closes_null_like_field_results_for_runtime_contract(): + identity = np.zeros(32, dtype=np.float32) + identity[0] = 1.0 + null_like = np.zeros(32, dtype=np.float32) + null_like[1] = 1.0 + null_like[5] = 1.0 + + result = versor_apply(identity, null_like) + + assert versor_condition(result) < 1e-6 + + def test_identity_versor(): identity = np.zeros(32, dtype=np.float32) identity[0] = 1.0