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:
Shay 2026-05-16 11:44:45 -07:00
parent 5ea47af91a
commit 07f49eb215
4 changed files with 166 additions and 6 deletions

View file

@ -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 (`` < 0``): ``R^α = cos(α·θ/2) + (sin(α·θ/2)/|B|) · B``
where ``θ/2 = atan2(|B|, a)``.
- boost plane (`` > 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.

View file

@ -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,

View file

@ -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
View 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)