use crate::dimension::Dimension;
use crate::unit::base::BaseUnit;
pub const POGSON_RATIO: f64 = 2.511_886_431_509_58;
pub const LOG10_POGSON: f64 = 0.4;
pub const MAG_FACTOR: f64 = -2.5;
pub const MAG: BaseUnit = BaseUnit::new(
"magnitude",
"mag",
&["magnitudes"],
Dimension::MAGNITUDE,
1.0,
);
pub const APPARENT_MAG: BaseUnit = BaseUnit::new(
"apparent_magnitude",
"m_app",
&["app_mag", "apparent_mag"],
Dimension::MAGNITUDE,
1.0,
);
pub const ABSOLUTE_MAG: BaseUnit = BaseUnit::new(
"absolute_magnitude",
"M_abs",
&["abs_mag", "absolute_mag"],
Dimension::MAGNITUDE,
1.0,
);
pub const DB: BaseUnit = BaseUnit::new("decibel", "dB", &["decibels"], Dimension::MAGNITUDE, 1.0);
pub const BEL: BaseUnit = BaseUnit::new("bel", "B", &["bels"], Dimension::MAGNITUDE, 10.0);
pub const DEX: BaseUnit = BaseUnit::new("dex", "dex", &[], Dimension::MAGNITUDE, 1.0);
pub const MILLIMAG: BaseUnit = BaseUnit::new(
"millimagnitude",
"mmag",
&["millimag"],
Dimension::MAGNITUDE,
0.001,
);
#[inline]
pub fn mag_to_flux_ratio(mag: f64) -> f64 {
10.0_f64.powf(-LOG10_POGSON * mag)
}
#[inline]
pub fn flux_ratio_to_mag(flux_ratio: f64) -> Result<f64, crate::error::UnitError> {
if !flux_ratio.is_finite() || flux_ratio <= 0.0 {
return Err(crate::error::UnitError::LogarithmicError(
"flux ratio must be finite and positive".to_string(),
));
}
Ok(MAG_FACTOR * flux_ratio.log10())
}
#[inline]
pub fn db_to_power_ratio(db: f64) -> f64 {
10.0_f64.powf(db / 10.0)
}
#[inline]
pub fn power_ratio_to_db(power_ratio: f64) -> Result<f64, crate::error::UnitError> {
if !power_ratio.is_finite() || power_ratio <= 0.0 {
return Err(crate::error::UnitError::LogarithmicError(
"power ratio must be finite and positive".to_string(),
));
}
Ok(10.0 * power_ratio.log10())
}
#[inline]
pub fn db_to_amplitude_ratio(db: f64) -> f64 {
10.0_f64.powf(db / 20.0)
}
#[inline]
pub fn amplitude_ratio_to_db(amplitude_ratio: f64) -> Result<f64, crate::error::UnitError> {
if !amplitude_ratio.is_finite() || amplitude_ratio <= 0.0 {
return Err(crate::error::UnitError::LogarithmicError(
"amplitude ratio must be finite and positive".to_string(),
));
}
Ok(20.0 * amplitude_ratio.log10())
}
#[inline]
pub fn dex_to_ratio(dex: f64) -> f64 {
10.0_f64.powf(dex)
}
#[inline]
pub fn ratio_to_dex(ratio: f64) -> Result<f64, crate::error::UnitError> {
if !ratio.is_finite() || ratio <= 0.0 {
return Err(crate::error::UnitError::LogarithmicError(
"ratio must be finite and positive".to_string(),
));
}
Ok(ratio.log10())
}
#[inline]
pub fn combine_magnitudes(mag1: f64, mag2: f64) -> Result<f64, crate::error::UnitError> {
let flux1 = mag_to_flux_ratio(mag1);
let flux2 = mag_to_flux_ratio(mag2);
flux_ratio_to_mag(flux1 + flux2)
}
#[inline]
pub fn magnitude_difference(mag_bright: f64, mag_faint: f64) -> f64 {
mag_faint - mag_bright
}
#[inline]
pub fn distance_modulus(apparent_mag: f64, absolute_mag: f64) -> f64 {
apparent_mag - absolute_mag
}
#[inline]
pub fn distance_from_modulus(distance_modulus: f64) -> f64 {
10.0_f64.powf((distance_modulus + 5.0) / 5.0)
}
#[inline]
pub fn modulus_from_distance(distance_pc: f64) -> Result<f64, crate::error::UnitError> {
if !distance_pc.is_finite() || distance_pc <= 0.0 {
return Err(crate::error::UnitError::LogarithmicError(
"distance must be finite and positive".to_string(),
));
}
Ok(5.0 * distance_pc.log10() - 5.0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mag_to_flux_roundtrip() {
for mag in [-5.0, -1.0, 0.0, 1.0, 5.0, 10.0, 20.0] {
let flux = mag_to_flux_ratio(mag);
let back = flux_ratio_to_mag(flux).unwrap();
assert!(
(back - mag).abs() < 1e-10,
"roundtrip failed for mag={}",
mag
);
}
}
#[test]
fn test_mag_5_is_100x_fainter() {
let flux_0 = mag_to_flux_ratio(0.0);
let flux_5 = mag_to_flux_ratio(5.0);
assert!((flux_0 / flux_5 - 100.0).abs() < 1e-10);
}
#[test]
fn test_db_to_power_roundtrip() {
for db in [-20.0, -10.0, -3.0, 0.0, 3.0, 10.0, 20.0] {
let power = db_to_power_ratio(db);
let back = power_ratio_to_db(power).unwrap();
assert!((back - db).abs() < 1e-10, "roundtrip failed for db={}", db);
}
}
#[test]
fn test_3db_is_2x_power() {
let power_3db = db_to_power_ratio(3.0);
assert!((power_3db - 2.0).abs() < 0.01);
}
#[test]
fn test_10db_is_10x_power() {
let power_10db = db_to_power_ratio(10.0);
assert!((power_10db - 10.0).abs() < 1e-10);
}
#[test]
fn test_dex_roundtrip() {
for dex in [-2.0, -1.0, -0.5, 0.0, 0.5, 1.0, 2.0] {
let ratio = dex_to_ratio(dex);
let back = ratio_to_dex(ratio).unwrap();
assert!(
(back - dex).abs() < 1e-10,
"roundtrip failed for dex={}",
dex
);
}
}
#[test]
fn test_combine_equal_magnitudes() {
let combined = combine_magnitudes(0.0, 0.0).unwrap();
let expected = flux_ratio_to_mag(2.0).unwrap(); assert!((combined - expected).abs() < 1e-10);
}
#[test]
fn test_distance_modulus() {
let mu = modulus_from_distance(10.0).unwrap();
assert!((mu - 0.0).abs() < 1e-10);
let mu_100 = modulus_from_distance(100.0).unwrap();
assert!((mu_100 - 5.0).abs() < 1e-10);
let dist_back = distance_from_modulus(mu_100);
assert!((dist_back - 100.0).abs() < 1e-10);
}
#[test]
fn test_error_on_non_positive_inputs() {
assert!(flux_ratio_to_mag(0.0).is_err());
assert!(power_ratio_to_db(0.0).is_err());
assert!(amplitude_ratio_to_db(0.0).is_err());
assert!(ratio_to_dex(0.0).is_err());
assert!(modulus_from_distance(0.0).is_err());
assert!(flux_ratio_to_mag(-1.0).is_err());
assert!(power_ratio_to_db(-1.0).is_err());
assert!(amplitude_ratio_to_db(-1.0).is_err());
assert!(ratio_to_dex(-1.0).is_err());
assert!(modulus_from_distance(-1.0).is_err());
assert!(flux_ratio_to_mag(f64::NAN).is_err());
assert!(power_ratio_to_db(f64::NAN).is_err());
assert!(amplitude_ratio_to_db(f64::NAN).is_err());
assert!(ratio_to_dex(f64::NAN).is_err());
assert!(modulus_from_distance(f64::NAN).is_err());
}
#[test]
fn test_unit_dimension() {
assert_eq!(MAG.dimension(), Dimension::MAGNITUDE);
assert_eq!(DB.dimension(), Dimension::MAGNITUDE);
assert_eq!(DEX.dimension(), Dimension::MAGNITUDE);
assert_eq!(APPARENT_MAG.dimension(), Dimension::MAGNITUDE);
assert_eq!(ABSOLUTE_MAG.dimension(), Dimension::MAGNITUDE);
}
#[test]
fn test_millimag_scale() {
let q = 1000.0 * MILLIMAG;
let q_mag = q.to(MAG).unwrap();
assert!((q_mag.value() - 1.0).abs() < 1e-10);
}
#[test]
fn test_bel_to_db() {
let q = 1.0 * BEL;
let q_db = q.to(DB).unwrap();
assert!((q_db.value() - 10.0).abs() < 1e-10);
}
}