arch: close coordinate back-door in vocab layer

- Add algebra/rotor.py: word_transition_rotor() as a free operator function
- Update algebra/__init__.py: export word_transition_rotor
- Refactor vocab/manifold.py: remove edge_rotor(), add versor grade-norm
  invariant check in add() to reject raw coordinate vectors at insertion time

VocabManifold now stores only algebraically valid Cl(4,1) versors and
exposes only relational lookup (CGA inner product). Rotor construction
is a contextual algebra-layer concern, not a vocabulary property.
This commit is contained in:
Shay 2026-05-12 20:52:14 -07:00
parent 7d814fac3f
commit bd423e489c
3 changed files with 67 additions and 16 deletions

View file

@ -2,3 +2,4 @@ from .cl41 import geometric_product, reverse, grade_project, scalar_part, norm_s
from .versor import versor_apply, normalize_to_versor, versor_condition
from .cga import cga_inner, outer_product, is_null, null_project, embed_point
from .holonomy import holonomy_encode, holonomy_similarity
from .rotor import word_transition_rotor

34
algebra/rotor.py Normal file
View file

@ -0,0 +1,34 @@
"""
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 normalize_to_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 = normalize(1 + B * reverse(A))
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: Normalized rotor in Cl(4,1), shape (32,).
"""
R = geometric_product(B, reverse(A))
R = R.copy()
R[0] += 1.0
return normalize_to_versor(R)

View file

@ -3,23 +3,51 @@ VocabManifold — the geometric vocabulary.
Each word is a versor in Cl(4,1). nearest(F) finds the closest word
by CGA inner product no cosine similarity, no ANN index.
Invariant: every stored versor must satisfy the Cl(4,1) grade-norm
condition |V * reverse(V)|_scalar ±1. This is enforced at insertion
time in add(). Raw coordinate vectors (e.g. from external embeddings)
will fail this check use normalize_to_versor() before calling add().
Rotor construction between word-versors is NOT a vocabulary concern.
Use algebra.word_transition_rotor(A, B) from the algebra layer when
a transition operator is needed in field or generation logic.
"""
import numpy as np
from algebra.cga import cga_inner
from algebra.versor import normalize_to_versor
from algebra.cl41 import geometric_product, reverse
from algebra.versor import normalize_to_versor
class VocabManifold:
def __init__(self):
self._words: list = []
self._versors: list = [] # each shape (32,)
self._versors: list = [] # each shape (32,), grade-normed to ±1
def add(self, word: str, versor: np.ndarray) -> None:
"""Register a word-versor pair."""
"""
Register a word-versor pair.
Enforces the Cl(4,1) versor invariant: the scalar part of
V * reverse(V) must be ±1. This rejects any raw coordinate
vector or external embedding that has not been lifted into the
algebra. If your source is a float array from outside the system,
call normalize_to_versor() first.
Raises:
ValueError: if the grade-norm condition is not satisfied.
"""
v = np.asarray(versor, dtype=np.float32).copy()
grade_norm = float(geometric_product(v, reverse(v))[0])
if not (0.95 <= abs(grade_norm) <= 1.05):
raise ValueError(
f"Word '{word}': versor grade-norm {grade_norm:.4f} ≠ ±1. "
"Pass a valid Cl(4,1) versor. "
"If lifting from a raw array, call normalize_to_versor() first."
)
self._words.append(word)
self._versors.append(np.asarray(versor, dtype=np.float32).copy())
self._versors.append(v)
def get_versor(self, word: str) -> np.ndarray:
"""Look up a word's versor. Raises KeyError if not found."""
@ -46,17 +74,5 @@ class VocabManifold:
best_idx = i
return self._words[best_idx], best_idx
def edge_rotor(self, from_idx: int, to_idx: int) -> np.ndarray:
"""
Compute the rotor that rotates from_versor toward to_versor.
R = normalize(1 + B * reverse(A))
"""
A = self._versors[from_idx]
B = self._versors[to_idx]
R = geometric_product(B, reverse(A))
R = R.copy()
R[0] += 1.0
return normalize_to_versor(R)
def __len__(self) -> int:
return len(self._words)