use crate::atmosphere::extinction::transmission;
use crate::atmosphere::mie::{mie_optical_depth, MieParams};
use crate::atmosphere::rayleigh::{rayleigh_optical_depth_bodhaine99, DEFAULT_SCALE_HEIGHT};
use crate::atmosphere::Transmittances;
use crate::qtty::{Airmasses, Hectopascals, Kilometers, Nanometers, OpticalDepths};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct AtmosphereProfile {
pub surface_pressure: Hectopascals,
pub observer_altitude: Kilometers,
pub rayleigh_scale_height: Kilometers,
pub mie_params: MieParams,
}
impl AtmosphereProfile {
pub const EL_PARANAL: AtmosphereProfile = AtmosphereProfile {
surface_pressure: Hectopascals::new(744.0),
observer_altitude: Kilometers::new(2.635),
rayleigh_scale_height: DEFAULT_SCALE_HEIGHT,
mie_params: MieParams::PARANAL,
};
pub const ROQUE_DE_LOS_MUCHACHOS: AtmosphereProfile = AtmosphereProfile {
surface_pressure: Hectopascals::new(770.0),
observer_altitude: Kilometers::new(2.396),
rayleigh_scale_height: DEFAULT_SCALE_HEIGHT,
mie_params: MieParams::LA_PALMA,
};
pub const MAUNA_KEA: AtmosphereProfile = AtmosphereProfile {
surface_pressure: Hectopascals::new(615.0),
observer_altitude: Kilometers::new(4.205),
rayleigh_scale_height: DEFAULT_SCALE_HEIGHT,
mie_params: MieParams::MAUNA_KEA,
};
pub const LA_SILLA: AtmosphereProfile = AtmosphereProfile {
surface_pressure: Hectopascals::new(770.0),
observer_altitude: Kilometers::new(2.400),
rayleigh_scale_height: DEFAULT_SCALE_HEIGHT,
mie_params: MieParams::LA_SILLA,
};
pub fn optical_depth(&self, lambda: Nanometers) -> OpticalDepths {
let tau_r = rayleigh_optical_depth_bodhaine99(
lambda,
self.surface_pressure,
self.observer_altitude,
self.rayleigh_scale_height,
);
let tau_m = mie_optical_depth(&self.mie_params, lambda);
tau_r + tau_m
}
pub fn transmission(&self, lambda: Nanometers, airmass: Airmasses) -> Transmittances {
transmission(self.optical_depth(lambda), airmass)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn presets_have_typed_units() {
let p = AtmosphereProfile::EL_PARANAL;
assert_eq!(p.surface_pressure.value(), 744.0);
assert_eq!(p.observer_altitude.value(), 2.635);
assert_eq!(p.rayleigh_scale_height.value(), 8.0);
}
#[test]
fn optical_depth_is_positive_and_decreases_with_wavelength() {
let p = AtmosphereProfile::EL_PARANAL;
let blue = p.optical_depth(Nanometers::new(400.0));
let red = p.optical_depth(Nanometers::new(700.0));
assert!(blue.value() > 0.0);
assert!(red.value() > 0.0);
assert!(
blue.value() > red.value(),
"Rayleigh + Mie both fall with λ"
);
}
#[test]
fn transmission_decreases_with_airmass() {
let p = AtmosphereProfile::EL_PARANAL;
let lambda = Nanometers::new(550.0);
let t1 = p.transmission(lambda, Airmasses::new(1.0));
let t2 = p.transmission(lambda, Airmasses::new(2.0));
assert!(t1.value() > t2.value());
assert!(t1.value() > 0.0 && t1.value() < 1.0);
}
#[test]
fn higher_altitude_lowers_rayleigh_contribution() {
let lambda = Nanometers::new(500.0);
let mk = AtmosphereProfile::MAUNA_KEA.optical_depth(lambda);
let pa = AtmosphereProfile::EL_PARANAL.optical_depth(lambda);
assert!(
mk.value() < pa.value(),
"Mauna Kea τ ({}) should be smaller than Paranal τ ({})",
mk.value(),
pa.value(),
);
}
}