use faer::{Mat, MatRef};
use faer_ext::*;
use ndarray::{Array2, ArrayView1, Axis};
pub fn lp_norm_arrayview(x: &ArrayView1<f64>, p: f64) -> f64 {
x.iter()
.map(|&val| val.abs().powf(p))
.sum::<f64>()
.powf(1.0 / p)
}
fn faer_dot(x: MatRef<f64>, y: MatRef<f64>) -> Mat<f64> {
x * y.transpose()
}
pub fn faer_dot_from_array(x: &Array2<f64>, y: &Array2<f64>) -> Mat<f64> {
let faer_x = x.view().into_faer();
let faer_y = y.view().into_faer();
faer_dot(faer_x, faer_y)
}
pub fn faer_squared_norm(x: MatRef<f64>) -> Mat<f64> {
let x_norm = Mat::from_fn(x.nrows(), 1, |i, _| {
let row = x.row(i);
row * row.transpose()
});
x_norm
}
pub fn faer_dot_and_norms(x: &Array2<f64>, y: &Array2<f64>) -> (Mat<f64>, Mat<f64>, Mat<f64>) {
let faer_x = x.view().into_faer();
let faer_y = y.view().into_faer();
let x_norm = faer_squared_norm(faer_x);
let y_norm = faer_squared_norm(faer_y);
let faer_dot = faer_dot(faer_x, faer_y);
(x_norm, y_norm, faer_dot)
}
pub fn cross_euclidean_distances(data: &Array2<f64>, reference: &Array2<f64>) -> Mat<f64> {
let (data_norm, reference_norm, faer_dot) = faer_dot_and_norms(data, reference);
let n = data.nrows();
let m = reference.nrows();
let faer_dist: Mat<f64> = Mat::from_fn(n, m, |i, j| {
data_norm.get(i, 0) + reference_norm.get(j, 0) - 2.0 * faer_dot.get(i, j)
});
faer_dist
}
pub fn cross_euclidean_distances_as_array(
data: &Array2<f64>,
reference: &Array2<f64>,
) -> Array2<f64> {
let faer_dist = cross_euclidean_distances(data, reference);
faer_dist.as_ref().into_ndarray().to_owned()
}
pub fn cross_p_distances(data: &Array2<f64>, reference: &Array2<f64>, p: f64) -> Array2<f64> {
let data_expanded = data.view().insert_axis(Axis(1)).to_owned(); let reference_expanded = reference.view().insert_axis(Axis(0)).to_owned();
let diff = data_expanded - reference_expanded;
let dists_p = diff.mapv(|x| x.abs().powf(p)).sum_axis(Axis(2));
dists_p
}
#[cfg(test)]
mod tests {
use super::*;
use faer_ext::IntoNdarray;
use ndarray::array;
#[test]
fn test_cross_euclidean_distances() {
let data = array![[0.0, 0.0], [1.0, 1.0]];
let reference = array![[0.0, 0.0], [2.0, 2.0]];
let expected = array![[0.0, 8.0], [2.0, 2.0]];
let result = cross_euclidean_distances(&data, &reference);
let result_ndarray = result.as_ref().into_ndarray();
assert_eq!(result_ndarray, expected);
}
#[test]
fn test_cross_p_distances() {
let data = array![[0.0, 0.0], [1.0, 1.0]];
let reference = array![[0.0, 0.0], [2.0, 2.0]];
let expected_p2 = array![[0.0, 8.0], [2.0, 2.0]];
let result_p2 = cross_p_distances(&data, &reference, 2.0);
assert_eq!(result_p2, expected_p2);
let expected_p1 = array![[0.0, 4.0], [2.0, 2.0]];
let result_p1 = cross_p_distances(&data, &reference, 1.0);
assert_eq!(result_p1, expected_p1);
}
}