core/core-rs/src/cl41.rs
Shay 523c072818 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.
2026-05-15 15:34:39 -07:00

187 lines
5.6 KiB
Rust

//! Cl(4,1) geometric product via precomputed table.
//!
//! 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].
use thiserror::Error;
#[derive(Debug, Error)]
pub enum Cl41Error {
#[error("Multivector length must be 32, got {0}")]
BadLength(usize),
}
// Blade ordering: grade-0 (1), grade-1 (5), grade-2 (10), grade-3 (10), grade-4 (5), grade-5 (1)
// 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];
// 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] {
// 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] {
let blades = build_blade_masks();
let mut lut = [0u8; 32];
let mut i = 0usize;
while i < 32 {
lut[blades[i] as usize] = i as u8;
i += 1;
}
lut
}
const fn popcount5(x: u8) -> u8 {
let mut n = x & 0x1F;
let mut c = 0u8;
while n != 0 { c += n & 1; n >>= 1; }
c
}
// Multiply two basis blades given as bitmasks. Returns (result_mask, sign).
// The sign is the parity of swaps needed to canonicalize A followed by B,
// multiplied by the metric contractions for repeated basis vectors.
const fn blade_product(a: u8, b: u8) -> (u8, i8) {
let mut sign: i8 = 1;
// Anticommutation sign: every pair (a_i, b_j) with a_i > b_j swaps once.
let mut swaps = 0u8;
let mut ai = 0u8;
while ai < 5 {
if (a >> ai) & 1 == 1 {
let mut bj = 0u8;
while bj < 5 {
if (b >> bj) & 1 == 1 && ai > bj {
swaps += 1;
}
bj += 1;
}
}
ai += 1;
}
if swaps % 2 == 1 {
sign *= -1;
}
// Metric contractions for duplicate basis vectors.
let common = a & b;
let mut bit = 0u8;
while bit < 5 {
if (common >> bit) & 1 == 1 {
sign *= SIG[bit as usize];
}
bit += 1;
}
(a ^ b, sign)
}
struct Table {
idx: [[u8; 32]; 32],
sign: [[i8; 32]; 32],
}
fn build_table() -> Table {
let mut idx = [[0u8; 32]; 32];
let mut sign = [[0i8; 32]; 32];
for i in 0..32usize {
for j in 0..32usize {
let (result_mask, s) = blade_product(BLADE_MASKS[i], BLADE_MASKS[j]);
idx[i][j] = MASK_TO_IDX[result_mask as usize];
sign[i][j] = s;
}
}
Table { idx, sign }
}
use std::sync::OnceLock;
static TABLE: OnceLock<Table> = OnceLock::new();
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> {
let t = table();
let mut result = [0f32; 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 f32;
result[k] += s * ai * bj;
}
}
Ok(result)
}
/// Reverse anti-automorphism.
/// Grade-k blade sign: (-1)^(k*(k-1)/2)
/// 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;
for i in 6..=15 { r[i] = -r[i]; }
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
}