use super::{Converter, Equivalency};
use crate::dimension::Dimension;
use crate::unit::Unit;
const PARSEC_M: f64 = 3.085_677_581_491_367e16;
const ARCSEC_RAD: f64 = std::f64::consts::PI / 180.0 / 3600.0;
fn is_angle(unit: &Unit) -> bool {
unit.dimension() == Dimension::ANGLE
}
fn is_length(unit: &Unit) -> bool {
unit.dimension() == Dimension::LENGTH
}
pub fn parallax() -> Equivalency {
Equivalency::new("parallax", |from, to| {
let (is_angle_to_dist, _from_scale, _to_scale) = if is_angle(from) && is_length(to) {
(true, from.scale(), to.scale())
} else if is_length(from) && is_angle(to) {
(false, from.scale(), to.scale())
} else {
return None;
};
if is_angle_to_dist {
Some(Converter::new(
move |p_rad| {
if p_rad <= 0.0 {
return Err(format!("parallax angle must be positive, got {}", p_rad));
}
Ok(PARSEC_M * ARCSEC_RAD / p_rad)
},
move |d_m| {
if d_m <= 0.0 {
return Err(format!("distance must be positive, got {}", d_m));
}
Ok(PARSEC_M * ARCSEC_RAD / d_m)
},
))
} else {
Some(Converter::new(
move |d_m| {
if d_m <= 0.0 {
return Err(format!("distance must be positive, got {}", d_m));
}
Ok(PARSEC_M * ARCSEC_RAD / d_m)
},
move |p_rad| {
if p_rad <= 0.0 {
return Err(format!("parallax angle must be positive, got {}", p_rad));
}
Ok(PARSEC_M * ARCSEC_RAD / p_rad)
},
))
}
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::systems::astrophysical::PARSEC;
use crate::systems::si::{ARCSEC, MAS};
#[test]
fn test_parallax_1_arcsec() {
let plx = 1.0 * ARCSEC.clone();
let dist = plx.to_equiv(&PARSEC, parallax()).unwrap();
assert!((dist.value() - 1.0).abs() < 1e-10);
}
#[test]
fn test_parallax_proxima_centauri() {
let plx = 768.5 * MAS.clone();
let dist = plx.to_equiv(&PARSEC, parallax()).unwrap();
let expected = 1.0 / 0.7685; assert!((dist.value() - expected).abs() / expected < 1e-6);
}
#[test]
fn test_distance_to_parallax() {
let dist = 10.0 * PARSEC.clone();
let plx = dist.to_equiv(&ARCSEC, parallax()).unwrap();
assert!((plx.value() - 0.1).abs() < 1e-10);
}
#[test]
fn test_parallax_roundtrip() {
let plx = 0.5 * ARCSEC.clone();
let dist = plx.to_equiv(&PARSEC, parallax()).unwrap();
let plx_back = dist.to_equiv(&ARCSEC, parallax()).unwrap();
assert!((plx.value() - plx_back.value()).abs() / plx.value() < 1e-10);
}
#[test]
fn test_zero_parallax_fails() {
let plx = 0.0 * ARCSEC.clone();
let result = plx.to_equiv(&PARSEC, parallax());
assert!(result.is_err());
}
#[test]
fn test_negative_parallax_fails() {
let plx = -1.0 * ARCSEC.clone();
let result = plx.to_equiv(&PARSEC, parallax());
assert!(result.is_err());
}
#[test]
fn test_zero_distance_fails() {
let dist = 0.0 * PARSEC.clone();
let result = dist.to_equiv(&ARCSEC, parallax());
assert!(result.is_err());
}
}