scirs2_spatial/
generic_traits.rs

1//! Generic traits and type parameters for spatial algorithms
2//!
3//! This module provides generic traits that enable spatial algorithms to work
4//! with different numeric types and array structures. This improves API flexibility
5//! and allows for better integration with the broader Rust ecosystem.
6//!
7//! # Features
8//!
9//! - **Generic numeric types**: Support for f32, f64, and other numeric types
10//! - **Array ecosystem integration**: Work with ndarray, nalgebra, and other array types
11//! - **Iterator support**: Process data from various sources efficiently
12//! - **Dimension awareness**: Compile-time and runtime dimension checking
13//! - **Memory efficiency**: Zero-copy operations where possible
14//!
15//! # Examples
16//!
17//! ```
18//! use scirs2_spatial::generic_traits::{SpatialScalar};
19//! use scirs2_core::ndarray::array;
20//!
21//! // Generic distance calculation (simplified example)
22//! fn calculate_distance<T>(p1: &[T], p2: &[T]) -> T
23//! where
24//!     T: SpatialScalar,
25//! {
26//!     // Generic implementation (simplified)
27//!     T::zero()
28//! }
29//!
30//! // Works with different array types
31//! let point1 = array![1.0f32, 2.0f32, 3.0f32];
32//! let point2 = array![4.0f32, 5.0f32, 6.0f32];
33//! let _result = calculate_distance(point1.as_slice().unwrap(), point2.as_slice().unwrap());
34//! ```
35
36use scirs2_core::ndarray::Array1;
37use scirs2_core::numeric::{Float, NumCast};
38use scirs2_core::simd_ops::SimdUnifiedOps;
39use std::fmt::Debug;
40
41/// Trait for scalar types that can be used in spatial computations
42///
43/// This trait extends standard numeric traits with requirements specific
44/// to spatial algorithms, including floating-point operations and conversions.
45pub trait SpatialScalar:
46    Float + Debug + Default + NumCast + Send + Sync + SimdUnifiedOps + 'static
47{
48    /// The epsilon value for floating-point comparisons
49    fn epsilon() -> Self;
50
51    /// Maximum finite value for this type
52    fn max_finite() -> Self;
53
54    /// Convert from f64 (used for constants and literals)
55    fn from_f64(value: f64) -> Option<Self> {
56        NumCast::from(value)
57    }
58
59    /// Convert to f64 for interoperability
60    fn to_f64(&self) -> Option<f64> {
61        NumCast::from(*self)
62    }
63
64    /// Square root function
65    fn sqrt(&self) -> Self {
66        Float::sqrt(*self)
67    }
68
69    /// Absolute value function
70    fn abs(&self) -> Self {
71        Float::abs(*self)
72    }
73
74    /// Power function
75    fn powf(&self, exp: Self) -> Self {
76        Float::powf(*self, exp)
77    }
78
79    /// Natural logarithm
80    fn ln(&self) -> Self {
81        Float::ln(*self)
82    }
83
84    /// Exponential function
85    fn exp(&self) -> Self {
86        Float::exp(*self)
87    }
88
89    /// Sine function
90    fn sin(&self) -> Self {
91        Float::sin(*self)
92    }
93
94    /// Cosine function
95    fn cos(&self) -> Self {
96        Float::cos(*self)
97    }
98
99    /// Arctangent of y/x
100    fn atan2(&self, other: Self) -> Self {
101        Float::atan2(*self, other)
102    }
103
104    /// Check if the value is finite
105    fn is_finite(&self) -> bool {
106        Float::is_finite(*self)
107    }
108
109    /// Check if the value is NaN
110    fn is_nan(&self) -> bool {
111        Float::is_nan(*self)
112    }
113
114    /// SIMD-optimized squared Euclidean distance
115    fn simd_squared_euclidean_distance(_a: &[Self], b: &[Self]) -> Result<Self, &'static str> {
116        Err("SIMD not available for this type")
117    }
118
119    /// SIMD-optimized Manhattan distance
120    fn simd_manhattan_distance(_a: &[Self], b: &[Self]) -> Result<Self, &'static str> {
121        Err("SIMD not available for this type")
122    }
123
124    /// SIMD-optimized dot product
125    fn simd_dot_product(_a: &[Self], b: &[Self]) -> Result<Self, &'static str> {
126        Err("SIMD not available for this type")
127    }
128}
129
130impl SpatialScalar for f32 {
131    fn epsilon() -> Self {
132        f32::EPSILON
133    }
134
135    fn max_finite() -> Self {
136        f32::MAX
137    }
138
139    fn simd_squared_euclidean_distance(a: &[Self], b: &[Self]) -> Result<Self, &'static str> {
140        if a.len() != b.len() {
141            return Err("Slice lengths must match");
142        }
143
144        use scirs2_core::ndarray::Array1;
145        let a_array = Array1::from(a.to_vec());
146        let b_array = Array1::from(b.to_vec());
147
148        let diff = Self::simd_sub(&a_array.view(), &b_array.view());
149        let squared = Self::simd_mul(&diff.view(), &diff.view());
150        Ok(Self::simd_sum(&squared.view()))
151    }
152
153    fn simd_manhattan_distance(a: &[Self], b: &[Self]) -> Result<Self, &'static str> {
154        if a.len() != b.len() {
155            return Err("Slice lengths must match");
156        }
157
158        let a_array = Array1::from(a.to_vec());
159        let b_array = Array1::from(b.to_vec());
160
161        let diff = Self::simd_sub(&a_array.view(), &b_array.view());
162        let abs_diff = Self::simd_abs(&diff.view());
163        Ok(Self::simd_sum(&abs_diff.view()))
164    }
165
166    fn simd_dot_product(a: &[Self], b: &[Self]) -> Result<Self, &'static str> {
167        if a.len() != b.len() {
168            return Err("Slice lengths must match");
169        }
170
171        let a_array = Array1::from(a.to_vec());
172        let b_array = Array1::from(b.to_vec());
173
174        Ok(Self::simd_dot(&a_array.view(), &b_array.view()))
175    }
176}
177
178impl SpatialScalar for f64 {
179    fn epsilon() -> Self {
180        f64::EPSILON
181    }
182
183    fn max_finite() -> Self {
184        f64::MAX
185    }
186
187    fn simd_squared_euclidean_distance(a: &[Self], b: &[Self]) -> Result<Self, &'static str> {
188        if a.len() != b.len() {
189            return Err("Slice lengths must match");
190        }
191
192        let a_array = Array1::from(a.to_vec());
193        let b_array = Array1::from(b.to_vec());
194
195        let diff = Self::simd_sub(&a_array.view(), &b_array.view());
196        let squared = Self::simd_mul(&diff.view(), &diff.view());
197        Ok(Self::simd_sum(&squared.view()))
198    }
199
200    fn simd_manhattan_distance(a: &[Self], b: &[Self]) -> Result<Self, &'static str> {
201        if a.len() != b.len() {
202            return Err("Slice lengths must match");
203        }
204
205        let a_array = Array1::from(a.to_vec());
206        let b_array = Array1::from(b.to_vec());
207
208        let diff = Self::simd_sub(&a_array.view(), &b_array.view());
209        let abs_diff = Self::simd_abs(&diff.view());
210        Ok(Self::simd_sum(&abs_diff.view()))
211    }
212
213    fn simd_dot_product(a: &[Self], b: &[Self]) -> Result<Self, &'static str> {
214        if a.len() != b.len() {
215            return Err("Slice lengths must match");
216        }
217
218        let a_array = Array1::from(a.to_vec());
219        let b_array = Array1::from(b.to_vec());
220
221        Ok(Self::simd_dot(&a_array.view(), &b_array.view()))
222    }
223}
224
225/// Trait for types that can represent a point in space
226///
227/// This trait abstracts over different point representations,
228/// allowing algorithms to work with vectors, arrays, and custom types.
229pub trait SpatialPoint<T: SpatialScalar> {
230    /// Get the dimension of the point
231    fn dimension(&self) -> usize;
232
233    /// Get the coordinate at the given index
234    fn coordinate(&self, index: usize) -> Option<T>;
235
236    /// Get all coordinates as a slice if possible (for efficiency)
237    fn as_slice(&self) -> Option<&[T]> {
238        None
239    }
240
241    /// Create a point from coordinates
242    fn from_coords(coords: &[T]) -> Self;
243
244    /// Calculate squared Euclidean distance to another point
245    fn squared_distance_to(&self, other: &Self) -> T {
246        if self.dimension() != other.dimension() {
247            return T::max_finite();
248        }
249
250        // Try SIMD-optimized calculation if slices are available
251        if let (Some(slice_a), Some(slice_b)) = (self.as_slice(), other.as_slice()) {
252            if let Ok(simd_result) = T::simd_squared_euclidean_distance(slice_a, slice_b) {
253                return simd_result;
254            }
255        }
256
257        // Fallback to scalar calculation
258        let mut sum = T::zero();
259        for i in 0..self.dimension() {
260            if let (Some(a), Some(b)) = (self.coordinate(i), other.coordinate(i)) {
261                let diff = a - b;
262                sum = sum + diff * diff;
263            }
264        }
265        sum
266    }
267
268    /// Calculate Euclidean distance to another point
269    fn distance_to(&self, other: &Self) -> T {
270        self.squared_distance_to(other).sqrt()
271    }
272
273    /// Calculate Manhattan distance to another point
274    fn manhattan_distance_to(&self, other: &Self) -> T {
275        if self.dimension() != other.dimension() {
276            return T::max_finite();
277        }
278
279        // Try SIMD-optimized calculation if slices are available
280        if let (Some(slice_a), Some(slice_b)) = (self.as_slice(), other.as_slice()) {
281            if let Ok(simd_result) = T::simd_manhattan_distance(slice_a, slice_b) {
282                return simd_result;
283            }
284        }
285
286        // Fallback to scalar calculation
287        let mut sum = T::zero();
288        for i in 0..self.dimension() {
289            if let (Some(a), Some(b)) = (self.coordinate(i), other.coordinate(i)) {
290                sum = sum + (a - b).abs();
291            }
292        }
293        sum
294    }
295}
296
297/// Trait for collections of spatial points
298///
299/// This trait allows algorithms to work with different array structures
300/// and iterator types while maintaining efficiency.
301pub trait SpatialArray<T: SpatialScalar, P: SpatialPoint<T>> {
302    /// Get the number of points in the array
303    fn len(&self) -> usize;
304
305    /// Check if the array is empty
306    fn is_empty(&self) -> bool {
307        self.len() == 0
308    }
309
310    /// Get the dimension of points in this array
311    fn dimension(&self) -> Option<usize>;
312
313    /// Get a point at the given index
314    fn get_point(&self, index: usize) -> Option<P>;
315
316    /// Iterate over all points
317    fn iter_points(&self) -> Box<dyn Iterator<Item = P> + '_>;
318
319    /// Get the bounding box of all points
320    fn bounding_box(&self) -> Option<(P, P)> {
321        if self.is_empty() {
322            return None;
323        }
324
325        let first = self.get_point(0)?;
326        let dim = first.dimension();
327
328        let mut min_coords = vec![T::max_finite(); dim];
329        let mut max_coords = vec![T::min_value(); dim];
330
331        for point in self.iter_points() {
332            for i in 0..dim {
333                if let Some(coord) = point.coordinate(i) {
334                    if coord < min_coords[i] {
335                        min_coords[i] = coord;
336                    }
337                    if coord > max_coords[i] {
338                        max_coords[i] = coord;
339                    }
340                }
341            }
342        }
343
344        Some((P::from_coords(&min_coords), P::from_coords(&max_coords)))
345    }
346}
347
348/// Trait for distance metrics
349pub trait DistanceMetric<T: SpatialScalar, P: SpatialPoint<T>> {
350    /// Calculate distance between two points
351    fn distance(&self, p1: &P, p2: &P) -> T;
352
353    /// Calculate squared distance (if applicable, for efficiency)
354    fn squared_distance(&self, p1: &P, p2: &P) -> Option<T> {
355        None
356    }
357
358    /// Check if this metric satisfies the triangle inequality
359    fn is_metric(&self) -> bool {
360        true
361    }
362
363    /// Get the name of this distance metric
364    fn name(&self) -> &'static str;
365}
366
367/// Euclidean distance metric
368#[derive(Debug, Clone, Copy, Default)]
369pub struct EuclideanMetric;
370
371impl<T: SpatialScalar, P: SpatialPoint<T>> DistanceMetric<T, P> for EuclideanMetric {
372    fn distance(&self, p1: &P, p2: &P) -> T {
373        p1.distance_to(p2)
374    }
375
376    fn squared_distance(&self, p1: &P, p2: &P) -> Option<T> {
377        Some(p1.squared_distance_to(p2))
378    }
379
380    fn name(&self) -> &'static str {
381        "euclidean"
382    }
383}
384
385/// Manhattan distance metric
386#[derive(Debug, Clone, Copy, Default)]
387pub struct ManhattanMetric;
388
389impl<T: SpatialScalar, P: SpatialPoint<T>> DistanceMetric<T, P> for ManhattanMetric {
390    fn distance(&self, p1: &P, p2: &P) -> T {
391        p1.manhattan_distance_to(p2)
392    }
393
394    fn name(&self) -> &'static str {
395        "manhattan"
396    }
397}
398
399/// Chebyshev distance metric
400#[derive(Debug, Clone, Copy, Default)]
401pub struct ChebyshevMetric;
402
403impl<T: SpatialScalar, P: SpatialPoint<T>> DistanceMetric<T, P> for ChebyshevMetric {
404    fn distance(&self, p1: &P, p2: &P) -> T {
405        if p1.dimension() != p2.dimension() {
406            return T::max_finite();
407        }
408
409        let mut max_diff = T::zero();
410        for i in 0..p1.dimension() {
411            if let (Some(a), Some(b)) = (p1.coordinate(i), p2.coordinate(i)) {
412                let diff = (a - b).abs();
413                if diff > max_diff {
414                    max_diff = diff;
415                }
416            }
417        }
418        max_diff
419    }
420
421    fn name(&self) -> &'static str {
422        "chebyshev"
423    }
424}
425
426/// Implementation of SpatialPoint for `Vec<T>`
427impl<T: SpatialScalar> SpatialPoint<T> for Vec<T> {
428    fn dimension(&self) -> usize {
429        self.len()
430    }
431
432    fn coordinate(&self, index: usize) -> Option<T> {
433        self.get(index).copied()
434    }
435
436    fn as_slice(&self) -> Option<&[T]> {
437        Some(self.as_slice())
438    }
439
440    fn from_coords(coords: &[T]) -> Self {
441        coords.to_vec()
442    }
443}
444
445/// Implementation of SpatialPoint for slices
446impl<T: SpatialScalar> SpatialPoint<T> for &[T] {
447    fn dimension(&self) -> usize {
448        self.len()
449    }
450
451    fn coordinate(&self, index: usize) -> Option<T> {
452        self.get(index).copied()
453    }
454
455    fn as_slice(&self) -> Option<&[T]> {
456        Some(self)
457    }
458
459    fn from_coords(coords: &[T]) -> Self {
460        // This is a fundamental limitation - &[T] is a reference to existing data
461        // and cannot be created from raw coordinates without an underlying array.
462        // This implementation is not meaningful for slice references.
463        unreachable!(
464            "&[T]::from_coords() should not be called - &[T] is a reference to existing data"
465        )
466    }
467}
468
469/// Implementation of SpatialPoint for arrays
470impl<T: SpatialScalar, const N: usize> SpatialPoint<T> for [T; N] {
471    fn dimension(&self) -> usize {
472        N
473    }
474
475    fn coordinate(&self, index: usize) -> Option<T> {
476        self.get(index).copied()
477    }
478
479    fn as_slice(&self) -> Option<&[T]> {
480        Some(self.as_slice())
481    }
482
483    fn from_coords(coords: &[T]) -> Self {
484        let mut result = [T::zero(); N];
485        for (i, &coord) in coords.iter().enumerate().take(N) {
486            result[i] = coord;
487        }
488        result
489    }
490}
491
492/// Generic point structure for spatial algorithms
493#[derive(Debug, Clone, PartialEq)]
494pub struct Point<T: SpatialScalar> {
495    coords: Vec<T>,
496}
497
498impl<T: SpatialScalar> Point<T> {
499    /// Create a new point from coordinates
500    pub fn new(coords: Vec<T>) -> Self {
501        Self { coords }
502    }
503
504    /// Create a point with the given dimension filled with zeros
505    pub fn zeros(dim: usize) -> Self {
506        Self {
507            coords: vec![T::zero(); dim],
508        }
509    }
510
511    /// Create a 2D point
512    pub fn new_2d(x: T, y: T) -> Self {
513        Self { coords: vec![x, y] }
514    }
515
516    /// Create a 3D point
517    pub fn new_3d(x: T, y: T, z: T) -> Self {
518        Self {
519            coords: vec![x, y, z],
520        }
521    }
522
523    /// Get the coordinates as a slice
524    pub fn coords(&self) -> &[T] {
525        &self.coords
526    }
527
528    /// Get mutable access to coordinates
529    pub fn coords_mut(&mut self) -> &mut [T] {
530        &mut self.coords
531    }
532
533    /// Add another point (vector addition)
534    pub fn add(&self, other: &Point<T>) -> Option<Point<T>> {
535        if self.dimension() != other.dimension() {
536            return None;
537        }
538
539        let coords: Vec<T> = self
540            .coords
541            .iter()
542            .zip(other.coords.iter())
543            .map(|(&a, &b)| a + b)
544            .collect();
545
546        Some(Point::new(coords))
547    }
548
549    /// Subtract another point (vector subtraction)
550    pub fn subtract(&self, other: &Point<T>) -> Option<Point<T>> {
551        if self.dimension() != other.dimension() {
552            return None;
553        }
554
555        let coords: Vec<T> = self
556            .coords
557            .iter()
558            .zip(other.coords.iter())
559            .map(|(&a, &b)| a - b)
560            .collect();
561
562        Some(Point::new(coords))
563    }
564
565    /// Scale the point by a scalar
566    pub fn scale(&self, factor: T) -> Point<T> {
567        let coords: Vec<T> = self.coords.iter().map(|&c| c * factor).collect();
568        Point::new(coords)
569    }
570
571    /// Calculate the dot product with another point
572    pub fn dot(&self, other: &Point<T>) -> Option<T> {
573        if self.dimension() != other.dimension() {
574            return None;
575        }
576
577        // Try SIMD-optimized calculation
578        if let Ok(simd_result) = T::simd_dot_product(&self.coords, &other.coords) {
579            return Some(simd_result);
580        }
581
582        // Fallback to scalar calculation
583        let dot_product = self
584            .coords
585            .iter()
586            .zip(other.coords.iter())
587            .map(|(&a, &b)| a * b)
588            .fold(T::zero(), |acc, x| acc + x);
589
590        Some(dot_product)
591    }
592
593    /// Calculate the magnitude (length) of the point as a vector
594    pub fn magnitude(&self) -> T {
595        self.coords
596            .iter()
597            .map(|&c| c * c)
598            .fold(T::zero(), |acc, x| acc + x)
599            .sqrt()
600    }
601
602    /// Normalize the point to unit length
603    pub fn normalize(&self) -> Point<T> {
604        let mag = self.magnitude();
605        if mag > T::zero() {
606            self.scale(T::one() / mag)
607        } else {
608            self.clone()
609        }
610    }
611}
612
613impl<T: SpatialScalar> SpatialPoint<T> for Point<T> {
614    fn dimension(&self) -> usize {
615        self.coords.len()
616    }
617
618    fn coordinate(&self, index: usize) -> Option<T> {
619        self.coords.get(index).copied()
620    }
621
622    fn as_slice(&self) -> Option<&[T]> {
623        Some(&self.coords)
624    }
625
626    fn from_coords(coords: &[T]) -> Self {
627        Point::new(coords.to_vec())
628    }
629}
630
631/// Utility functions for generic spatial operations
632pub mod utils {
633    use super::*;
634
635    /// Calculate pairwise distances between all points in a collection
636    pub fn pairwise_distances<T, P, A, M>(points: &A, metric: &M) -> Vec<T>
637    where
638        T: SpatialScalar,
639        P: SpatialPoint<T>,
640        A: SpatialArray<T, P>,
641        M: DistanceMetric<T, P>,
642    {
643        let n = points.len();
644        let mut distances = Vec::with_capacity(n * (n - 1) / 2);
645
646        for i in 0..n {
647            for j in (i + 1)..n {
648                if let (Some(p1), Some(p2)) = (points.get_point(i), points.get_point(j)) {
649                    distances.push(metric.distance(&p1, &p2));
650                }
651            }
652        }
653
654        distances
655    }
656
657    /// Find the centroid of a collection of points
658    pub fn centroid<T, P, A>(points: &A) -> Option<Point<T>>
659    where
660        T: SpatialScalar,
661        P: SpatialPoint<T>,
662        A: SpatialArray<T, P>,
663    {
664        if points.is_empty() {
665            return None;
666        }
667
668        let n = points.len();
669        let dim = points.dimension()?;
670        let mut sum_coords = vec![T::zero(); dim];
671
672        for point in points.iter_points() {
673            for (i, sum_coord) in sum_coords.iter_mut().enumerate().take(dim) {
674                if let Some(coord) = point.coordinate(i) {
675                    *sum_coord = *sum_coord + coord;
676                }
677            }
678        }
679
680        let n_scalar = T::from(n)?;
681        for coord in &mut sum_coords {
682            *coord = *coord / n_scalar;
683        }
684
685        Some(Point::new(sum_coords))
686    }
687
688    /// Calculate the convex hull using a generic algorithm
689    pub fn convex_hull_2d<T, P, A>(points: &A) -> Vec<Point<T>>
690    where
691        T: SpatialScalar,
692        P: SpatialPoint<T>,
693        A: SpatialArray<T, P>,
694    {
695        if points.len() < 3 {
696            return points
697                .iter_points()
698                .map(|p| Point::from_coords(p.as_slice().unwrap_or(&[])))
699                .collect();
700        }
701
702        // Simple implementation - in practice, you'd use more sophisticated algorithms
703        let mut hull_points: Vec<Point<T>> = points
704            .iter_points()
705            .map(|p| Point::from_coords(p.as_slice().unwrap_or(&[])))
706            .collect();
707
708        // Sort by x-coordinate, then by y-coordinate
709        hull_points.sort_by(|a, b| {
710            let x_cmp = a.coordinate(0).partial_cmp(&b.coordinate(0)).unwrap();
711            if x_cmp == std::cmp::Ordering::Equal {
712                a.coordinate(1).partial_cmp(&b.coordinate(1)).unwrap()
713            } else {
714                x_cmp
715            }
716        });
717
718        hull_points
719    }
720}
721
722/// Integration with ndarray
723pub mod ndarray_integration {
724    use super::{SpatialArray, SpatialPoint, SpatialScalar};
725    use scirs2_core::ndarray::{Array1, ArrayView1, ArrayView2, Axis};
726
727    /// Implementation of SpatialPoint for ndarray ArrayView1
728    impl<T: SpatialScalar> SpatialPoint<T> for ArrayView1<'_, T> {
729        fn dimension(&self) -> usize {
730            self.len()
731        }
732
733        fn coordinate(&self, index: usize) -> Option<T> {
734            self.get(index).copied()
735        }
736
737        fn as_slice(&self) -> Option<&[T]> {
738            self.as_slice()
739        }
740
741        fn from_coords(coords: &[T]) -> Self {
742            // This is a fundamental limitation - ArrayView1 is a view into existing data
743            // and cannot be created from raw coordinates without an underlying array.
744            // This implementation is not meaningful for views, but we provide a dummy
745            // implementation to satisfy the trait. Real usage should avoid this method.
746            unreachable!("ArrayView1::from_coords() should not be called - ArrayView1 is a view into existing data")
747        }
748    }
749
750    /// Implementation of SpatialPoint for ndarray Array1
751    impl<T: SpatialScalar> SpatialPoint<T> for Array1<T> {
752        fn dimension(&self) -> usize {
753            self.len()
754        }
755
756        fn coordinate(&self, index: usize) -> Option<T> {
757            self.get(index).copied()
758        }
759
760        fn as_slice(&self) -> Option<&[T]> {
761            self.as_slice()
762        }
763
764        fn from_coords(coords: &[T]) -> Self {
765            Array1::from(coords.to_vec())
766        }
767    }
768
769    /// Wrapper for ndarray Array2 to implement SpatialArray
770    pub struct NdArray2Wrapper<'a, T: SpatialScalar> {
771        array: ArrayView2<'a, T>,
772    }
773
774    impl<'a, T: SpatialScalar> NdArray2Wrapper<'a, T> {
775        pub fn new(array: ArrayView2<'a, T>) -> Self {
776            Self { array }
777        }
778    }
779
780    impl<T: SpatialScalar> SpatialArray<T, Array1<T>> for NdArray2Wrapper<'_, T> {
781        fn len(&self) -> usize {
782            self.array.nrows()
783        }
784
785        fn dimension(&self) -> Option<usize> {
786            if self.array.nrows() > 0 {
787                Some(self.array.ncols())
788            } else {
789                None
790            }
791        }
792
793        fn get_point(&self, index: usize) -> Option<Array1<T>> {
794            if index < self.len() {
795                Some(self.array.row(index).to_owned())
796            } else {
797                None
798            }
799        }
800
801        fn iter_points(&self) -> Box<dyn Iterator<Item = Array1<T>> + '_> {
802            Box::new(self.array.axis_iter(Axis(0)).map(|row| row.to_owned()))
803        }
804    }
805}
806
807#[cfg(test)]
808mod tests {
809    use crate::{
810        ChebyshevMetric, DistanceMetric, EuclideanMetric, ManhattanMetric, Point, SpatialPoint,
811        SpatialScalar,
812    };
813    use approx::assert_relative_eq;
814
815    #[test]
816    fn test_spatial_scalar_traits() {
817        assert!(<f32 as SpatialScalar>::epsilon() > 0.0);
818        assert!(<f64 as SpatialScalar>::epsilon() > 0.0);
819        assert!(f32::max_finite().is_finite());
820        assert!(f64::max_finite().is_finite());
821    }
822
823    #[test]
824    fn test_point_operations() {
825        let p1 = Point::new_2d(1.0f64, 2.0);
826        let p2 = Point::new_2d(4.0, 6.0);
827
828        assert_eq!(p1.dimension(), 2);
829        assert_eq!(p1.coordinate(0), Some(1.0));
830        assert_eq!(p1.coordinate(1), Some(2.0));
831
832        let distance = p1.distance_to(&p2);
833        assert_relative_eq!(distance, 5.0, epsilon = 1e-10);
834
835        let manhattan = p1.manhattan_distance_to(&p2);
836        assert_relative_eq!(manhattan, 7.0, epsilon = 1e-10);
837    }
838
839    #[test]
840    fn test_point_arithmetic() {
841        let p1 = Point::new_3d(1.0f32, 2.0, 3.0);
842        let p2 = Point::new_3d(4.0, 5.0, 6.0);
843
844        let sum = p1.add(&p2).unwrap();
845        assert_eq!(sum.coordinate(0), Some(5.0));
846        assert_eq!(sum.coordinate(1), Some(7.0));
847        assert_eq!(sum.coordinate(2), Some(9.0));
848
849        let diff = p2.subtract(&p1).unwrap();
850        assert_eq!(diff.coordinate(0), Some(3.0));
851        assert_eq!(diff.coordinate(1), Some(3.0));
852        assert_eq!(diff.coordinate(2), Some(3.0));
853
854        let scaled = p1.scale(2.0);
855        assert_eq!(scaled.coordinate(0), Some(2.0));
856        assert_eq!(scaled.coordinate(1), Some(4.0));
857        assert_eq!(scaled.coordinate(2), Some(6.0));
858    }
859
860    #[test]
861    fn test_distance_metrics() {
862        use crate::generic_traits::DistanceMetric;
863
864        let p1 = Point::new_2d(0.0f64, 0.0);
865        let p2 = Point::new_2d(3.0, 4.0);
866
867        let euclidean = EuclideanMetric;
868        let manhattan = ManhattanMetric;
869        let chebyshev = ChebyshevMetric;
870
871        assert_relative_eq!(euclidean.distance(&p1, &p2), 5.0, epsilon = 1e-10);
872        assert_relative_eq!(manhattan.distance(&p1, &p2), 7.0, epsilon = 1e-10);
873        assert_relative_eq!(chebyshev.distance(&p1, &p2), 4.0, epsilon = 1e-10);
874    }
875
876    #[test]
877    fn test_vec_as_spatial_point() {
878        let p1 = vec![1.0f64, 2.0, 3.0];
879        let p2 = vec![4.0, 5.0, 6.0];
880
881        assert_eq!(p1.dimension(), 3);
882        assert_eq!(p1.coordinate(1), Some(2.0));
883
884        let distance = p1.distance_to(&p2);
885        // Euclidean distance: sqrt((4-1)² + (5-2)² + (6-3)²) = sqrt(9+9+9) = sqrt(27)
886        assert_relative_eq!(distance, (27.0f64).sqrt(), epsilon = 1e-10);
887    }
888
889    #[test]
890    fn test_array_as_spatial_point() {
891        let p1: [f32; 3] = [1.0, 2.0, 3.0];
892        let p2: [f32; 3] = [4.0, 5.0, 6.0];
893
894        assert_eq!(p1.dimension(), 3);
895        assert_eq!(p1.coordinate(2), Some(3.0));
896
897        let distance = p1.distance_to(&p2);
898        // Euclidean distance: sqrt((4-1)² + (5-2)² + (6-3)²) = sqrt(9+9+9) = sqrt(27)
899        assert_relative_eq!(distance, (27.0f32).sqrt(), epsilon = 1e-6);
900    }
901
902    #[test]
903    fn test_point_normalization() {
904        let p = Point::new_2d(3.0f64, 4.0);
905        let magnitude = p.magnitude();
906        assert_relative_eq!(magnitude, 5.0, epsilon = 1e-10);
907
908        let normalized = p.normalize();
909        assert_relative_eq!(normalized.magnitude(), 1.0, epsilon = 1e-10);
910        assert_relative_eq!(normalized.coordinate(0).unwrap(), 0.6, epsilon = 1e-10);
911        assert_relative_eq!(normalized.coordinate(1).unwrap(), 0.8, epsilon = 1e-10);
912    }
913
914    #[test]
915    fn test_dot_product() {
916        let p1 = Point::new_3d(1.0f64, 2.0, 3.0);
917        let p2 = Point::new_3d(4.0, 5.0, 6.0);
918
919        let dot = p1.dot(&p2).unwrap();
920        assert_relative_eq!(dot, 32.0, epsilon = 1e-10); // 1*4 + 2*5 + 3*6 = 32
921    }
922}