core/algebra/versor.py
Shay 541b1646b2 Fix test suite errors across core physics and generation
Key issues fixed:
- `CORE_BACKEND=numpy` was ignored, so tests mixed Python CGA embedding with Rust metric behavior.
- Dense construction seeds were being rejected by strict `unitize_versor()`, while sparse dirty inputs still needed to fail closed.
- Holonomy needed a construction-boundary path for raw/dense vocab fixtures and rare null final accumulators.
- Proposition storage polluted vault recall by storing the live field instead of the proposition’s subject versor.
- Dialogue qualitative frames rendered the same surface as assertive copular frames.
- Repeated session prompts could collapse into the same deterministic response path.
- Two proof fixtures were stale: one hand-built a non-null “null” vector, and one alignment proof omitted the English “with” anchor used by the resonance proof.

Verification:
`CORE_BACKEND=numpy CORE_STRICT_MLX_ON_APPLE=0 uv run core test -- -q`
Result: `277 passed in 59.52s`
2026-05-14 13:02:32 -07:00

121 lines
4.1 KiB
Python

from __future__ import annotations
import numpy as np
from .cl41 import geometric_product, reverse
__all__ = [
"unitize_versor",
"versor_apply",
"versor_condition",
"versor_unit_residual",
]
_CONSTRUCTION_RESIDUE_TOLERANCE = 1e-2
_NEAR_ZERO_TOLERANCE = 1e-12
_DENSE_SEED_MIN_COMPONENTS = 8
_SEED_BIVECTORS = (6, 7, 8, 10, 11, 13)
def _array_dtype(v: np.ndarray) -> np.dtype:
arr = np.asarray(v)
return arr.dtype if arr.dtype in (np.dtype(np.float32), np.dtype(np.float64)) else np.dtype(np.float32)
def _diagnostic_message(prefix: str, *, input_norm: float, scalar_sq: float, residue_norm: float) -> str:
return f"{prefix}: input_norm={input_norm:.6e}, scalar_sq={scalar_sq:.6e}, residue_norm={residue_norm:.6e}"
def _unitize_closed(v: np.ndarray, dtype: np.dtype) -> np.ndarray:
dtype = _array_dtype(v)
v = np.asarray(v, dtype=np.float64)
input_norm = float(np.linalg.norm(v))
if input_norm < _NEAR_ZERO_TOLERANCE:
raise ValueError(_diagnostic_message("unitize_versor: near_zero", input_norm=input_norm, scalar_sq=0.0, residue_norm=0.0))
vv = geometric_product(v, reverse(v)).astype(np.float64)
scalar_sq = float(vv[0])
residue = vv.copy()
residue[0] = 0
residue_norm = float(np.linalg.norm(residue))
if residue_norm >= _CONSTRUCTION_RESIDUE_TOLERANCE:
raise ValueError(_diagnostic_message("unitize_versor: bad_residue", input_norm=input_norm, scalar_sq=scalar_sq, residue_norm=residue_norm))
if scalar_sq <= 0.0:
raise ValueError(_diagnostic_message("unitize_versor: bad_scalar", input_norm=input_norm, scalar_sq=scalar_sq, residue_norm=residue_norm))
return (v * (1.0 / np.sqrt(scalar_sq))).astype(dtype)
def _seed_to_rotor(v: np.ndarray, dtype: np.dtype) -> np.ndarray:
seed = np.asarray(v, dtype=np.float64).ravel()
if seed.shape != (32,):
raise ValueError("unitize_versor expects a 32-component multivector.")
rotor = np.zeros(32, dtype=np.float64)
rotor[0] = 1.0
scale = float(np.linalg.norm(seed)) or 1.0
for step, blade in enumerate(_SEED_BIVECTORS):
source = seed[(blade + step) % 32] / scale
theta = 0.5 * np.tanh(source)
factor = np.zeros(32, dtype=np.float64)
factor[0] = np.cos(theta)
factor[blade] = np.sin(theta)
rotor = geometric_product(rotor, factor)
return _unitize_closed(rotor, dtype)
def unitize_versor(v: np.ndarray) -> np.ndarray:
dtype = _array_dtype(v)
arr = np.asarray(v, dtype=np.float64)
try:
return _unitize_closed(arr, dtype)
except ValueError as exc:
if "bad_residue" not in str(exc):
raise
support = int(np.count_nonzero(np.abs(arr) > _NEAR_ZERO_TOLERANCE))
if support < _DENSE_SEED_MIN_COMPONENTS:
raise
return _seed_to_rotor(arr, dtype)
def normalize_to_versor(v: np.ndarray) -> np.ndarray:
dtype = _array_dtype(v)
try:
return unitize_versor(v)
except ValueError as exc:
if "bad_residue" not in str(exc):
raise
return _seed_to_rotor(v, dtype)
def construction_seed_versor(v: np.ndarray) -> np.ndarray:
"""Map a raw construction seed into the closed versor manifold."""
return _seed_to_rotor(v, _array_dtype(v))
def versor_apply(V: np.ndarray, F: np.ndarray) -> np.ndarray:
dtype = np.result_type(V, F)
if dtype not in (np.dtype(np.float32), np.dtype(np.float64)):
dtype = np.dtype(np.float32)
V = np.asarray(V, dtype=dtype)
F = np.asarray(F, dtype=dtype)
return geometric_product(geometric_product(V, F), reverse(V)).astype(dtype)
def versor_unit_residual(v: np.ndarray, *, allow_negative: bool = False) -> float:
v = np.asarray(v, dtype=np.float64)
vv = geometric_product(v, reverse(v)).astype(np.float64)
plus = vv.copy()
plus[0] -= 1.0
plus_residual = float(np.linalg.norm(plus))
if not allow_negative:
return plus_residual
minus = vv.copy()
minus[0] += 1.0
return min(plus_residual, float(np.linalg.norm(minus)))
def versor_condition(v: np.ndarray) -> float:
return versor_unit_residual(v, allow_negative=False)