use super::matrix::DistanceMatrix;
use super::sparse::SparseDistanceMatrix;
use crate::{Error, Result};
impl DistanceMatrix {
pub fn from_points(points: &[Vec<f64>]) -> Result<Self> {
let n = points.len();
validate_points(points)?;
let mut data = Vec::with_capacity(n.saturating_sub(1) * n / 2);
for i in 1..n {
for j in 0..i {
data.push(euclidean(&points[i], &points[j]));
}
}
Ok(Self {
n,
data,
square: false,
})
}
pub fn from_condensed(mut condensed: Vec<f64>) -> Result<Self> {
let m = condensed.len();
let n = ((1.0 + 8.0 * m as f64).sqrt() as usize).div_ceil(2);
if n * (n - 1) / 2 != m {
return Err(Error::InvalidInput(format!(
"condensed length {m} is not n(n-1)/2 for any n"
)));
}
for (i, d) in condensed.iter_mut().enumerate() {
if d.is_nan() {
return Err(Error::InvalidDistance(format!(
"NaN at condensed index {i}"
)));
}
if *d < 0.0 {
return Err(Error::InvalidDistance(format!(
"negative entry {d} at condensed index {i}"
)));
}
if *d == 0.0 {
*d = 0.0;
}
}
Ok(Self {
n,
data: condensed,
square: false,
})
}
}
impl DistanceMatrix {
pub(crate) fn to_sparse_at(&self, threshold: f64) -> Result<SparseDistanceMatrix> {
let n = self.n;
if n > u32::MAX as usize {
return Err(Error::InvalidInput(format!(
"sparse matrix holds at most {} points, got {n}",
u32::MAX
)));
}
let keep = |d: f64| d.is_finite() && d <= threshold;
let mut degree = vec![0usize; n];
for i in 1..n {
for (j, &d) in self.lower_row(i).iter().enumerate() {
if keep(d) {
degree[i] += 1;
degree[j] += 1;
}
}
}
let mut offsets = vec![0usize; n + 1];
let mut total = 0usize;
for (v, °) in degree.iter().enumerate() {
offsets[v] = total;
total += deg;
}
offsets[n] = total;
let mut indices = vec![0u32; total];
let mut values = vec![0.0f64; total];
let mut cursor = offsets[..n].to_vec();
let mut max_distance = 0.0f64;
for i in 1..n {
for (j, &d) in self.lower_row(i).iter().enumerate() {
if !keep(d) {
continue;
}
max_distance = max_distance.max(d);
indices[cursor[i]] = j as u32;
values[cursor[i]] = d;
cursor[i] += 1;
indices[cursor[j]] = i as u32;
values[cursor[j]] = d;
cursor[j] += 1;
}
}
Ok(SparseDistanceMatrix {
n,
offsets,
indices,
values,
max_distance,
})
}
}
pub(super) fn validate_points(points: &[Vec<f64>]) -> Result<usize> {
let dimensions = points.first().map_or(0, Vec::len);
if let Some(point) = points.iter().find(|point| point.len() != dimensions) {
return Err(Error::InvalidInput(format!(
"inconsistent point dimensions: {} vs {}",
dimensions,
point.len()
)));
}
if points
.iter()
.flatten()
.any(|coordinate| !coordinate.is_finite())
{
return Err(Error::InvalidInput("non-finite coordinate".into()));
}
Ok(dimensions)
}
pub(super) fn euclidean(a: &[f64], b: &[f64]) -> f64 {
let m = a
.iter()
.zip(b)
.map(|(x, y)| (x - y).abs())
.fold(0.0f64, f64::max);
if m == 0.0 {
return 0.0;
}
if m.is_infinite() {
return f64::INFINITY;
}
let s: f64 = a
.iter()
.zip(b)
.map(|(x, y)| {
let r = (x - y) / m;
r * r
})
.sum();
m * s.sqrt()
}