siderust 0.6.0

High-precision astronomy and satellite mechanics in Rust.
Documentation
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2026 Vallés Puig, Ramon

// src/macros.rs
use crate::coordinates::{cartesian, centers::ReferenceCenter, frames::ReferenceFrame, spherical};
use core::f64;
use qtty::{LengthUnit, Quantity};

#[doc(hidden)]
pub(crate) fn __assert_cartesian_eq<C, F, U>(
    a: &cartesian::Position<C, F, U>,
    b: &cartesian::Position<C, F, U>,
    epsilon: f64,
    msg: Option<String>,
) where
    C: ReferenceCenter,
    F: ReferenceFrame,
    U: LengthUnit,
    Quantity<U>: std::cmp::PartialOrd + std::fmt::Display,
{
    let dx = (a.x() - b.x()).abs();
    let dy = (a.y() - b.y()).abs();
    let dz = (a.z() - b.z()).abs();
    if dx >= Quantity::<U>::new(epsilon)
        || dy >= Quantity::<U>::new(epsilon)
        || dz >= Quantity::<U>::new(epsilon)
    {
        if let Some(m) = msg {
            panic!(
                "{}. Cartesian coords differ: {} vs {} (ε = {})",
                m, a, b, epsilon
            );
        } else {
            panic!("Cartesian coords differ: {} vs {} (ε = {})", a, b, epsilon);
        }
    }
}

#[doc(hidden)]
pub(crate) fn __assert_spherical_eq<C, F, U>(
    a: &spherical::Position<C, F, U>,
    b: &spherical::Position<C, F, U>,
    epsilon: f64,
    msg: Option<String>,
) where
    C: ReferenceCenter,
    F: ReferenceFrame,
    U: LengthUnit,
    Quantity<U>: std::cmp::PartialOrd + std::fmt::Display,
{
    let d1 = a.distance;
    let d2 = b.distance;
    let dp = (a.polar - b.polar).abs();
    let da = (a.azimuth - b.azimuth).abs();
    if (d1 - d2).abs() >= Quantity::<U>::new(epsilon) || dp >= epsilon || da >= epsilon {
        if let Some(m) = msg {
            panic!(
                "{}. Spherical coords differ: {} vs {} (ε = {})",
                m, a, b, epsilon
            );
        } else {
            panic!("Spherical coords differ: {} vs {} (ε = {})", a, b, epsilon);
        }
    }
}

#[macro_export]
macro_rules! assert_cartesian_eq {
    ($a:expr, $b:expr, $eps:expr $(,)?) => {{
        fn _check<T>(_: &T, _: &T) {}
        _check(&$a, &$b);
        $crate::macros::__assert_cartesian_eq(&$a, &$b, $eps, None);
    }};
    ($a:expr, $b:expr, $eps:expr, $($msg:tt)+) => {{
        fn _check<T>(_: &T, _: &T) {}
        _check(&$a, &$b);
        $crate::macros::__assert_cartesian_eq(
            &$a,
            &$b,
            $eps,
            Some(format!($($msg)+))
        );
    }};
}

#[macro_export]
macro_rules! assert_spherical_eq {
    ($a:expr, $b:expr, $eps:expr $(,)?) => {{
        fn _check<T>(_: &T, _: &T) {}
        _check(&$a, &$b);
        $crate::macros::__assert_spherical_eq(&$a, &$b, $eps, None);
    }};
    ($a:expr, $b:expr, $eps:expr, $($msg:tt)+) => {{
        fn _check<T>(_: &T, _: &T) {}
        _check(&$a, &$b);
        $crate::macros::__assert_spherical_eq(
            &$a,
            &$b,
            $eps,
            Some(format!($($msg)+))
        );
    }};
}

#[allow(unused_imports)]
pub(crate) use assert_cartesian_eq;
#[allow(unused_imports)]
pub(crate) use assert_spherical_eq;

#[cfg(test)]
mod tests {
    use crate::coordinates::{cartesian, spherical};
    use qtty::{AstronomicalUnit, Degrees, AU};

    #[test]
    #[should_panic(expected = "Cartesian coords differ")]
    fn cartesian_macro_panics_on_mismatch() {
        let a = cartesian::position::ICRS::<AstronomicalUnit>::new(0.0 * AU, 0.0 * AU, 0.0 * AU);
        let b = cartesian::position::ICRS::<AstronomicalUnit>::new(1.0 * AU, 0.0 * AU, 0.0 * AU);
        assert_cartesian_eq!(a, b, 1e-6);
    }

    #[test]
    #[should_panic(expected = "custom cart message")]
    fn cartesian_macro_reports_custom_message() {
        let a = cartesian::position::ICRS::<AstronomicalUnit>::new(0.0 * AU, 0.0 * AU, 0.0 * AU);
        let b = cartesian::position::ICRS::<AstronomicalUnit>::new(0.0 * AU, 1.0 * AU, 0.0 * AU);
        assert_cartesian_eq!(a, b, 1e-8, "custom cart message");
    }

    #[test]
    #[should_panic(expected = "Spherical coords differ")]
    fn spherical_macro_panics_on_mismatch() {
        let a = spherical::position::EquatorialMeanJ2000::<AstronomicalUnit>::new(
            Degrees::new(0.0),
            Degrees::new(0.0),
            1.0,
        );
        let b = spherical::position::EquatorialMeanJ2000::<AstronomicalUnit>::new(
            Degrees::new(10.0),
            Degrees::new(0.0),
            1.0,
        );
        assert_spherical_eq!(a, b, 1e-6);
    }

    #[test]
    #[should_panic(expected = "custom spherical message")]
    fn spherical_macro_reports_custom_message() {
        let a = spherical::position::EquatorialMeanJ2000::<AstronomicalUnit>::new(
            Degrees::new(0.0),
            Degrees::new(0.0),
            1.0,
        );
        let b = spherical::position::EquatorialMeanJ2000::<AstronomicalUnit>::new(
            Degrees::new(0.0),
            Degrees::new(20.0),
            1.0,
        );
        assert_spherical_eq!(a, b, 1e-6, "custom spherical message");
    }
}