algebra/rotor.py and persona/motor.py were calling normalize_to_versor() which is the gate-only injection primitive. Both are construction-time sites (building rotors and motors from raw arrays), so the correct call is unitize_versor(). Also tightens TestINV02 to scan for normalize_to_versor violations only — unitize_versor has its own legitimate call sites and is not under the same single-site restriction. Adds a new TestINV02b that verifies unitize_versor is NOT called inside propagation, generation, or vault recall paths. Fixes: INV-02 architectural invariant test failure.
38 lines
1.3 KiB
Python
38 lines
1.3 KiB
Python
"""
|
|
algebra/rotor.py — Rotor construction operators for Cl(4,1).
|
|
|
|
Rotors are operators. They live here, in algebra/, not in vocab/.
|
|
A rotor between two word-versors is a contextual, field-level concern:
|
|
it describes a transformation being applied, not a property of the vocabulary.
|
|
"""
|
|
|
|
import numpy as np
|
|
from .cl41 import geometric_product, reverse
|
|
from .versor import unitize_versor
|
|
|
|
|
|
def word_transition_rotor(A: np.ndarray, B: np.ndarray) -> np.ndarray:
|
|
"""
|
|
Compute the rotor R that rotates versor A toward versor B in Cl(4,1).
|
|
|
|
R = unitize(1 + B * reverse(A))
|
|
|
|
This is a pure construction operation — building a new algebraic object
|
|
from two input versors. unitize_versor() is the correct primitive here,
|
|
not normalize_to_versor() (which is reserved for the injection gate).
|
|
|
|
This is a pure operator — it transforms a field state, it does not
|
|
encode a position. Call this from algebra-aware field logic; never
|
|
store the result on a vocabulary structure.
|
|
|
|
Args:
|
|
A: Source versor, shape (32,), grade-normed to ±1.
|
|
B: Target versor, shape (32,), grade-normed to ±1.
|
|
|
|
Returns:
|
|
R: Unitized rotor in Cl(4,1), shape (32,).
|
|
"""
|
|
R = geometric_product(B, reverse(A))
|
|
R = R.copy()
|
|
R[0] += 1.0
|
|
return unitize_versor(R)
|