fix: route production hot paths through algebra.backend (Commit 2)

Three surgical import changes. No behavior change. No new semantics.
Backend decides Rust vs pure-Python transparently.

- field/propagate.py: versor_apply <- algebra.backend
- vocab/manifold.py: cga_inner    <- algebra.backend
- vault/store.py:    recall loop replaced with vault_recall() from
  algebra.backend; public result shape preserved ({versor, score,
  metadata, index}). null_project stays on algebra.cga (not the
  recall hot path). store() and reproject() unchanged.

Rust path for vault_recall uses Rayon parallel scan and releases
the GIL. Python fallback is sequential and behaviorally identical.
No batching introduced; that is Commit 3+.
This commit is contained in:
Shay 2026-05-13 12:35:22 -07:00
parent 3746f06898
commit 3620c967b9
3 changed files with 30 additions and 7 deletions

View file

@ -5,9 +5,12 @@ Each step: F <- versor_apply(V, F)
V is the rotor for the current node's outgoing edge in the vocab manifold.
No correction. No normalization. No conditional branching. The loop is tight.
Hot path routes through algebra.backend, which dispatches to the Rust
extension (core_rs) when available and falls back to pure Python silently.
"""
from algebra.versor import versor_apply
from algebra.backend import versor_apply
from field.state import FieldState

View file

@ -4,10 +4,19 @@ VaultStore — exact memory via CGA inner product scan.
No HNSW. No approximate nearest neighbor. No index rebuild.
Recall is exact: argmax_i { cga_inner(query, X_i) } over stored versors.
Periodic null_project() prevents floating-point null-cone drift in long sessions.
Hot path: recall() routes through algebra.backend.vault_recall(), which
dispatches to a Rayon parallel scan (releases GIL) when core_rs is available
and falls back to a sequential Python scan silently. Public result shape
is unchanged: list of {versor, score, metadata, index}.
null_project() remains on algebra.cga it is not the recall hot path
and does not benefit from the same batching pattern.
"""
import numpy as np
from algebra.cga import cga_inner, null_project
from algebra.backend import vault_recall
from algebra.cga import null_project
class VaultStore:
@ -30,25 +39,31 @@ class VaultStore:
"""
Return top_k closest stored versors by CGA inner product.
Each result: {versor, score, metadata, index}
Routes through algebra.backend.vault_recall():
Rust path Rayon parallel scan, GIL released.
Python path sequential, behaviorally identical.
"""
if not self._versors:
return []
scores = [cga_inner(query, v) for v in self._versors]
top_indices = sorted(range(len(scores)), key=lambda i: -scores[i])[:top_k]
ranked = vault_recall(self._versors, query, top_k)
return [
{
"versor": self._versors[i],
"score": scores[i],
"score": float(score),
"metadata": self._metadata[i],
"index": i,
}
for i in top_indices
for i, score in ranked
]
def reproject(self) -> None:
"""
Re-project all stored versors onto the null cone.
Corrects floating-point drift. Run between turns or asynchronously.
null_project stays on algebra.cga not the recall hot path.
"""
self._versors = [null_project(v) for v in self._versors]

View file

@ -21,10 +21,13 @@ Indexed access:
get_word_at(idx) returns the word string by integer index.
These are the primitives generation uses; VocabManifold does not build
operators. Algebra builds operators. Vocab stores points.
Hot path: nearest() routes cga_inner through algebra.backend, which
dispatches to the Rust extension when available.
"""
import numpy as np
from algebra.cga import cga_inner
from algebra.backend import cga_inner
from algebra.cl41 import geometric_product, reverse
@ -86,6 +89,8 @@ class VocabManifold:
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.
Hot path: cga_inner routes through algebra.backend.
"""
best_score = -np.inf
best_idx = 0