use core::fmt;
use num_complex::Complex;
use crate::mie::MieOutput;
#[derive(Clone, Copy, Debug, PartialEq)]
#[non_exhaustive]
pub struct ValidatedDomain {
pub x_min: f64,
pub x_max: f64,
pub m_re_max: f64,
pub m_im_max: f64,
pub abs_m_min: f64,
pub max_layers: usize,
pub max_phase: f64,
pub derivative_x_max: f64,
}
pub const VALIDATED_DOMAIN: ValidatedDomain = ValidatedDomain {
x_min: 1e-6,
x_max: 1e4,
m_re_max: 10.0,
m_im_max: 10.0,
abs_m_min: 0.1,
max_layers: 16,
max_phase: 3e4,
derivative_x_max: 1e3,
};
impl ValidatedDomain {
pub fn contains_derivatives(&self, x: &[f64], m: &[Complex<f64>]) -> bool {
self.contains(x, m)
&& (x.len() == 1 || x.last().is_some_and(|&v| v <= self.derivative_x_max))
}
pub fn contains(&self, x: &[f64], m: &[Complex<f64>]) -> bool {
if let ([x], [m]) = (x, m) {
return crate::mie::VALIDATED_DOMAIN.contains(*x, *m);
}
let Some(&outer) = x.last() else {
return false;
};
x.len() <= self.max_layers
&& (self.x_min..=self.x_max).contains(&outer)
&& m.iter().all(|m| m.norm() * outer <= self.max_phase)
&& m.iter().all(|m| {
m.re > 0.0
&& m.re <= self.m_re_max
&& (0.0..=self.m_im_max).contains(&m.im)
&& m.norm() >= self.abs_m_min
})
}
pub fn condition_factor(x: f64, m: &[Complex<f64>]) -> f64 {
let big = m.iter().map(|m| m.norm()).fold(1.0, f64::max);
let one = Complex::new(1.0, 0.0);
let near_one = m
.iter()
.filter(|&&m| m != one)
.map(|m| (m.norm() / (m - one).norm()).max(1.0))
.fold(f64::NEG_INFINITY, f64::max);
let near_one = if near_one.is_finite() {
near_one
} else {
f64::INFINITY
};
(1.0 + big * x) * near_one
}
pub fn tolerance(&self, output: MieOutput, x: f64, m: &[Complex<f64>], condition: f64) -> f64 {
let kappa = Self::condition_factor(x, m);
let big = m.iter().map(|m| m.norm()).fold(1.0, f64::max);
let near_one = (kappa / (1.0 + big * x)).max(1.0);
let quadrature = |order: usize| ((order as f64 + 1.0).powi(2) + x * x) / 8.0;
let prior = match output {
MieOutput::CoefficientDerivatives | MieOutput::AmplitudeDerivatives => kappa,
MieOutput::EfficiencyJacobian | MieOutput::PhaseMatrixDerivatives => kappa * near_one,
MieOutput::GreekCoefficients { order } => kappa + quadrature(order),
MieOutput::GreekDerivatives { order } => kappa * near_one + quadrature(order),
_ => kappa,
};
Self::constant(output) * (prior + condition) * f64::EPSILON
}
pub fn constant(output: MieOutput) -> f64 {
match output {
MieOutput::Coefficients | MieOutput::Extinction => 32.0,
MieOutput::Scattering | MieOutput::Asymmetry | MieOutput::GreekCoefficients { .. } => {
16.0
}
MieOutput::Amplitudes => 128.0,
MieOutput::Backscattering | MieOutput::PhaseMatrix => 64.0,
MieOutput::CoefficientDerivatives | MieOutput::AmplitudeDerivatives => 1024.0,
MieOutput::Absorption
| MieOutput::EfficiencyJacobian
| MieOutput::PhaseMatrixDerivatives
| MieOutput::GreekDerivatives { .. } => 256.0,
}
}
}
impl fmt::Display for ValidatedDomain {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"x_L in [{:e}, {:e}], every layer 0 < Re m <= {}, 0 <= Im m <= {}, |m| >= {} and \
|m| x_L <= {:e}, at most {} layers",
self.x_min,
self.x_max,
self.m_re_max,
self.m_im_max,
self.abs_m_min,
self.max_phase,
self.max_layers
)
}
}