- Cartan: recover_dilation → peel D → recover_translation → peel T;
Spin remainder for non-similarities; strict close (no seed-to-rotor);
recon residual fallback. Flips fidelity xfail.
- Procrustes: full 5-D Kabsch on null-point clouds; field conjugacy via
raw sandwich + Spin GN; delete word_transition_rotor averaging path.
Non-vacuous harness fixture.
- rotor_power: null-bivector power (a+B)^α = a^α + α a^{α-1} B so
translators no longer silently zero under dual-slerp.
- Ledger scorecard: #2 and #3 → 🟢; #4 remains 🟡 (bootstrap deferred).
549 passed (fidelity + ADR-0239 + null_point + 0240 + rotor_power).
96 lines
3.5 KiB
Python
96 lines
3.5 KiB
Python
"""Tests for algebra.rotor.rotor_power — manifold-preserving rotor scaling.
|
||
|
||
The drift-fix #2 originally used linear interpolation between a rotor and
|
||
identity, which produced multivectors with versor_condition ≈ 10⁻², violating
|
||
the non-negotiable 1e-6 invariant. ``rotor_power`` replaces that with a proper
|
||
slerp on the rotor manifold: identity -> R^α stays on the manifold for any α.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import numpy as np
|
||
import pytest
|
||
|
||
from algebra.rotor import make_rotor_from_angle, rotor_power, word_transition_rotor
|
||
from algebra.versor import versor_condition
|
||
|
||
_TOL = 1e-6
|
||
|
||
|
||
@pytest.mark.parametrize("angle", [0.05, 0.3, 0.7, 1.2, np.pi / 2])
|
||
@pytest.mark.parametrize("alpha", [0.0, 0.1, 0.3, 0.5, 0.7, 0.9, 1.0])
|
||
def test_rotor_power_preserves_versor_closure(angle: float, alpha: float) -> None:
|
||
"""For any rotation rotor and any fractional power, output is a closed unit rotor."""
|
||
R = make_rotor_from_angle(angle, bivector_idx=7)
|
||
R_alpha = rotor_power(R, alpha)
|
||
assert versor_condition(R_alpha) < _TOL, (
|
||
f"rotor_power(R(angle={angle}), {alpha}) violates closure: "
|
||
f"versor_condition = {versor_condition(R_alpha):.3e}"
|
||
)
|
||
|
||
|
||
def test_rotor_power_alpha_zero_returns_identity() -> None:
|
||
R = make_rotor_from_angle(0.7, bivector_idx=7)
|
||
R_zero = rotor_power(R, 0.0)
|
||
expected = np.zeros(32, dtype=R_zero.dtype)
|
||
expected[0] = 1.0
|
||
np.testing.assert_allclose(R_zero, expected, atol=1e-9)
|
||
|
||
|
||
def test_rotor_power_alpha_one_returns_input() -> None:
|
||
R = make_rotor_from_angle(0.4, bivector_idx=7)
|
||
R_one = rotor_power(R, 1.0)
|
||
np.testing.assert_allclose(R_one, R, atol=1e-9)
|
||
|
||
|
||
def test_rotor_power_half_angle_halves_rotation() -> None:
|
||
"""R^0.5 applied twice equals R."""
|
||
from algebra.cl41 import geometric_product
|
||
|
||
R = make_rotor_from_angle(0.8, bivector_idx=7)
|
||
R_half = rotor_power(R, 0.5)
|
||
R_half_squared = geometric_product(R_half, R_half)
|
||
np.testing.assert_allclose(R_half_squared, R, atol=1e-6)
|
||
|
||
|
||
def test_rotor_power_handles_identity_input() -> None:
|
||
"""Identity rotor under any power stays identity."""
|
||
identity = np.zeros(32, dtype=np.float64)
|
||
identity[0] = 1.0
|
||
for alpha in [0.0, 0.3, 1.0, 1.5]:
|
||
result = rotor_power(identity, alpha)
|
||
np.testing.assert_allclose(result, identity, atol=1e-9)
|
||
|
||
|
||
def test_rotor_power_on_word_transition_preserves_closure() -> None:
|
||
"""The real-world case: rotors produced by word_transition_rotor."""
|
||
A = np.zeros(32, dtype=np.float64)
|
||
A[0] = 1.0
|
||
B = np.zeros(32, dtype=np.float64)
|
||
B[0] = np.cos(0.4)
|
||
B[7] = np.sin(0.4)
|
||
|
||
R = word_transition_rotor(A, B)
|
||
for alpha in [0.05, 0.2, 0.5, 0.8, 0.95]:
|
||
R_alpha = rotor_power(R, alpha)
|
||
cond = versor_condition(R_alpha)
|
||
assert cond < _TOL, f"alpha={alpha}: versor_condition = {cond:.3e}"
|
||
|
||
|
||
def test_rotor_power_null_translator_scales_translation() -> None:
|
||
"""B²=0 (CGA translator): T^α = 1 + αB, not identity (Cartan dual-slerp)."""
|
||
from algebra.null_point import recover_translation, translator
|
||
|
||
T = translator(np.array([2.0, 0.0, 0.0], dtype=np.float64))
|
||
half = rotor_power(T, 0.5)
|
||
assert versor_condition(half) < _TOL
|
||
a, _ = recover_translation(half)
|
||
np.testing.assert_allclose(a, [1.0, 0.0, 0.0], atol=1e-9)
|
||
# Full power recovers the original translator.
|
||
full = rotor_power(T, 1.0)
|
||
np.testing.assert_allclose(full, T, atol=1e-9)
|
||
|
||
|
||
def test_rotor_power_rejects_wrong_shape() -> None:
|
||
with pytest.raises(ValueError):
|
||
rotor_power(np.zeros(16, dtype=np.float64), 0.5)
|