#![cfg_attr(docsrs, feature(doc_cfg))]
#![forbid(unsafe_code)]
use std::fmt;
#[derive(Copy, Clone, Debug)]
pub struct SpectralBudget {
pub principal_period: f64,
pub ring_down_factor: f64,
}
impl SpectralBudget {
pub fn for_scene_diameter(diam_m: f64, c: f64) -> Self {
Self {
principal_period: 2.0 * diam_m / c,
ring_down_factor: 3.0,
}
}
#[must_use = "handle the Err variant to surface budget violations"]
pub fn try_admit(&self, diameter: f64) -> Result<(), BudgetError> {
let bound = self.ring_down_factor * self.principal_period;
if diameter <= bound {
Ok(())
} else {
Err(BudgetError::Exceeded {
diameter,
bound,
principal_period: self.principal_period,
ring_down_factor: self.ring_down_factor,
})
}
}
#[must_use]
pub fn admits(&self, diameter: f64) -> bool {
self.try_admit(diameter).is_ok()
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum BudgetError {
Exceeded {
diameter: f64,
bound: f64,
principal_period: f64,
ring_down_factor: f64,
},
}
impl fmt::Display for BudgetError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BudgetError::Exceeded {
diameter,
bound,
principal_period,
ring_down_factor,
} => write!(
f,
"spectral budget exceeded: diameter {diameter:.3e} > \
bound {bound:.3e} (T_1 = {principal_period:.3e}, \
ring-down factor = {ring_down_factor:.2})"
),
}
}
}
impl std::error::Error for BudgetError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn admits_within_bound() {
let b = SpectralBudget { principal_period: 100.0, ring_down_factor: 3.0 };
assert!(b.admits(0.0));
assert!(b.admits(100.0));
assert!(b.admits(300.0));
assert!(!b.admits(300.1));
}
#[test]
fn exceeded_carries_diagnostic_fields() {
let b = SpectralBudget { principal_period: 100.0, ring_down_factor: 3.0 };
match b.try_admit(500.0) {
Err(BudgetError::Exceeded { diameter, bound, principal_period, ring_down_factor }) => {
assert_eq!(diameter, 500.0);
assert_eq!(bound, 300.0);
assert_eq!(principal_period, 100.0);
assert_eq!(ring_down_factor, 3.0);
}
other => panic!("expected Exceeded, got {other:?}"),
}
}
#[test]
fn for_scene_diameter_matches_faber_krahn() {
const C: f64 = 2.998e8;
let b = SpectralBudget::for_scene_diameter(0.95, C);
assert!((b.principal_period - 2.0 * 0.95 / C).abs() < 1e-18);
assert_eq!(b.ring_down_factor, 3.0);
}
}