use scirs2_core::ndarray::{Array1, Array2, ArrayView1};
pub struct SimdDistanceComputer {}
impl Default for SimdDistanceComputer {
fn default() -> Self {
Self::new()
}
}
impl SimdDistanceComputer {
pub fn new() -> Self {
Self {}
}
pub fn pairwise_euclidean(&self, data: &Array2<f64>) -> Array2<f64> {
let n_samples = data.nrows();
let mut distances = Array2::zeros((n_samples, n_samples));
self.pairwise_euclidean_internal(data, &mut distances);
distances
}
pub fn pairwise_euclidean_squared(&self, data: &Array2<f64>) -> Array2<f64> {
let n_samples = data.nrows();
let mut distances = Array2::zeros((n_samples, n_samples));
self.pairwise_euclidean_squared_internal(data, &mut distances);
distances
}
pub fn knn_distances(&self, data: &Array2<f64>, k: usize) -> (Array2<usize>, Array2<f64>) {
let n_samples = data.nrows();
let mut indices = Array2::zeros((n_samples, k));
let mut distances = Array2::zeros((n_samples, k));
for i in 0..n_samples {
let query = data.row(i);
let mut row_distances = self.distances_to_point(data, &query);
row_distances[i] = f64::INFINITY;
let mut sorted_indices: Vec<usize> = (0..n_samples).collect();
sorted_indices.sort_by(|&a, &b| {
row_distances[a]
.partial_cmp(&row_distances[b])
.expect("operation should succeed")
});
for j in 0..k {
indices[[i, j]] = sorted_indices[j];
distances[[i, j]] = row_distances[sorted_indices[j]];
}
}
(indices, distances)
}
pub fn distances_to_point(&self, data: &Array2<f64>, query: &ArrayView1<f64>) -> Array1<f64> {
let n_samples = data.nrows();
let mut distances = Array1::zeros(n_samples);
self.distances_to_point_internal(data, query, &mut distances);
distances
}
pub fn pairwise_manhattan(&self, data: &Array2<f64>) -> Array2<f64> {
let n_samples = data.nrows();
let mut distances = Array2::zeros((n_samples, n_samples));
self.pairwise_manhattan_internal(data, &mut distances);
distances
}
pub fn pairwise_cosine(&self, data: &Array2<f64>) -> Array2<f64> {
let n_samples = data.nrows();
let mut distances = Array2::zeros((n_samples, n_samples));
let mut norms = Array1::zeros(n_samples);
for i in 0..n_samples {
let row = data.row(i);
norms[i] = self.compute_norm(&row);
}
self.pairwise_cosine_internal(data, &norms, &mut distances);
distances
}
pub fn compute_norm(&self, vector: &ArrayView1<f64>) -> f64 {
self.compute_norm_internal(vector)
}
fn pairwise_euclidean_internal(&self, data: &Array2<f64>, distances: &mut Array2<f64>) {
let n_samples = data.nrows();
for i in 0..n_samples {
for j in 0..n_samples {
let diff = &data.row(i) - &data.row(j);
distances[[i, j]] = diff.mapv(|x| x * x).sum().sqrt();
}
}
}
fn pairwise_euclidean_squared_internal(&self, data: &Array2<f64>, distances: &mut Array2<f64>) {
let n_samples = data.nrows();
for i in 0..n_samples {
for j in 0..n_samples {
let diff = &data.row(i) - &data.row(j);
distances[[i, j]] = diff.mapv(|x| x * x).sum();
}
}
}
fn distances_to_point_internal(
&self,
data: &Array2<f64>,
query: &ArrayView1<f64>,
distances: &mut Array1<f64>,
) {
for i in 0..data.nrows() {
let diff = &data.row(i) - query;
distances[i] = diff.mapv(|x| x * x).sum().sqrt();
}
}
fn pairwise_manhattan_internal(&self, data: &Array2<f64>, distances: &mut Array2<f64>) {
let n_samples = data.nrows();
for i in 0..n_samples {
for j in 0..n_samples {
let diff = &data.row(i) - &data.row(j);
distances[[i, j]] = diff.mapv(|x| x.abs()).sum();
}
}
}
fn pairwise_cosine_internal(
&self,
data: &Array2<f64>,
norms: &Array1<f64>,
distances: &mut Array2<f64>,
) {
let n_samples = data.nrows();
for i in 0..n_samples {
for j in 0..n_samples {
let dot_product = data.row(i).dot(&data.row(j));
let denom = norms[i] * norms[j];
if denom > 1e-12 {
distances[[i, j]] = 1.0 - (dot_product / denom);
} else {
distances[[i, j]] = 0.0;
}
}
}
}
fn compute_norm_internal(&self, vector: &ArrayView1<f64>) -> f64 {
vector.mapv(|x| x * x).sum().sqrt()
}
}