#![allow(unsafe_code)]
pub mod scalar;
#[cfg(target_arch = "x86_64")]
pub mod simd_avx2;
pub use scalar::{
l2_distance,
l2_distance_squared,
inner_product,
cosine_similarity,
cosine_distance,
norm,
norm_squared,
normalize,
normalize_in_place,
normalize_batch,
normalize_batch_flat,
compute_norms_batch,
find_unnormalized,
};
use crate::index::DistanceType;
#[cfg(target_arch = "x86_64")]
#[inline]
fn has_avx2_fma() -> bool {
is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma")
}
#[inline]
pub fn l2_distance_fast(a: &[f32], b: &[f32]) -> f32 {
#[cfg(target_arch = "x86_64")]
{
if has_avx2_fma() {
return unsafe { simd_avx2::l2_distance_avx2(a, b) };
}
}
scalar::l2_distance(a, b)
}
#[inline]
pub fn inner_product_fast(a: &[f32], b: &[f32]) -> f32 {
#[cfg(target_arch = "x86_64")]
{
if has_avx2_fma() {
return unsafe { simd_avx2::inner_product_avx2(a, b) };
}
}
scalar::inner_product(a, b)
}
#[inline]
pub fn cosine_similarity_fast(a: &[f32], b: &[f32]) -> f32 {
#[cfg(target_arch = "x86_64")]
{
if has_avx2_fma() {
return unsafe { simd_avx2::cosine_similarity_avx2(a, b) };
}
}
scalar::cosine_similarity(a, b)
}
#[inline]
pub fn cosine_distance_fast(a: &[f32], b: &[f32]) -> f32 {
1.0 - cosine_similarity_fast(a, b)
}
#[inline]
pub fn norm_fast(a: &[f32]) -> f32 {
#[cfg(target_arch = "x86_64")]
{
if has_avx2_fma() {
return unsafe { simd_avx2::norm_avx2(a) };
}
}
scalar::norm(a)
}
#[inline]
pub fn normalize_in_place_fast(a: &mut [f32]) -> f32 {
#[cfg(target_arch = "x86_64")]
{
if has_avx2_fma() {
return unsafe { simd_avx2::normalize_in_place_avx2(a) };
}
}
scalar::normalize_in_place(a)
}
#[inline]
pub fn compute_distance(a: &[f32], b: &[f32], distance_type: DistanceType) -> f32 {
debug_assert_eq!(a.len(), b.len(), "Vector dimension mismatch");
match distance_type {
DistanceType::L2 => l2_distance_fast(a, b),
DistanceType::InnerProduct => inner_product_fast(a, b),
DistanceType::Cosine => cosine_similarity_fast(a, b),
}
}
#[inline]
pub fn compute_distance_for_heap(a: &[f32], b: &[f32], distance_type: DistanceType) -> f32 {
debug_assert_eq!(a.len(), b.len(), "Vector dimension mismatch");
match distance_type {
DistanceType::L2 => l2_distance_fast(a, b),
DistanceType::InnerProduct => -inner_product_fast(a, b), DistanceType::Cosine => cosine_distance_fast(a, b), }
}
#[inline]
#[must_use]
pub const fn is_similarity_metric(distance_type: DistanceType) -> bool {
matches!(distance_type, DistanceType::InnerProduct | DistanceType::Cosine)
}
#[cfg(feature = "parallel")]
pub fn normalize_batch_parallel(data: &mut [f32], dim: usize) -> Vec<f32> {
use rayon::prelude::*;
assert!(dim > 0, "Dimension must be > 0");
assert!(data.len() % dim == 0, "Data length must be multiple of dimension");
data.par_chunks_mut(dim)
.map(|vector| normalize_in_place_fast(vector))
.collect()
}
#[cfg(not(feature = "parallel"))]
pub fn normalize_batch_parallel(data: &mut [f32], dim: usize) -> Vec<f32> {
normalize_batch_flat_fast(data, dim)
}
pub fn normalize_batch_flat_fast(data: &mut [f32], dim: usize) -> Vec<f32> {
assert!(dim > 0, "Dimension must be > 0");
assert!(data.len() % dim == 0, "Data length must be multiple of dimension");
let n_vectors = data.len() / dim;
let mut norms = Vec::with_capacity(n_vectors);
for i in 0..n_vectors {
let start = i * dim;
let end = start + dim;
let vector = &mut data[start..end];
let n = normalize_in_place_fast(vector);
norms.push(n);
}
norms
}
pub fn compute_norms_batch_fast(data: &[f32], dim: usize) -> Vec<f32> {
assert!(dim > 0, "Dimension must be > 0");
assert!(data.len() % dim == 0, "Data length must be multiple of dimension");
let n_vectors = data.len() / dim;
let mut norms = Vec::with_capacity(n_vectors);
for i in 0..n_vectors {
let start = i * dim;
let end = start + dim;
let vector = &data[start..end];
norms.push(norm_fast(vector));
}
norms
}
use crate::trajectory::{TrajectoryCoordinate, TrajectoryCoordinate5D};
#[inline]
pub fn trajectory_weighted_cosine(
a: &[f32],
b: &[f32],
coord_a: &TrajectoryCoordinate,
coord_b: &TrajectoryCoordinate,
coord_weight: f32,
) -> f32 {
let coord_weight = coord_weight.clamp(0.0, 1.0);
let semantic_sim = cosine_similarity_fast(a, b);
let coord_dist = coord_a.distance(coord_b);
let coord_sim = (1.0 - coord_dist / 4.0).clamp(0.0, 1.0);
(1.0 - coord_weight) * semantic_sim + coord_weight * coord_sim
}
#[inline]
pub fn trajectory_weighted_cosine_5d(
a: &[f32],
b: &[f32],
coord_a: &TrajectoryCoordinate5D,
coord_b: &TrajectoryCoordinate5D,
coord_weight: f32,
) -> f32 {
let coord_weight = coord_weight.clamp(0.0, 1.0);
let semantic_sim = cosine_similarity_fast(a, b);
let coord_dist = coord_a.distance(coord_b);
let coord_sim = (1.0 - coord_dist / 4.5).clamp(0.0, 1.0);
(1.0 - coord_weight) * semantic_sim + coord_weight * coord_sim
}
#[inline]
pub fn trajectory_weighted_l2(
a: &[f32],
b: &[f32],
coord_a: &TrajectoryCoordinate,
coord_b: &TrajectoryCoordinate,
coord_weight: f32,
) -> f32 {
let coord_weight = coord_weight.clamp(0.0, 1.0);
let semantic_dist = l2_distance_fast(a, b);
let coord_dist = coord_a.distance(coord_b);
(1.0 - coord_weight) * semantic_dist + coord_weight * coord_dist
}
#[inline]
pub fn trajectory_weighted_inner_product(
a: &[f32],
b: &[f32],
coord_a: &TrajectoryCoordinate,
coord_b: &TrajectoryCoordinate,
coord_weight: f32,
) -> f32 {
let coord_weight = coord_weight.clamp(0.0, 1.0);
let semantic_sim = inner_product_fast(a, b);
let coord_dist = coord_a.distance(coord_b);
let coord_sim = (4.0 - coord_dist).max(0.0);
(1.0 - coord_weight) * semantic_sim + coord_weight * coord_sim
}
#[inline]
pub fn trajectory_weighted_distance(
a: &[f32],
b: &[f32],
coord_a: &TrajectoryCoordinate,
coord_b: &TrajectoryCoordinate,
distance_type: DistanceType,
coord_weight: f32,
) -> f32 {
match distance_type {
DistanceType::L2 => trajectory_weighted_l2(a, b, coord_a, coord_b, coord_weight),
DistanceType::InnerProduct => trajectory_weighted_inner_product(a, b, coord_a, coord_b, coord_weight),
DistanceType::Cosine => trajectory_weighted_cosine(a, b, coord_a, coord_b, coord_weight),
}
}
#[derive(Debug, Clone, Copy)]
pub struct TrajectoryDistanceConfig {
pub coord_weight: f32,
pub distance_type: DistanceType,
pub phase_boost: bool,
pub phase_boost_amount: f32,
}
impl Default for TrajectoryDistanceConfig {
fn default() -> Self {
Self {
coord_weight: 0.2, distance_type: DistanceType::Cosine,
phase_boost: true,
phase_boost_amount: 0.1, }
}
}
impl TrajectoryDistanceConfig {
pub fn semantic_only() -> Self {
Self {
coord_weight: 0.0,
..Default::default()
}
}
pub fn balanced() -> Self {
Self {
coord_weight: 0.5,
..Default::default()
}
}
pub fn trajectory_focused() -> Self {
Self {
coord_weight: 0.7,
..Default::default()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compute_distance_l2() {
let a = [1.0, 0.0, 0.0, 0.0];
let b = [0.0, 1.0, 0.0, 0.0];
let dist = compute_distance(&a, &b, DistanceType::L2);
assert!((dist - std::f32::consts::SQRT_2).abs() < 1e-6);
}
#[test]
fn test_compute_distance_inner_product() {
let a = [1.0, 2.0, 3.0];
let b = [4.0, 5.0, 6.0];
let dist = compute_distance(&a, &b, DistanceType::InnerProduct);
assert!((dist - 32.0).abs() < 1e-6); }
#[test]
fn test_compute_distance_cosine() {
let a = [1.0, 0.0];
let b = [1.0, 0.0];
let dist = compute_distance(&a, &b, DistanceType::Cosine);
assert!((dist - 1.0).abs() < 1e-6); }
#[test]
fn test_heap_distance_ordering() {
let a = [1.0, 0.0, 0.0];
let b_close = [0.9, 0.1, 0.0];
let b_far = [0.0, 1.0, 0.0];
for dt in [DistanceType::L2, DistanceType::InnerProduct, DistanceType::Cosine] {
let d_close = compute_distance_for_heap(&a, &b_close, dt);
let d_far = compute_distance_for_heap(&a, &b_far, dt);
}
}
#[test]
fn test_is_similarity_metric() {
assert!(!is_similarity_metric(DistanceType::L2));
assert!(is_similarity_metric(DistanceType::InnerProduct));
assert!(is_similarity_metric(DistanceType::Cosine));
}
#[test]
fn test_trajectory_weighted_cosine_pure_semantic() {
use crate::trajectory::TrajectoryCoordinate;
let a = [1.0, 0.0, 0.0];
let b = [1.0, 0.0, 0.0];
let coord_a = TrajectoryCoordinate::new(0, 0, 1.0, 0.0);
let coord_b = TrajectoryCoordinate::new(5, 3, 0.2, 1.0);
let sim = trajectory_weighted_cosine(&a, &b, &coord_a, &coord_b, 0.0);
assert!((sim - 1.0).abs() < 1e-6); }
#[test]
fn test_trajectory_weighted_cosine_pure_spatial() {
use crate::trajectory::TrajectoryCoordinate;
let a = [1.0, 0.0, 0.0];
let b = [0.0, 1.0, 0.0];
let coord_a = TrajectoryCoordinate::new(1, 0, 0.9, 0.5);
let coord_b = TrajectoryCoordinate::new(1, 0, 0.9, 0.5);
let sim = trajectory_weighted_cosine(&a, &b, &coord_a, &coord_b, 1.0);
assert!((sim - 1.0).abs() < 1e-6); }
#[test]
fn test_trajectory_weighted_cosine_mixed() {
use crate::trajectory::TrajectoryCoordinate;
let a = [1.0, 0.0, 0.0];
let b = [0.9, 0.436, 0.0];
let coord_a = TrajectoryCoordinate::new(1, 0, 0.9, 0.2);
let coord_b = TrajectoryCoordinate::new(2, 0, 0.8, 0.3);
let sim = trajectory_weighted_cosine(&a, &b, &coord_a, &coord_b, 0.3);
assert!(sim > 0.7 && sim < 1.0); }
#[test]
fn test_trajectory_weighted_cosine_5d() {
use crate::trajectory::TrajectoryCoordinate5D;
let a = [1.0, 0.0, 0.0];
let b = [1.0, 0.0, 0.0];
let coord_a = TrajectoryCoordinate5D::new(1, 0, 0.9, 0.2, 1);
let coord_b = TrajectoryCoordinate5D::new(1, 0, 0.9, 0.2, 3);
let sim = trajectory_weighted_cosine_5d(&a, &b, &coord_a, &coord_b, 0.3);
assert!(sim > 0.9); }
#[test]
fn test_trajectory_weighted_l2() {
use crate::trajectory::TrajectoryCoordinate;
let a = [1.0, 0.0, 0.0];
let b = [1.0, 0.0, 0.0];
let coord_a = TrajectoryCoordinate::new(0, 0, 1.0, 0.0);
let coord_b = TrajectoryCoordinate::new(0, 0, 1.0, 0.0);
let dist = trajectory_weighted_l2(&a, &b, &coord_a, &coord_b, 0.5);
assert!(dist.abs() < 1e-6);
}
#[test]
fn test_trajectory_weighted_inner_product() {
use crate::trajectory::TrajectoryCoordinate;
let a = [1.0, 0.0, 0.0];
let b = [1.0, 0.0, 0.0];
let coord_a = TrajectoryCoordinate::new(1, 0, 0.9, 0.2);
let coord_b = TrajectoryCoordinate::new(1, 0, 0.9, 0.2);
let sim = trajectory_weighted_inner_product(&a, &b, &coord_a, &coord_b, 0.5);
assert!((sim - 2.5).abs() < 1e-6);
}
#[test]
fn test_trajectory_weighted_distance_dispatch() {
use crate::trajectory::TrajectoryCoordinate;
let a = [1.0, 0.0, 0.0];
let b = [0.9, 0.436, 0.0];
let coord_a = TrajectoryCoordinate::new(1, 0, 0.9, 0.2);
let coord_b = TrajectoryCoordinate::new(2, 0, 0.8, 0.3);
let _ = trajectory_weighted_distance(&a, &b, &coord_a, &coord_b, DistanceType::L2, 0.3);
let _ = trajectory_weighted_distance(&a, &b, &coord_a, &coord_b, DistanceType::InnerProduct, 0.3);
let _ = trajectory_weighted_distance(&a, &b, &coord_a, &coord_b, DistanceType::Cosine, 0.3);
}
#[test]
fn test_trajectory_distance_config_presets() {
let semantic = TrajectoryDistanceConfig::semantic_only();
assert_eq!(semantic.coord_weight, 0.0);
let balanced = TrajectoryDistanceConfig::balanced();
assert_eq!(balanced.coord_weight, 0.5);
let trajectory = TrajectoryDistanceConfig::trajectory_focused();
assert_eq!(trajectory.coord_weight, 0.7);
}
#[test]
fn test_trajectory_distance_config_defaults() {
let config = TrajectoryDistanceConfig::default();
assert_eq!(config.coord_weight, 0.2);
assert!(config.phase_boost);
assert_eq!(config.phase_boost_amount, 0.1);
}
#[test]
fn test_coord_weight_clamping() {
use crate::trajectory::TrajectoryCoordinate;
let a = [1.0, 0.0, 0.0];
let b = [1.0, 0.0, 0.0];
let coord_a = TrajectoryCoordinate::new(0, 0, 1.0, 0.0);
let coord_b = TrajectoryCoordinate::new(0, 0, 1.0, 0.0);
let sim_over = trajectory_weighted_cosine(&a, &b, &coord_a, &coord_b, 5.0);
let sim_one = trajectory_weighted_cosine(&a, &b, &coord_a, &coord_b, 1.0);
assert!((sim_over - sim_one).abs() < 1e-6);
let sim_under = trajectory_weighted_cosine(&a, &b, &coord_a, &coord_b, -2.0);
let sim_zero = trajectory_weighted_cosine(&a, &b, &coord_a, &coord_b, 0.0);
assert!((sim_under - sim_zero).abs() < 1e-6);
}
}