#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Band {
GalexFuv,
GalexNuv,
SdssU,
SdssG,
SdssR,
SdssI,
SdssZ,
TwoMassJ,
TwoMassH,
TwoMassK,
GaiaG,
GaiaBp,
GaiaRp,
HipparcosHp,
JohnsonV,
JohnsonB,
}
pub trait Photometry {
fn flux_nmgy(&self, _band: Band) -> Option<f64> {
None
}
fn flux_ivar(&self, _band: Band) -> Option<f64> {
None
}
fn extinction_mag(&self, _band: Band) -> Option<f64> {
None
}
fn k_correction_mag(&self, _band: Band) -> Option<f64> {
None
}
fn ab_magnitude(&self, band: Band, deredden: bool, k_correct: bool) -> Option<f64> {
let f = self.flux_nmgy(band)?;
if f <= 0.0 {
return None;
}
let mut m = 22.5 - 2.5 * f.log10();
if deredden {
m -= self.extinction_mag(band).unwrap_or(0.0);
}
if k_correct {
m -= self.k_correction_mag(band).unwrap_or(0.0);
}
Some(m)
}
}
#[cfg(test)]
mod tests {
use super::*;
struct NoPhotometry;
impl Photometry for NoPhotometry {}
#[test]
fn default_impl_returns_none_everywhere() {
let s = NoPhotometry;
assert!(s.flux_nmgy(Band::SdssR).is_none());
assert!(s.flux_ivar(Band::SdssR).is_none());
assert!(s.extinction_mag(Band::SdssR).is_none());
assert!(s.k_correction_mag(Band::SdssR).is_none());
assert!(s.ab_magnitude(Band::SdssR, true, true).is_none());
}
struct OneBand {
r_flux: f64,
r_ext: f64,
r_kcorr: f64,
}
impl Photometry for OneBand {
fn flux_nmgy(&self, band: Band) -> Option<f64> {
match band {
Band::SdssR => Some(self.r_flux),
_ => None,
}
}
fn extinction_mag(&self, band: Band) -> Option<f64> {
match band {
Band::SdssR => Some(self.r_ext),
_ => None,
}
}
fn k_correction_mag(&self, band: Band) -> Option<f64> {
match band {
Band::SdssR => Some(self.r_kcorr),
_ => None,
}
}
}
#[test]
fn ab_magnitude_inverts_nanomaggies_at_zeropoint() {
let s = OneBand {
r_flux: 1.0,
r_ext: 0.0,
r_kcorr: 0.0,
};
let m = s.ab_magnitude(Band::SdssR, false, false).unwrap();
assert!((m - 22.5).abs() < 1e-12, "expected 22.5, got {m}");
}
#[test]
fn ab_magnitude_applies_corrections_only_when_requested() {
let s = OneBand {
r_flux: 1.0,
r_ext: 0.10,
r_kcorr: 0.25,
};
let raw = s.ab_magnitude(Band::SdssR, false, false).unwrap();
let dered = s.ab_magnitude(Band::SdssR, true, false).unwrap();
let kcorr = s.ab_magnitude(Band::SdssR, false, true).unwrap();
let both = s.ab_magnitude(Band::SdssR, true, true).unwrap();
assert!((raw - 22.5).abs() < 1e-12);
assert!((dered - (22.5 - 0.10)).abs() < 1e-12);
assert!((kcorr - (22.5 - 0.25)).abs() < 1e-12);
assert!((both - (22.5 - 0.10 - 0.25)).abs() < 1e-12);
}
#[test]
fn ab_magnitude_returns_none_for_missing_or_nonpositive_flux() {
let missing = OneBand {
r_flux: 1.0,
r_ext: 0.0,
r_kcorr: 0.0,
};
assert!(missing.ab_magnitude(Band::SdssG, false, false).is_none());
let nonpositive = OneBand {
r_flux: -0.1,
r_ext: 0.0,
r_kcorr: 0.0,
};
assert!(nonpositive
.ab_magnitude(Band::SdssR, false, false)
.is_none());
let zero = OneBand {
r_flux: 0.0,
r_ext: 0.0,
r_kcorr: 0.0,
};
assert!(zero.ab_magnitude(Band::SdssR, false, false).is_none());
}
}