diff --git a/tests/test_versor_closure.py b/tests/test_versor_closure.py index 5b1ac38a..040f0680 100644 --- a/tests/test_versor_closure.py +++ b/tests/test_versor_closure.py @@ -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)