use std::sync::OnceLock;
use crate::atmosphere::{Transmittance, Transmittances};
use crate::ext_qtty::length::Nanometer;
use crate::provenance::Provenance;
use crate::qtty::Nanometers;
use crate::spectra::interp::{Interpolation, OutOfRange};
use crate::spectra::loaders::ascii;
use crate::spectra::sampled::SampledSpectrum;
const RAW: &str = include_str!("../archive/data/o3trans.dat");
#[cfg(test)]
const OZONE_SHA256: &str = "cb06c173f393d6d55e3c39551665abb8f5d6c1a846cd0fd739a15d0155f94502";
static TABLE: OnceLock<SampledSpectrum<Nanometer, Transmittance>> = OnceLock::new();
pub fn transmission_table() -> &'static SampledSpectrum<Nanometer, Transmittance> {
TABLE.get_or_init(|| {
let provenance = Provenance::bundled_file("siderust/data/o3trans.dat")
.with_notes("Original NSB/darknsb o3trans.dat; wavelengths converted µm→nm.");
ascii::two_column::<Nanometer, Transmittance>(
RAW,
1000.0, 1.0,
Interpolation::Linear,
OutOfRange::ClampToEndpoints,
Some(provenance),
)
.expect("o3trans.dat is a well-formed, monotonic table — parse must not fail")
})
}
pub fn transmittance_at(wavelength: Nanometers) -> Transmittances {
transmission_table()
.interp_at(wavelength)
.expect("ozone table is clamped at endpoints; interp_at cannot fail")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pinned_sha256_matches_runtime_hash() {
use crate::provenance::checksum::{sha256, to_hex};
assert_eq!(to_hex(&sha256(RAW.as_bytes())), OZONE_SHA256);
}
#[test]
fn table_is_nonempty() {
let t = transmission_table();
assert!(!t.is_empty(), "ozone table must have entries");
assert!(t.len() >= 2, "OnceLock returns the same instance");
}
#[test]
fn wavelengths_are_monotonically_increasing_in_nm() {
let t = transmission_table();
let xs = t.xs_raw();
for w in xs.windows(2) {
assert!(
w[1] > w[0],
"wavelengths must be strictly increasing: {} <= {}",
w[1],
w[0]
);
}
}
#[test]
fn transmittances_in_unit_interval() {
let t = transmission_table();
for (i, y) in t.ys_raw().iter().enumerate() {
assert!(
*y >= 0.0 && *y <= 1.0,
"transmittance[{i}] = {y} is outside [0, 1]"
);
}
}
#[test]
fn wavelength_range_covers_expected_band() {
let t = transmission_table();
let xs = t.xs_raw();
let lo = xs[0];
let hi = xs[xs.len() - 1];
assert!(lo < 310.0, "first wavelength ({lo} nm) should be ≲ 310 nm");
assert!(hi > 900.0, "last wavelength ({hi} nm) should be ≳ 900 nm");
}
#[test]
fn typed_helper_matches_table_lookup() {
let lambda = Nanometers::new(550.0);
let direct = transmission_table().interp_at(lambda).unwrap();
let typed = transmittance_at(lambda);
assert_eq!(direct.value(), typed.value());
}
}