pub use polydat::numeric::vector::{
add_f32, add_f32_into, check_lens, cosine_f32, dot_f32, dot_scalar, hash_vec_into, l2sq_f32,
l2sq_scalar, lid_mle_of, norm_f32_into, scale_f32, scale_f32_into, xxhash3_vec_into,
};
#[polydat::polydat_node(category = Arithmetic)]
fn vec_add(a: &[f32], b: &[f32]) -> Vec<f32> {
check_lens("vec_add", a.len(), b.len());
add_f32(a, b)
}
#[polydat::polydat_node(category = Arithmetic)]
fn vec_scale(a: &[f32], k: f64) -> Vec<f32> {
scale_f32(a, k as f32)
}
#[polydat::polydat_node(category = Arithmetic)]
fn vec_dot(a: &[f32], b: &[f32]) -> f64 {
check_lens("vec_dot", a.len(), b.len());
dot_f32(a, b) as f64
}
#[polydat::polydat_node(category = Arithmetic)]
fn vec_l2(a: &[f32], b: &[f32]) -> f64 {
check_lens("vec_l2", a.len(), b.len());
(l2sq_f32(a, b) as f64).sqrt()
}
#[polydat::polydat_node(category = Arithmetic)]
fn vec_cosine(a: &[f32], b: &[f32]) -> f64 {
check_lens("vec_cosine", a.len(), b.len());
cosine_f32(a, b)
}
#[polydat::polydat_node(category = Arithmetic)]
fn vec_norm(a: &[f32]) -> Vec<f32> {
let mut out = Vec::new();
norm_f32_into(a, &mut out);
out
}
#[polydat::polydat_node(category = Arithmetic)]
fn lid_mle(distances: &[f32], k: f64) -> f64 {
lid_mle_of(distances, k)
}
#[polydat::polydat_node(category = Hashing)]
fn hash_vec(seed: u64, dim: u64) -> Vec<f32> {
let mut out = Vec::new();
hash_vec_into(seed, dim, &mut out);
out
}
#[polydat::polydat_node(category = Hashing)]
fn xxhash3_vec(seed: u64, dim: u64) -> Vec<f32> {
let mut out = Vec::new();
xxhash3_vec_into(seed, dim, &mut out);
out
}
#[cfg(test)]
mod tests {
use super::*;
use polydat::ast::{PolydatNode, SliceArc, Value};
fn vecv(v: Vec<f32>) -> Value {
Value::VecF32(SliceArc::from_vec(v))
}
fn test_vec(n: usize, seed: u64) -> Vec<f32> {
(0..n)
.map(|i| {
let h = xxhash_rust::xxh3::xxh3_64(&(seed ^ i as u64).to_le_bytes());
(h as f64 / u64::MAX as f64 * 2.0 - 1.0) as f32
})
.collect()
}
fn eval2<N: PolydatNode>(node: &N, a: Vec<f32>, b: Vec<f32>) -> Value {
let mut out = [Value::None];
node.eval(&[vecv(a), vecv(b)], &mut out);
out[0].clone()
}
#[test]
fn vec_math_matches_scalar_reference() {
let a = test_vec(1029, 1);
let b = test_vec(1029, 2);
let dot = eval2(&VecDot::new(), a.clone(), b.clone()).as_f64();
let dot_ref = dot_scalar(&a, &b) as f64;
assert!((dot - dot_ref).abs() / dot_ref.abs().max(1e-6) < 1e-4);
let l2 = eval2(&VecL2::new(), a.clone(), b.clone()).as_f64();
let l2_ref = (l2sq_scalar(&a, &b) as f64).sqrt();
assert!((l2 - l2_ref).abs() / l2_ref.max(1e-6) < 1e-4);
let sum = eval2(&VecAdd::new(), a.clone(), b.clone());
let sum = sum.as_vec_f32();
for i in 0..a.len() {
assert_eq!(sum[i], a[i] + b[i], "vec_add lane {i}");
}
let cos_self = eval2(&VecCosine::new(), a.clone(), a.clone()).as_f64();
assert!((cos_self - 1.0).abs() < 1e-4, "self-cosine = {cos_self}");
}
#[test]
fn vec_scale_and_norm() {
let a = test_vec(37, 3);
let mut out = [Value::None];
VecScale::new().eval(&[vecv(a.clone()), Value::F64(2.0)], &mut out);
let scaled = out[0].as_vec_f32();
for i in 0..a.len() {
assert_eq!(scaled[i], a[i] * 2.0, "vec_scale lane {i}");
}
let mut out = [Value::None];
VecNorm::new().eval(&[vecv(a.clone())], &mut out);
let unit = out[0].as_vec_f32().to_vec();
let mag = (dot_scalar(&unit, &unit) as f64).sqrt();
assert!((mag - 1.0).abs() < 1e-4, "norm magnitude = {mag}");
let mut out = [Value::None];
VecNorm::new().eval(&[vecv(vec![0.0; 4])], &mut out);
assert_eq!(out[0].as_vec_f32(), &[0.0, 0.0, 0.0, 0.0]);
}
#[test]
fn lid_mle_matches_closed_form_and_handles_degenerate() {
let dists: Vec<f32> = (0..10).map(|j| (j as f32).exp()).collect();
let mut out = [Value::None];
LidMle::new().eval(&[vecv(dists), Value::F64(10.0)], &mut out);
assert!(
(out[0].as_f64() - 0.2).abs() < 1e-4,
"got {}",
out[0].as_f64()
);
let mut out = [Value::None];
LidMle::new().eval(&[vecv(vec![1.0]), Value::F64(10.0)], &mut out);
assert_eq!(out[0].as_f64(), 0.0);
let mut out = [Value::None];
LidMle::new().eval(&[vecv(vec![0.0, 0.0, 0.0]), Value::F64(3.0)], &mut out);
assert_eq!(out[0].as_f64(), 0.0);
let mut out = [Value::None];
let d: Vec<f32> = vec![
0.0,
std::f32::consts::E,
std::f32::consts::E * std::f32::consts::E,
];
LidMle::new().eval(&[vecv(d), Value::F64(3.0)], &mut out);
assert!(
(out[0].as_f64() - 1.0).abs() < 1e-4,
"got {}",
out[0].as_f64()
);
}
#[test]
fn hash_vec_is_deterministic_and_seed_sensitive() {
let mut out = [Value::None];
HashVec::new().eval(&[Value::U64(7), Value::U64(16)], &mut out);
let v1 = out[0].as_vec_f32().to_vec();
let mut out = [Value::None];
HashVec::new().eval(&[Value::U64(7), Value::U64(16)], &mut out);
assert_eq!(v1, out[0].as_vec_f32(), "same seed must reproduce");
let mut out = [Value::None];
HashVec::new().eval(&[Value::U64(8), Value::U64(16)], &mut out);
assert_ne!(v1, out[0].as_vec_f32(), "different seed must differ");
assert_eq!(v1.len(), 16);
assert!(v1.iter().all(|x| (-1.0..1.0).contains(x)));
}
#[test]
fn vec_flow_rides_compiled_kernels() {
let src = r#"
input cycle: u64
a := hash_vec(cycle, 37)
b := hash_vec(hash(cycle), 37)
s := vec_add(a, b)
out := vec_dot(s, b)
"#;
let mut p1 = polydat::dsl::compile_polydat(src).unwrap();
let asm = polydat::dsl::compile::compile_polydat_to_assembler(src).unwrap();
let mut p2 = asm
.try_compile_raw()
.expect("slice-bearing nodes are P2-eligible via compiled_slot");
for cycle in [0u64, 7, 0xFEED] {
p1.set_inputs(&[cycle]);
let want = p1.pull("out").as_f64();
let slot = p2.resolve_output("out").unwrap();
let got = f64::from_bits(p2.eval_for_slot(&[cycle], slot));
assert_eq!(got, want, "P2 vec flow mismatch at cycle={cycle}");
}
#[cfg(feature = "jit")]
{
let asm = polydat::dsl::compile::compile_polydat_to_assembler(src).unwrap();
let mut hy = asm.compile_hybrid().unwrap();
for cycle in [0u64, 7, 0xFEED] {
p1.set_inputs(&[cycle]);
let want = p1.pull("out").as_f64();
let slot = hy.resolve_output("out").unwrap();
hy.eval(&[cycle]);
let got = f64::from_bits(hy.get_slot(slot));
assert_eq!(got, want, "hybrid vec flow mismatch at cycle={cycle}");
}
}
}
#[test]
fn vec_scratch_reuses_across_evals() {
let src = r#"
input cycle: u64
a := hash_vec(cycle, 16)
out := vec_l2(a, vec_scale(a, 2.0))
"#;
let mut p1 = polydat::dsl::compile_polydat(src).unwrap();
let asm = polydat::dsl::compile::compile_polydat_to_assembler(src).unwrap();
let mut p2 = asm.try_compile_raw().expect("P2-eligible");
let slot = p2.resolve_output("out").unwrap();
for cycle in [1u64, 9, 1, 42, 9, 1] {
p1.set_inputs(&[cycle]);
let want = p1.pull("out").as_f64();
let got = f64::from_bits(p2.eval_for_slot(&[cycle], slot));
assert_eq!(got, want, "scratch reuse mismatch at cycle={cycle}");
}
}
#[test]
fn vec_length_mismatch_panics() {
let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let mut out = [Value::None];
VecDot::new().eval(&[vecv(vec![1.0]), vecv(vec![1.0, 2.0])], &mut out);
}));
assert!(r.is_err(), "vec_dot accepted mismatched lengths");
}
}