1use scirs2_core::ndarray::Array1;
37use scirs2_core::numeric::{Float, NumCast};
38use scirs2_core::simd_ops::SimdUnifiedOps;
39use std::fmt::Debug;
40
41pub trait SpatialScalar:
46 Float + Debug + Default + NumCast + Send + Sync + SimdUnifiedOps + 'static
47{
48 fn epsilon() -> Self;
50
51 fn max_finite() -> Self;
53
54 fn from_f64(value: f64) -> Option<Self> {
56 NumCast::from(value)
57 }
58
59 fn to_f64(&self) -> Option<f64> {
61 NumCast::from(*self)
62 }
63
64 fn sqrt(&self) -> Self {
66 Float::sqrt(*self)
67 }
68
69 fn abs(&self) -> Self {
71 Float::abs(*self)
72 }
73
74 fn powf(&self, exp: Self) -> Self {
76 Float::powf(*self, exp)
77 }
78
79 fn ln(&self) -> Self {
81 Float::ln(*self)
82 }
83
84 fn exp(&self) -> Self {
86 Float::exp(*self)
87 }
88
89 fn sin(&self) -> Self {
91 Float::sin(*self)
92 }
93
94 fn cos(&self) -> Self {
96 Float::cos(*self)
97 }
98
99 fn atan2(&self, other: Self) -> Self {
101 Float::atan2(*self, other)
102 }
103
104 fn is_finite(&self) -> bool {
106 Float::is_finite(*self)
107 }
108
109 fn is_nan(&self) -> bool {
111 Float::is_nan(*self)
112 }
113
114 fn simd_squared_euclidean_distance(_a: &[Self], b: &[Self]) -> Result<Self, &'static str> {
116 Err("SIMD not available for this type")
117 }
118
119 fn simd_manhattan_distance(_a: &[Self], b: &[Self]) -> Result<Self, &'static str> {
121 Err("SIMD not available for this type")
122 }
123
124 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
225pub trait SpatialPoint<T: SpatialScalar> {
230 fn dimension(&self) -> usize;
232
233 fn coordinate(&self, index: usize) -> Option<T>;
235
236 fn as_slice(&self) -> Option<&[T]> {
238 None
239 }
240
241 fn from_coords(coords: &[T]) -> Self;
243
244 fn squared_distance_to(&self, other: &Self) -> T {
246 if self.dimension() != other.dimension() {
247 return T::max_finite();
248 }
249
250 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 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 fn distance_to(&self, other: &Self) -> T {
270 self.squared_distance_to(other).sqrt()
271 }
272
273 fn manhattan_distance_to(&self, other: &Self) -> T {
275 if self.dimension() != other.dimension() {
276 return T::max_finite();
277 }
278
279 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 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
297pub trait SpatialArray<T: SpatialScalar, P: SpatialPoint<T>> {
302 fn len(&self) -> usize;
304
305 fn is_empty(&self) -> bool {
307 self.len() == 0
308 }
309
310 fn dimension(&self) -> Option<usize>;
312
313 fn get_point(&self, index: usize) -> Option<P>;
315
316 fn iter_points(&self) -> Box<dyn Iterator<Item = P> + '_>;
318
319 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
348pub trait DistanceMetric<T: SpatialScalar, P: SpatialPoint<T>> {
350 fn distance(&self, p1: &P, p2: &P) -> T;
352
353 fn squared_distance(&self, p1: &P, p2: &P) -> Option<T> {
355 None
356 }
357
358 fn is_metric(&self) -> bool {
360 true
361 }
362
363 fn name(&self) -> &'static str;
365}
366
367#[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#[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#[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
426impl<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
445impl<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 unreachable!(
464 "&[T]::from_coords() should not be called - &[T] is a reference to existing data"
465 )
466 }
467}
468
469impl<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#[derive(Debug, Clone, PartialEq)]
494pub struct Point<T: SpatialScalar> {
495 coords: Vec<T>,
496}
497
498impl<T: SpatialScalar> Point<T> {
499 pub fn new(coords: Vec<T>) -> Self {
501 Self { coords }
502 }
503
504 pub fn zeros(dim: usize) -> Self {
506 Self {
507 coords: vec![T::zero(); dim],
508 }
509 }
510
511 pub fn new_2d(x: T, y: T) -> Self {
513 Self { coords: vec![x, y] }
514 }
515
516 pub fn new_3d(x: T, y: T, z: T) -> Self {
518 Self {
519 coords: vec![x, y, z],
520 }
521 }
522
523 pub fn coords(&self) -> &[T] {
525 &self.coords
526 }
527
528 pub fn coords_mut(&mut self) -> &mut [T] {
530 &mut self.coords
531 }
532
533 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 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 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 pub fn dot(&self, other: &Point<T>) -> Option<T> {
573 if self.dimension() != other.dimension() {
574 return None;
575 }
576
577 if let Ok(simd_result) = T::simd_dot_product(&self.coords, &other.coords) {
579 return Some(simd_result);
580 }
581
582 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 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 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
631pub mod utils {
633 use super::*;
634
635 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 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 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 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 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
722pub mod ndarray_integration {
724 use super::{SpatialArray, SpatialPoint, SpatialScalar};
725 use scirs2_core::ndarray::{Array1, ArrayView1, ArrayView2, Axis};
726
727 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 unreachable!("ArrayView1::from_coords() should not be called - ArrayView1 is a view into existing data")
747 }
748 }
749
750 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 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 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 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); }
922}