use crate::error::{SpatialError, SpatialResult};
use scirs2_core::ndarray::{Array2, ArrayView1};
use scirs2_core::numeric::Float;
use super::functions::Distance;
pub fn cdist_optimized<T, F>(x_a: &Array2<T>, xb: &Array2<T>, metric: F) -> SpatialResult<Array2<T>>
where
T: Float + std::fmt::Debug,
F: Fn(ArrayView1<T>, ArrayView1<T>) -> T,
{
let n_a = x_a.nrows();
let n_b = xb.nrows();
if x_a.ncols() != xb.ncols() {
return Err(SpatialError::DimensionError(format!(
"Dimension mismatch: x_a has {} columns, xb has {} columns",
x_a.ncols(),
xb.ncols()
)));
}
let mut result = Array2::zeros((n_a, n_b));
for i in 0..n_a {
let row_i = x_a.row(i);
for j in 0..n_b {
let row_j = xb.row(j);
result[(i, j)] = metric(row_i, row_j);
}
}
Ok(result)
}
#[allow(dead_code)]
pub fn is_valid_condensed_distance_matrix<T: Float>(distances: &[T]) -> bool {
let n = (1.0 + (1.0 + 8.0 * distances.len() as f64).sqrt()) / 2.0;
if n.fract() != 0.0 {
return false;
}
for &dist in distances {
if dist < T::zero() {
return false;
}
}
true
}
#[allow(dead_code)]
pub fn squareform<T: Float>(distances: &[T]) -> SpatialResult<Array2<T>> {
if !is_valid_condensed_distance_matrix(distances) {
return Err(SpatialError::ValueError(
"Invalid condensed distance matrix".to_string(),
));
}
let n = (1.0 + (1.0 + 8.0 * distances.len() as f64).sqrt()) / 2.0;
let n = n as usize;
let mut result = Array2::zeros((n, n));
let mut k = 0;
for i in 0..n - 1 {
for j in i + 1..n {
result[(i, j)] = distances[k];
result[(j, i)] = distances[k];
k += 1;
}
}
Ok(result)
}
#[allow(dead_code)]
pub fn squareform_to_condensed<T: Float>(distances: &Array2<T>) -> SpatialResult<Vec<T>> {
let n = distances.nrows();
if n != distances.ncols() {
return Err(SpatialError::ValueError(
"Distance matrix must be square".to_string(),
));
}
for i in 0..n {
for j in i + 1..n {
if (distances[(i, j)] - distances[(j, i)]).abs() > T::epsilon() {
return Err(SpatialError::ValueError(
"Distance matrix must be symmetric".to_string(),
));
}
}
}
let size = n * (n - 1) / 2;
let mut result = Vec::with_capacity(size);
for i in 0..n - 1 {
for j in i + 1..n {
result.push(distances[(i, j)]);
}
}
Ok(result)
}
#[allow(dead_code)]
pub fn dice<T: Float>(point1: &[bool], point2: &[bool]) -> T {
if point1.len() != point2.len() {
return T::nan();
}
let mut n_true_true = 0;
let mut n_true_false = 0;
let mut n_false_true = 0;
for i in 0..point1.len() {
if point1[i] && point2[i] {
n_true_true += 1;
} else if point1[i] && !point2[i] {
n_true_false += 1;
} else if !point1[i] && point2[i] {
n_false_true += 1;
}
}
let num = T::from(n_true_false + n_false_true).expect("Operation failed");
let denom = T::from(2 * n_true_true + n_true_false + n_false_true).expect("Operation failed");
if denom > T::zero() {
num / denom
} else {
T::zero()
}
}
#[allow(dead_code)]
pub fn kulsinski<T: Float>(point1: &[bool], point2: &[bool]) -> T {
if point1.len() != point2.len() {
return T::nan();
}
let mut n_true_true = 0;
let mut n_true_false = 0;
let mut n_false_true = 0;
let n = point1.len();
for i in 0..n {
if point1[i] && point2[i] {
n_true_true += 1;
} else if point1[i] && !point2[i] {
n_true_false += 1;
} else if !point1[i] && point2[i] {
n_false_true += 1;
}
}
let num = T::from(n_true_false + n_false_true - n_true_true + n).expect("Operation failed");
let denom = T::from(n_true_false + n_false_true + n).expect("Operation failed");
if denom > T::zero() {
num / denom
} else {
T::zero()
}
}
#[allow(dead_code)]
pub fn rogerstanimoto<T: Float>(point1: &[bool], point2: &[bool]) -> T {
if point1.len() != point2.len() {
return T::nan();
}
let mut n_true_true = 0;
let mut n_true_false = 0;
let mut n_false_true = 0;
let mut n_false_false = 0;
for i in 0..point1.len() {
if point1[i] && point2[i] {
n_true_true += 1;
} else if point1[i] && !point2[i] {
n_true_false += 1;
} else if !point1[i] && point2[i] {
n_false_true += 1;
} else {
n_false_false += 1;
}
}
let r = n_true_false + n_false_true;
let num = T::from(2 * r).expect("Operation failed");
let denom = T::from(n_true_true + n_false_false + 2 * r).expect("Operation failed");
if denom > T::zero() {
num / denom
} else {
T::zero()
}
}
#[allow(dead_code)]
pub fn russellrao<T: Float>(point1: &[bool], point2: &[bool]) -> T {
if point1.len() != point2.len() {
return T::nan();
}
let mut n_true_true = 0;
let n = point1.len();
for i in 0..n {
if point1[i] && point2[i] {
n_true_true += 1;
}
}
let num = T::from(n - n_true_true).expect("Operation failed");
let denom = T::from(n).expect("Operation failed");
if denom > T::zero() {
num / denom
} else {
T::zero()
}
}
#[allow(dead_code)]
pub fn sokalmichener<T: Float>(point1: &[bool], point2: &[bool]) -> T {
rogerstanimoto(point1, point2)
}
#[allow(dead_code)]
pub fn sokalsneath<T: Float>(point1: &[bool], point2: &[bool]) -> T {
if point1.len() != point2.len() {
return T::nan();
}
let mut n_true_true = 0;
let mut n_true_false = 0;
let mut n_false_true = 0;
for i in 0..point1.len() {
if point1[i] && point2[i] {
n_true_true += 1;
} else if point1[i] && !point2[i] {
n_true_false += 1;
} else if !point1[i] && point2[i] {
n_false_true += 1;
}
}
let r = n_true_false + n_false_true;
let num = T::from(2 * r).expect("Operation failed");
let denom = T::from(n_true_true + 2 * r).expect("Operation failed");
if denom > T::zero() {
num / denom
} else {
T::zero()
}
}
#[allow(dead_code)]
pub fn yule<T: Float>(point1: &[bool], point2: &[bool]) -> T {
if point1.len() != point2.len() {
return T::nan();
}
let mut n_true_true = 0;
let mut n_true_false = 0;
let mut n_false_true = 0;
let mut n_false_false = 0;
for i in 0..point1.len() {
if point1[i] && point2[i] {
n_true_true += 1;
} else if point1[i] && !point2[i] {
n_true_false += 1;
} else if !point1[i] && point2[i] {
n_false_true += 1;
} else {
n_false_false += 1;
}
}
let num = T::from(2 * n_true_false * n_false_true).expect("Operation failed");
let denom = T::from(n_true_true * n_false_false + n_true_false * n_false_true)
.expect("Operation failed");
if denom > T::zero() {
num / denom
} else {
T::zero()
}
}