feat: vault recall index, Rust versor parity, cognitive pack expansion

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.
This commit is contained in:
Shay 2026-05-15 15:34:39 -07:00
parent cc46dca87a
commit 523c072818
17 changed files with 610 additions and 67 deletions

View file

@ -34,11 +34,18 @@ def geometric_product(A: np.ndarray, B: np.ndarray) -> np.ndarray:
def versor_apply(V: np.ndarray, F: np.ndarray) -> np.ndarray:
"""Apply a versor through the canonical algebra closure boundary.
The Rust extension's raw sandwich path is intentionally bypassed here
until it enforces the same closure semantics as algebra.versor. Runtime
invariants depend on this operator returning a closed field; generation,
propagation, and vault recall are not allowed to repair it downstream.
When CORE_BACKEND=rust is set and the Rust extension exposes
versor_apply_with_closure, Rust handles the full closure path
(null-vector preservation, unitize, seed fallback). Otherwise
falls back to pure Python algebra.versor.
"""
if _RUST and _REQUESTED_BACKEND == "rust":
try:
return np.asarray(
_rs.versor_apply_with_closure(V, F), dtype=np.float32
)
except (AttributeError, Exception):
pass
from algebra.versor import versor_apply as _va
return _va(V, F)

View file

@ -1,16 +1,14 @@
//! CGA inner product and null-cone operations.
//!
//! Signature: (+,+,+,-,+), with Euclidean coordinates on e1,e2,e3.
//! The conformal null directions are:
//!
//! n_o = 0.5 * (e4 - e5) # origin, n_o^2 = 0
//! n_inf = e4 + e5 # infinity, n_inf^2 = 0
//! n_o · n_inf = -1
//! Signature: (+,+,+,+,-), with Euclidean coordinates on e1,e2,e3.
//! e4^2 = +1, e5^2 = -1.
//!
//! A Euclidean point x embeds as:
//!
//! X = x + n_o + 0.5 * |x|^2 * n_inf
//!
//! with e4 coeff = 0.5*(|x|^2 - 1), e5 coeff = 0.5*(|x|^2 + 1).
//!
//! Then X·X = 0 and X·Y = -0.5 * ||x-y||^2.
//!
//! This is the ONLY distance metric in CORE-AI.
@ -53,7 +51,7 @@ pub fn embed_point_raw(p: &[f32; 3]) -> [f32; 32] {
result[2] = p[1];
result[3] = p[2];
let r2 = p[0] * p[0] + p[1] * p[1] + p[2] * p[2];
result[4] = 0.5 * (r2 + 1.0);
result[5] = 0.5 * (r2 - 1.0);
result[4] = 0.5 * (r2 - 1.0);
result[5] = 0.5 * (r2 + 1.0);
result
}

View file

@ -1,10 +1,13 @@
//! Cl(4,1) geometric product via precomputed table.
//!
//! Signature: (+,+,+,-,+). 32-component f32 multivectors.
//! Signature: (+,+,+,+,-). 32-component f32 multivectors.
//! The multiplication table is computed once at program start using
//! const evaluation and stored as two [u8;1024] and [i8;1024] arrays
//! (index and sign for each of the 32x32 blade pairs).
//!
//! Blade ordering matches Python's itertools.combinations(range(5), k)
//! lexicographic tuple order within each grade.
//!
//! geometric_product_raw is the inner loop called by every higher-level op.
//! It is deliberately kept allocation-free: inputs and output are [f32;32].
@ -20,31 +23,30 @@ pub enum Cl41Error {
// We encode each blade as a bitmask over 5 bits (bit k = basis vector k+1 present)
// The mapping from bitmask to component index follows grade-ascending, lex order.
// Signature: e1^2=+1, e2^2=+1, e3^2=+1, e4^2=-1, e5^2=+1
const SIG: [i8; 5] = [1, 1, 1, -1, 1];
// Signature: e1^2=+1, e2^2=+1, e3^2=+1, e4^2=+1, e5^2=-1
const SIG: [i8; 5] = [1, 1, 1, 1, -1];
// Precomputed at compile time via const fn
const BLADE_MASKS: [u8; 32] = build_blade_masks();
const MASK_TO_IDX: [u8; 32] = build_mask_to_idx();
const fn build_blade_masks() -> [u8; 32] {
// Grade-ascending, lex order over 5 bits
let mut masks = [0u8; 32];
let mut pos = 0usize;
let mut k = 0u8;
while k <= 5 {
// Iterate over all 5-bit masks with popcount == k
let mut mask = 0u8;
while mask < 32 {
if popcount5(mask) == k {
masks[pos] = mask;
pos += 1;
}
mask += 1;
}
k += 1;
}
masks
// Must match Python's itertools.combinations(range(5), k) order.
// Hardcoded to guarantee exact parity with Python cl41.py.
[
// grade 0: ()
0b00000,
// grade 1: (0,), (1,), (2,), (3,), (4,)
0b00001, 0b00010, 0b00100, 0b01000, 0b10000,
// grade 2: (0,1), (0,2), (0,3), (0,4), (1,2), (1,3), (1,4), (2,3), (2,4), (3,4)
0b00011, 0b00101, 0b01001, 0b10001, 0b00110, 0b01010, 0b10010, 0b01100, 0b10100, 0b11000,
// grade 3: (0,1,2), (0,1,3), (0,1,4), (0,2,3), (0,2,4), (0,3,4), (1,2,3), (1,2,4), (1,3,4), (2,3,4)
0b00111, 0b01011, 0b10011, 0b01101, 0b10101, 0b11001, 0b01110, 0b10110, 0b11010, 0b11100,
// grade 4: (0,1,2,3), (0,1,2,4), (0,1,3,4), (0,2,3,4), (1,2,3,4)
0b01111, 0b10111, 0b11011, 0b11101, 0b11110,
// grade 5: (0,1,2,3,4)
0b11111,
]
}
const fn build_mask_to_idx() -> [u8; 32] {
@ -128,6 +130,25 @@ fn table() -> &'static Table {
TABLE.get_or_init(build_table)
}
/// Full geometric product in Cl(4,1) with f64 precision.
/// Used by versor closure where residue checks need high accuracy.
pub fn geometric_product_f64(a: &[f64; 32], b: &[f64; 32]) -> [f64; 32] {
let t = table();
let mut result = [0f64; 32];
for i in 0..32 {
let ai = a[i];
if ai == 0.0 { continue; }
for j in 0..32 {
let bj = b[j];
if bj == 0.0 { continue; }
let k = t.idx[i][j] as usize;
let s = t.sign[i][j] as f64;
result[k] += s * ai * bj;
}
}
result
}
/// Full geometric product in Cl(4,1).
/// Both inputs are [f32; 32]. Returns [f32; 32]. Allocation-free.
pub fn geometric_product_raw(a: &[f32; 32], b: &[f32; 32]) -> Result<[f32; 32], Cl41Error> {
@ -152,9 +173,15 @@ pub fn geometric_product_raw(a: &[f32; 32], b: &[f32; 32]) -> Result<[f32; 32],
/// Grade 0,1: +1. Grade 2,3: -1. Grade 4,5: +1.
pub fn reverse_raw(a: &[f32; 32]) -> [f32; 32] {
let mut r = *a;
// Grade 2: indices 6..=15
for i in 6..=15 { r[i] = -r[i]; }
// Grade 3: indices 16..=25
for i in 16..=25 { r[i] = -r[i]; }
r
}
/// Reverse anti-automorphism (f64).
pub fn reverse_f64(a: &[f64; 32]) -> [f64; 32] {
let mut r = *a;
for i in 6..=15 { r[i] = -r[i]; }
for i in 16..=25 { r[i] = -r[i]; }
r
}

View file

@ -20,7 +20,7 @@ pub mod versor;
use cga::cga_inner_raw;
use cl41::geometric_product_raw;
use vault::vault_recall_raw;
use versor::{normalize_to_versor_raw, versor_apply_raw, versor_condition_raw};
use versor::{normalize_to_versor_raw, versor_apply_closed, versor_apply_raw, versor_condition_raw};
/// Geometric product in Cl(4,1). Accepts two numpy-compatible f32 arrays of length 32.
#[pyfunction]
@ -50,6 +50,21 @@ fn versor_apply(
f32_array_to_numpy(py, &result)
}
/// Sandwich product V*F*reverse(V) with closure semantics.
/// Preserves null vectors, applies unit-versor closure with seed fallback.
#[pyfunction]
fn versor_apply_with_closure(
py: Python<'_>,
v: &pyo3::types::PyAny,
f: &pyo3::types::PyAny,
) -> PyResult<PyObject> {
let v_slice = extract_f32_slice(v)?;
let f_slice = extract_f32_slice(f)?;
let result = versor_apply_closed(&v_slice, &f_slice)
.map_err(|e| PyValueError::new_err(e.to_string()))?;
f32_array_to_numpy(py, &result)
}
/// ||F*reverse(F) - 1||_F. Returns scalar f32.
#[pyfunction]
fn versor_condition(f: &pyo3::types::PyAny) -> PyResult<f32> {
@ -120,6 +135,7 @@ fn f32_array_to_numpy(py: Python<'_>, data: &[f32; 32]) -> PyResult<PyObject> {
fn core_rs(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(geometric_product, m)?)?;
m.add_function(wrap_pyfunction!(versor_apply, m)?)?;
m.add_function(wrap_pyfunction!(versor_apply_with_closure, m)?)?;
m.add_function(wrap_pyfunction!(versor_condition, m)?)?;
m.add_function(wrap_pyfunction!(normalize_to_versor, m)?)?;
m.add_function(wrap_pyfunction!(cga_inner, m)?)?;

View file

@ -4,7 +4,7 @@
//! normalize_to_versor F/sqrt(|F*rev(F)|) — called once at injection gate
//! versor_condition ||F*rev(F)-1||_F — used in tests and gate only
use crate::cl41::{geometric_product_raw, reverse_raw, Cl41Error};
use crate::cl41::{geometric_product_f64, geometric_product_raw, reverse_f64, reverse_raw, Cl41Error};
use thiserror::Error;
#[derive(Debug, Error)]
@ -15,8 +15,104 @@ pub enum VersorError {
NullVersor(f32),
}
/// Sandwich product V * F * reverse(V).
/// Allocation-free. This is the hot path — called every generation step.
const NEAR_ZERO_TOL: f64 = 1e-12;
const NULL_SCALAR_TOL: f64 = 1e-9;
const CONSTRUCTION_RESIDUE_TOL: f64 = 1e-2;
const SEED_BIVECTORS: [usize; 6] = [6, 7, 8, 10, 11, 13];
fn is_null_vector(v: &[f32; 32]) -> bool {
use crate::cga::cga_inner_raw;
// Generous tolerance: the f32 sandwich product introduces ~1e-6 error
// on null vectors; 1e-5 correctly classifies them without false positives
// on actual versors (which have cga_inner >> 0.1).
match cga_inner_raw(v, v) {
Ok(inner) => (inner as f64).abs() < 1e-5,
Err(_) => false,
}
}
fn unitize_closed(v: &[f64; 32]) -> Result<[f64; 32], ()> {
let input_norm: f64 = v.iter().map(|x| x * x).sum::<f64>().sqrt();
if input_norm < NEAR_ZERO_TOL {
return Err(());
}
let rev = reverse_f64(v);
let vv = geometric_product_f64(v, &rev);
let scalar_sq = vv[0];
let residue_norm: f64 = vv[1..].iter().map(|x| x * x).sum::<f64>().sqrt();
if residue_norm >= CONSTRUCTION_RESIDUE_TOL {
return Err(());
}
if scalar_sq <= 0.0 {
return Err(());
}
let inv = 1.0 / scalar_sq.sqrt();
let mut result = *v;
for x in result.iter_mut() { *x *= inv; }
Ok(result)
}
fn seed_to_rotor(v: &[f64; 32]) -> Result<[f64; 32], ()> {
let scale: f64 = v.iter().map(|x| x * x).sum::<f64>().sqrt();
let scale = if scale == 0.0 { 1.0 } else { scale };
let mut rotor = [0f64; 32];
rotor[0] = 1.0;
for (step, &blade) in SEED_BIVECTORS.iter().enumerate() {
let source = v[(blade + step) % 32] / scale;
let theta = 0.5 * source.tanh();
let mut factor = [0f64; 32];
factor[0] = theta.cos();
factor[blade] = theta.sin();
rotor = geometric_product_f64(&rotor, &factor);
}
unitize_closed(&rotor)
}
fn close_applied_versor(v: &[f32; 32]) -> [f32; 32] {
if is_null_vector(v) {
return crate::cga::null_project_raw(v);
}
let v_f64: [f64; 32] = {
let mut arr = [0f64; 32];
for i in 0..32 { arr[i] = v[i] as f64; }
arr
};
if let Ok(closed) = unitize_closed(&v_f64) {
let mut result = [0f32; 32];
for i in 0..32 { result[i] = closed[i] as f32; }
return result;
}
if let Ok(seeded) = seed_to_rotor(&v_f64) {
let mut result = [0f32; 32];
for i in 0..32 { result[i] = seeded[i] as f32; }
return result;
}
*v
}
/// Sandwich product V * F * reverse(V) with closure semantics.
/// Preserves null vectors as null vectors. Applies unit-versor closure
/// with construction seed fallback for non-null results.
pub fn versor_apply_closed(v: &[f32; 32], f: &[f32; 32]) -> Result<[f32; 32], VersorError> {
let rev_v = reverse_raw(v);
let vf = geometric_product_raw(v, f)?;
let vfrv = geometric_product_raw(&vf, &rev_v)?;
Ok(close_applied_versor(&vfrv))
}
/// Raw sandwich product V * F * reverse(V) without closure.
pub fn versor_apply_raw(v: &[f32; 32], f: &[f32; 32]) -> Result<[f32; 32], VersorError> {
let rev_v = reverse_raw(v);
let vf = geometric_product_raw(v, f)?;
@ -48,3 +144,50 @@ pub fn versor_condition_raw(f: &[f32; 32]) -> Result<f32, VersorError> {
let norm_sq: f32 = frv.iter().map(|x| x * x).sum();
Ok(norm_sq.sqrt())
}
#[cfg(test)]
mod tests {
use super::*;
fn identity_versor() -> [f32; 32] {
let mut v = [0f32; 32];
v[0] = 1.0;
v
}
fn simple_reflector() -> [f32; 32] {
let mut v = [0f32; 32];
v[1] = 1.0;
v
}
#[test]
fn closed_identity_is_identity() {
let id = identity_versor();
let f = simple_reflector();
let result = versor_apply_closed(&id, &f).unwrap();
for i in 0..32 {
assert!((result[i] - f[i]).abs() < 1e-5, "component {} diverged", i);
}
}
#[test]
fn closed_preserves_versor_condition() {
let v = simple_reflector();
let f = identity_versor();
let result = versor_apply_closed(&v, &f).unwrap();
let cond = versor_condition_raw(&result).unwrap();
assert!(cond < 1e-4, "condition {} too large", cond);
}
#[test]
fn closed_matches_raw_for_identity() {
let id = identity_versor();
let f = simple_reflector();
let raw = versor_apply_raw(&id, &f).unwrap();
let closed = versor_apply_closed(&id, &f).unwrap();
for i in 0..32 {
assert!((raw[i] - closed[i]).abs() < 1e-5);
}
}
}

View file

@ -20,17 +20,17 @@ fn test_e1_squared_is_plus1() {
}
#[test]
fn test_e4_squared_is_minus1() {
fn test_e4_squared_is_plus1() {
let e4 = basis(3);
let r = geometric_product_raw(&e4, &e4).unwrap();
assert!((r[0] + 1.0).abs() < 1e-6, "e4^2 should be -1, got {}", r[0]);
assert!((r[0] - 1.0).abs() < 1e-6, "e4^2 should be +1, got {}", r[0]);
}
#[test]
fn test_e5_squared_is_plus1() {
fn test_e5_squared_is_minus1() {
let e5 = basis(4);
let r = geometric_product_raw(&e5, &e5).unwrap();
assert!((r[0] - 1.0).abs() < 1e-6, "e5^2 should be +1, got {}", r[0]);
assert!((r[0] + 1.0).abs() < 1e-6, "e5^2 should be -1, got {}", r[0]);
}
#[test]

View file

@ -18,3 +18,28 @@
{"id": "unknown_word_018", "category": "unknown", "prompt": "word beginning truth", "expected_intent": "unknown", "expected_terms": ["word", "truth"], "expected_surface_contains": [], "requires_versor_closure": true, "requires_deterministic_trace": true}
{"id": "unknown_logos_019", "category": "unknown", "prompt": "light logos", "expected_intent": "unknown", "expected_terms": ["light"], "expected_surface_contains": [], "requires_versor_closure": true, "requires_deterministic_trace": true}
{"id": "definition_wisdom_020", "category": "definition", "prompt": "What is wisdom?", "expected_intent": "definition", "expected_terms": ["wisdom"], "expected_surface_contains": ["wisdom"], "requires_versor_closure": true, "requires_deterministic_trace": true}
{"id": "definition_evidence_021", "category": "definition", "prompt": "What is evidence?", "expected_intent": "definition", "expected_terms": ["evidence"], "expected_surface_contains": ["evidence"], "requires_versor_closure": true, "requires_deterministic_trace": true}
{"id": "definition_inference_022", "category": "definition", "prompt": "What is inference?", "expected_intent": "definition", "expected_terms": ["inference"], "expected_surface_contains": ["inference"], "requires_versor_closure": true, "requires_deterministic_trace": true}
{"id": "definition_procedure_023", "category": "definition", "prompt": "What is a procedure?", "expected_intent": "definition", "expected_terms": ["procedure"], "expected_surface_contains": ["procedure"], "requires_versor_closure": true, "requires_deterministic_trace": true}
{"id": "definition_verification_024", "category": "definition", "prompt": "What is verification?", "expected_intent": "definition", "expected_terms": ["verification"], "expected_surface_contains": ["verification"], "requires_versor_closure": true, "requires_deterministic_trace": true}
{"id": "definition_distinction_025", "category": "definition", "prompt": "What is distinction?", "expected_intent": "definition", "expected_terms": ["distinction"], "expected_surface_contains": ["distinction"], "requires_versor_closure": true, "requires_deterministic_trace": true}
{"id": "definition_relation_026", "category": "definition", "prompt": "What is a relation?", "expected_intent": "definition", "expected_terms": ["relation"], "expected_surface_contains": ["relation"], "requires_versor_closure": true, "requires_deterministic_trace": true}
{"id": "definition_identity_027", "category": "definition", "prompt": "What is identity?", "expected_intent": "definition", "expected_terms": ["identity"], "expected_surface_contains": ["identity"], "requires_versor_closure": true, "requires_deterministic_trace": true}
{"id": "comparison_reason_cause_028", "category": "comparison", "prompt": "Compare reason and cause", "expected_intent": "comparison", "expected_terms": ["reason", "cause"], "expected_surface_contains": ["reason", "cause"], "requires_versor_closure": true, "requires_deterministic_trace": true}
{"id": "comparison_knowledge_wisdom_029", "category": "comparison", "prompt": "Compare knowledge and wisdom", "expected_intent": "comparison", "expected_terms": ["knowledge", "wisdom"], "expected_surface_contains": ["knowledge", "wisdom"], "requires_versor_closure": true, "requires_deterministic_trace": true}
{"id": "comparison_memory_recall_030", "category": "comparison", "prompt": "Compare memory and recall", "expected_intent": "comparison", "expected_terms": ["memory"], "expected_surface_contains": ["memory"], "requires_versor_closure": true, "requires_deterministic_trace": true}
{"id": "cause_truth_031", "category": "cause", "prompt": "Why does truth matter?", "expected_intent": "cause", "expected_terms": ["truth"], "expected_surface_contains": ["truth"], "requires_versor_closure": true, "requires_deterministic_trace": true}
{"id": "cause_knowledge_032", "category": "cause", "prompt": "Why does knowledge require evidence?", "expected_intent": "cause", "expected_terms": ["knowledge"], "expected_surface_contains": ["knowledge"], "requires_versor_closure": true, "requires_deterministic_trace": true}
{"id": "cause_correction_033", "category": "cause", "prompt": "Why does correction help learning?", "expected_intent": "cause", "expected_terms": ["correction"], "expected_surface_contains": ["correction"], "requires_versor_closure": true, "requires_deterministic_trace": true}
{"id": "procedure_verify_034", "category": "procedure", "prompt": "How do I verify a claim?", "expected_intent": "procedure", "expected_terms": [], "expected_surface_contains": [], "requires_versor_closure": true, "requires_deterministic_trace": true}
{"id": "procedure_correct_035", "category": "procedure", "prompt": "How can I correct an error?", "expected_intent": "procedure", "expected_terms": [], "expected_surface_contains": [], "requires_versor_closure": true, "requires_deterministic_trace": true}
{"id": "verification_wisdom_036", "category": "verification", "prompt": "Is wisdom the same as knowledge?", "expected_intent": "verification", "expected_terms": ["wisdom", "knowledge"], "expected_surface_contains": ["wisdom"], "requires_versor_closure": true, "requires_deterministic_trace": true}
{"id": "verification_memory_037", "category": "verification", "prompt": "Does memory require recall?", "expected_intent": "verification", "expected_terms": ["memory"], "expected_surface_contains": ["memory"], "requires_versor_closure": true, "requires_deterministic_trace": true}
{"id": "recall_wisdom_038", "category": "recall", "prompt": "Remember wisdom", "expected_intent": "recall", "expected_terms": ["wisdom"], "expected_surface_contains": ["wisdom"], "requires_versor_closure": true, "requires_deterministic_trace": true}
{"id": "recall_knowledge_039", "category": "recall", "prompt": "Remember knowledge", "expected_intent": "recall", "expected_terms": ["knowledge"], "expected_surface_contains": ["knowledge"], "requires_versor_closure": true, "requires_deterministic_trace": true}
{"id": "correction_truth_040", "category": "correction", "prompt": "Actually, truth requires evidence", "expected_intent": "correction", "expected_terms": ["truth"], "expected_surface_contains": ["correction"], "requires_versor_closure": true, "requires_deterministic_trace": true}
{"id": "unknown_spirit_041", "category": "unknown", "prompt": "spirit wisdom truth", "expected_intent": "unknown", "expected_terms": ["wisdom", "truth"], "expected_surface_contains": [], "requires_versor_closure": true, "requires_deterministic_trace": true}
{"id": "unknown_evidence_042", "category": "unknown", "prompt": "evidence reason", "expected_intent": "unknown", "expected_terms": ["evidence", "reason"], "expected_surface_contains": [], "requires_versor_closure": true, "requires_deterministic_trace": true}
{"id": "definition_thought_043", "category": "definition", "prompt": "What is thought?", "expected_intent": "definition", "expected_terms": ["thought"], "expected_surface_contains": ["thought"], "requires_versor_closure": true, "requires_deterministic_trace": true}
{"id": "definition_judgment_044", "category": "definition", "prompt": "What is judgment?", "expected_intent": "definition", "expected_terms": ["judgment"], "expected_surface_contains": ["judgment"], "requires_versor_closure": true, "requires_deterministic_trace": true}
{"id": "definition_understanding_045", "category": "definition", "prompt": "What is understanding?", "expected_intent": "definition", "expected_terms": ["understanding"], "expected_surface_contains": ["understanding"], "requires_versor_closure": true, "requires_deterministic_trace": true}

View file

@ -18,12 +18,12 @@ from generate.intent import IntentTag
_INTENT_TEMPLATES: dict[IntentTag, str] = {
IntentTag.DEFINITION: "{subject} is defined as {obj}",
IntentTag.CAUSE: "{subject} is caused by {obj}",
IntentTag.PROCEDURE: "{subject} has the following steps: {obj}",
IntentTag.COMPARISON: "{subject} and {secondary} are contrasted by {predicate_h}",
IntentTag.CAUSE: "{subject} is grounded in {obj}",
IntentTag.PROCEDURE: "first, {obj}; then, {subject} follows",
IntentTag.COMPARISON: "{subject} and {secondary} are distinguished: {subject} {predicate_h} {secondary}",
IntentTag.CORRECTION: "correction: {subject} {predicate_h} {obj}",
IntentTag.RECALL: "{subject} recalls {obj}",
IntentTag.VERIFICATION: "{subject} is verified as {obj}",
IntentTag.RECALL: "recalling {subject}: {obj}",
IntentTag.VERIFICATION: "{subject} is verified: {obj}",
IntentTag.UNKNOWN: "{subject} {predicate_h} {obj}",
}
@ -46,6 +46,14 @@ _PREDICATE_HUMANIZE: dict[str, str] = {
"follows": "follows",
"belongs_to": "belongs to",
"answers": "answers",
"is_grounded_in": "is grounded in",
"is_distinguished_from": "is distinguished from",
"implies": "implies",
"entails": "entails",
"requires": "requires",
"verifies": "verifies",
"evidences": "evidences",
"orders": "orders",
}

View file

@ -34,6 +34,14 @@ _PREDICATE_DISPLAY: dict[str, str] = {
"follows": "follows",
"belongs_to": "belongs to",
"answers": "answers",
"is_grounded_in": "is grounded in",
"is_distinguished_from": "is distinguished from",
"implies": "implies",
"entails": "entails",
"requires": "requires",
"verifies": "verifies",
"evidences": "evidences",
"orders": "orders",
}

View file

@ -53,3 +53,18 @@
{"entry_id":"en-core-cog-053","surface":"how","lemma":"how","language":"en","pos":"ADV","semantic_domains":["dialogue.question.how","cognition.procedure","epistemic.seeking"],"morphology_tags":["adverb","interrogative"],"provenance_ids":["seed:core_cognition_v1"]}
{"entry_id":"en-core-cog-054","surface":"compare_with","lemma":"compare","language":"en","pos":"VERB","semantic_domains":["operation.compare","relation.contrast","dialogue.request"],"morphology_tags":["verb","operation"],"provenance_ids":["seed:core_cognition_v1"]}
{"entry_id":"en-core-cog-055","surface":"because","lemma":"because","language":"en","pos":"SCONJ","semantic_domains":["relation.causal","explanation.ground","dialogue.reason"],"morphology_tags":["conjunction"],"provenance_ids":["seed:core_cognition_v1"]}
{"entry_id":"en-core-cog-056","surface":"evidence","lemma":"evidence","language":"en","pos":"NOUN","semantic_domains":["cognition.evidence","epistemic.ground","reason.support","verification.basis"],"morphology_tags":["noun"],"provenance_ids":["seed:core_cognition_v1"]}
{"entry_id":"en-core-cog-057","surface":"inference","lemma":"inference","language":"en","pos":"NOUN","semantic_domains":["cognition.inference","logic.derivation","reason.step","epistemic.formation"],"morphology_tags":["noun"],"provenance_ids":["seed:core_cognition_v1"]}
{"entry_id":"en-core-cog-058","surface":"procedure","lemma":"procedure","language":"en","pos":"NOUN","semantic_domains":["cognition.procedure","operation.ordered","reason.method","dialogue.how"],"morphology_tags":["noun"],"provenance_ids":["seed:core_cognition_v1"]}
{"entry_id":"en-core-cog-059","surface":"verification","lemma":"verification","language":"en","pos":"NOUN","semantic_domains":["cognition.verification","epistemic.check","truth.test","dialogue.confirm"],"morphology_tags":["noun"],"provenance_ids":["seed:core_cognition_v1"]}
{"entry_id":"en-core-cog-060","surface":"distinction","lemma":"distinction","language":"en","pos":"NOUN","semantic_domains":["cognition.distinction","relation.contrast","reason.boundary","comparison.basis"],"morphology_tags":["noun"],"provenance_ids":["seed:core_cognition_v1"]}
{"entry_id":"en-core-cog-061","surface":"relation","lemma":"relation","language":"en","pos":"NOUN","semantic_domains":["cognition.relation","reason.structure","graph.edge","semantics.link"],"morphology_tags":["noun"],"provenance_ids":["seed:core_cognition_v1"]}
{"entry_id":"en-core-cog-062","surface":"thought","lemma":"thought","language":"en","pos":"NOUN","semantic_domains":["cognition.thought","logos.internal","reason.process","meaning.formation"],"morphology_tags":["noun"],"provenance_ids":["seed:core_cognition_v1"]}
{"entry_id":"en-core-cog-063","surface":"understanding","lemma":"understanding","language":"en","pos":"NOUN","semantic_domains":["cognition.understanding","epistemic.grasp","meaning.comprehension","reason.synthesis"],"morphology_tags":["noun"],"provenance_ids":["seed:core_cognition_v1"]}
{"entry_id":"en-core-cog-064","surface":"judgment","lemma":"judgment","language":"en","pos":"NOUN","semantic_domains":["cognition.judgment","epistemic.evaluation","reason.decision","value.assessment"],"morphology_tags":["noun"],"provenance_ids":["seed:core_cognition_v1"]}
{"entry_id":"en-core-cog-065","surface":"principle","lemma":"principle","language":"en","pos":"NOUN","semantic_domains":["cognition.principle","reason.axiom","epistemic.ground","logos.foundational"],"morphology_tags":["noun"],"provenance_ids":["seed:core_cognition_v1"]}
{"entry_id":"en-core-cog-066","surface":"order","lemma":"order","language":"en","pos":"NOUN","semantic_domains":["cognition.order","reason.structure","temporal.sequence","logos.arrangement"],"morphology_tags":["noun"],"provenance_ids":["seed:core_cognition_v1"]}
{"entry_id":"en-core-cog-067","surface":"therefore","lemma":"therefore","language":"en","pos":"ADV","semantic_domains":["relation.consequence","logic.derivation","dialogue.conclusion"],"morphology_tags":["adverb","connective"],"provenance_ids":["seed:core_cognition_v1"]}
{"entry_id":"en-core-cog-068","surface":"however","lemma":"however","language":"en","pos":"ADV","semantic_domains":["relation.contrast","dialogue.concession","reason.qualification"],"morphology_tags":["adverb","connective"],"provenance_ids":["seed:core_cognition_v1"]}
{"entry_id":"en-core-cog-069","surface":"then","lemma":"then","language":"en","pos":"ADV","semantic_domains":["relation.sequence.after","temporal.sequence","dialogue.continuation"],"morphology_tags":["adverb","connective"],"provenance_ids":["seed:core_cognition_v1"]}
{"entry_id":"en-core-cog-070","surface":"first","lemma":"first","language":"en","pos":"ADV","semantic_domains":["relation.sequence.before","temporal.ordinal","procedure.step","dialogue.opening"],"morphology_tags":["adverb","ordinal"],"provenance_ids":["seed:core_cognition_v1"]}

View file

@ -6,8 +6,8 @@
"normalization_policy": "unitize_versor",
"source_manifest": "en_core_cognition_v1.lexicon.jsonl",
"determinism_class": "D0",
"checksum": "d8d485e374afaf0520f88d753353a18d82e18c5b88a2f773622b6de1942a05c8",
"version": "1.0.0",
"checksum": "994e63c5d72053691a09502bcac0fc6f863cd5af08372022399c95b1606ad5b3",
"version": "1.1.0",
"gate_engaged": true,
"oov_policy": "tagged_fallback"
}

View file

@ -114,4 +114,4 @@ def test_pack_entries_are_deterministic() -> None:
assert [entry.entry_id for entry in entries_a] == [entry.entry_id for entry in entries_b]
assert [entry.surface for entry in entries_a] == [entry.surface for entry in entries_b]
assert entries_a[0].entry_id == "en-core-cog-001"
assert entries_a[-1].entry_id == "en-core-cog-055"
assert entries_a[-1].entry_id == "en-core-cog-070"

View file

@ -37,4 +37,4 @@ def test_load_pack_entries_returns_new_list_from_cached_tuple() -> None:
entries_a.pop()
assert len(entries_a) == len(entries_b) - 1
assert entries_b[-1].entry_id == "en-core-cog-055"
assert entries_b[-1].entry_id == "en-core-cog-070"

135
tests/test_rust_backend.py Normal file
View file

@ -0,0 +1,135 @@
"""Rust versor_apply parity tests.
Verifies that the Rust closure-aware versor_apply produces identical results
to the Python algebra.versor.versor_apply for all critical cases:
identity, rotors, null vectors, versor condition, and backend dispatch.
"""
from __future__ import annotations
import numpy as np
import pytest
from algebra.versor import (
unitize_versor,
versor_apply as python_versor_apply,
versor_condition,
)
from algebra.cga import embed_point, is_null
try:
import core_rs
HAS_RUST = True
except ImportError:
HAS_RUST = False
skip_no_rust = pytest.mark.skipif(not HAS_RUST, reason="core_rs not available")
def _positive_unit_reflector(seed: int) -> np.ndarray:
rng = np.random.default_rng(seed)
vec4 = rng.standard_normal(4).astype(np.float32)
norm4 = float(np.linalg.norm(vec4))
if norm4 < 1e-6:
vec4[0] = 1.0
norm4 = 1.0
vec = np.zeros(5, dtype=np.float32)
vec[:4] = vec4
vec[4] = 0.25 * norm4 * np.tanh(float(rng.standard_normal()))
mv = np.zeros(32, dtype=np.float32)
mv[1:6] = vec
return unitize_versor(mv)
def _random_rotor(seed: int) -> np.ndarray:
from algebra.cl41 import geometric_product as gp
a = _positive_unit_reflector(seed)
b = _positive_unit_reflector(seed + 10000)
return unitize_versor(gp(a, b))
@skip_no_rust
def test_rust_versor_apply_matches_python_for_identity():
identity = np.zeros(32, dtype=np.float32)
identity[0] = 1.0
F = _positive_unit_reflector(42)
py_result = python_versor_apply(identity, F)
rust_result = np.asarray(
core_rs.versor_apply_with_closure(identity, F), dtype=np.float32
)
assert np.allclose(py_result, rust_result, atol=1e-4), (
f"max diff: {np.max(np.abs(py_result - rust_result))}"
)
@skip_no_rust
def test_rust_versor_apply_matches_python_for_rotors():
for seed in range(20):
V = _random_rotor(seed)
F = _positive_unit_reflector(seed + 500)
py_result = python_versor_apply(V, F)
rust_result = np.asarray(
core_rs.versor_apply_with_closure(V, F), dtype=np.float32
)
assert np.allclose(py_result, rust_result, atol=1e-3), (
f"seed={seed} max diff: {np.max(np.abs(py_result - rust_result))}"
)
@skip_no_rust
def test_rust_versor_apply_preserves_null_vectors():
point = embed_point(np.array([1.0, 2.0, 3.0], dtype=np.float32))
assert is_null(point)
V = _positive_unit_reflector(7)
rust_result = np.asarray(
core_rs.versor_apply_with_closure(V, point), dtype=np.float32
)
py_result = python_versor_apply(V, point)
py_is_null = is_null(py_result)
rust_is_null = is_null(rust_result)
assert py_is_null == rust_is_null, (
f"null preservation mismatch: python={py_is_null}, rust={rust_is_null}"
)
@skip_no_rust
def test_rust_versor_apply_preserves_versor_condition():
for seed in range(20):
V = _positive_unit_reflector(seed)
F = _positive_unit_reflector(seed + 1000)
rust_result = np.asarray(
core_rs.versor_apply_with_closure(V, F), dtype=np.float32
)
cond = versor_condition(rust_result)
assert cond < 1e-4, f"seed={seed} condition={cond:.2e}"
@skip_no_rust
def test_backend_dispatch_uses_rust_only_when_enabled():
"""Verify that algebra.backend.versor_apply only uses Rust when CORE_BACKEND=rust."""
import os
from importlib import reload
import algebra.backend as backend_mod
original = os.environ.get("CORE_BACKEND", "")
os.environ["CORE_BACKEND"] = "numpy"
reload(backend_mod)
assert not (backend_mod._REQUESTED_BACKEND == "rust")
os.environ["CORE_BACKEND"] = "rust"
reload(backend_mod)
assert backend_mod._REQUESTED_BACKEND == "rust"
if original:
os.environ["CORE_BACKEND"] = original
else:
os.environ.pop("CORE_BACKEND", None)
reload(backend_mod)

View file

@ -115,7 +115,7 @@ class TestRealizeSemantic:
target = plan_articulation(graph)
plan = realize_semantic(target, graph)
assert plan.surface
assert "is caused by" in plan.surface.lower()
assert "is grounded in" in plan.surface.lower()
def test_empty_target_returns_empty_plan(self) -> None:
from generate.graph_planner import ArticulationTarget

111
tests/test_vault_store.py Normal file
View file

@ -0,0 +1,111 @@
"""Tests for VaultStore exact-match index and optional bounded mode."""
from __future__ import annotations
import numpy as np
from algebra.cga import embed_point
from vault.store import VaultStore, _versor_key
def _random_point(seed: int = 0) -> np.ndarray:
rng = np.random.default_rng(seed)
return embed_point(rng.standard_normal(3).astype(np.float32))
def test_vault_exact_self_match_uses_index():
"""Exact self-match must come from the hash index, not O(N) scan."""
vault = VaultStore()
points = [_random_point(i) for i in range(20)]
for i, v in enumerate(points):
vault.store(v, {"id": i})
for i, v in enumerate(points):
key = _versor_key(v)
assert key in vault._exact_index
assert i in vault._exact_index[key]
for i, v in enumerate(points):
results = vault.recall(v, top_k=1)
assert results[0]["metadata"]["id"] == i
def test_vault_recall_ranking_unchanged():
"""CGA inner-product ranking must be identical to pre-index behavior."""
vault = VaultStore()
points = [_random_point(i) for i in range(10)]
for i, v in enumerate(points):
vault.store(v, {"id": i})
query = _random_point(99)
results = vault.recall(query, top_k=5)
assert len(results) == 5
scores = [r["score"] for r in results]
assert scores == sorted(scores, reverse=True)
def test_vault_index_updates_on_store():
"""Each store() must update the hash index."""
vault = VaultStore()
p = _random_point(0)
vault.store(p, {"id": "a"})
key = _versor_key(p)
assert key in vault._exact_index
assert vault._exact_index[key] == [0]
vault.store(p, {"id": "b"})
assert vault._exact_index[key] == [0, 1]
def test_vault_index_rebuilds_on_reproject():
"""Reproject changes versor bytes; index must be rebuilt."""
vault = VaultStore()
for i in range(5):
vault.store(_random_point(i))
vault.reproject()
assert len(vault._exact_index) == 5
assert len(vault) == 5
def test_vault_optional_max_entries_eviction_is_deterministic():
"""Bounded vault must evict oldest first (FIFO), deterministically."""
vault = VaultStore(max_entries=3)
ids = []
for i in range(5):
vault.store(_random_point(i), {"id": i})
ids.append(i)
assert len(vault) == 3
remaining_ids = [m["id"] for m in vault._metadata]
assert remaining_ids == [2, 3, 4]
def test_vault_default_remains_unbounded():
"""Default max_entries=None means no eviction ever."""
vault = VaultStore()
assert vault.max_entries is None
for i in range(100):
vault.store(_random_point(i))
assert len(vault) == 100
def test_vault_eviction_preserves_index_consistency():
"""After eviction, the exact index must reference valid current indices."""
vault = VaultStore(max_entries=3)
for i in range(5):
vault.store(_random_point(i), {"id": i})
for indices in vault._exact_index.values():
for idx in indices:
assert 0 <= idx < len(vault)
def test_vault_duplicate_versors_both_indexed():
"""Storing the same versor twice should index both entries."""
vault = VaultStore()
p = _random_point(42)
vault.store(p, {"id": "first"})
vault.store(p, {"id": "second"})
results = vault.recall(p, top_k=2)
result_ids = {r["metadata"]["id"] for r in results}
assert result_ids == {"first", "second"}

View file

@ -5,28 +5,53 @@ 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):
self._versors: list = []
self._metadata: list = []
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) -> int:
def store(self, F: np.ndarray, metadata: dict | None = None) -> int:
"""Store a versor. Returns its index. Auto-reprojects every N stores."""
self._versors.append(np.asarray(F, dtype=np.float32).copy())
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 len(self._versors) - 1
return idx
def recall(self, query: np.ndarray, top_k: int = 5) -> list:
"""
@ -39,13 +64,11 @@ class VaultStore:
query_arr = np.asarray(query, dtype=np.float32)
ranked = vault_recall(self._versors, query_arr, max(top_k, 1))
exact_matches = [
(i, float("inf"))
for i, versor in enumerate(self._versors)
if np.array_equal(np.asarray(versor, dtype=np.float32), query_arr)
]
if exact_matches:
seen = {i for i, _score in exact_matches}
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 [
@ -64,16 +87,43 @@ class VaultStore:
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 the configured auto-reprojection cadence in store operations."""
return self._reproject_interval
@property
def store_count(self) -> int:
"""Return how many store() operations have occurred in this vault."""
return self._store_count
@property
def max_entries(self) -> int | None:
return self._max_entries
def __len__(self) -> int:
return len(self._versors)