Use true versor fixtures in closure tests
This commit is contained in:
parent
fca6216e3f
commit
bacf1f4084
1 changed files with 32 additions and 15 deletions
|
|
@ -4,41 +4,58 @@ It verifies the core algebraic invariant of the entire system.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pytest
|
|
||||||
from hypothesis import given, settings
|
from hypothesis import given, settings
|
||||||
from hypothesis import strategies as st
|
from hypothesis import strategies as st
|
||||||
|
|
||||||
from algebra.versor import versor_apply, normalize_to_versor, versor_condition
|
from algebra.versor import versor_apply, unitize_versor, versor_condition
|
||||||
|
|
||||||
|
|
||||||
def _random_versor(seed=None) -> np.ndarray:
|
def _unit_reflector(seed=None) -> np.ndarray:
|
||||||
|
"""
|
||||||
|
Construct a true Cl(4,1) versor: a unit grade-1 vector.
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
rng = np.random.default_rng(seed)
|
rng = np.random.default_rng(seed)
|
||||||
raw = rng.standard_normal(32).astype(np.float32)
|
vec = rng.standard_normal(5).astype(np.float32)
|
||||||
return normalize_to_versor(raw)
|
# 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
|
||||||
|
mv = np.zeros(32, dtype=np.float32)
|
||||||
|
mv[1:6] = vec
|
||||||
|
return unitize_versor(mv)
|
||||||
|
|
||||||
|
|
||||||
@given(st.integers(min_value=0, max_value=99))
|
@given(st.integers(min_value=0, max_value=99))
|
||||||
@settings(max_examples=100)
|
@settings(max_examples=100)
|
||||||
def test_versor_apply_preserves_manifold(seed):
|
def test_versor_apply_preserves_manifold(seed):
|
||||||
"""V*F*reverse(V) must be a versor if V and F are versors."""
|
"""V*F*reverse(V) must be a versor if V and F are versors."""
|
||||||
V = _random_versor(seed)
|
V = _unit_reflector(seed)
|
||||||
F = _random_versor(seed + 1000)
|
F = _unit_reflector(seed + 1000)
|
||||||
result = versor_apply(V, F)
|
result = versor_apply(V, F)
|
||||||
cond = versor_condition(result)
|
cond = versor_condition(result)
|
||||||
assert cond < 1e-4, f"versor_apply broke the manifold: condition={cond:.2e}"
|
assert cond < 1e-4, f"versor_apply broke the manifold: condition={cond:.2e}"
|
||||||
|
|
||||||
|
|
||||||
def test_normalize_produces_versor():
|
def test_unitize_random_multivector_is_not_claimed_to_create_versor():
|
||||||
raw = np.random.randn(32).astype(np.float32)
|
"""
|
||||||
V = normalize_to_versor(raw)
|
Unitizing arbitrary 32D garbage is not versor construction.
|
||||||
assert versor_condition(V) < 1e-6
|
|
||||||
|
unitize_versor() scales a construction product; it does not project an
|
||||||
|
arbitrary multivector onto the versor manifold. This test prevents the
|
||||||
|
old false fixture from returning.
|
||||||
|
"""
|
||||||
|
raw = np.random.default_rng(0).standard_normal(32).astype(np.float32)
|
||||||
|
V = unitize_versor(raw)
|
||||||
|
assert versor_condition(V) > 1e-3
|
||||||
|
|
||||||
|
|
||||||
def test_composition_closed():
|
def test_composition_closed():
|
||||||
"""Two sequential versor_apply calls stay on the manifold."""
|
"""Two sequential versor_apply calls stay on the manifold."""
|
||||||
V1 = _random_versor(0)
|
V1 = _unit_reflector(0)
|
||||||
V2 = _random_versor(1)
|
V2 = _unit_reflector(1)
|
||||||
F = _random_versor(2)
|
F = _unit_reflector(2)
|
||||||
F2 = versor_apply(V1, F)
|
F2 = versor_apply(V1, F)
|
||||||
F3 = versor_apply(V2, F2)
|
F3 = versor_apply(V2, F2)
|
||||||
assert versor_condition(F3) < 1e-4
|
assert versor_condition(F3) < 1e-4
|
||||||
|
|
@ -48,6 +65,6 @@ def test_identity_versor():
|
||||||
"""Scalar 1 is a valid versor and applies as identity."""
|
"""Scalar 1 is a valid versor and applies as identity."""
|
||||||
identity = np.zeros(32, dtype=np.float32)
|
identity = np.zeros(32, dtype=np.float32)
|
||||||
identity[0] = 1.0
|
identity[0] = 1.0
|
||||||
F = _random_versor(42)
|
F = _unit_reflector(42)
|
||||||
result = versor_apply(identity, F)
|
result = versor_apply(identity, F)
|
||||||
assert np.allclose(result, F, atol=1e-5)
|
assert np.allclose(result, F, atol=1e-5)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue