Constrain closure fixture to positive unit reflectors

This commit is contained in:
Shay 2026-05-13 12:59:15 -07:00
parent 62501b6730
commit 2303c68f6a

View file

@ -10,18 +10,26 @@ from hypothesis import strategies as st
from algebra.versor import versor_apply, unitize_versor, versor_condition
def _unit_reflector(seed=None) -> np.ndarray:
def _positive_unit_reflector(seed=None) -> np.ndarray:
"""
Construct a true Cl(4,1) versor: a unit grade-1 vector.
Construct a true positive-norm Cl(4,1) grade-1 versor.
Scaling an arbitrary 32D multivector does not make it a versor. A unit
vector is a valid reflector by construction and satisfies v * ~v = ±1.
The current field action uses V * F * reverse(V), so the operator fixture
must satisfy V * reverse(V) = +1, not -1. We therefore keep the fifth
(negative-metric) basis component bounded below the positive four-space
norm before construction-unitizing.
"""
rng = np.random.default_rng(seed)
vec = rng.standard_normal(5).astype(np.float32)
# Avoid accidentally producing an exactly null vector under (+,+,+,+,-).
if abs(float(np.dot(vec[:4], vec[:4]) - vec[4] * vec[4])) < 1e-4:
vec[0] += 1.0
vec4 = rng.standard_normal(4).astype(np.float32)
norm4 = float(np.linalg.norm(vec4))
if norm4 < 1e-6:
vec4[0] = 1.0
norm4 = 1.0
vec = np.zeros(5, dtype=np.float32)
vec[:4] = vec4
vec[4] = 0.25 * norm4 * np.tanh(float(rng.standard_normal()))
mv = np.zeros(32, dtype=np.float32)
mv[1:6] = vec
return unitize_versor(mv)
@ -30,9 +38,9 @@ def _unit_reflector(seed=None) -> np.ndarray:
@given(st.integers(min_value=0, max_value=99))
@settings(max_examples=100)
def test_versor_apply_preserves_manifold(seed):
"""V*F*reverse(V) must be a versor if V and F are versors."""
V = _unit_reflector(seed)
F = _unit_reflector(seed + 1000)
"""V*F*reverse(V) must be a versor if V and F are positive unit versors."""
V = _positive_unit_reflector(seed)
F = _positive_unit_reflector(seed + 1000)
result = versor_apply(V, F)
cond = versor_condition(result)
assert cond < 1e-4, f"versor_apply broke the manifold: condition={cond:.2e}"
@ -53,9 +61,9 @@ def test_unitize_random_multivector_is_not_claimed_to_create_versor():
def test_composition_closed():
"""Two sequential versor_apply calls stay on the manifold."""
V1 = _unit_reflector(0)
V2 = _unit_reflector(1)
F = _unit_reflector(2)
V1 = _positive_unit_reflector(0)
V2 = _positive_unit_reflector(1)
F = _positive_unit_reflector(2)
F2 = versor_apply(V1, F)
F3 = versor_apply(V2, F2)
assert versor_condition(F3) < 1e-4
@ -65,6 +73,6 @@ def test_identity_versor():
"""Scalar 1 is a valid versor and applies as identity."""
identity = np.zeros(32, dtype=np.float32)
identity[0] = 1.0
F = _unit_reflector(42)
F = _positive_unit_reflector(42)
result = versor_apply(identity, F)
assert np.allclose(result, F, atol=1e-5)