Add salience and attention operators, wire salience-gated candidate selection into generation, expose vault/salience trace telemetry, and add tests proving non-placeholder salience behavior.
52 lines
1.9 KiB
Python
52 lines
1.9 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
|
|
import numpy as np
|
|
|
|
from algebra.backend import cga_inner
|
|
from field.state import FieldState
|
|
from vocab.manifold import VocabManifold
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class SalienceMap:
|
|
indices: np.ndarray
|
|
scores: np.ndarray
|
|
budget: int
|
|
|
|
def __post_init__(self) -> None:
|
|
object.__setattr__(self, "indices", np.asarray(self.indices, dtype=np.int64).copy())
|
|
object.__setattr__(self, "scores", np.asarray(self.scores, dtype=np.float32).copy())
|
|
object.__setattr__(self, "budget", int(self.budget))
|
|
|
|
|
|
class SalienceOperator:
|
|
"""
|
|
Compute geometric salience of manifold points relative to current FieldState.
|
|
|
|
Salience is field-relative CGA activation:
|
|
salience(v_i) = |cga_inner(F, v_i)| / (||F|| * ||v_i||)
|
|
|
|
No learned weights. No softmax. Pure geometry routed through algebra.backend,
|
|
which uses core_rs when active.
|
|
"""
|
|
|
|
def compute(self, field: FieldState, vocab: VocabManifold, top_k: int = 16) -> SalienceMap:
|
|
if top_k <= 0:
|
|
return SalienceMap(indices=np.asarray([], dtype=np.int64), scores=np.asarray([], dtype=np.float32), budget=0)
|
|
if len(vocab) == 0:
|
|
return SalienceMap(indices=np.asarray([], dtype=np.int64), scores=np.asarray([], dtype=np.float32), budget=0)
|
|
|
|
query = np.asarray(field.F, dtype=np.float32)
|
|
query_norm = max(float(np.linalg.norm(query)), 1e-8)
|
|
scores: list[float] = []
|
|
for idx in range(len(vocab)):
|
|
v = vocab.get_versor_at(idx)
|
|
denom = query_norm * max(float(np.linalg.norm(v)), 1e-8)
|
|
scores.append(abs(float(cga_inner(query, v))) / denom)
|
|
|
|
scores_arr = np.asarray(scores, dtype=np.float32)
|
|
k = min(int(top_k), len(vocab))
|
|
order = np.argsort(-scores_arr, kind="stable")[:k]
|
|
return SalienceMap(indices=order.astype(np.int64), scores=scores_arr[order], budget=k)
|