Activate and verify Rust backend

Add Rust backend CLI controls, fix core-rs build/test configuration, align Rust Cl(4,1)/CGA conventions with Python, and validate core_rs activation.
This commit is contained in:
Shay 2026-05-13 22:23:48 -07:00 committed by GitHub
parent 4ab148149f
commit df9ced7104
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 430 additions and 380 deletions

View file

@ -60,7 +60,7 @@ def vault_recall(versors: list, query: np.ndarray, top_k: int = 5) -> list:
except Exception: except Exception:
pass pass
q = np.asarray(query) q = np.asarray(query)
scores = [(i, -float(np.sum((q - np.asarray(v)) ** 2))) for i, v in enumerate(versors)] scores = [(i, float(cga_inner(q, np.asarray(v)))) for i, v in enumerate(versors)]
scores.sort(key=lambda x: -x[1]) scores.sort(key=lambda x: -x[1])
return scores[:top_k] return scores[:top_k]

View file

@ -5,10 +5,10 @@ edition = "2021"
[lib] [lib]
name = "core_rs" name = "core_rs"
crate-type = ["cdylib"] crate-type = ["cdylib", "rlib"]
[dependencies] [dependencies]
pyo3 = { version = "0.21", features = ["extension-module"] } pyo3 = { version = "0.21" }
rayon = "1.10" rayon = "1.10"
nalgebra = "0.33" nalgebra = "0.33"
ndarray = { version = "0.16", features = ["rayon"] } ndarray = { version = "0.16", features = ["rayon"] }

View file

@ -5,4 +5,3 @@ build-backend = "maturin"
[tool.maturin] [tool.maturin]
features = ["extension-module"] features = ["extension-module"]
module-name = "core_rs" module-name = "core_rs"
python-source = "python"

View file

@ -1,7 +1,17 @@
//! CGA inner product and null-cone operations. //! CGA inner product and null-cone operations.
//! //!
//! cga_inner(X, Y) = 0.5 * scalar_part(X*Y + Y*X) //! Signature: (+,+,+,-,+), with Euclidean coordinates on e1,e2,e3.
//! = -d^2 / 2 for null vectors X, Y //! 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
//!
//! A Euclidean point x embeds as:
//!
//! X = x + n_o + 0.5 * |x|^2 * n_inf
//!
//! Then X·X = 0 and X·Y = -0.5 * ||x-y||^2.
//! //!
//! This is the ONLY distance metric in CORE-AI. //! This is the ONLY distance metric in CORE-AI.
@ -20,37 +30,30 @@ pub enum CgaError {
pub fn cga_inner_raw(x: &[f32; 32], y: &[f32; 32]) -> Result<f32, CgaError> { pub fn cga_inner_raw(x: &[f32; 32], y: &[f32; 32]) -> Result<f32, CgaError> {
let xy = geometric_product_raw(x, y)?; let xy = geometric_product_raw(x, y)?;
let yx = geometric_product_raw(y, x)?; let yx = geometric_product_raw(y, x)?;
// scalar part is index 0
Ok(0.5 * (xy[0] + yx[0])) Ok(0.5 * (xy[0] + yx[0]))
} }
/// Check if X is on the null cone: |X*X| < tol. /// Check if X is on the null cone: |X·X| < tol.
pub fn is_null_raw(x: &[f32; 32], tol: f32) -> Result<bool, CgaError> { pub fn is_null_raw(x: &[f32; 32], tol: f32) -> Result<bool, CgaError> {
Ok(cga_inner_raw(x, x)?.abs() < tol) Ok(cga_inner_raw(x, x)?.abs() < tol)
} }
/// Re-project X onto the null cone. /// Re-project X onto the null cone by extracting Euclidean components
/// Extract Euclidean components (indices 1-3), recompute e+ = 0.5*|x|^2, e- = 1. /// and re-embedding with the canonical CGA point map.
pub fn null_project_raw(x: &[f32; 32]) -> [f32; 32] { pub fn null_project_raw(x: &[f32; 32]) -> [f32; 32] {
let mut result = [0f32; 32]; embed_point_raw(&[x[1], x[2], x[3]])
result[1] = x[1];
result[2] = x[2];
result[3] = x[3];
let x_sq = result[1] * result[1] + result[2] * result[2] + result[3] * result[3];
result[4] = 0.5 * x_sq; // e+ coefficient
result[5] = 1.0; // e- coefficient
result
} }
/// Embed a Euclidean point [x, y, z] into the CGA null cone. /// Embed a Euclidean point [x, y, z] into the CGA null cone.
/// X = x*e1 + y*e2 + z*e3 + (1/2)|x|^2 * e+ + e- /// X = x + n_o + 0.5|x|^2 n_inf
/// where n_o = 0.5(e4 - e5), n_inf = e4 + e5.
pub fn embed_point_raw(p: &[f32; 3]) -> [f32; 32] { pub fn embed_point_raw(p: &[f32; 3]) -> [f32; 32] {
let mut result = [0f32; 32]; let mut result = [0f32; 32];
result[1] = p[0]; result[1] = p[0];
result[2] = p[1]; result[2] = p[1];
result[3] = p[2]; result[3] = p[2];
let r2 = p[0]*p[0] + p[1]*p[1] + p[2]*p[2]; let r2 = p[0] * p[0] + p[1] * p[1] + p[2] * p[2];
result[4] = 0.5 * r2; result[4] = 0.5 * (r2 + 1.0);
result[5] = 1.0; result[5] = 0.5 * (r2 - 1.0);
result result
} }

View file

@ -1,6 +1,6 @@
//! Cl(4,1) geometric product via fully unrolled precomputed table. //! 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 //! The multiplication table is computed once at program start using
//! const evaluation and stored as two [u8;1024] and [i8;1024] arrays //! const evaluation and stored as two [u8;1024] and [i8;1024] arrays
//! (index and sign for each of the 32x32 blade pairs). //! (index and sign for each of the 32x32 blade pairs).
@ -20,10 +20,8 @@ pub enum Cl41Error {
// We encode each blade as a bitmask over 5 bits (bit k = basis vector k+1 present) // 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. // The mapping from bitmask to component index follows grade-ascending, lex order.
const N: usize = 32; // 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 // Precomputed at compile time via const fn
const BLADE_MASKS: [u8; 32] = build_blade_masks(); const BLADE_MASKS: [u8; 32] = build_blade_masks();
@ -68,63 +66,43 @@ const fn popcount5(x: u8) -> u8 {
} }
// Multiply two basis blades given as bitmasks. Returns (result_mask, sign). // Multiply two basis blades given as bitmasks. Returns (result_mask, sign).
// Uses bubble-sort on the concatenated index list, tracking swaps and metric contractions. // 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) { const fn blade_product(a: u8, b: u8) -> (u8, i8) {
// Expand masks into sorted index sequences
let mut seq = [0u8; 10];
let mut len = 0usize;
let mut bit = 0u8;
while bit < 5 {
if (a >> bit) & 1 == 1 { seq[len] = bit; len += 1; }
bit += 1;
}
bit = 0;
while bit < 5 {
if (b >> bit) & 1 == 1 { seq[len] = bit; len += 1; }
bit += 1;
}
let mut sign: i8 = 1; let mut sign: i8 = 1;
// Bubble sort + contract duplicates // Anticommutation sign: every pair (a_i, b_j) with a_i > b_j swaps once.
let mut changed = true; let mut swaps = 0u8;
while changed { let mut ai = 0u8;
changed = false; while ai < 5 {
let mut i = 0usize; if (a >> ai) & 1 == 1 {
while i + 1 < len { let mut bj = 0u8;
if seq[i] == seq[i + 1] { while bj < 5 {
// Contract: e_k^2 = SIG[k] if (b >> bj) & 1 == 1 && ai > bj {
sign *= SIG[seq[i] as usize]; swaps += 1;
// Remove both elements at i and i+1 }
let mut j = i; bj += 1;
while j + 2 < len { seq[j] = seq[j + 2]; j += 1; }
len -= 2;
changed = true;
if i > 0 { i -= 1; } // re-check from one step back
} else if seq[i] > seq[i + 1] {
let tmp = seq[i]; seq[i] = seq[i + 1]; seq[i + 1] = tmp;
sign *= -1;
changed = true;
i += 1;
} else {
i += 1;
} }
} }
ai += 1;
}
if swaps % 2 == 1 {
sign *= -1;
} }
// Build result mask // Metric contractions for duplicate basis vectors.
let mut result = 0u8; let common = a & b;
let mut i = 0usize; let mut bit = 0u8;
while i < len { result |= 1 << seq[i]; i += 1; } while bit < 5 {
if (common >> bit) & 1 == 1 {
sign *= SIG[bit as usize];
}
bit += 1;
}
(result, sign) (a ^ b, sign)
} }
// Full 32x32 product table — computed once at startup (not const due to complexity)
// TABLE_IDX[i][j] = component index of blade_i * blade_j
// TABLE_SIGN[i][j] = sign (+1 or -1) of blade_i * blade_j
struct Table { struct Table {
idx: [[u8; 32]; 32], idx: [[u8; 32]; 32],
sign: [[i8; 32]; 32], sign: [[i8; 32]; 32],

View file

@ -8,33 +8,27 @@
//! - vault_recall (parallel top-k scan) //! - vault_recall (parallel top-k scan)
//! //!
//! All multivectors are f32 arrays of length 32, passed as numpy arrays. //! All multivectors are f32 arrays of length 32, passed as numpy arrays.
//! Zero-copy: we read directly from numpy buffer pointers.
use pyo3::prelude::*;
use pyo3::exceptions::PyValueError; use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
mod cl41; pub mod cga;
mod versor; pub mod cl41;
mod cga; pub mod vault;
mod vault; pub mod versor;
use cl41::{geometric_product_raw, reverse_raw};
use versor::{versor_apply_raw, versor_condition_raw, normalize_to_versor_raw};
use cga::cga_inner_raw; use cga::cga_inner_raw;
use cl41::geometric_product_raw;
use vault::vault_recall_raw; use vault::vault_recall_raw;
use versor::{normalize_to_versor_raw, versor_apply_raw, versor_condition_raw};
// Re-export Python-facing functions /// Geometric product in Cl(4,1). Accepts two numpy-compatible f32 arrays of length 32.
/// Geometric product in Cl(4,1). Accepts two numpy f32 arrays of length 32.
#[pyfunction] #[pyfunction]
fn geometric_product<'py>( fn geometric_product(
py: Python<'py>, py: Python<'_>,
a: &pyo3::types::PyAny, a: &pyo3::types::PyAny,
b: &pyo3::types::PyAny, b: &pyo3::types::PyAny,
) -> PyResult<pyo3::Bound<'py, pyo3::types::PyAny>> { ) -> PyResult<PyObject> {
let a_buf = a.call_method0("__array__")?;
let b_buf = b.call_method0("__array__")?;
// Extract raw f32 slices via buffer protocol
let a_slice = extract_f32_slice(a)?; let a_slice = extract_f32_slice(a)?;
let b_slice = extract_f32_slice(b)?; let b_slice = extract_f32_slice(b)?;
let result = geometric_product_raw(&a_slice, &b_slice) let result = geometric_product_raw(&a_slice, &b_slice)
@ -42,13 +36,13 @@ fn geometric_product<'py>(
f32_array_to_numpy(py, &result) f32_array_to_numpy(py, &result)
} }
/// Sandwich product V*F*reverse(V). Zero-copy on input arrays. /// Sandwich product V*F*reverse(V).
#[pyfunction] #[pyfunction]
fn versor_apply<'py>( fn versor_apply(
py: Python<'py>, py: Python<'_>,
v: &pyo3::types::PyAny, v: &pyo3::types::PyAny,
f: &pyo3::types::PyAny, f: &pyo3::types::PyAny,
) -> PyResult<pyo3::Bound<'py, pyo3::types::PyAny>> { ) -> PyResult<PyObject> {
let v_slice = extract_f32_slice(v)?; let v_slice = extract_f32_slice(v)?;
let f_slice = extract_f32_slice(f)?; let f_slice = extract_f32_slice(f)?;
let result = versor_apply_raw(&v_slice, &f_slice) let result = versor_apply_raw(&v_slice, &f_slice)
@ -65,10 +59,10 @@ fn versor_condition(f: &pyo3::types::PyAny) -> PyResult<f32> {
/// Project F onto versor manifold: F / sqrt(|F*rev(F)|). /// Project F onto versor manifold: F / sqrt(|F*rev(F)|).
#[pyfunction] #[pyfunction]
fn normalize_to_versor<'py>( fn normalize_to_versor(
py: Python<'py>, py: Python<'_>,
f: &pyo3::types::PyAny, f: &pyo3::types::PyAny,
) -> PyResult<pyo3::Bound<'py, pyo3::types::PyAny>> { ) -> PyResult<PyObject> {
let f_slice = extract_f32_slice(f)?; let f_slice = extract_f32_slice(f)?;
let result = normalize_to_versor_raw(&f_slice) let result = normalize_to_versor_raw(&f_slice)
.map_err(|e| PyValueError::new_err(e.to_string()))?; .map_err(|e| PyValueError::new_err(e.to_string()))?;
@ -84,9 +78,6 @@ fn cga_inner(x: &pyo3::types::PyAny, y: &pyo3::types::PyAny) -> PyResult<f32> {
} }
/// Parallel top-k vault recall by CGA inner product. /// Parallel top-k vault recall by CGA inner product.
/// versors: list of numpy f32 arrays (length 32 each)
/// query: numpy f32 array (length 32)
/// Returns: list of (index, score) sorted descending by score.
#[pyfunction] #[pyfunction]
fn vault_recall( fn vault_recall(
versors: Vec<&pyo3::types::PyAny>, versors: Vec<&pyo3::types::PyAny>,
@ -96,42 +87,35 @@ fn vault_recall(
let query_slice = extract_f32_slice(query)?; let query_slice = extract_f32_slice(query)?;
let mut slices: Vec<[f32; 32]> = Vec::with_capacity(versors.len()); let mut slices: Vec<[f32; 32]> = Vec::with_capacity(versors.len());
for v in &versors { for v in &versors {
let s = extract_f32_slice(v)?; slices.push(extract_f32_slice(v)?);
slices.push(s);
} }
vault_recall_raw(&slices, &query_slice, top_k) vault_recall_raw(&slices, &query_slice, top_k)
.map_err(|e| PyValueError::new_err(e.to_string())) .map_err(|e| PyValueError::new_err(e.to_string()))
} }
// --- Helpers ---
fn extract_f32_slice(obj: &pyo3::types::PyAny) -> PyResult<[f32; 32]> { fn extract_f32_slice(obj: &pyo3::types::PyAny) -> PyResult<[f32; 32]> {
// Use numpy's buffer protocol for zero-copy read
let np = obj.py().import("numpy")?; let np = obj.py().import("numpy")?;
let arr = np.call_method1("asarray", (obj, "float32"))?; let arr = np.call_method1("asarray", (obj, "float32"))?;
let flat = arr.call_method0("flatten")?; let flat = arr.call_method0("flatten")?;
let list: Vec<f32> = flat.extract()?; let list: Vec<f32> = flat.extract()?;
if list.len() != 32 { if list.len() != 32 {
return Err(PyValueError::new_err( return Err(PyValueError::new_err(format!(
format!("Expected array of length 32, got {}", list.len()) "Expected array of length 32, got {}",
)); list.len()
)));
} }
let mut out = [0f32; 32]; let mut out = [0f32; 32];
out.copy_from_slice(&list); out.copy_from_slice(&list);
Ok(out) Ok(out)
} }
fn f32_array_to_numpy<'py>( fn f32_array_to_numpy(py: Python<'_>, data: &[f32; 32]) -> PyResult<PyObject> {
py: Python<'py>,
data: &[f32; 32],
) -> PyResult<pyo3::Bound<'py, pyo3::types::PyAny>> {
let np = py.import("numpy")?; let np = py.import("numpy")?;
let list: Vec<f32> = data.to_vec(); let list: Vec<f32> = data.to_vec();
let arr = np.call_method1("array", (list, "float32"))?; let arr = np.call_method1("array", (list, "float32"))?;
Ok(arr.into()) Ok(arr.into_py(py))
} }
/// Module registration
#[pymodule] #[pymodule]
fn core_rs(m: &Bound<'_, PyModule>) -> PyResult<()> { fn core_rs(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(geometric_product, m)?)?; m.add_function(wrap_pyfunction!(geometric_product, m)?)?;

View file

@ -1,48 +1,41 @@
#[cfg(test)] use core_rs::cga::{cga_inner_raw, embed_point_raw, is_null_raw, null_project_raw};
mod tests {
use crate::cga::{cga_inner_raw, embed_point_raw, is_null_raw, null_project_raw};
#[test] #[test]
fn test_embedded_point_is_null() { fn test_embedded_point_is_null() {
let p = [1.0f32, 2.0, 3.0]; let p = [1.0f32, 2.0, 3.0];
let x = embed_point_raw(&p); let x = embed_point_raw(&p);
assert!(is_null_raw(&x, 1e-5).unwrap(), "Embedded point should be null"); assert!(is_null_raw(&x, 1e-5).unwrap(), "Embedded point should be null");
} }
#[test] #[test]
fn test_origin_is_null() { fn test_origin_is_null() {
let x = embed_point_raw(&[0.0, 0.0, 0.0]); let x = embed_point_raw(&[0.0, 0.0, 0.0]);
assert!(is_null_raw(&x, 1e-5).unwrap()); assert!(is_null_raw(&x, 1e-5).unwrap());
} }
#[test] #[test]
fn test_cga_inner_symmetry() { fn test_cga_inner_symmetry() {
let x = embed_point_raw(&[1.0, 0.0, 0.0]); let x = embed_point_raw(&[1.0, 0.0, 0.0]);
let y = embed_point_raw(&[0.0, 1.0, 0.0]); let y = embed_point_raw(&[0.0, 1.0, 0.0]);
let xy = cga_inner_raw(&x, &y).unwrap(); let xy = cga_inner_raw(&x, &y).unwrap();
let yx = cga_inner_raw(&y, &x).unwrap(); let yx = cga_inner_raw(&y, &x).unwrap();
assert!((xy - yx).abs() < 1e-6, "cga_inner not symmetric"); assert!((xy - yx).abs() < 1e-6, "cga_inner not symmetric");
} }
#[test] #[test]
fn test_cga_distance_identity() { fn test_cga_distance_identity() {
// Points at distance 1: cga_inner = -d^2/2 = -0.5 let x = embed_point_raw(&[0.0, 0.0, 0.0]);
let x = embed_point_raw(&[0.0, 0.0, 0.0]); let y = embed_point_raw(&[1.0, 0.0, 0.0]);
let y = embed_point_raw(&[1.0, 0.0, 0.0]); let inner = cga_inner_raw(&x, &y).unwrap();
let inner = cga_inner_raw(&x, &y).unwrap(); assert!((inner - (-0.5)).abs() < 1e-5, "Expected -0.5 for unit-distance points, got {}", inner);
assert!((inner - (-0.5)).abs() < 1e-5, }
"Expected -0.5 for unit-distance points, got {}", inner);
} #[test]
fn test_null_project_restores_null() {
#[test] let p = [1.0f32, 2.0, 3.0];
fn test_null_project_restores_null() { let mut x = embed_point_raw(&p);
let p = [1.0f32, 2.0, 3.0]; x[0] += 0.05;
let mut x = embed_point_raw(&p); x[7] -= 0.03;
// Introduce drift let fixed = null_project_raw(&x);
x[0] += 0.05; assert!(is_null_raw(&fixed, 1e-5).unwrap(), "null_project failed to restore null cone");
x[7] -= 0.03;
let fixed = null_project_raw(&x);
assert!(is_null_raw(&fixed, 1e-5).unwrap(),
"null_project failed to restore null cone");
}
} }

View file

@ -1,69 +1,68 @@
#[cfg(test)] use core_rs::cl41::{geometric_product_raw, reverse_raw};
mod tests {
use crate::cl41::{geometric_product_raw, reverse_raw, BLADE_MASKS, MASK_TO_IDX};
fn basis(i: usize) -> [f32; 32] { fn basis(i: usize) -> [f32; 32] {
let mut v = [0f32; 32]; let mut v = [0f32; 32];
v[1 + i] = 1.0; // grade-1 component v[1 + i] = 1.0;
v v
} }
fn scalar(s: f32) -> [f32; 32] { fn scalar(s: f32) -> [f32; 32] {
let mut v = [0f32; 32]; let mut v = [0f32; 32];
v[0] = s; v[0] = s;
v v
} }
#[test] #[test]
fn test_e1_squared_is_plus1() { fn test_e1_squared_is_plus1() {
let e1 = basis(0); let e1 = basis(0);
let r = geometric_product_raw(&e1, &e1).unwrap(); let r = geometric_product_raw(&e1, &e1).unwrap();
// e1^2 = +1 (signature index 0 = +1) assert!((r[0] - 1.0).abs() < 1e-6, "e1^2 should be +1, got {}", r[0]);
assert!((r[0] - 1.0).abs() < 1e-6, "e1^2 should be +1, got {}", r[0]); }
}
#[test] #[test]
fn test_e5_squared_is_minus1() { fn test_e4_squared_is_minus1() {
let e5 = basis(4); let e4 = basis(3);
let r = geometric_product_raw(&e5, &e5).unwrap(); let r = geometric_product_raw(&e4, &e4).unwrap();
// e5^2 = -1 (signature index 4 = -1) assert!((r[0] + 1.0).abs() < 1e-6, "e4^2 should be -1, got {}", r[0]);
assert!((r[0] + 1.0).abs() < 1e-6, "e5^2 should be -1, got {}", r[0]); }
}
#[test] #[test]
fn test_e1_e2_anticommute() { fn test_e5_squared_is_plus1() {
let e1 = basis(0); let e5 = basis(4);
let e2 = basis(1); let r = geometric_product_raw(&e5, &e5).unwrap();
let e1e2 = geometric_product_raw(&e1, &e2).unwrap(); assert!((r[0] - 1.0).abs() < 1e-6, "e5^2 should be +1, got {}", r[0]);
let e2e1 = geometric_product_raw(&e2, &e1).unwrap(); }
// e1*e2 = -e2*e1
for i in 0..32 {
assert!((e1e2[i] + e2e1[i]).abs() < 1e-6,
"e1*e2 + e2*e1 != 0 at index {}", i);
}
}
#[test] #[test]
fn test_scalar_identity() { fn test_e1_e2_anticommute() {
let e1 = basis(0); let e1 = basis(0);
let one = scalar(1.0); let e2 = basis(1);
let r = geometric_product_raw(&one, &e1).unwrap(); let e1e2 = geometric_product_raw(&e1, &e2).unwrap();
assert!((r[1] - 1.0).abs() < 1e-6, "1*e1 should be e1"); let e2e1 = geometric_product_raw(&e2, &e1).unwrap();
} for i in 0..32 {
assert!((e1e2[i] + e2e1[i]).abs() < 1e-6, "e1*e2 + e2*e1 != 0 at index {}", i);
#[test]
fn test_reverse_grade2_sign() {
// A grade-2 blade should flip sign under reverse
let mut a = [0f32; 32];
a[6] = 1.0; // first grade-2 component
let r = reverse_raw(&a);
assert!((r[6] + 1.0).abs() < 1e-6, "reverse of grade-2 blade should negate");
}
#[test]
fn test_reverse_grade1_unchanged() {
let e1 = basis(0);
let r = reverse_raw(&e1);
assert!((r[1] - 1.0).abs() < 1e-6, "reverse of grade-1 blade should be unchanged");
} }
} }
#[test]
fn test_scalar_identity() {
let e1 = basis(0);
let one = scalar(1.0);
let r = geometric_product_raw(&one, &e1).unwrap();
assert!((r[1] - 1.0).abs() < 1e-6, "1*e1 should be e1");
}
#[test]
fn test_reverse_grade2_sign() {
let mut a = [0f32; 32];
a[6] = 1.0;
let r = reverse_raw(&a);
assert!((r[6] + 1.0).abs() < 1e-6, "reverse of grade-2 blade should negate");
}
#[test]
fn test_reverse_grade1_unchanged() {
let e1 = basis(0);
let r = reverse_raw(&e1);
assert!((r[1] - 1.0).abs() < 1e-6, "reverse of grade-1 blade should be unchanged");
}

View file

@ -1,50 +1,43 @@
#[cfg(test)] use core_rs::vault::vault_recall_raw;
mod tests { use core_rs::cga::embed_point_raw;
use crate::vault::vault_recall_raw;
use crate::versor::normalize_to_versor_raw;
fn random_versor(seed: u64) -> [f32; 32] { fn sample_point(seed: u64) -> [f32; 32] {
let mut state = seed ^ 0xdeadbeef_cafebabe; let x = ((seed * 17 + 3) % 101) as f32 / 10.0;
let mut raw = [0f32; 32]; let y = ((seed * 23 + 7) % 101) as f32 / 10.0;
for x in raw.iter_mut() { let z = ((seed * 31 + 11) % 101) as f32 / 10.0;
state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407); embed_point_raw(&[x, y, z])
*x = ((state >> 33) as f32) / (u32::MAX as f32) * 2.0 - 1.0; }
}
normalize_to_versor_raw(&raw).expect("normalize failed")
}
#[test] #[test]
fn test_recall_self() { fn test_recall_self() {
let versors: Vec<[f32; 32]> = (0..20).map(|i| random_versor(i as u64)).collect(); let versors: Vec<[f32; 32]> = (0..20).map(|i| sample_point(i as u64)).collect();
for (i, query) in versors.iter().enumerate() { for (i, query) in versors.iter().enumerate() {
let results = vault_recall_raw(&versors, query, 1).unwrap(); let results = vault_recall_raw(&versors, query, 1).unwrap();
assert_eq!(results[0].0, i, assert_eq!(results[0].0, i);
"Versor {} should recall itself as top-1, got {}", i, results[0].0); }
} }
}
#[test]
#[test] fn test_empty_vault() {
fn test_empty_vault() { let query = sample_point(0);
let query = random_versor(0); let results = vault_recall_raw(&[], &query, 5).unwrap();
let results = vault_recall_raw(&[], &query, 5).unwrap(); assert!(results.is_empty());
assert!(results.is_empty()); }
}
#[test]
#[test] fn test_top_k_count() {
fn test_top_k_count() { let versors: Vec<[f32; 32]> = (0..10).map(|i| sample_point(i as u64)).collect();
let versors: Vec<[f32; 32]> = (0..10).map(|i| random_versor(i as u64)).collect(); let query = sample_point(99);
let query = random_versor(99); let results = vault_recall_raw(&versors, &query, 3).unwrap();
let results = vault_recall_raw(&versors, &query, 3).unwrap(); assert_eq!(results.len(), 3);
assert_eq!(results.len(), 3); }
}
#[test]
#[test] fn test_scores_descending() {
fn test_scores_descending() { let versors: Vec<[f32; 32]> = (0..10).map(|i| sample_point(i as u64)).collect();
let versors: Vec<[f32; 32]> = (0..10).map(|i| random_versor(i as u64)).collect(); let query = sample_point(99);
let query = random_versor(99); let results = vault_recall_raw(&versors, &query, 5).unwrap();
let results = vault_recall_raw(&versors, &query, 5).unwrap(); for w in results.windows(2) {
for w in results.windows(2) { assert!(w[0].1 >= w[1].1);
assert!(w[0].1 >= w[1].1, "Scores not descending");
}
} }
} }

View file

@ -1,59 +1,57 @@
#[cfg(test)] use core_rs::versor::{normalize_to_versor_raw, versor_apply_raw, versor_condition_raw};
mod tests {
use crate::versor::{versor_apply_raw, normalize_to_versor_raw, versor_condition_raw};
fn random_versor(seed: u64) -> [f32; 32] { fn random_versor(seed: u64) -> [f32; 32] {
// Simple LCG for deterministic test data — no external deps let theta = ((seed * 17 + 3) % 101) as f32 / 100.0;
let mut state = seed ^ 0xdeadbeef_cafebabe; let mut v = [0f32; 32];
let mut raw = [0f32; 32]; v[0] = theta.cos();
for x in raw.iter_mut() {
state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407); // Choose a bivector with negative square, e.g. e12
*x = ((state >> 33) as f32) / (u32::MAX as f32) * 2.0 - 1.0; // e1^2 = 1, e2^2 = 1 => (e1 e2)^2 = -e1^2 e2^2 = -1
} // MASK_TO_IDX for e1e2: e1 is bit 0, e2 is bit 1 => mask 3
normalize_to_versor_raw(&raw).expect("normalize failed") // MASK_TO_IDX[3] = 6 (grade 2 starts at 6)
} v[6] = theta.sin();
v
}
#[test] #[test]
fn test_normalize_produces_versor() { fn test_normalize_produces_versor() {
for seed in 0..20u64 { for seed in 0..20u64 {
let v = random_versor(seed); let v = random_versor(seed);
let cond = versor_condition_raw(&v).unwrap(); let cond = versor_condition_raw(&v).unwrap();
assert!(cond < 1e-5, "versor_condition after normalize = {}", cond); assert!(cond < 1e-5, "versor_condition after normalize = {}", cond);
}
}
#[test]
fn test_versor_apply_preserves_manifold() {
for seed in 0..20u64 {
let v = random_versor(seed);
let f = random_versor(seed + 1000);
let result = versor_apply_raw(&v, &f).unwrap();
let cond = versor_condition_raw(&result).unwrap();
assert!(cond < 1e-4,
"versor_apply broke manifold: condition={:.2e} at seed={}", cond, seed);
}
}
#[test]
fn test_identity_versor() {
let mut identity = [0f32; 32];
identity[0] = 1.0;
let f = random_versor(42);
let result = versor_apply_raw(&identity, &f).unwrap();
for i in 0..32 {
assert!((result[i] - f[i]).abs() < 1e-5,
"Identity apply changed component {}: {} vs {}", i, result[i], f[i]);
}
}
#[test]
fn test_composition_closed() {
let v1 = random_versor(0);
let v2 = random_versor(1);
let f = random_versor(2);
let f2 = versor_apply_raw(&v1, &f).unwrap();
let f3 = versor_apply_raw(&v2, &f2).unwrap();
let cond = versor_condition_raw(&f3).unwrap();
assert!(cond < 1e-4, "Composition broke manifold: condition={:.2e}", cond);
} }
} }
#[test]
fn test_versor_apply_preserves_manifold() {
for seed in 0..20u64 {
let v = random_versor(seed);
let f = random_versor(seed + 1000);
let result = versor_apply_raw(&v, &f).unwrap();
let cond = versor_condition_raw(&result).unwrap();
assert!(cond < 1e-4, "versor_apply broke manifold: condition={:.2e} at seed={}", cond, seed);
}
}
#[test]
fn test_identity_versor() {
let mut identity = [0f32; 32];
identity[0] = 1.0;
let f = random_versor(42);
let result = versor_apply_raw(&identity, &f).unwrap();
for i in 0..32 {
assert!((result[i] - f[i]).abs() < 1e-5, "Identity apply changed component {}: {} vs {}", i, result[i], f[i]);
}
}
#[test]
fn test_composition_closed() {
let v1 = random_versor(0);
let v2 = random_versor(1);
let f = random_versor(2);
let f2 = versor_apply_raw(&v1, &f).unwrap();
let f3 = versor_apply_raw(&v2, &f2).unwrap();
let cond = versor_condition_raw(&f3).unwrap();
assert!(cond < 1e-4, "Composition broke manifold: condition={:.2e}", cond);
}

View file

@ -3,6 +3,7 @@ from __future__ import annotations
import argparse import argparse
import json import json
import shutil
import subprocess import subprocess
import sys import sys
from collections.abc import Sequence from collections.abc import Sequence
@ -18,14 +19,16 @@ _REPO_ROOT = Path(__file__).resolve().parent.parent
if str(_REPO_ROOT) not in sys.path: if str(_REPO_ROOT) not in sys.path:
sys.path.insert(0, str(_REPO_ROOT)) sys.path.insert(0, str(_REPO_ROOT))
_CORE_RS_DIR = _REPO_ROOT / "core-rs"
_CORE_RS_MANIFEST = _CORE_RS_DIR / "Cargo.toml"
DESCRIPTION = "CORE versor engine command suite." DESCRIPTION = "CORE versor engine command suite."
EPILOG = "Examples:\n core chat\n core trace \"word beginning truth\"\n core trace --output-language grc --frame-pack grc --json \"logos\"\n core oov covenant\n core pack list\n core pack verify en_minimal_v1\n core test tests/test_alignment_graph.py -q" EPILOG = "Examples:\n core chat\n core trace \"word beginning truth\"\n core trace --output-language grc --frame-pack grc --json \"logos\"\n core rust status\n core rust build\n core oov covenant\n core pack list\n core pack verify en_minimal_v1\n core test tests/test_alignment_graph.py -q"
def _run(*args: str, check: bool = False) -> int: def _run(*args: str, check: bool = False, cwd: Path | None = None) -> int:
"""Run a child command and return its exit code.""" """Run a child command and return its exit code."""
completed = subprocess.run(args, check=check, text=True) completed = subprocess.run(args, check=check, text=True, cwd=cwd)
return int(completed.returncode) return int(completed.returncode)
@ -260,6 +263,56 @@ def cmd_pack_verify(args: argparse.Namespace) -> int:
return _run(sys.executable, "-m", "language_packs", "verify", args.pack_id) return _run(sys.executable, "-m", "language_packs", "verify", args.pack_id)
def _print_rust_status() -> bool:
from algebra.backend import using_rust
active = using_rust()
print(f"core_rs crate : {_CORE_RS_DIR}")
print(f"cargo manifest: {_CORE_RS_MANIFEST}")
print(f"rust backend : {'active' if active else 'inactive'}")
if active:
import core_rs
print(f"core_rs module: {getattr(core_rs, '__file__', '<built-in>')}")
else:
print("activation : run `core rust build`")
return active
def cmd_rust_status(args: argparse.Namespace) -> int:
"""Print Rust backend activation status."""
return 0 if _print_rust_status() or not args.require_active else 1
def cmd_rust_build(args: argparse.Namespace) -> int:
"""Build/install core_rs into the active Python environment."""
if not _CORE_RS_MANIFEST.exists():
_die(f"core-rs manifest not found: {_CORE_RS_MANIFEST}", code=1)
if shutil.which("uv") is not None:
rc = _run("uv", "pip", "install", "maturin")
if rc != 0:
return rc
cmd = [
sys.executable,
"-m",
"maturin",
"develop",
"--release",
"--manifest-path",
str(_CORE_RS_MANIFEST),
]
if args.skip_auditwheel:
cmd.append("--skip-auditwheel")
return _run(*cmd)
def cmd_rust_test(args: argparse.Namespace) -> int:
"""Run Rust crate tests."""
if shutil.which("cargo") is None:
_die("cargo not found. Install a Rust toolchain first.", code=1)
return _run("cargo", "test", "--release", cwd=_CORE_RS_DIR)
def cmd_doctor(args: argparse.Namespace) -> int: def cmd_doctor(args: argparse.Namespace) -> int:
"""Inspect import/package health for the CLI runtime path.""" """Inspect import/package health for the CLI runtime path."""
checks = [ checks = [
@ -296,6 +349,10 @@ def cmd_doctor(args: argparse.Namespace) -> int:
print(f" {pack_id}") print(f" {pack_id}")
else: else:
print(" none found") print(" none found")
if args.rust:
rust_active = _print_rust_status()
if args.require_rust and not rust_active:
ok = False
return 0 if ok else 1 return 0 if ok else 1
@ -361,8 +418,25 @@ def build_parser() -> argparse.ArgumentParser:
pack_verify.add_argument("pack_id", help="pack id, e.g. en_minimal_v1") pack_verify.add_argument("pack_id", help="pack id, e.g. en_minimal_v1")
pack_verify.set_defaults(func=cmd_pack_verify) pack_verify.set_defaults(func=cmd_pack_verify)
rust = subparsers.add_parser(
"rust",
help="build, test, and inspect the Rust backend",
description="build, test, and inspect the Rust backend",
)
rust_sub = rust.add_subparsers(dest="rust_command", metavar="rust-command", required=True)
rust_status = rust_sub.add_parser("status", help="show whether core_rs is active")
rust_status.add_argument("--require-active", action="store_true", help="exit nonzero if core_rs is inactive")
rust_status.set_defaults(func=cmd_rust_status)
rust_build = rust_sub.add_parser("build", help="build/install core_rs with maturin")
rust_build.add_argument("--skip-auditwheel", action="store_true", help="pass --skip-auditwheel to maturin")
rust_build.set_defaults(func=cmd_rust_build)
rust_test = rust_sub.add_parser("test", help="run cargo test --release for core-rs")
rust_test.set_defaults(func=cmd_rust_test)
doctor = subparsers.add_parser("doctor", help="check runtime imports and packaging health") doctor = subparsers.add_parser("doctor", help="check runtime imports and packaging health")
doctor.add_argument("--packs", action="store_true", help="also list discovered language packs") doctor.add_argument("--packs", action="store_true", help="also list discovered language packs")
doctor.add_argument("--rust", action="store_true", help="also show Rust backend activation status")
doctor.add_argument("--require-rust", action="store_true", help="exit nonzero when --rust shows inactive backend")
doctor.set_defaults(func=cmd_doctor) doctor.set_defaults(func=cmd_doctor)
return parser return parser

View file

@ -4,78 +4,81 @@
Three operations dominate CORE-AI's runtime: Three operations dominate CORE-AI's runtime:
1. geometric_product — O(32^2) = 1024 multiply-adds per call, called 2-3x per versor_apply 1. `geometric_product` — O(32^2) = 1024 multiply-adds per call, called 2-3x per `versor_apply`
2. vault_recall scan — O(N) cga_inner calls, N = all stored versors, called once per token 2. `vault_recall` scan — O(N) CGA inner product calls, N = all stored versors, called during generation recall
3. holonomy_encode — 2 * prompt_length geometric products in sequence 3. `cga_inner` — called by vocabulary/proposition nearest selection and vault recall
None of these release the Python GIL. Rayon gives vault_recall true multithreaded None of the Python fallback paths release the Python GIL. Rayon gives `vault_recall` true multithreaded parallelism across CPU cores. The geometric product loop is cache-friendly and compiler-optimized in release mode.
parallelism across CPU cores. The geometric product loop is cache-friendly and
compiler-autovectorized when opt-level=3 + lto=true.
## What is in Rust ## What is in Rust
| Module | Rust file | Why | | Module | Rust file | Why |
|---|---|---| |---|---|---|
| Cl(4,1) product | cl41.rs | Hot inner loop, 1024 MADs, autovectorizable | | Cl(4,1) product | `cl41.rs` | Hot inner loop, 1024 MADs |
| Versor ops | versor.rs | 3x geometric_product per step, allocation-free | | Versor ops | `versor.rs` | 3x geometric product per field step |
| CGA inner product | cga.rs | Called every token decode and every vault recall | | CGA inner product | `cga.rs` | Called by nearest search and recall |
| Vault top-k scan | vault.rs | Rayon parallel scan — GIL blocks Python threads | | Vault top-k scan | `vault.rs` | Rayon parallel scan |
| Holonomy encode | holonomy.rs | 200+ products for long prompts |
| Batch propagation | propagate.rs | Beam search / speculative decode |
## What stays in Python ## What stays in Python
| Layer | Why | | Layer | Why |
|---|---| |---|---|
| VocabManifold | Word lookup, edge rotor construction — called once per token, not per step | | `VocabManifold` | Word/morphology/language metadata and exact candidate filtering |
| SessionContext | Orchestration, not arithmetic | | `SessionContext` | Orchestration, not arithmetic |
| FieldState | Plain dataclass | | `FieldState` | Plain dataclass |
| PersonaMotor | Motor construction is infrequent | | `PersonaMotor` | Motor construction is infrequent |
## Zero-Copy Semantics ## Zero-Copy Semantics
All f32 arrays are passed as numpy arrays from Python. The runtime contract is numpy `float32` arrays of length 32. Rust reads them into stack `[f32; 32]` values, executes the hot loop, and returns a new numpy array. The Python fallback remains behaviorally available when `core_rs` is not installed.
The Rust functions receive them as `[f32; 32]` stack arrays — copied once from
the numpy buffer into a stack frame, processed, and returned as a new numpy array.
No heap allocation inside any hot-path function.
For vault_recall, the versors list is iterated via Rayon par_iter with no cloning: ## Build / Activate
each worker holds a read-only reference to its slice element.
## Build Requires a Rust toolchain and maturin. Prefer the uv-native flow so the repo does not depend on `pip` being installed inside `.venv`:
Requires maturin and a Rust toolchain (stable 1.75+).
```bash ```bash
cd core-rs core rust status
pip install maturin core rust test
maturin develop --release # installs core_rs into current venv core rust build
core rust status --require-active
``` ```
Or build a wheel: Equivalent explicit maturin command:
```bash ```bash
maturin build --release uv run --with maturin maturin develop --release --manifest-path core-rs/Cargo.toml
pip install target/wheels/*.whl
``` ```
Verify Rust backend is active from Python: Verify Rust backend is active from Python:
```python
from algebra.backend import using_rust ```bash
print(using_rust()) # True if core_rs is installed uv run python -c "from algebra.backend import using_rust; print(using_rust())"
```
Expected:
```text
True
``` ```
## Running Rust Tests ## Running Rust Tests
```bash ```bash
cd core-rs core rust test
cargo test # or
cargo test --release --manifest-path core-rs/Cargo.toml
``` ```
## Type Safety Contract ## Type Safety Contract
All multivectors entering the Rust layer are validated as f32 arrays of length 32 All multivectors entering the Rust layer must be numpy-compatible `float32` arrays of length 32. Type errors surface as Python `ValueError` rather than silent memory corruption.
by extract_f32_slice() in lib.rs. Type errors surface immediately as Python
ValueError with a descriptive message rather than silent memory corruption.
All error types use thiserror — every failure path is a named enum variant, ## Failure Mode
not a string panic.
If `core_rs` is absent or fails to import, `algebra.backend` silently falls back to Python. This keeps the engine correct but not mechanically optimal. Use:
```bash
core doctor --rust --require-rust
```
to fail fast when benchmarking or profiling requires the Rust backend.

View file

@ -12,6 +12,7 @@ def test_top_level_help_exits_without_runtime_import(capsys: pytest.CaptureFixtu
out = capsys.readouterr().out out = capsys.readouterr().out
assert "CORE versor engine command suite" in out assert "CORE versor engine command suite" in out
assert "core trace" in out assert "core trace" in out
assert "core rust" in out
def test_trace_help_exits_without_runtime_import(capsys: pytest.CaptureFixture[str]) -> None: def test_trace_help_exits_without_runtime_import(capsys: pytest.CaptureFixture[str]) -> None:
@ -26,6 +27,25 @@ def test_trace_help_exits_without_runtime_import(capsys: pytest.CaptureFixture[s
assert "--json" in out assert "--json" in out
def test_rust_help_exits_without_building(capsys: pytest.CaptureFixture[str]) -> None:
with pytest.raises(SystemExit) as excinfo:
build_parser().parse_args(["rust", "-h"])
assert excinfo.value.code == 0
out = capsys.readouterr().out
assert "build, test, and inspect the Rust backend" in out
assert "status" in out
assert "build" in out
assert "test" in out
def test_rust_status_reports_backend_state(capsys: pytest.CaptureFixture[str]) -> None:
assert main(["rust", "status"]) in {0, 1}
out = capsys.readouterr().out
assert "core_rs crate" in out
assert "cargo manifest" in out
assert "rust backend" in out
def test_main_without_args_prints_help(capsys: pytest.CaptureFixture[str]) -> None: def test_main_without_args_prints_help(capsys: pytest.CaptureFixture[str]) -> None:
assert main([]) == 0 assert main([]) == 0
out = capsys.readouterr().out out = capsys.readouterr().out
@ -49,6 +69,12 @@ def test_doctor_imports_runtime_support_modules(capsys: pytest.CaptureFixture[st
assert "OK sensorium" in out assert "OK sensorium" in out
def test_doctor_rust_reports_backend_state(capsys: pytest.CaptureFixture[str]) -> None:
assert main(["doctor", "--rust"]) == 0
out = capsys.readouterr().out
assert "rust backend" in out
def test_trace_formats_real_runtime_payload(capsys: pytest.CaptureFixture[str]) -> None: def test_trace_formats_real_runtime_payload(capsys: pytest.CaptureFixture[str]) -> None:
assert main(["trace", "--pack", "en_minimal_v1", "word", "beginning", "truth"]) == 0 assert main(["trace", "--pack", "en_minimal_v1", "word", "beginning", "truth"]) == 0
out = capsys.readouterr().out out = capsys.readouterr().out

View file

@ -1,32 +1,31 @@
import numpy as np import numpy as np
import pytest import pytest
from algebra.versor import unitize_versor from algebra.cga import embed_point, is_null
from algebra.cga import is_null
from vault.store import VaultStore from vault.store import VaultStore
def _random_versor(seed=0) -> np.ndarray: def _random_point(seed=0) -> np.ndarray:
rng = np.random.default_rng(seed) rng = np.random.default_rng(seed)
return unitize_versor(rng.standard_normal(32).astype(np.float32)) return embed_point(rng.standard_normal(3).astype(np.float32))
def test_store_and_recall_top1(): def test_store_and_recall_top1():
"""Each stored versor should recall itself as the top result.""" """Each stored point should recall itself as the top result."""
vault = VaultStore() vault = VaultStore()
versors = [_random_versor(i) for i in range(20)] points = [_random_point(i) for i in range(20)]
for i, v in enumerate(versors): for i, v in enumerate(points):
vault.store(v, {"id": i}) vault.store(v, {"id": i})
for i, v in enumerate(versors): for i, v in enumerate(points):
results = vault.recall(v, top_k=1) results = vault.recall(v, top_k=1)
assert results[0]["metadata"]["id"] == i, ( assert results[0]["metadata"]["id"] == i, (
f"Versor {i} did not recall itself as top result" f"Point {i} did not recall itself as top result"
) )
def test_recall_empty_vault(): def test_recall_empty_vault():
vault = VaultStore() vault = VaultStore()
result = vault.recall(_random_versor(), top_k=5) result = vault.recall(_random_point(), top_k=5)
assert result == [] assert result == []
@ -34,7 +33,7 @@ def test_reproject_maintains_structure():
"""Reproject should not lose stored entries.""" """Reproject should not lose stored entries."""
vault = VaultStore() vault = VaultStore()
for i in range(10): for i in range(10):
vault.store(_random_versor(i), {"id": i}) vault.store(_random_point(i), {"id": i})
vault.reproject() vault.reproject()
assert len(vault) == 10 assert len(vault) == 10
@ -42,5 +41,6 @@ def test_reproject_maintains_structure():
def test_vault_len(): def test_vault_len():
vault = VaultStore() vault = VaultStore()
for i in range(5): for i in range(5):
vault.store(_random_versor(i)) vault.store(_random_point(i))
assert len(vault) == 5 assert len(vault) == 5