Wire alignment graph into load_pack as post-compilation correction pass

- VocabManifold.update(): replace a stored versor in-place (grade-norm
  enforced, KeyError if word missing)
- compile_entries_to_manifold() now also returns entry_id->surface map
  as a second return value; callers that only need the manifold use [0]
- _alignment_nudge_rotor(): slerp-style rotor that rotates source toward
  target by edge.weight * ALIGNMENT_NUDGE_STRENGTH (0.06) — small enough
  to preserve intra-pack geometry, large enough to pull cross-lang pairs
  into proximity
- load_pack(): after both manifolds are built, loads alignment.jsonl,
  resolves entry_id->surface on each side, applies nudge to source versor
  using the already-compiled target versor as the attractor
- _is_hebrew_root() guard retained; _triliteral_root() Hebrew-only
This commit is contained in:
Shay 2026-05-13 15:40:33 -07:00
parent 5e75d46323
commit db7eabd62f
2 changed files with 192 additions and 5 deletions

View file

@ -22,6 +22,12 @@ if TYPE_CHECKING:
from morphology.registry import MorphologyRegistry
from sensorium.protocol import ModalityVocabulary
# Strength of the cross-language alignment nudge applied in load_pack().
# Each aligned pair's source versor is rotated by this fraction of the
# geodesic arc toward the target versor. Small enough to preserve
# intra-pack geometry; large enough to pull cross-lang pairs into proximity.
_ALIGNMENT_NUDGE_STRENGTH: float = 0.06
def _hash_to_blade(name: str, salt: str) -> int:
digest = hashlib.sha256(f"{salt}:{name}".encode("utf-8")).digest()
@ -207,15 +213,76 @@ def _resolved_morphology(
return morphology_registry.get(entry.morphology_id)
def _alignment_nudge_rotor(
source: np.ndarray,
target: np.ndarray,
strength: float,
) -> np.ndarray:
"""
Build a rotor that rotates *source* a fraction *strength* of the way
toward *target* along the geodesic arc between them.
Uses the geometric product of target and reverse(source) to find the
full-arc rotor, then scales the bivector angle by *strength* via slerp.
Falls back to identity if source and target are anti-parallel (degenerate).
"""
# Full-arc rotor: R = target * reverse(source)
from algebra.cl41 import reverse as cl_reverse
R_full = geometric_product(target, cl_reverse(source))
# Extract scalar and bivector norm to find the full rotation angle
scalar = float(R_full[0])
# Clamp to valid acos domain
scalar = max(-1.0, min(1.0, scalar))
theta_full = float(np.arccos(scalar)) # half-angle of full rotation
if abs(theta_full) < 1e-6:
# Already aligned — identity rotor
identity = np.zeros(N_COMPONENTS, dtype=np.float32)
identity[0] = 1.0
return identity
# Bivector part of R_full (components 1..N_COMPONENTS-1)
biv = R_full.copy()
biv[0] = 0.0
biv_norm = float(np.linalg.norm(biv))
if biv_norm < 1e-6:
identity = np.zeros(N_COMPONENTS, dtype=np.float32)
identity[0] = 1.0
return identity
# Unit bivector
biv_unit = biv / biv_norm
# Scale angle by strength
theta_nudge = theta_full * strength
nudge = np.zeros(N_COMPONENTS, dtype=np.float32)
nudge[0] = float(np.cos(theta_nudge))
nudge += (biv_unit * float(np.sin(theta_nudge))).astype(np.float32)
return nudge
def compile_entries_to_manifold(
entries: list[LexicalEntry],
morphology_registry: "MorphologyRegistry | None" = None,
) -> VocabManifold:
) -> tuple[VocabManifold, dict[str, str]]:
"""
Compile entries into a VocabManifold.
Returns:
(manifold, entry_id_to_surface): the compiled manifold and a mapping
from entry_id to surface string, used by the alignment correction pass
in load_pack() to resolve AlignmentEdge source/target IDs.
"""
manifold = VocabManifold()
entry_id_to_surface: dict[str, str] = {}
for entry in entries:
versor = _entry_to_coordinate(entry, _resolved_morphology(entry, morphology_registry))
manifold.add(entry.surface, versor)
return manifold
entry_id_to_surface[entry.entry_id] = entry.surface
return manifold, entry_id_to_surface
def compile_entries_to_modality_vocab(
@ -247,6 +314,43 @@ def _parse_entry(payload: dict) -> LexicalEntry:
)
def _apply_alignment_corrections(
home_manifold: VocabManifold,
home_id_map: dict[str, str],
foreign_manifold: VocabManifold,
foreign_id_map: dict[str, str],
pack_id: str,
) -> None:
"""
Load alignment edges for *pack_id* and nudge each source versor toward
its aligned foreign target versor.
Modifies *home_manifold* in-place via VocabManifold.update().
Silently skips edges whose source or target cannot be resolved
alignment is best-effort; missing entries must not block compilation.
"""
from alignment.graph import load_alignment
graph = load_alignment(pack_id)
if len(graph) == 0:
return
for edge in graph.aligned_pairs("cross_lang"):
source_surface = home_id_map.get(edge.source_id)
target_surface = foreign_id_map.get(edge.target_id)
if source_surface is None or target_surface is None:
continue
try:
source_v = home_manifold.get_versor(source_surface)
target_v = foreign_manifold.get_versor(target_surface)
except KeyError:
continue
nudge = _alignment_nudge_rotor(source_v, target_v, edge.weight * _ALIGNMENT_NUDGE_STRENGTH)
corrected = unitize_versor(geometric_product(nudge, source_v))
home_manifold.update(source_surface, corrected)
def load_pack(pack_id: str) -> tuple[LanguagePackManifest, VocabManifold]:
pack_dir = Path(__file__).parent / "data" / pack_id
manifest_path = pack_dir / "manifest.json"
@ -262,7 +366,6 @@ def load_pack(pack_id: str) -> tuple[LanguagePackManifest, VocabManifold]:
morphology_registry = None
if any(entry.morphology_id for entry in entries):
from morphology.registry import load_morphology
morphology_registry = load_morphology(pack_id)
manifest = LanguagePackManifest(
@ -278,7 +381,65 @@ def load_pack(pack_id: str) -> tuple[LanguagePackManifest, VocabManifold]:
gate_engaged=manifest_payload.get("gate_engaged", False),
oov_policy=OOVPolicy(manifest_payload.get("oov_policy", OOVPolicy.FAIL_CLOSED.value)),
)
return manifest, compile_entries_to_manifold(entries, morphology_registry=morphology_registry)
home_manifold, home_id_map = compile_entries_to_manifold(
entries, morphology_registry=morphology_registry
)
# Alignment correction pass: load the sibling pack(s) referenced by
# this pack's alignment.jsonl and nudge aligned pairs into proximity.
# This is only attempted when a sibling pack exists on disk; missing
# sibling packs are silently skipped so packs remain independently loadable.
from alignment.graph import load_alignment
alignment_graph = load_alignment(pack_id)
if len(alignment_graph) > 0:
# Collect all foreign pack_ids referenced by this pack's edges
foreign_pack_ids = _infer_foreign_pack_ids(pack_id, alignment_graph)
for foreign_pack_id in foreign_pack_ids:
foreign_pack_dir = Path(__file__).parent / "data" / foreign_pack_id
if not foreign_pack_dir.exists():
continue
foreign_entries = load_pack_entries(foreign_pack_id)
foreign_morph_registry = None
if any(e.morphology_id for e in foreign_entries):
from morphology.registry import load_morphology
foreign_morph_registry = load_morphology(foreign_pack_id)
foreign_manifold, foreign_id_map = compile_entries_to_manifold(
foreign_entries, morphology_registry=foreign_morph_registry
)
_apply_alignment_corrections(
home_manifold, home_id_map,
foreign_manifold, foreign_id_map,
pack_id,
)
return manifest, home_manifold
def _infer_foreign_pack_ids(
home_pack_id: str,
graph: "alignment.graph.AlignmentGraph",
) -> list[str]:
"""
Derive foreign pack_ids from target_id prefixes in the alignment graph.
Convention: target_id is "<lang_prefix>-NNN" where lang_prefix maps to
a known pack directory name. Currently supports he grc cross-links.
"""
from alignment.graph import AlignmentGraph # local import to avoid cycle
_PREFIX_TO_PACK: dict[str, str] = {
"he": "he_logos_micro_v1",
"grc": "grc_logos_micro_v1",
"en": "en_minimal_v1",
}
foreign: set[str] = set()
for edge in graph.edges:
prefix = edge.target_id.split("-")[0]
pack = _PREFIX_TO_PACK.get(prefix)
if pack and pack != home_pack_id:
foreign.add(pack)
return sorted(foreign)
def load_pack_entries(pack_id: str) -> list[LexicalEntry]:

View file

@ -6,7 +6,7 @@ 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().
time in add() and at replacement time in update().
Normalization doctrine for this module:
- Raw coordinate vectors (e.g. from external embeddings) must be
@ -65,6 +65,32 @@ class VocabManifold:
self._words.append(word)
self._versors.append(v)
def update(self, word: str, versor: np.ndarray) -> None:
"""
Replace the versor for an existing word in-place.
Used by the alignment correction pass after compilation to nudge
cross-language aligned pairs toward each other without rebuilding
the full manifold. The grade-norm invariant is enforced identically
to add().
Raises:
KeyError: if the word is not already in the manifold.
ValueError: if the grade-norm condition is not satisfied.
"""
try:
idx = self._words.index(word)
except ValueError:
raise KeyError(f"Word '{word}' not in vocabulary; use add() for new entries.")
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}': replacement versor grade-norm {grade_norm:.4f} ≠ ±1. "
"Call algebra.versor.unitize_versor() before update()."
)
self._versors[idx] = v
def get_versor(self, word: str) -> np.ndarray:
"""Look up a word's versor by string. Raises KeyError if not found."""
try: