use crate::atmosphere::rayleigh::rayleigh_phase;
use crate::ext_qtty::{Dimensionless, Quantity, Unit};
use crate::qtty::unit::{Degree, Nanometer};
use crate::qtty::{Nanometers, Radians};
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct ScatteringFactor;
impl Unit for ScatteringFactor {
const RATIO: f64 = 1.0;
type Dim = Dimensionless;
const SYMBOL: &'static str = "";
}
pub trait PhaseFunction {
fn phase(
&self,
wavelength: Nanometers,
scattering_angle: Radians,
) -> Quantity<ScatteringFactor>;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct RayleighPhaseFunction;
impl PhaseFunction for RayleighPhaseFunction {
#[inline]
fn phase(
&self,
_wavelength: Nanometers,
scattering_angle: Radians,
) -> Quantity<ScatteringFactor> {
rayleigh_phase(scattering_angle)
}
}
#[derive(Debug, Clone)]
pub struct TabulatedPhaseFunction {
grid: optica::grid::Grid2D<Nanometer, Degree, ScatteringFactor>,
}
impl TabulatedPhaseFunction {
pub fn from_raw_row_major(
wavelengths_nm: &[f64],
angles_deg: &[f64],
row_major: &[f64],
) -> Result<Self, optica::grid::GridError> {
Ok(Self {
grid: optica::grid::Grid2D::from_raw_row_major(
wavelengths_nm,
angles_deg,
row_major,
optica::grid::OutOfRange::ClampToEndpoints,
)?,
})
}
pub fn with_provenance(mut self, provenance: optica::data::Provenance) -> Self {
self.grid = self.grid.with_provenance(provenance);
self
}
pub fn interp_at(
&self,
wavelength: Nanometers,
scattering_angle: Radians,
) -> Quantity<ScatteringFactor> {
self.grid
.interp_at(wavelength, scattering_angle.to::<Degree>())
}
}
impl PhaseFunction for TabulatedPhaseFunction {
fn phase(
&self,
wavelength: Nanometers,
scattering_angle: Radians,
) -> Quantity<ScatteringFactor> {
self.interp_at(wavelength, scattering_angle)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::qtty::{unit::Radian, Degrees};
#[test]
fn rayleigh_phase_is_symmetric() {
let r = RayleighPhaseFunction;
let p0 = r
.phase(Nanometers::new(550.0), Degrees::new(0.0).to::<Radian>())
.value();
let p180 = r
.phase(Nanometers::new(550.0), Degrees::new(180.0).to::<Radian>())
.value();
let p90 = r
.phase(Nanometers::new(550.0), Degrees::new(90.0).to::<Radian>())
.value();
assert!((p0 - p180).abs() < 1.0e-15);
assert!(p0 > p90);
}
#[test]
fn tabulated_phase_interpolates() {
let t = TabulatedPhaseFunction::from_raw_row_major(
&[500.0, 600.0],
&[0.0, 90.0],
&[1.0, 2.0, 3.0, 4.0],
)
.unwrap();
let v = t
.phase(Nanometers::new(550.0), Degrees::new(45.0).to::<Radian>())
.value();
assert!((v - 2.5).abs() < 1.0e-12);
}
}