Phase 3 — vault exact recall index: - Replace O(N) np.array_equal scan with hash-based exact-match index - Add optional max_entries with deterministic FIFO eviction - Index rebuilds on reproject for consistency Phase 4 — Rust versor_apply parity: - Fix CGA metric signature (+,+,+,+,-) and blade ordering to match Python - Implement versor_apply_closed with null-vector preservation, f64 unitize, and construction seed fallback matching Python closure semantics - Gate Rust dispatch behind CORE_BACKEND=rust; Python remains default - Add f64 geometric product for closure-path precision Phase 5 — cognitive quality pack expansion: - Expand lexicon from 55 to 70 entries (evidence, inference, procedure, verification, distinction, relation, thought, understanding, judgment, principle, order, connectives) - Improve semantic templates for cause, procedure, comparison, recall, verification intents - Expand eval cases from 20 to 45 across all categories Validation: 491 tests pass, 45 eval cases at 100% all metrics.
129 lines
4.1 KiB
Python
129 lines
4.1 KiB
Python
"""
|
|
VaultStore — exact memory via CGA inner product scan.
|
|
|
|
No HNSW. No approximate nearest neighbor. No index rebuild.
|
|
Recall is exact and deterministic over stored versors. When the query is the
|
|
same point that was stored, exact self-match is promoted ahead of metric ties
|
|
or CGA-sign artifacts.
|
|
|
|
Exact self-match uses a hash index (versor bytes -> stored indices) instead of
|
|
O(N) np.array_equal scans.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import numpy as np
|
|
from algebra.backend import vault_recall
|
|
from algebra.cga import null_project
|
|
|
|
|
|
def _versor_key(F: np.ndarray) -> bytes:
|
|
return np.asarray(F, dtype=np.float32).tobytes()
|
|
|
|
|
|
class VaultStore:
|
|
def __init__(
|
|
self,
|
|
reproject_interval: int = 100,
|
|
max_entries: int | None = None,
|
|
):
|
|
self._versors: list[np.ndarray] = []
|
|
self._metadata: list[dict] = []
|
|
self._store_count: int = 0
|
|
self._reproject_interval = reproject_interval
|
|
self._max_entries = max_entries
|
|
self._exact_index: dict[bytes, list[int]] = {}
|
|
|
|
def store(self, F: np.ndarray, metadata: dict | None = None) -> int:
|
|
"""Store a versor. Returns its index. Auto-reprojects every N stores."""
|
|
arr = np.asarray(F, dtype=np.float32).copy()
|
|
|
|
if self._max_entries is not None and len(self._versors) >= self._max_entries:
|
|
self._evict_oldest()
|
|
|
|
self._versors.append(arr)
|
|
self._metadata.append(metadata or {})
|
|
idx = len(self._versors) - 1
|
|
|
|
key = _versor_key(arr)
|
|
self._exact_index.setdefault(key, []).append(idx)
|
|
|
|
self._store_count += 1
|
|
if self._reproject_interval > 0 and self._store_count % self._reproject_interval == 0:
|
|
self.reproject()
|
|
return idx
|
|
|
|
def recall(self, query: np.ndarray, top_k: int = 5) -> list:
|
|
"""
|
|
Return top_k closest stored versors by CGA inner product.
|
|
Each result: {versor, score, metadata, index}
|
|
"""
|
|
if not self._versors or top_k <= 0:
|
|
return []
|
|
|
|
query_arr = np.asarray(query, dtype=np.float32)
|
|
ranked = vault_recall(self._versors, query_arr, max(top_k, 1))
|
|
|
|
key = _versor_key(query_arr)
|
|
exact_indices = self._exact_index.get(key, [])
|
|
if exact_indices:
|
|
exact_matches = [(i, float("inf")) for i in exact_indices]
|
|
seen = set(exact_indices)
|
|
ranked = exact_matches + [(i, score) for i, score in ranked if i not in seen]
|
|
|
|
return [
|
|
{
|
|
"versor": self._versors[i],
|
|
"score": float(score),
|
|
"metadata": self._metadata[i],
|
|
"index": i,
|
|
}
|
|
for i, score in ranked[:top_k]
|
|
]
|
|
|
|
def reproject(self) -> None:
|
|
"""
|
|
Re-project all stored versors onto the null cone.
|
|
Corrects floating-point drift. Run between turns or asynchronously.
|
|
"""
|
|
self._versors = [null_project(v) for v in self._versors]
|
|
self._rebuild_index()
|
|
|
|
def _rebuild_index(self) -> None:
|
|
self._exact_index = {}
|
|
for i, v in enumerate(self._versors):
|
|
key = _versor_key(v)
|
|
self._exact_index.setdefault(key, []).append(i)
|
|
|
|
def _evict_oldest(self) -> None:
|
|
"""Remove the oldest entry. Deterministic FIFO eviction."""
|
|
if not self._versors:
|
|
return
|
|
evicted = self._versors.pop(0)
|
|
self._metadata.pop(0)
|
|
key = _versor_key(evicted)
|
|
indices = self._exact_index.get(key, [])
|
|
if indices:
|
|
indices.pop(0)
|
|
if not indices:
|
|
del self._exact_index[key]
|
|
self._reindex_after_eviction()
|
|
|
|
def _reindex_after_eviction(self) -> None:
|
|
"""Rebuild index after front-removal shifts all indices by -1."""
|
|
self._rebuild_index()
|
|
|
|
@property
|
|
def reproject_interval(self) -> int:
|
|
return self._reproject_interval
|
|
|
|
@property
|
|
def store_count(self) -> int:
|
|
return self._store_count
|
|
|
|
@property
|
|
def max_entries(self) -> int | None:
|
|
return self._max_entries
|
|
|
|
def __len__(self) -> int:
|
|
return len(self._versors)
|