use core::f64::consts::PI;
use core::marker::PhantomData;
use core::ops::{Add, Div, Mul, Neg, Sub};
#[allow(unused_imports)]
use crate::math::F64Ext;
pub mod cio_chain;
pub mod cip;
pub mod fundamental_arguments;
pub mod precession;
pub(crate) mod tables_gen;
#[cfg(test)]
mod tables_pin;
mod sealed {
pub trait Sealed {}
}
pub trait AngleUnit: sealed::Sealed {
const TO_RADIANS: f64;
const NAME: &'static str;
}
pub struct Radians;
impl sealed::Sealed for Radians {}
impl AngleUnit for Radians {
const TO_RADIANS: f64 = 1.0;
const NAME: &'static str = "rad";
}
pub struct Arcseconds;
impl sealed::Sealed for Arcseconds {}
impl AngleUnit for Arcseconds {
const TO_RADIANS: f64 = DAS2R;
const NAME: &'static str = "arcsec";
}
pub struct Milliarcseconds;
impl sealed::Sealed for Milliarcseconds {}
impl AngleUnit for Milliarcseconds {
const TO_RADIANS: f64 = DMAS2R;
const NAME: &'static str = "mas";
}
pub struct Microarcseconds;
impl sealed::Sealed for Microarcseconds {}
impl AngleUnit for Microarcseconds {
const TO_RADIANS: f64 = DUAS2R;
const NAME: &'static str = "µas";
}
#[repr(transparent)]
pub struct Angle<U: AngleUnit>(f64, PhantomData<U>);
impl<U: AngleUnit> Clone for Angle<U> {
#[inline]
fn clone(&self) -> Self {
*self
}
}
impl<U: AngleUnit> Copy for Angle<U> {}
pub type Rad = Angle<Radians>;
pub type Arcsec = Angle<Arcseconds>;
pub type Mas = Angle<Milliarcseconds>;
pub type Uas = Angle<Microarcseconds>;
impl<U: AngleUnit> Angle<U> {
#[inline]
pub const fn new(value: f64) -> Self {
Self(value, PhantomData)
}
#[inline]
pub const fn raw(self) -> f64 {
self.0
}
#[inline]
pub const fn zero() -> Self {
Self::new(0.0)
}
#[inline]
pub fn to_radians(self) -> Rad {
Rad::new(self.0 * U::TO_RADIANS)
}
#[inline]
pub fn is_finite(self) -> bool {
self.0.is_finite()
}
}
impl Rad {
#[inline]
pub fn sin(self) -> f64 {
self.0.sin()
}
#[inline]
pub fn cos(self) -> f64 {
self.0.cos()
}
#[inline]
pub fn tan(self) -> f64 {
self.0.tan()
}
}
impl<U: AngleUnit> core::fmt::Debug for Angle<U> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Angle<{}>({})", U::NAME, self.0)
}
}
impl<U: AngleUnit> Add for Angle<U> {
type Output = Self;
#[inline]
fn add(self, rhs: Self) -> Self {
Self::new(self.0 + rhs.0)
}
}
impl<U: AngleUnit> Sub for Angle<U> {
type Output = Self;
#[inline]
fn sub(self, rhs: Self) -> Self {
Self::new(self.0 - rhs.0)
}
}
impl<U: AngleUnit> Neg for Angle<U> {
type Output = Self;
#[inline]
fn neg(self) -> Self {
Self::new(-self.0)
}
}
impl<U: AngleUnit> Mul<f64> for Angle<U> {
type Output = Self;
#[inline]
fn mul(self, rhs: f64) -> Self {
Self::new(self.0 * rhs)
}
}
impl<U: AngleUnit> Mul<Angle<U>> for f64 {
type Output = Angle<U>;
#[inline]
fn mul(self, rhs: Angle<U>) -> Angle<U> {
Angle::new(self * rhs.0)
}
}
impl<U: AngleUnit> Div<f64> for Angle<U> {
type Output = Self;
#[inline]
fn div(self, rhs: f64) -> Self {
Self::new(self.0 / rhs)
}
}
pub const DAS2R: f64 = PI / (180.0 * 3600.0);
pub const DMAS2R: f64 = DAS2R * 1e-3;
pub const DUAS2R: f64 = DAS2R * 1e-6;
pub const TURNAS: f64 = 1_296_000.0;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn das2r_matches_sofa_published_value() {
let sofa_published = 4.848_136_811_095_359_9e-6;
assert!(
(DAS2R - sofa_published).abs() < 1e-22,
"DAS2R = {DAS2R}, expected {sofa_published}"
);
}
#[test]
fn one_degree_roundtrips_via_arcsec() {
let one_deg = Arcsec::new(3600.0).to_radians();
assert!((one_deg.raw() - PI / 180.0).abs() < 1e-15);
}
#[test]
fn sub_arcsec_units_agree_with_arcsec() {
let one_arcsec = Arcsec::new(1.0).to_radians();
let mas_form = Mas::new(1000.0).to_radians();
let uas_form = Uas::new(1_000_000.0).to_radians();
assert!((mas_form.raw() - one_arcsec.raw()).abs() < 1e-20);
assert!((uas_form.raw() - one_arcsec.raw()).abs() < 1e-18);
}
#[test]
fn arithmetic_operators_match_underlying_f64() {
let a = Arcsec::new(10.0);
let b = Arcsec::new(3.0);
assert_eq!((a + b).raw(), 13.0);
assert_eq!((a - b).raw(), 7.0);
assert_eq!((-a).raw(), -10.0);
assert_eq!((a * 2.5).raw(), 25.0);
assert_eq!((2.5 * a).raw(), 25.0);
assert_eq!((a / 4.0).raw(), 2.5);
}
#[test]
fn rad_trig_uses_radian_argument() {
let r = Rad::new(PI / 2.0);
assert!((r.sin() - 1.0).abs() < 1e-15);
assert!(r.cos().abs() < 1e-15);
}
}