- 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.
78 lines
3 KiB
Python
78 lines
3 KiB
Python
"""
|
|
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.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,), grade-normed to ±1
|
|
|
|
def add(self, word: str, versor: np.ndarray) -> None:
|
|
"""
|
|
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(v)
|
|
|
|
def get_versor(self, word: str) -> np.ndarray:
|
|
"""Look up a word's versor. Raises KeyError if not found."""
|
|
try:
|
|
idx = self._words.index(word)
|
|
return self._versors[idx].copy()
|
|
except ValueError:
|
|
raise KeyError(f"Word '{word}' not in vocabulary.")
|
|
|
|
def nearest(self, F: np.ndarray, exclude_idx: int = -1) -> tuple:
|
|
"""
|
|
Find the word whose versor is closest to F by CGA inner product.
|
|
Returns (word, index). O(|vocab|), exact, no approximation.
|
|
cga_inner(X, Y) = -d^2 / 2 for null vectors: maximizing = minimizing distance.
|
|
"""
|
|
best_score = -np.inf
|
|
best_idx = 0
|
|
for i, v in enumerate(self._versors):
|
|
if i == exclude_idx:
|
|
continue
|
|
score = cga_inner(F, v)
|
|
if score > best_score:
|
|
best_score = score
|
|
best_idx = i
|
|
return self._words[best_idx], best_idx
|
|
|
|
def __len__(self) -> int:
|
|
return len(self._words)
|