use geometry_coords::CoordinateScalar;
use geometry_cs::{CartesianFamily, CoordinateSystem};
use geometry_tag::SameAs;
use geometry_trait::Point;
use crate::distance::{DefaultDistance, DistanceStrategy};
#[derive(Debug, Default, Clone, Copy)]
pub struct Pythagoras;
#[derive(Debug, Default, Clone, Copy)]
pub struct ComparablePythagoras;
impl<P1, P2> DistanceStrategy<P1, P2> for Pythagoras
where
P1: Point,
P2: Point<Scalar = P1::Scalar>,
<P1::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
<P2::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
{
type Out = P1::Scalar;
type Comparable = ComparablePythagoras;
#[inline]
fn distance(&self, a: &P1, b: &P2) -> Self::Out {
sum_squared_diffs::<P1, P2>(a, b).sqrt()
}
#[inline]
fn comparable(&self) -> Self::Comparable {
ComparablePythagoras
}
}
impl<P1, P2> DistanceStrategy<P1, P2> for ComparablePythagoras
where
P1: Point,
P2: Point<Scalar = P1::Scalar>,
<P1::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
<P2::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
{
type Out = P1::Scalar;
type Comparable = Self;
#[inline]
fn distance(&self, a: &P1, b: &P2) -> Self::Out {
sum_squared_diffs::<P1, P2>(a, b)
}
#[inline]
fn comparable(&self) -> Self::Comparable {
ComparablePythagoras
}
}
impl DefaultDistance<CartesianFamily> for CartesianFamily {
type Strategy = Pythagoras;
}
const MAX_DIM: usize = 4;
#[inline]
fn sum_squared_diffs<P1, P2>(a: &P1, b: &P2) -> P1::Scalar
where
P1: Point,
P2: Point<Scalar = P1::Scalar>,
{
match P1::DIM {
1 => <Walk<0, 1> as SumSquares<0, 1>>::step(P1::Scalar::ZERO, a, b),
2 => <Walk<0, 2> as SumSquares<0, 2>>::step(P1::Scalar::ZERO, a, b),
3 => <Walk<0, 3> as SumSquares<0, 3>>::step(P1::Scalar::ZERO, a, b),
4 => <Walk<0, 4> as SumSquares<0, 4>>::step(P1::Scalar::ZERO, a, b),
_ => panic!("Pythagoras: P1::DIM exceeds MAX_DIM ({MAX_DIM})"),
}
}
struct Walk<const I: usize, const N: usize>;
trait SumSquares<const I: usize, const N: usize>: sealed::Sealed<I, N> {
fn step<P1, P2>(acc: P1::Scalar, a: &P1, b: &P2) -> P1::Scalar
where
P1: Point,
P2: Point<Scalar = P1::Scalar>;
}
mod sealed {
pub trait Sealed<const I: usize, const N: usize> {}
}
impl<const N: usize> sealed::Sealed<N, N> for Walk<N, N> {}
impl<const N: usize> SumSquares<N, N> for Walk<N, N> {
#[inline]
fn step<P1, P2>(acc: P1::Scalar, _a: &P1, _b: &P2) -> P1::Scalar
where
P1: Point,
P2: Point<Scalar = P1::Scalar>,
{
acc
}
}
macro_rules! impl_sum_squares {
($i:expr, $n:expr) => {
impl sealed::Sealed<$i, $n> for Walk<$i, $n> {}
impl SumSquares<$i, $n> for Walk<$i, $n> {
#[inline]
fn step<P1, P2>(acc: P1::Scalar, a: &P1, b: &P2) -> P1::Scalar
where
P1: Point,
P2: Point<Scalar = P1::Scalar>,
{
let d = a.get::<$i>() - b.get::<$i>();
let acc = acc + d * d;
<Walk<{ $i + 1 }, $n> as SumSquares<{ $i + 1 }, $n>>::step(acc, a, b)
}
}
};
}
impl_sum_squares!(0, 1);
impl_sum_squares!(0, 2);
impl_sum_squares!(1, 2);
impl_sum_squares!(0, 3);
impl_sum_squares!(1, 3);
impl_sum_squares!(2, 3);
impl_sum_squares!(0, 4);
impl_sum_squares!(1, 4);
impl_sum_squares!(2, 4);
impl_sum_squares!(3, 4);
#[cfg(test)]
mod tests {
use super::{ComparablePythagoras, Pythagoras};
use crate::distance::DistanceStrategy;
use geometry_cs::Cartesian;
use geometry_model::{Point2D, Point3D};
#[test]
fn three_four_five_2d() {
let a = Point2D::<f64, Cartesian>::new(0.0, 0.0);
let b = Point2D::<f64, Cartesian>::new(3.0, 4.0);
assert!((Pythagoras.distance(&a, &b) - 5.0).abs() < 1e-12);
assert!((ComparablePythagoras.distance(&a, &b) - 25.0).abs() < 1e-12);
}
#[test]
fn unit_axis_3d() {
let o = Point3D::<f64, Cartesian>::new(0.0, 0.0, 0.0);
let px = Point3D::<f64, Cartesian>::new(1.0, 0.0, 0.0);
let py = Point3D::<f64, Cartesian>::new(0.0, 1.0, 0.0);
let pz = Point3D::<f64, Cartesian>::new(0.0, 0.0, 1.0);
assert!((Pythagoras.distance(&o, &px) - 1.0).abs() < 1e-12);
assert!((Pythagoras.distance(&o, &py) - 1.0).abs() < 1e-12);
assert!((Pythagoras.distance(&o, &pz) - 1.0).abs() < 1e-12);
}
#[test]
fn arbitrary_3d() {
let a = Point3D::<f64, Cartesian>::new(1.0, 2.0, 3.0);
let b = Point3D::<f64, Cartesian>::new(9.0, 8.0, 7.0);
let d = Pythagoras.distance(&a, &b);
assert!((d - 10.770_329_614_269_007).abs() < 1e-9);
assert!((ComparablePythagoras.distance(&a, &b) - 116.0).abs() < 1e-12);
}
#[test]
fn symmetric_in_arguments() {
let a = Point3D::<f64, Cartesian>::new(1.0, 2.0, 3.0);
let b = Point3D::<f64, Cartesian>::new(4.0, 5.0, 6.0);
let ab = Pythagoras.distance(&a, &b);
let ba = Pythagoras.distance(&b, &a);
assert!((ab - ba).abs() < 1e-12);
assert!((ab - 27.0_f64.sqrt()).abs() < 1e-12);
}
#[test]
fn comparable_orders_match_real_distance() {
let o = Point2D::<f64, Cartesian>::new(0.0, 0.0);
let p_25 = Point2D::<f64, Cartesian>::new(3.0, 4.0);
let p_50 = Point2D::<f64, Cartesian>::new(5.0, 5.0);
let c25 = ComparablePythagoras.distance(&o, &p_25);
let c50 = ComparablePythagoras.distance(&o, &p_50);
assert!((c25 - 25.0).abs() < 1e-12);
assert!((c50 - 50.0).abs() < 1e-12);
assert!(c25 < c50);
}
fn _accepts_readonly_point<P, S>(s: &S, a: &P, b: &P) -> S::Out
where
P: geometry_trait::Point,
S: DistanceStrategy<P, P>,
{
s.distance(a, b)
}
}
#[cfg(test)]
mod large_coordinate_tests {
use super::{ComparablePythagoras, Pythagoras};
use crate::distance::DistanceStrategy;
use geometry_cs::Cartesian;
use geometry_model::Point2D;
const REF: f64 = 1_076_554.548_583_395_567_829_438_778_905_7;
fn close(actual: f64, expected: f64) -> bool {
(actual - expected).abs() <= expected.abs() * 1e-5
}
#[test]
fn big_2d_f64_x_f64() {
let p1 = Point2D::<f64, Cartesian>::new(123_456.789_000_01, 234_567.891_000_01);
let p2 = Point2D::<f64, Cartesian>::new(987_654.321_000_01, 876_543.219_000_01);
let d = Pythagoras.distance(&p1, &p2);
assert!(close(d, REF), "got {d} expected ≈ {REF} (within 0.001%)");
}
#[test]
fn big_2d_comparable() {
let p1 = Point2D::<f64, Cartesian>::new(123_456.789_000_01, 234_567.891_000_01);
let p2 = Point2D::<f64, Cartesian>::new(987_654.321_000_01, 876_543.219_000_01);
let cmp = ComparablePythagoras.distance(&p1, &p2);
let expected = REF * REF;
assert!(
(cmp - expected).abs() <= expected.abs() * 1e-5,
"got {cmp} expected ≈ {expected} (within 0.001%)",
);
}
#[test]
fn big_2d_from_string_parse() {
let p1 = Point2D::<f64, Cartesian>::new(
"123456.78900001".parse::<f64>().unwrap(),
"234567.89100001".parse::<f64>().unwrap(),
);
let p2 = Point2D::<f64, Cartesian>::new(
"987654.32100001".parse::<f64>().unwrap(),
"876543.21900001".parse::<f64>().unwrap(),
);
let d = Pythagoras.distance(&p1, &p2);
assert!(close(d, REF));
}
}