fix(drift): proper rotor-manifold scaling; restore respond contract
Three issues in the drift-fix landing (922bddc) addressed:
1. algebra/rotor.py: add rotor_power(R, alpha) — slerp on the rotor manifold
via the rotor's exp/log decomposition. Handles both rotation planes
(cos/sin) and boost planes (cosh/sinh); falls back to identity for
non-simple bivectors or null cases.
2. generate/stream.py: the score-weighted vault recall previously did
`weight*V + (1-weight)*np.eye(V.shape[0])`. Two bugs:
- np.eye produced a 32x32 matrix for a 1D multivector, crashing
versor_apply with a broadcasting error (2 cognition tests failing
on main).
- The linear blend produced multivectors with versor_condition up to
2.2e-2, violating the non-negotiable 1e-6 invariant declared in
CLAUDE.md. Now uses rotor_power(V, weight) which stays on the
manifold by construction (versor_condition <= 1.1e-16).
3. session/context.py: respond() now re-binds result.final_state to
self.state after finalize_turn's anchor pull, restoring the
"respond returns the same object that was vaulted" contract
(test_engine_loop_proof regression).
Verification:
- 41 new tests in tests/test_rotor_power.py covering closure preservation,
alpha=0/1 boundaries, half-angle composition, and word-transition rotors.
- Empirical multi-turn versor_condition stays at machine epsilon with
anchor pull, max 9.4e-7 without (under threshold either way after fix).
- Full suite: 609 passed, 4 skipped, 0 failed.
This commit is contained in:
parent
5ea47af91a
commit
07f49eb215
4 changed files with 166 additions and 6 deletions
|
|
@ -72,6 +72,78 @@ def make_rotor_from_angle(angle: float, bivector_idx: int = 6) -> np.ndarray:
|
|||
return unitize_versor(rotor)
|
||||
|
||||
|
||||
def rotor_power(R: np.ndarray, alpha: float) -> np.ndarray:
|
||||
"""Return R^alpha — the rotor on the manifold path from identity to R by alpha.
|
||||
|
||||
For a simple unit rotor decomposed as ``R = a + B`` (scalar + bivector):
|
||||
|
||||
- rotation plane (``B² < 0``): ``R^α = cos(α·θ/2) + (sin(α·θ/2)/|B|) · B``
|
||||
where ``θ/2 = atan2(|B|, a)``.
|
||||
- boost plane (``B² > 0``): ``R^α = cosh(α·η/2) + (sinh(α·η/2)/|B|) · B``
|
||||
where ``η/2 = atanh(|B|/a)``.
|
||||
|
||||
This is the proper slerp on the rotor manifold: it stays on the manifold
|
||||
by construction, so ``versor_condition(rotor_power(R, α)) < 1e-6`` for any
|
||||
α whenever ``R`` is itself a closed unit rotor.
|
||||
|
||||
Falls back to the identity rotor when ``R`` is not a closed scalar+bivector
|
||||
rotor (e.g. carries higher-grade components or a non-simple bivector) so
|
||||
callers never receive a manifold-violating output.
|
||||
"""
|
||||
R_arr = np.asarray(R, dtype=np.float64)
|
||||
if R_arr.shape != (N_COMPONENTS,):
|
||||
raise ValueError(
|
||||
f"rotor_power expects a {N_COMPONENTS}-component rotor; got {R_arr.shape}."
|
||||
)
|
||||
|
||||
dtype = _result_dtype(R_arr)
|
||||
a = float(R_arr[0])
|
||||
B = R_arr.copy()
|
||||
B[0] = 0.0
|
||||
|
||||
# Quick guard: bivector must be a simple bivector (B² is grade-0 only).
|
||||
B_sq_full = geometric_product(B, B).astype(np.float64)
|
||||
bsq_scalar = float(B_sq_full[0])
|
||||
B_sq_higher = B_sq_full.copy()
|
||||
B_sq_higher[0] = 0.0
|
||||
if float(np.linalg.norm(B_sq_higher)) > 1e-6:
|
||||
# Non-simple bivector — return identity to avoid drift.
|
||||
return _identity(dtype)
|
||||
|
||||
# Near-identity: nothing to scale.
|
||||
bivector_norm = float(np.linalg.norm(B))
|
||||
if bivector_norm < _NEAR_ZERO_TOL:
|
||||
return _identity(dtype)
|
||||
|
||||
if bsq_scalar < 0.0:
|
||||
# Rotation plane. B² = -|B|² under signature, so the effective
|
||||
# magnitude is the Euclidean norm of the bivector coefficients.
|
||||
b_mag = float(np.sqrt(-bsq_scalar))
|
||||
theta_half = float(np.arctan2(b_mag, a))
|
||||
new_a = float(np.cos(alpha * theta_half))
|
||||
new_b_mag = float(np.sin(alpha * theta_half))
|
||||
elif bsq_scalar > 0.0:
|
||||
# Boost plane.
|
||||
b_mag = float(np.sqrt(bsq_scalar))
|
||||
# atanh requires |b_mag/a| < 1; for closed rotors a² - B² = 1 means
|
||||
# |b_mag| < |a|, so this is safe when a > 0.
|
||||
if a == 0.0:
|
||||
return _identity(dtype)
|
||||
eta_half = float(np.arctanh(b_mag / a))
|
||||
new_a = float(np.cosh(alpha * eta_half))
|
||||
new_b_mag = float(np.sinh(alpha * eta_half))
|
||||
else:
|
||||
# B² = 0: null bivector. Cannot interpolate on the manifold;
|
||||
# return identity to fail safely.
|
||||
return _identity(dtype)
|
||||
|
||||
result = np.zeros(N_COMPONENTS, dtype=np.float64)
|
||||
result[0] = new_a
|
||||
if b_mag > _NEAR_ZERO_TOL:
|
||||
result += (new_b_mag / b_mag) * B
|
||||
return result.astype(dtype, copy=False)
|
||||
|
||||
|
||||
def word_transition_rotor(A: np.ndarray, B: np.ndarray) -> np.ndarray:
|
||||
"""
|
||||
Compute the closed transition operator from source versor A to target B.
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import numpy as np
|
|||
|
||||
from field.state import FieldState
|
||||
from field.propagate import propagate_step
|
||||
from algebra.rotor import word_transition_rotor
|
||||
from algebra.rotor import rotor_power, word_transition_rotor
|
||||
from algebra.versor import unitize_versor
|
||||
from generate.attention import AttentionOperator
|
||||
from generate.result import GenerationResult
|
||||
|
|
@ -191,10 +191,11 @@ def _recall_state(state: FieldState, vault, top_k: int) -> tuple[FieldState, int
|
|||
V = word_transition_rotor(current.F, recalled_F)
|
||||
except ValueError:
|
||||
continue
|
||||
# Scale the rotor toward identity by (1 - weight) so a weight of
|
||||
# ~0.0 leaves the field nearly unchanged and weight ~1.0 applies
|
||||
# the full transition.
|
||||
V_scaled = weight * V + (1.0 - weight) * np.eye(V.shape[0], dtype=V.dtype)
|
||||
# Scale the rotor toward identity by raising it to the (weight)
|
||||
# power on the rotor manifold. ``rotor_power`` stays on the manifold
|
||||
# by construction (versor_condition stays < 1e-6), unlike a linear
|
||||
# blend ``weight·V + (1-weight)·identity`` which violates closure.
|
||||
V_scaled = rotor_power(V, float(weight))
|
||||
current = propagate_step(current, V_scaled)
|
||||
current = FieldState(
|
||||
F=current.F,
|
||||
|
|
|
|||
|
|
@ -325,7 +325,12 @@ class SessionContext:
|
|||
)
|
||||
result = generate(pivot, self.vocab, self.persona, max_tokens, vault=self.vault)
|
||||
self.finalize_turn(result, input_versor=input_versor, dialogue_role="assert")
|
||||
return result
|
||||
# Drift fix 3 may have rotated/pulled the state inside finalize_turn;
|
||||
# re-bind result.final_state so the returned result mirrors the actual
|
||||
# post-turn session state (preserves the "respond returns the same
|
||||
# state object that was vaulted" contract).
|
||||
from dataclasses import replace as _replace
|
||||
return _replace(result, final_state=self.state)
|
||||
|
||||
def recall(self, query_tokens: list, top_k: int = 5) -> list:
|
||||
query_state = inject(query_tokens, self.vocab)
|
||||
|
|
|
|||
82
tests/test_rotor_power.py
Normal file
82
tests/test_rotor_power.py
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
"""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_rejects_wrong_shape() -> None:
|
||||
with pytest.raises(ValueError):
|
||||
rotor_power(np.zeros(16, dtype=np.float64), 0.5)
|
||||
Loading…
Reference in a new issue