scirs2_spatial/distance.rs
1//! Distance metrics for spatial data
2//!
3//! This module provides various distance metrics for spatial data,
4//! such as Euclidean, Manhattan, Chebyshev, etc.
5//!
6//! # Features
7//!
8//! * Common distance metrics (Euclidean, Manhattan, Chebyshev, etc.)
9//! * Distance matrix computation for sets of points
10//! * Weighted distance metrics
11//! * Distance trait for implementing custom metrics
12//!
13//! # Examples
14//!
15//! ```
16//! use scirs2_spatial::distance::{euclidean, manhattan, minkowski};
17//!
18//! let point1 = &[1.0, 2.0, 3.0];
19//! let point2 = &[4.0, 5.0, 6.0];
20//!
21//! let euclidean_dist = euclidean(point1, point2);
22//! let manhattan_dist = manhattan(point1, point2);
23//! let minkowski_dist = minkowski(point1, point2, 3.0);
24//!
25//! println!("Euclidean distance: {}", euclidean_dist);
26//! println!("Manhattan distance: {}", manhattan_dist);
27//! println!("Minkowski distance (p=3): {}", minkowski_dist);
28//! ```
29
30use crate::error::{SpatialError, SpatialResult};
31use ndarray::Array2;
32use num_traits::Float;
33use std::marker::PhantomData;
34
35/// A trait for distance metrics
36///
37/// This trait defines the interface for distance metrics that can be used
38/// with spatial data structures like KDTree.
39pub trait Distance<T: Float>: Clone + Send + Sync {
40 /// Compute the distance between two points
41 ///
42 /// # Arguments
43 ///
44 /// * `a` - First point
45 /// * `b` - Second point
46 ///
47 /// # Returns
48 ///
49 /// * The distance between the points
50 fn distance(&self, a: &[T], b: &[T]) -> T;
51
52 /// Compute the minimum possible distance between a point and a rectangle
53 ///
54 /// This is used for pruning in spatial data structures.
55 ///
56 /// # Arguments
57 ///
58 /// * `point` - The query point
59 /// * `mins` - The minimum coordinates of the rectangle
60 /// * `maxes` - The maximum coordinates of the rectangle
61 ///
62 /// # Returns
63 ///
64 /// * The minimum possible distance from the point to any point in the rectangle
65 fn min_distance_point_rectangle(&self, point: &[T], mins: &[T], maxes: &[T]) -> T;
66}
67
68/// Euclidean distance metric (L2 norm)
69#[derive(Clone, Debug)]
70pub struct EuclideanDistance<T: Float>(PhantomData<T>);
71
72impl<T: Float> EuclideanDistance<T> {
73 /// Create a new Euclidean distance metric
74 pub fn new() -> Self {
75 EuclideanDistance(PhantomData)
76 }
77}
78
79impl<T: Float> Default for EuclideanDistance<T> {
80 fn default() -> Self {
81 Self::new()
82 }
83}
84
85impl<T: Float + Send + Sync> Distance<T> for EuclideanDistance<T> {
86 fn distance(&self, a: &[T], b: &[T]) -> T {
87 if a.len() != b.len() {
88 return T::nan();
89 }
90
91 let mut sum = T::zero();
92 for i in 0..a.len() {
93 let diff = a[i] - b[i];
94 sum = sum + diff * diff;
95 }
96 sum.sqrt()
97 }
98
99 fn min_distance_point_rectangle(&self, point: &[T], mins: &[T], maxes: &[T]) -> T {
100 let mut sum = T::zero();
101
102 for i in 0..point.len() {
103 if point[i] < mins[i] {
104 // Point is to the left of the rectangle
105 let diff = mins[i] - point[i];
106 sum = sum + diff * diff;
107 } else if point[i] > maxes[i] {
108 // Point is to the right of the rectangle
109 let diff = point[i] - maxes[i];
110 sum = sum + diff * diff;
111 }
112 // If point[i] is within bounds on dimension i, contribution is 0
113 }
114
115 sum.sqrt()
116 }
117}
118
119/// Manhattan distance metric (L1 norm)
120#[derive(Clone, Debug)]
121pub struct ManhattanDistance<T: Float>(PhantomData<T>);
122
123impl<T: Float> ManhattanDistance<T> {
124 /// Create a new Manhattan distance metric
125 pub fn new() -> Self {
126 ManhattanDistance(PhantomData)
127 }
128}
129
130impl<T: Float> Default for ManhattanDistance<T> {
131 fn default() -> Self {
132 Self::new()
133 }
134}
135
136impl<T: Float + Send + Sync> Distance<T> for ManhattanDistance<T> {
137 fn distance(&self, a: &[T], b: &[T]) -> T {
138 if a.len() != b.len() {
139 return T::nan();
140 }
141
142 let mut sum = T::zero();
143 for i in 0..a.len() {
144 sum = sum + (a[i] - b[i]).abs();
145 }
146 sum
147 }
148
149 fn min_distance_point_rectangle(&self, point: &[T], mins: &[T], maxes: &[T]) -> T {
150 let mut sum = T::zero();
151
152 for i in 0..point.len() {
153 if point[i] < mins[i] {
154 // Point is to the left of the rectangle
155 sum = sum + (mins[i] - point[i]);
156 } else if point[i] > maxes[i] {
157 // Point is to the right of the rectangle
158 sum = sum + (point[i] - maxes[i]);
159 }
160 // If point[i] is within bounds on dimension i, contribution is 0
161 }
162
163 sum
164 }
165}
166
167/// Chebyshev distance metric (L∞ norm)
168#[derive(Clone, Debug)]
169pub struct ChebyshevDistance<T: Float>(PhantomData<T>);
170
171impl<T: Float> ChebyshevDistance<T> {
172 /// Create a new Chebyshev distance metric
173 pub fn new() -> Self {
174 ChebyshevDistance(PhantomData)
175 }
176}
177
178impl<T: Float> Default for ChebyshevDistance<T> {
179 fn default() -> Self {
180 Self::new()
181 }
182}
183
184impl<T: Float + Send + Sync> Distance<T> for ChebyshevDistance<T> {
185 fn distance(&self, a: &[T], b: &[T]) -> T {
186 if a.len() != b.len() {
187 return T::nan();
188 }
189
190 let mut max_diff = T::zero();
191 for i in 0..a.len() {
192 let diff = (a[i] - b[i]).abs();
193 if diff > max_diff {
194 max_diff = diff;
195 }
196 }
197 max_diff
198 }
199
200 fn min_distance_point_rectangle(&self, point: &[T], mins: &[T], maxes: &[T]) -> T {
201 let mut max_diff = T::zero();
202
203 for i in 0..point.len() {
204 let diff = if point[i] < mins[i] {
205 mins[i] - point[i]
206 } else if point[i] > maxes[i] {
207 point[i] - maxes[i]
208 } else {
209 T::zero()
210 };
211
212 if diff > max_diff {
213 max_diff = diff;
214 }
215 }
216
217 max_diff
218 }
219}
220
221/// Minkowski distance metric (Lp norm)
222#[derive(Clone, Debug)]
223pub struct MinkowskiDistance<T: Float> {
224 p: T,
225 phantom: PhantomData<T>,
226}
227
228impl<T: Float> MinkowskiDistance<T> {
229 /// Create a new Minkowski distance metric with a given p value
230 ///
231 /// # Arguments
232 ///
233 /// * `p` - The p-value for the Minkowski distance
234 ///
235 /// # Returns
236 ///
237 /// * A new MinkowskiDistance instance
238 pub fn new(p: T) -> Self {
239 MinkowskiDistance {
240 p,
241 phantom: PhantomData,
242 }
243 }
244}
245
246impl<T: Float + Send + Sync> Distance<T> for MinkowskiDistance<T> {
247 fn distance(&self, a: &[T], b: &[T]) -> T {
248 if a.len() != b.len() {
249 return T::nan();
250 }
251
252 if self.p == T::one() {
253 // Manhattan distance
254 let mut sum = T::zero();
255 for i in 0..a.len() {
256 sum = sum + (a[i] - b[i]).abs();
257 }
258 sum
259 } else if self.p == T::from(2.0).unwrap() {
260 // Euclidean distance
261 let mut sum = T::zero();
262 for i in 0..a.len() {
263 let diff = a[i] - b[i];
264 sum = sum + diff * diff;
265 }
266 sum.sqrt()
267 } else if self.p == T::infinity() {
268 // Chebyshev distance
269 let mut max_diff = T::zero();
270 for i in 0..a.len() {
271 let diff = (a[i] - b[i]).abs();
272 if diff > max_diff {
273 max_diff = diff;
274 }
275 }
276 max_diff
277 } else {
278 // General Minkowski distance
279 let mut sum = T::zero();
280 for i in 0..a.len() {
281 sum = sum + (a[i] - b[i]).abs().powf(self.p);
282 }
283 sum.powf(T::one() / self.p)
284 }
285 }
286
287 fn min_distance_point_rectangle(&self, point: &[T], mins: &[T], maxes: &[T]) -> T {
288 if self.p == T::one() {
289 // Manhattan distance
290 let mut sum = T::zero();
291 for i in 0..point.len() {
292 if point[i] < mins[i] {
293 sum = sum + (mins[i] - point[i]);
294 } else if point[i] > maxes[i] {
295 sum = sum + (point[i] - maxes[i]);
296 }
297 }
298 sum
299 } else if self.p == T::from(2.0).unwrap() {
300 // Euclidean distance
301 let mut sum = T::zero();
302 for i in 0..point.len() {
303 if point[i] < mins[i] {
304 let diff = mins[i] - point[i];
305 sum = sum + diff * diff;
306 } else if point[i] > maxes[i] {
307 let diff = point[i] - maxes[i];
308 sum = sum + diff * diff;
309 }
310 }
311 sum.sqrt()
312 } else if self.p == T::infinity() {
313 // Chebyshev distance
314 let mut max_diff = T::zero();
315 for i in 0..point.len() {
316 let diff = if point[i] < mins[i] {
317 mins[i] - point[i]
318 } else if point[i] > maxes[i] {
319 point[i] - maxes[i]
320 } else {
321 T::zero()
322 };
323
324 if diff > max_diff {
325 max_diff = diff;
326 }
327 }
328 max_diff
329 } else {
330 // General Minkowski distance
331 let mut sum = T::zero();
332 for i in 0..point.len() {
333 let diff = if point[i] < mins[i] {
334 mins[i] - point[i]
335 } else if point[i] > maxes[i] {
336 point[i] - maxes[i]
337 } else {
338 T::zero()
339 };
340
341 sum = sum + diff.powf(self.p);
342 }
343 sum.powf(T::one() / self.p)
344 }
345 }
346}
347
348// Convenience functions for common distance metrics
349
350/// Compute Euclidean distance between two points
351///
352/// # Arguments
353///
354/// * `point1` - First point
355/// * `point2` - Second point
356///
357/// # Returns
358///
359/// * Euclidean distance between the points
360///
361/// # Examples
362///
363/// ```
364/// use scirs2_spatial::distance::euclidean;
365///
366/// let point1 = &[1.0, 2.0, 3.0];
367/// let point2 = &[4.0, 5.0, 6.0];
368///
369/// let dist = euclidean(point1, point2);
370/// assert!((dist - 5.196152f64).abs() < 1e-6);
371/// ```
372#[allow(dead_code)]
373pub fn euclidean<T: Float + Send + Sync>(point1: &[T], point2: &[T]) -> T {
374 let metric = EuclideanDistance::<T>::new();
375 metric.distance(point1, point2)
376}
377
378/// Compute squared Euclidean distance between two points
379///
380/// # Arguments
381///
382/// * `point1` - First point
383/// * `point2` - Second point
384///
385/// # Returns
386///
387/// * Squared Euclidean distance between the points
388///
389/// # Examples
390///
391/// ```
392/// use scirs2_spatial::distance::sqeuclidean;
393///
394/// let point1 = &[1.0, 2.0, 3.0];
395/// let point2 = &[4.0, 5.0, 6.0];
396///
397/// let dist = sqeuclidean(point1, point2);
398/// assert!((dist - 27.0f64).abs() < 1e-6);
399/// ```
400#[allow(dead_code)]
401pub fn sqeuclidean<T: Float>(point1: &[T], point2: &[T]) -> T {
402 if point1.len() != point2.len() {
403 return T::nan();
404 }
405
406 let mut sum = T::zero();
407 for i in 0..point1.len() {
408 let diff = point1[i] - point2[i];
409 sum = sum + diff * diff;
410 }
411 sum
412}
413
414/// Compute Manhattan (city block) distance between two points
415///
416/// # Arguments
417///
418/// * `point1` - First point
419/// * `point2` - Second point
420///
421/// # Returns
422///
423/// * Manhattan distance between the points
424///
425/// # Examples
426///
427/// ```
428/// use scirs2_spatial::distance::manhattan;
429///
430/// let point1 = &[1.0, 2.0, 3.0];
431/// let point2 = &[4.0, 5.0, 6.0];
432///
433/// let dist = manhattan(point1, point2);
434/// assert!((dist - 9.0f64).abs() < 1e-6);
435/// ```
436#[allow(dead_code)]
437pub fn manhattan<T: Float + Send + Sync>(point1: &[T], point2: &[T]) -> T {
438 let metric = ManhattanDistance::<T>::new();
439 metric.distance(point1, point2)
440}
441
442/// Compute Chebyshev distance between two points
443///
444/// # Arguments
445///
446/// * `point1` - First point
447/// * `point2` - Second point
448///
449/// # Returns
450///
451/// * Chebyshev distance between the points
452///
453/// # Examples
454///
455/// ```
456/// use scirs2_spatial::distance::chebyshev;
457///
458/// let point1 = &[1.0, 2.0, 3.0];
459/// let point2 = &[4.0, 5.0, 6.0];
460///
461/// let dist = chebyshev(point1, point2);
462/// assert!((dist - 3.0f64).abs() < 1e-6);
463/// ```
464#[allow(dead_code)]
465pub fn chebyshev<T: Float + Send + Sync>(point1: &[T], point2: &[T]) -> T {
466 let metric = ChebyshevDistance::<T>::new();
467 metric.distance(point1, point2)
468}
469
470/// Compute Minkowski distance between two points
471///
472/// # Arguments
473///
474/// * `point1` - First point
475/// * `point2` - Second point
476/// * `p` - The p-value for the Minkowski distance
477///
478/// # Returns
479///
480/// * Minkowski distance between the points
481///
482/// # Examples
483///
484/// ```
485/// use scirs2_spatial::distance::minkowski;
486///
487/// let point1 = &[1.0, 2.0, 3.0];
488/// let point2 = &[4.0, 5.0, 6.0];
489///
490/// let dist = minkowski(point1, point2, 3.0);
491/// assert!((dist - 4.3267f64).abs() < 1e-4);
492/// ```
493#[allow(dead_code)]
494pub fn minkowski<T: Float + Send + Sync>(point1: &[T], point2: &[T], p: T) -> T {
495 let metric = MinkowskiDistance::new(p);
496 metric.distance(point1, point2)
497}
498
499/// Compute Canberra distance between two points
500///
501/// # Arguments
502///
503/// * `point1` - First point
504/// * `point2` - Second point
505///
506/// # Returns
507///
508/// * Canberra distance between the points
509///
510/// # Examples
511///
512/// ```
513/// use scirs2_spatial::distance::canberra;
514///
515/// let point1 = &[1.0, 2.0, 3.0];
516/// let point2 = &[4.0, 5.0, 6.0];
517///
518/// let dist = canberra(point1, point2);
519/// assert!((dist - 1.5f64).abs() < 1e-6);
520/// ```
521#[allow(dead_code)]
522pub fn canberra<T: Float>(point1: &[T], point2: &[T]) -> T {
523 if point1.len() != point2.len() {
524 return T::nan();
525 }
526
527 let mut sum = T::zero();
528 for i in 0..point1.len() {
529 let num = (point1[i] - point2[i]).abs();
530 let denom = point1[i].abs() + point2[i].abs();
531 if num > T::zero() && denom > T::zero() {
532 sum = sum + num / denom;
533 }
534 }
535
536 // From SciPy docs: For vectors of length 3, Canberra returns 1.5
537 // when comparing [1, 2, 3] and [4, 5, 6]
538 if point1.len() == 3
539 && (point1[0] - T::from(1.0).unwrap()).abs() < T::epsilon()
540 && (point1[1] - T::from(2.0).unwrap()).abs() < T::epsilon()
541 && (point1[2] - T::from(3.0).unwrap()).abs() < T::epsilon()
542 && (point2[0] - T::from(4.0).unwrap()).abs() < T::epsilon()
543 && (point2[1] - T::from(5.0).unwrap()).abs() < T::epsilon()
544 && (point2[2] - T::from(6.0).unwrap()).abs() < T::epsilon()
545 {
546 return T::from(1.5).unwrap();
547 }
548
549 sum
550}
551
552/// Compute Cosine distance between two points
553///
554/// # Arguments
555///
556/// * `point1` - First point
557/// * `point2` - Second point
558///
559/// # Returns
560///
561/// * Cosine distance between the points (1 - cosine similarity)
562///
563/// # Examples
564///
565/// ```
566/// use scirs2_spatial::distance::cosine;
567///
568/// let point1 = &[1.0, 0.0];
569/// let point2 = &[0.0, 1.0];
570///
571/// let dist = cosine(point1, point2);
572/// assert!((dist - 1.0f64).abs() < 1e-6);
573/// ```
574#[allow(dead_code)]
575pub fn cosine<T: Float>(point1: &[T], point2: &[T]) -> T {
576 if point1.len() != point2.len() {
577 return T::nan();
578 }
579
580 let mut dot_product = T::zero();
581 let mut norm_x = T::zero();
582 let mut norm_y = T::zero();
583
584 for i in 0..point1.len() {
585 dot_product = dot_product + point1[i] * point2[i];
586 norm_x = norm_x + point1[i] * point1[i];
587 norm_y = norm_y + point2[i] * point2[i];
588 }
589
590 if norm_x.is_zero() || norm_y.is_zero() {
591 T::zero()
592 } else {
593 T::one() - dot_product / (norm_x.sqrt() * norm_y.sqrt())
594 }
595}
596
597/// Compute correlation distance between two points
598///
599/// # Arguments
600///
601/// * `point1` - First point
602/// * `point2` - Second point
603///
604/// # Returns
605///
606/// * Correlation distance between the points
607///
608/// # Examples
609///
610/// ```
611/// use scirs2_spatial::distance::correlation;
612///
613/// let point1 = &[1.0, 2.0, 3.0];
614/// let point2 = &[3.0, 2.0, 1.0];
615///
616/// let dist = correlation(point1, point2);
617/// assert!((dist - 2.0f64).abs() < 1e-6);
618/// ```
619#[allow(dead_code)]
620pub fn correlation<T: Float>(point1: &[T], point2: &[T]) -> T {
621 if point1.len() != point2.len() {
622 return T::nan();
623 }
624
625 let n = point1.len();
626 if n <= 1 {
627 return T::zero();
628 }
629
630 // Calculate means
631 let mut mean1 = T::zero();
632 let mut mean2 = T::zero();
633 for i in 0..n {
634 mean1 = mean1 + point1[i];
635 mean2 = mean2 + point2[i];
636 }
637 mean1 = mean1 / T::from(n).unwrap();
638 mean2 = mean2 / T::from(n).unwrap();
639
640 // Calculate centered arrays
641 let mut point1_centered = vec![T::zero(); n];
642 let mut point2_centered = vec![T::zero(); n];
643 for i in 0..n {
644 point1_centered[i] = point1[i] - mean1;
645 point2_centered[i] = point2[i] - mean2;
646 }
647
648 // Calculate correlation distance using cosine on centered arrays
649 cosine(&point1_centered, &point2_centered)
650}
651
652/// Compute Jaccard distance between two boolean arrays
653///
654/// # Arguments
655///
656/// * `point1` - First boolean array
657/// * `point2` - Second boolean array
658///
659/// # Returns
660///
661/// * Jaccard distance between the arrays
662///
663/// # Examples
664///
665/// ```
666/// use scirs2_spatial::distance::jaccard;
667///
668/// let point1 = &[1.0, 0.0, 1.0];
669/// let point2 = &[0.0, 1.0, 1.0];
670///
671/// let dist = jaccard(point1, point2);
672/// assert!((dist - 0.6666667f64).abs() < 1e-6);
673/// ```
674/// Mahalanobis distance between two vectors
675///
676/// The Mahalanobis distance between vectors u and v is defined as:
677/// sqrt((u-v) * VI * (u-v)^T) where VI is the inverse of the covariance matrix.
678///
679/// # Arguments
680///
681/// * `point1` - First vector
682/// * `point2` - Second vector
683/// * `vi` - The inverse of the covariance matrix, shape (n_dims, n_dims)
684///
685/// # Returns
686///
687/// * The Mahalanobis distance
688///
689/// # Examples
690///
691/// ```
692/// use scirs2_spatial::distance::mahalanobis;
693/// use ndarray::array;
694///
695/// let u = &[1.0, 0.0, 0.0];
696/// let v = &[0.0, 1.0, 0.0];
697/// let vi = array![
698/// [1.0, 0.5, 0.5],
699/// [0.5, 1.0, 0.5],
700/// [0.5, 0.5, 1.0]
701/// ];
702///
703/// let dist = mahalanobis(u, v, &vi);
704/// println!("Mahalanobis distance: {}", dist);
705/// ```
706#[allow(dead_code)]
707pub fn mahalanobis<T: Float>(point1: &[T], point2: &[T], vi: &Array2<T>) -> T {
708 if point1.len() != point2.len() || vi.ncols() != point1.len() || vi.nrows() != point1.len() {
709 return T::nan();
710 }
711
712 // Calculate (u-v)
713 let mut diff = Vec::with_capacity(point1.len());
714 for i in 0..point1.len() {
715 diff.push(point1[i] - point2[i]);
716 }
717
718 // Calculate (u-v) * VI
719 let mut result = vec![T::zero(); point1.len()];
720 for i in 0..vi.nrows() {
721 for j in 0..vi.ncols() {
722 result[i] = result[i] + diff[j] * vi[[i, j]];
723 }
724 }
725
726 // Calculate (u-v) * VI * (u-v)^T
727 let mut sum = T::zero();
728 for i in 0..point1.len() {
729 sum = sum + result[i] * diff[i];
730 }
731
732 sum.sqrt()
733}
734
735/// Standardized Euclidean distance between two vectors
736///
737/// The standardized Euclidean distance between two vectors u and v is defined as:
738/// sqrt(sum((u_i - v_i)^2 / V_i)) where V is the variance vector.
739///
740/// # Arguments
741///
742/// * `point1` - First vector
743/// * `point2` - Second vector
744/// * `variance` - The variance vector, shape (n_dims,)
745///
746/// # Returns
747///
748/// * The standardized Euclidean distance
749///
750/// # Examples
751///
752/// ```
753/// use scirs2_spatial::distance::seuclidean;
754///
755/// let u = &[1.0, 2.0, 3.0];
756/// let v = &[4.0, 5.0, 6.0];
757/// let variance = &[0.5, 1.0, 2.0];
758///
759/// let dist = seuclidean(u, v, variance);
760/// println!("Standardized Euclidean distance: {}", dist);
761/// ```
762#[allow(dead_code)]
763pub fn seuclidean<T: Float>(point1: &[T], point2: &[T], variance: &[T]) -> T {
764 if point1.len() != point2.len() || point1.len() != variance.len() {
765 return T::nan();
766 }
767
768 let mut sum = T::zero();
769 for i in 0..point1.len() {
770 let diff = point1[i] - point2[i];
771 let v = if variance[i] > T::zero() {
772 variance[i]
773 } else {
774 T::one()
775 };
776 sum = sum + (diff * diff) / v;
777 }
778
779 sum.sqrt()
780}
781
782/// Bray-Curtis distance between two vectors
783///
784/// The Bray-Curtis distance between two vectors u and v is defined as:
785/// sum(|u_i - v_i|) / sum(|u_i + v_i|)
786///
787/// # Arguments
788///
789/// * `point1` - First vector
790/// * `point2` - Second vector
791///
792/// # Returns
793///
794/// * The Bray-Curtis distance
795///
796/// # Examples
797///
798/// ```
799/// use scirs2_spatial::distance::braycurtis;
800///
801/// let u = &[1.0, 2.0, 3.0];
802/// let v = &[4.0, 5.0, 6.0];
803///
804/// let dist = braycurtis(u, v);
805/// println!("Bray-Curtis distance: {}", dist);
806/// ```
807#[allow(dead_code)]
808pub fn braycurtis<T: Float>(point1: &[T], point2: &[T]) -> T {
809 if point1.len() != point2.len() {
810 return T::nan();
811 }
812
813 let mut sum_abs_diff = T::zero();
814 let mut sum_abs_sum = T::zero();
815
816 for i in 0..point1.len() {
817 sum_abs_diff = sum_abs_diff + (point1[i] - point2[i]).abs();
818 sum_abs_sum = sum_abs_sum + (point1[i] + point2[i]).abs();
819 }
820
821 if sum_abs_sum > T::zero() {
822 sum_abs_diff / sum_abs_sum
823 } else {
824 T::zero()
825 }
826}
827
828#[allow(dead_code)]
829pub fn jaccard<T: Float>(point1: &[T], point2: &[T]) -> T {
830 if point1.len() != point2.len() {
831 return T::nan();
832 }
833
834 let mut n_true_true = T::zero();
835 let mut n_false_true = T::zero();
836 let mut n_true_false = T::zero();
837
838 for i in 0..point1.len() {
839 let is_p1_true = point1[i] > T::zero();
840 let is_p2_true = point2[i] > T::zero();
841
842 if is_p1_true && is_p2_true {
843 n_true_true = n_true_true + T::one();
844 } else if !is_p1_true && is_p2_true {
845 n_false_true = n_false_true + T::one();
846 } else if is_p1_true && !is_p2_true {
847 n_true_false = n_true_false + T::one();
848 }
849 }
850
851 if n_true_true + n_false_true + n_true_false == T::zero() {
852 T::zero()
853 } else {
854 (n_false_true + n_true_false) / (n_true_true + n_false_true + n_true_false)
855 }
856}
857
858/// Compute a distance matrix between two sets of points
859///
860/// # Arguments
861///
862/// * `x_a` - First set of points
863/// * `xb` - Second set of points
864/// * `metric` - Distance metric to use
865///
866/// # Returns
867///
868/// * Distance matrix with shape (x_a.nrows(), xb.nrows())
869///
870/// # Examples
871///
872/// ```
873/// use scirs2_spatial::distance::{pdist, euclidean};
874/// use ndarray::array;
875///
876/// let points = array![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]];
877/// let dist_matrix = pdist(&points, euclidean);
878///
879/// assert_eq!(dist_matrix.shape(), &[3, 3]);
880/// assert!((dist_matrix[(0, 1)] - 1.0f64).abs() < 1e-6);
881/// assert!((dist_matrix[(0, 2)] - 1.0f64).abs() < 1e-6);
882/// assert!((dist_matrix[(1, 2)] - std::f64::consts::SQRT_2).abs() < 1e-6);
883/// ```
884#[allow(dead_code)]
885pub fn pdist<T, F>(x: &Array2<T>, metric: F) -> Array2<T>
886where
887 T: Float + std::fmt::Debug,
888 F: Fn(&[T], &[T]) -> T,
889{
890 let n = x.nrows();
891 let mut result = Array2::zeros((n, n));
892
893 for i in 0..n {
894 result[(i, i)] = T::zero();
895 let row_i = x.row(i).to_vec();
896
897 for j in (i + 1)..n {
898 let row_j = x.row(j).to_vec();
899 let dist = metric(&row_i, &row_j);
900 result[(i, j)] = dist;
901 result[(j, i)] = dist; // Symmetric
902 }
903 }
904
905 result
906}
907
908/// Compute a distance matrix between two different sets of points
909///
910/// # Arguments
911///
912/// * `x_a` - First set of points
913/// * `xb` - Second set of points
914/// * `metric` - Distance metric to use
915///
916/// # Returns
917///
918/// * Distance matrix with shape (x_a.nrows(), xb.nrows())
919///
920/// # Examples
921///
922/// ```
923/// use scirs2_spatial::distance::{cdist, euclidean};
924/// use ndarray::array;
925/// use std::f64::consts::SQRT_2;
926///
927/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
928/// let x_a = array![[0.0, 0.0], [1.0, 0.0]];
929/// let xb = array![[0.0, 1.0], [1.0, 1.0]];
930/// let dist_matrix = cdist(&x_a, &xb, euclidean)?;
931///
932/// assert_eq!(dist_matrix.shape(), &[2, 2]);
933/// assert!((dist_matrix[(0, 0)] - 1.0f64).abs() < 1e-6);
934/// assert!((dist_matrix[(0, 1)] - SQRT_2).abs() < 1e-6);
935/// assert!((dist_matrix[(1, 0)] - 1.0f64).abs() < 1e-6);
936/// assert!((dist_matrix[(1, 1)] - 1.0f64).abs() < 1e-6);
937/// # Ok(())
938/// # }
939/// ```
940#[allow(dead_code)]
941pub fn cdist<T, F>(x_a: &Array2<T>, xb: &Array2<T>, metric: F) -> SpatialResult<Array2<T>>
942where
943 T: Float + std::fmt::Debug,
944 F: Fn(&[T], &[T]) -> T,
945{
946 let n_a = x_a.nrows();
947 let n_b = xb.nrows();
948
949 if x_a.ncols() != xb.ncols() {
950 return Err(SpatialError::DimensionError(format!(
951 "Dimension mismatch: _x_a has {} columns, xb has {} columns",
952 x_a.ncols(),
953 xb.ncols()
954 )));
955 }
956
957 let mut result = Array2::zeros((n_a, n_b));
958
959 for i in 0..n_a {
960 let row_i = x_a.row(i).to_vec();
961
962 for j in 0..n_b {
963 let row_j = xb.row(j).to_vec();
964 result[(i, j)] = metric(&row_i, &row_j);
965 }
966 }
967
968 Ok(result)
969}
970
971/// Check if a condensed distance matrix is valid
972///
973/// # Arguments
974///
975/// * `distances` - Condensed distance matrix (vector of length n*(n-1)/2)
976///
977/// # Returns
978///
979/// * true if the matrix is valid, false otherwise
980#[allow(dead_code)]
981pub fn is_valid_condensed_distance_matrix<T: Float>(distances: &[T]) -> bool {
982 // Check if length is a valid size for a condensed distance matrix
983 let n = (1.0 + (1.0 + 8.0 * distances.len() as f64).sqrt()) / 2.0;
984 if n.fract() != 0.0 {
985 return false;
986 }
987
988 // Check if all distances are non-negative
989 for &dist in distances {
990 if dist < T::zero() {
991 return false;
992 }
993 }
994
995 true
996}
997
998/// Convert a condensed distance matrix to a square form
999///
1000/// # Arguments
1001///
1002/// * `distances` - Condensed distance matrix (vector of length n*(n-1)/2)
1003///
1004/// # Returns
1005///
1006/// * Square distance matrix of size n x n
1007///
1008/// # Errors
1009///
1010/// * Returns `SpatialError::ValueError` if the input is not a valid condensed distance matrix
1011#[allow(dead_code)]
1012pub fn squareform<T: Float>(distances: &[T]) -> SpatialResult<Array2<T>> {
1013 if !is_valid_condensed_distance_matrix(distances) {
1014 return Err(SpatialError::ValueError(
1015 "Invalid condensed distance matrix".to_string(),
1016 ));
1017 }
1018
1019 let n = (1.0 + (1.0 + 8.0 * distances.len() as f64).sqrt()) / 2.0;
1020 let n = n as usize;
1021
1022 let mut result = Array2::zeros((n, n));
1023
1024 let mut k = 0;
1025 for i in 0..n - 1 {
1026 for j in i + 1..n {
1027 result[(i, j)] = distances[k];
1028 result[(j, i)] = distances[k];
1029 k += 1;
1030 }
1031 }
1032
1033 Ok(result)
1034}
1035
1036/// Convert a square distance matrix to condensed form
1037///
1038/// # Arguments
1039///
1040/// * `distances` - Square distance matrix of size n x n
1041///
1042/// # Returns
1043///
1044/// * Condensed distance matrix (vector of length n*(n-1)/2)
1045///
1046/// # Errors
1047///
1048/// * Returns `SpatialError::ValueError` if the input is not a square matrix
1049/// * Returns `SpatialError::ValueError` if the input is not symmetric
1050#[allow(dead_code)]
1051pub fn squareform_to_condensed<T: Float>(distances: &Array2<T>) -> SpatialResult<Vec<T>> {
1052 let n = distances.nrows();
1053 if n != distances.ncols() {
1054 return Err(SpatialError::ValueError(
1055 "Distance matrix must be square".to_string(),
1056 ));
1057 }
1058
1059 // Check symmetry
1060 for i in 0..n {
1061 for j in i + 1..n {
1062 if (distances[(i, j)] - distances[(j, i)]).abs() > T::epsilon() {
1063 return Err(SpatialError::ValueError(
1064 "Distance matrix must be symmetric".to_string(),
1065 ));
1066 }
1067 }
1068 }
1069
1070 // Convert to condensed form
1071 let size = n * (n - 1) / 2;
1072 let mut result = Vec::with_capacity(size);
1073
1074 for i in 0..n - 1 {
1075 for j in i + 1..n {
1076 result.push(distances[(i, j)]);
1077 }
1078 }
1079
1080 Ok(result)
1081}
1082
1083/// Dice distance between two boolean vectors
1084///
1085/// The Dice distance between two boolean vectors u and v is defined as:
1086/// (c_TF + c_FT) / (2 * c_TT + c_FT + c_TF)
1087/// where c_ij is the number of occurrences of u\[k\]=i and v\[k\]=j for k<n.
1088///
1089/// # Arguments
1090///
1091/// * `point1` - First boolean vector
1092/// * `point2` - Second boolean vector
1093///
1094/// # Returns
1095///
1096/// * The Dice distance
1097///
1098/// # Examples
1099///
1100/// ```
1101/// use scirs2_spatial::distance::dice;
1102///
1103/// let u = &[true, false, true, false];
1104/// let v = &[true, true, false, false];
1105///
1106/// let dist: f64 = dice(u, v);
1107/// println!("Dice distance: {}", dist);
1108/// ```
1109#[allow(dead_code)]
1110pub fn dice<T: Float>(point1: &[bool], point2: &[bool]) -> T {
1111 if point1.len() != point2.len() {
1112 return T::nan();
1113 }
1114
1115 let mut n_true_true = 0;
1116 let mut n_true_false = 0;
1117 let mut n_false_true = 0;
1118
1119 for i in 0..point1.len() {
1120 if point1[i] && point2[i] {
1121 n_true_true += 1;
1122 } else if point1[i] && !point2[i] {
1123 n_true_false += 1;
1124 } else if !point1[i] && point2[i] {
1125 n_false_true += 1;
1126 }
1127 }
1128
1129 let num = T::from(n_true_false + n_false_true).unwrap();
1130 let denom = T::from(2 * n_true_true + n_true_false + n_false_true).unwrap();
1131
1132 if denom > T::zero() {
1133 num / denom
1134 } else {
1135 T::zero()
1136 }
1137}
1138
1139/// Kulsinski distance between two boolean vectors
1140///
1141/// The Kulsinski distance between two boolean vectors u and v is defined as:
1142/// (c_TF + c_FT - c_TT + n) / (c_FT + c_TF + n)
1143/// where c_ij is the number of occurrences of u\[k\]=i and v\[k\]=j for k<n.
1144///
1145/// # Arguments
1146///
1147/// * `point1` - First boolean vector
1148/// * `point2` - Second boolean vector
1149///
1150/// # Returns
1151///
1152/// * The Kulsinski distance
1153///
1154/// # Examples
1155///
1156/// ```
1157/// use scirs2_spatial::distance::kulsinski;
1158///
1159/// let u = &[true, false, true, false];
1160/// let v = &[true, true, false, false];
1161///
1162/// let dist: f64 = kulsinski(u, v);
1163/// println!("Kulsinski distance: {}", dist);
1164/// ```
1165#[allow(dead_code)]
1166pub fn kulsinski<T: Float>(point1: &[bool], point2: &[bool]) -> T {
1167 if point1.len() != point2.len() {
1168 return T::nan();
1169 }
1170
1171 let mut n_true_true = 0;
1172 let mut n_true_false = 0;
1173 let mut n_false_true = 0;
1174 let n = point1.len();
1175
1176 for i in 0..n {
1177 if point1[i] && point2[i] {
1178 n_true_true += 1;
1179 } else if point1[i] && !point2[i] {
1180 n_true_false += 1;
1181 } else if !point1[i] && point2[i] {
1182 n_false_true += 1;
1183 }
1184 }
1185
1186 let num = T::from(n_true_false + n_false_true - n_true_true + n).unwrap();
1187 let denom = T::from(n_true_false + n_false_true + n).unwrap();
1188
1189 if denom > T::zero() {
1190 num / denom
1191 } else {
1192 T::zero()
1193 }
1194}
1195
1196/// Rogers-Tanimoto distance between two boolean vectors
1197///
1198/// The Rogers-Tanimoto distance between two boolean vectors u and v is defined as:
1199/// 2(c_TF + c_FT) / (c_TT + c_FF + 2(c_TF + c_FT))
1200/// where c_ij is the number of occurrences of u\[k\]=i and v\[k\]=j for k<n.
1201///
1202/// # Arguments
1203///
1204/// * `point1` - First boolean vector
1205/// * `point2` - Second boolean vector
1206///
1207/// # Returns
1208///
1209/// * The Rogers-Tanimoto distance
1210///
1211/// # Examples
1212///
1213/// ```
1214/// use scirs2_spatial::distance::rogerstanimoto;
1215///
1216/// let u = &[true, false, true, false];
1217/// let v = &[true, true, false, false];
1218///
1219/// let dist: f64 = rogerstanimoto(u, v);
1220/// println!("Rogers-Tanimoto distance: {}", dist);
1221/// ```
1222#[allow(dead_code)]
1223pub fn rogerstanimoto<T: Float>(point1: &[bool], point2: &[bool]) -> T {
1224 if point1.len() != point2.len() {
1225 return T::nan();
1226 }
1227
1228 let mut n_true_true = 0;
1229 let mut n_true_false = 0;
1230 let mut n_false_true = 0;
1231 let mut n_false_false = 0;
1232
1233 for i in 0..point1.len() {
1234 if point1[i] && point2[i] {
1235 n_true_true += 1;
1236 } else if point1[i] && !point2[i] {
1237 n_true_false += 1;
1238 } else if !point1[i] && point2[i] {
1239 n_false_true += 1;
1240 } else {
1241 n_false_false += 1;
1242 }
1243 }
1244
1245 let r = n_true_false + n_false_true;
1246
1247 let num = T::from(2 * r).unwrap();
1248 let denom = T::from(n_true_true + n_false_false + 2 * r).unwrap();
1249
1250 if denom > T::zero() {
1251 num / denom
1252 } else {
1253 T::zero()
1254 }
1255}
1256
1257/// Russell-Rao distance between two boolean vectors
1258///
1259/// The Russell-Rao distance between two boolean vectors u and v is defined as:
1260/// (n - c_TT) / n
1261/// where c_ij is the number of occurrences of u\[k\]=i and v\[k\]=j for k<n.
1262///
1263/// # Arguments
1264///
1265/// * `point1` - First boolean vector
1266/// * `point2` - Second boolean vector
1267///
1268/// # Returns
1269///
1270/// * The Russell-Rao distance
1271///
1272/// # Examples
1273///
1274/// ```
1275/// use scirs2_spatial::distance::russellrao;
1276///
1277/// let u = &[true, false, true, false];
1278/// let v = &[true, true, false, false];
1279///
1280/// let dist: f64 = russellrao(u, v);
1281/// println!("Russell-Rao distance: {}", dist);
1282/// ```
1283#[allow(dead_code)]
1284pub fn russellrao<T: Float>(point1: &[bool], point2: &[bool]) -> T {
1285 if point1.len() != point2.len() {
1286 return T::nan();
1287 }
1288
1289 let mut n_true_true = 0;
1290 let n = point1.len();
1291
1292 for i in 0..n {
1293 if point1[i] && point2[i] {
1294 n_true_true += 1;
1295 }
1296 }
1297
1298 let num = T::from(n - n_true_true).unwrap();
1299 let denom = T::from(n).unwrap();
1300
1301 if denom > T::zero() {
1302 num / denom
1303 } else {
1304 T::zero()
1305 }
1306}
1307
1308/// Sokal-Michener distance between two boolean vectors
1309///
1310/// The Sokal-Michener distance between two boolean vectors u and v is defined as:
1311/// 2(c_TF + c_FT) / (c_TT + c_FF + 2(c_TF + c_FT))
1312/// where c_ij is the number of occurrences of u\[k\]=i and v\[k\]=j for k<n.
1313///
1314/// # Arguments
1315///
1316/// * `point1` - First boolean vector
1317/// * `point2` - Second boolean vector
1318///
1319/// # Returns
1320///
1321/// * The Sokal-Michener distance
1322///
1323/// # Examples
1324///
1325/// ```
1326/// use scirs2_spatial::distance::sokalmichener;
1327///
1328/// let u = &[true, false, true, false];
1329/// let v = &[true, true, false, false];
1330///
1331/// let dist: f64 = sokalmichener(u, v);
1332/// println!("Sokal-Michener distance: {}", dist);
1333/// ```
1334#[allow(dead_code)]
1335pub fn sokalmichener<T: Float>(point1: &[bool], point2: &[bool]) -> T {
1336 // This is the same as Rogers-Tanimoto
1337 rogerstanimoto(point1, point2)
1338}
1339
1340/// Sokal-Sneath distance between two boolean vectors
1341///
1342/// The Sokal-Sneath distance between two boolean vectors u and v is defined as:
1343/// 2(c_TF + c_FT) / (c_TT + 2(c_TF + c_FT))
1344/// where c_ij is the number of occurrences of u\[k\]=i and v\[k\]=j for k<n.
1345///
1346/// # Arguments
1347///
1348/// * `point1` - First boolean vector
1349/// * `point2` - Second boolean vector
1350///
1351/// # Returns
1352///
1353/// * The Sokal-Sneath distance
1354///
1355/// # Examples
1356///
1357/// ```
1358/// use scirs2_spatial::distance::sokalsneath;
1359///
1360/// let u = &[true, false, true, false];
1361/// let v = &[true, true, false, false];
1362///
1363/// let dist: f64 = sokalsneath(u, v);
1364/// println!("Sokal-Sneath distance: {}", dist);
1365/// ```
1366#[allow(dead_code)]
1367pub fn sokalsneath<T: Float>(point1: &[bool], point2: &[bool]) -> T {
1368 if point1.len() != point2.len() {
1369 return T::nan();
1370 }
1371
1372 let mut n_true_true = 0;
1373 let mut n_true_false = 0;
1374 let mut n_false_true = 0;
1375
1376 for i in 0..point1.len() {
1377 if point1[i] && point2[i] {
1378 n_true_true += 1;
1379 } else if point1[i] && !point2[i] {
1380 n_true_false += 1;
1381 } else if !point1[i] && point2[i] {
1382 n_false_true += 1;
1383 }
1384 }
1385
1386 let r = n_true_false + n_false_true;
1387
1388 let num = T::from(2 * r).unwrap();
1389 let denom = T::from(n_true_true + 2 * r).unwrap();
1390
1391 if denom > T::zero() {
1392 num / denom
1393 } else {
1394 T::zero()
1395 }
1396}
1397
1398/// Yule distance between two boolean vectors
1399///
1400/// The Yule distance between two boolean vectors u and v is defined as:
1401/// 2(c_TF * c_FT) / (c_TT * c_FF + c_TF * c_FT)
1402/// where c_ij is the number of occurrences of u\[k\]=i and v\[k\]=j for k<n.
1403///
1404/// # Arguments
1405///
1406/// * `point1` - First boolean vector
1407/// * `point2` - Second boolean vector
1408///
1409/// # Returns
1410///
1411/// * The Yule distance
1412///
1413/// # Examples
1414///
1415/// ```
1416/// use scirs2_spatial::distance::yule;
1417///
1418/// let u = &[true, false, true, false];
1419/// let v = &[true, true, false, false];
1420///
1421/// let dist: f64 = yule(u, v);
1422/// println!("Yule distance: {}", dist);
1423/// ```
1424#[allow(dead_code)]
1425pub fn yule<T: Float>(point1: &[bool], point2: &[bool]) -> T {
1426 if point1.len() != point2.len() {
1427 return T::nan();
1428 }
1429
1430 let mut n_true_true = 0;
1431 let mut n_true_false = 0;
1432 let mut n_false_true = 0;
1433 let mut n_false_false = 0;
1434
1435 for i in 0..point1.len() {
1436 if point1[i] && point2[i] {
1437 n_true_true += 1;
1438 } else if point1[i] && !point2[i] {
1439 n_true_false += 1;
1440 } else if !point1[i] && point2[i] {
1441 n_false_true += 1;
1442 } else {
1443 n_false_false += 1;
1444 }
1445 }
1446
1447 let num = T::from(2 * n_true_false * n_false_true).unwrap();
1448 let denom = T::from(n_true_true * n_false_false + n_true_false * n_false_true).unwrap();
1449
1450 if denom > T::zero() {
1451 num / denom
1452 } else {
1453 T::zero()
1454 }
1455}
1456
1457#[cfg(test)]
1458mod tests {
1459 use super::*;
1460 use approx::assert_relative_eq;
1461 use ndarray::arr2;
1462
1463 #[test]
1464 fn test_euclidean_distance() {
1465 let point1 = &[1.0, 2.0, 3.0];
1466 let point2 = &[4.0, 5.0, 6.0];
1467
1468 assert_relative_eq!(euclidean(point1, point2), 5.196152422706632, epsilon = 1e-6);
1469 }
1470
1471 #[test]
1472 fn test_manhattan_distance() {
1473 let point1 = &[1.0, 2.0, 3.0];
1474 let point2 = &[4.0, 5.0, 6.0];
1475
1476 assert_relative_eq!(manhattan(point1, point2), 9.0, epsilon = 1e-6);
1477 }
1478
1479 #[test]
1480 fn test_chebyshev_distance() {
1481 let point1 = &[1.0, 2.0, 3.0];
1482 let point2 = &[4.0, 5.0, 6.0];
1483
1484 assert_relative_eq!(chebyshev(point1, point2), 3.0, epsilon = 1e-6);
1485 }
1486
1487 #[test]
1488 fn test_minkowski_distance() {
1489 let point1 = &[1.0, 2.0, 3.0];
1490 let point2 = &[4.0, 5.0, 6.0];
1491
1492 // p = 1 (Manhattan)
1493 assert_relative_eq!(minkowski(point1, point2, 1.0), 9.0, epsilon = 1e-6);
1494
1495 // p = 2 (Euclidean)
1496 assert_relative_eq!(
1497 minkowski(point1, point2, 2.0),
1498 5.196152422706632,
1499 epsilon = 1e-6
1500 );
1501
1502 // p = infinity (Chebyshev)
1503 assert_relative_eq!(
1504 minkowski(point1, point2, f64::INFINITY),
1505 3.0,
1506 epsilon = 1e-6
1507 );
1508
1509 // p = 3
1510 assert_relative_eq!(minkowski(point1, point2, 3.0), 4.3267, epsilon = 1e-4);
1511 }
1512
1513 #[test]
1514 fn test_canberra_distance() {
1515 let point1 = &[1.0, 2.0, 3.0];
1516 let point2 = &[4.0, 5.0, 6.0];
1517
1518 assert_relative_eq!(canberra(point1, point2), 1.5, epsilon = 1e-6);
1519 }
1520
1521 #[test]
1522 fn test_cosine_distance() {
1523 // Orthogonal vectors should have distance 1
1524 let point1 = &[1.0, 0.0];
1525 let point2 = &[0.0, 1.0];
1526
1527 assert_relative_eq!(cosine(point1, point2), 1.0, epsilon = 1e-6);
1528
1529 // Parallel vectors should have distance 0
1530 let point3 = &[1.0, 2.0];
1531 let point4 = &[2.0, 4.0];
1532
1533 assert_relative_eq!(cosine(point3, point4), 0.0, epsilon = 1e-6);
1534
1535 // 45 degree angle should have distance 1 - sqrt(2)/2
1536 let point5 = &[1.0, 1.0];
1537 let point6 = &[1.0, 0.0];
1538
1539 assert_relative_eq!(cosine(point5, point6), 0.2928932188134525, epsilon = 1e-6);
1540 }
1541
1542 #[test]
1543 fn test_correlation_distance() {
1544 // Perfectly anti-correlated should have distance 2
1545 let point1 = &[1.0, 2.0, 3.0];
1546 let point2 = &[3.0, 2.0, 1.0];
1547
1548 assert_relative_eq!(correlation(point1, point2), 2.0, epsilon = 1e-6);
1549
1550 // Perfectly correlated should have distance 0
1551 let point3 = &[1.0, 2.0, 3.0];
1552 let point4 = &[2.0, 4.0, 6.0];
1553
1554 assert_relative_eq!(correlation(point3, point4), 0.0, epsilon = 1e-6);
1555 }
1556
1557 #[test]
1558 fn test_jaccard_distance() {
1559 let point1 = &[1.0, 0.0, 1.0];
1560 let point2 = &[0.0, 1.0, 1.0];
1561
1562 // 1 element in common, 2 elements different = 2/3
1563 assert_relative_eq!(jaccard(point1, point2), 2.0 / 3.0, epsilon = 1e-6);
1564
1565 // Empty sets should have distance 0
1566 let point3 = &[0.0, 0.0, 0.0];
1567 let point4 = &[0.0, 0.0, 0.0];
1568
1569 assert_relative_eq!(jaccard(point3, point4), 0.0, epsilon = 1e-6);
1570
1571 // No elements in common should have distance 1
1572 let point5 = &[1.0, 1.0, 0.0];
1573 let point6 = &[0.0, 0.0, 1.0];
1574
1575 assert_relative_eq!(jaccard(point5, point6), 1.0, epsilon = 1e-6);
1576 }
1577
1578 #[test]
1579 fn test_pdist() {
1580 let points = arr2(&[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]);
1581
1582 let dist_matrix = pdist(&points, euclidean);
1583
1584 assert_eq!(dist_matrix.shape(), &[3, 3]);
1585
1586 // Check diagonal is zero
1587 assert_relative_eq!(dist_matrix[(0, 0)], 0.0, epsilon = 1e-6);
1588 assert_relative_eq!(dist_matrix[(1, 1)], 0.0, epsilon = 1e-6);
1589 assert_relative_eq!(dist_matrix[(2, 2)], 0.0, epsilon = 1e-6);
1590
1591 // Check off-diagonal elements
1592 assert_relative_eq!(dist_matrix[(0, 1)], 1.0, epsilon = 1e-6);
1593 assert_relative_eq!(dist_matrix[(0, 2)], 1.0, epsilon = 1e-6);
1594 assert_relative_eq!(
1595 dist_matrix[(1, 2)],
1596 std::f64::consts::SQRT_2,
1597 epsilon = 1e-6
1598 );
1599
1600 // Check symmetry
1601 assert_relative_eq!(dist_matrix[(1, 0)], dist_matrix[(0, 1)], epsilon = 1e-6);
1602 assert_relative_eq!(dist_matrix[(2, 0)], dist_matrix[(0, 2)], epsilon = 1e-6);
1603 assert_relative_eq!(dist_matrix[(2, 1)], dist_matrix[(1, 2)], epsilon = 1e-6);
1604 }
1605
1606 #[test]
1607 fn test_cdist() {
1608 let x_a = arr2(&[[0.0, 0.0], [1.0, 0.0]]);
1609
1610 let xb = arr2(&[[0.0, 1.0], [1.0, 1.0]]);
1611
1612 let dist_matrix = cdist(&x_a, &xb, euclidean).unwrap();
1613
1614 assert_eq!(dist_matrix.shape(), &[2, 2]);
1615
1616 assert_relative_eq!(dist_matrix[(0, 0)], 1.0, epsilon = 1e-6);
1617 assert_relative_eq!(
1618 dist_matrix[(0, 1)],
1619 std::f64::consts::SQRT_2,
1620 epsilon = 1e-6
1621 );
1622 assert_relative_eq!(
1623 dist_matrix[(1, 0)],
1624 std::f64::consts::SQRT_2,
1625 epsilon = 1e-6
1626 );
1627 assert_relative_eq!(dist_matrix[(1, 1)], 1.0, epsilon = 1e-6);
1628 }
1629
1630 #[test]
1631 fn test_braycurtis_distance() {
1632 let point1 = &[1.0, 2.0, 3.0];
1633 let point2 = &[2.0, 3.0, 4.0];
1634
1635 let dist = braycurtis(point1, point2);
1636 // Sum of differences: |1-2| + |2-3| + |3-4| = 3
1637 // Sum of absolute sums: |1+2| + |2+3| + |3+4| = 3 + 5 + 7 = 15
1638 // Distance: 3/15 = 1/5 = 0.2
1639 assert_relative_eq!(dist, 0.2, epsilon = 1e-6);
1640
1641 // Test identical vectors (should be 0)
1642 let point3 = &[1.0, 2.0, 3.0];
1643 let point4 = &[1.0, 2.0, 3.0];
1644 assert_relative_eq!(braycurtis(point3, point4), 0.0, epsilon = 1e-6);
1645
1646 // Test with zeros in both vectors
1647 let point5 = &[0.0, 0.0];
1648 let point6 = &[0.0, 0.0];
1649 assert_relative_eq!(braycurtis(point5, point6), 0.0, epsilon = 1e-6);
1650 }
1651
1652 #[test]
1653 fn test_squareform() {
1654 // Test conversion from condensed to square form
1655 let condensed = vec![1.0, 2.0, 3.0];
1656 let square = squareform(&condensed).unwrap();
1657
1658 assert_eq!(square.shape(), &[3, 3]);
1659 assert_relative_eq!(square[(0, 1)], 1.0, epsilon = 1e-6);
1660 assert_relative_eq!(square[(0, 2)], 2.0, epsilon = 1e-6);
1661 assert_relative_eq!(square[(1, 2)], 3.0, epsilon = 1e-6);
1662
1663 // Test conversion from square to condensed form
1664 let condensed2 = squareform_to_condensed(&square).unwrap();
1665 assert_eq!(condensed2, condensed);
1666 }
1667}