use num_complex::Complex;
use super::Particle;
use crate::Result;
use crate::layered::{self, Layered};
use crate::mie::{Coefficients, Efficiencies, EfficiencyJacobian, Group, Mie, MieOptions, n_terms};
impl Particle {
pub(super) fn optics(&self, i: usize) -> Optics<'_> {
match self {
Particle::Sphere { refractive_index } => Optics::Sphere(refractive_index[i]),
Particle::LayeredSphere {
radius_fractions,
refractive_index,
} => Optics::Layered {
fractions: radius_fractions,
m: refractive_index,
i,
},
}
}
pub(super) fn wavelengths(&self) -> usize {
match self {
Particle::Sphere { refractive_index } => refractive_index.len(),
Particle::LayeredSphere {
refractive_index, ..
} => refractive_index.first().map_or(0, Vec::len),
}
}
pub(super) fn validate(&self) -> core::result::Result<(), String> {
let Particle::LayeredSphere {
radius_fractions: f,
refractive_index: m,
} = self
else {
return Ok(());
};
let layers = m.len();
if layers == 0 || layers > layered::MAX_LAYERS {
return Err(format!(
"a layered sphere needs 1 to {} layers, got {layers}",
layered::MAX_LAYERS
));
}
if f.len() + 1 != layers {
return Err(format!(
"a layered sphere of {layers} layers needs {} radius fractions, got {}",
layers - 1,
f.len()
));
}
if let Some((l, v)) = f
.iter()
.enumerate()
.find(|&(_, v)| !(v.is_finite() && *v > 0.0 && *v <= 1.0))
{
return Err(format!("radius fraction {l} must be in (0, 1], got {v}"));
}
if let Some(l) = (1..f.len()).find(|&l| f[l] < f[l - 1]) {
return Err(format!(
"the radius fractions must be nondecreasing from the core out, but fraction {l} \
is {} < {}",
f[l],
f[l - 1]
));
}
if let Some(l) = (1..layers).find(|&l| m[l].len() != m[0].len()) {
return Err(format!(
"layer {l} has {} refractive indices and the core {}: one per wavelength",
m[l].len(),
m[0].len()
));
}
Ok(())
}
pub(super) fn groups(&self) -> usize {
match self {
Particle::Sphere { .. } => 1,
Particle::LayeredSphere {
refractive_index, ..
} => refractive_index.len(),
}
}
pub(super) fn reals(&self) -> usize {
match self {
Particle::Sphere { .. } => 0,
Particle::LayeredSphere {
radius_fractions, ..
} => radius_fractions.len(),
}
}
pub(super) fn directions(&self) -> usize {
2 * self.groups() + self.reals()
}
}
#[derive(Clone, Copy, Debug)]
pub(super) enum Optics<'a> {
Sphere(Complex<f64>),
Layered {
fractions: &'a [f64],
m: &'a [Vec<Complex<f64>>],
i: usize,
},
}
impl Optics<'_> {
pub(super) fn n_terms(self, x: f64, opts: &MieOptions) -> Result<usize> {
match self {
Optics::Sphere(m) => n_terms(x, m, opts),
Optics::Layered { .. } => {
let (mut xs, mut ms) = (Vec::new(), Vec::new());
self.layers(x, &mut xs, &mut ms);
layered::n_terms(&xs, &ms, opts)
}
}
}
fn layers(self, x: f64, xs: &mut Vec<f64>, ms: &mut Vec<Complex<f64>>) {
let Optics::Layered { fractions, m, i } = self else {
unreachable!("a layered sphere")
};
xs.clear();
xs.extend(fractions.iter().map(|f| f * x));
xs.push(x);
ms.clear();
ms.extend(m.iter().map(|layer| layer[i]));
}
}
pub(super) struct Solved<'a> {
pub(super) c: Coefficients<'a, f64>,
pub(super) groups: Vec<Group<'a, f64>>,
pub(super) fractions: &'a [f64],
}
fn scaled(e: &Efficiencies<f64>, s: f64) -> Efficiencies<f64> {
Efficiencies {
q_ext: s * e.q_ext,
q_sca: s * e.q_sca,
q_abs: s * e.q_abs,
q_back: s * e.q_back,
g: s * e.g,
}
}
impl Solved<'_> {
pub(super) fn direction(
&self,
jacs: &[EfficiencyJacobian<f64>],
k: usize,
) -> Efficiencies<f64> {
let groups = self.groups.len();
if k < 2 * groups {
let j = &jacs[k / 2];
if k % 2 == 0 { j.m_re } else { j.m_im }
} else {
scaled(&jacs[k - 2 * groups].x, self.c.x)
}
}
pub(super) fn size_derivative(&self, jacs: &[EfficiencyJacobian<f64>]) -> Efficiencies<f64> {
let (outer, inner) = jacs.split_last().expect("a group");
let mut d = outer.x;
for (f, j) in self.fractions.iter().zip(inner) {
let e = scaled(&j.x, *f);
d.q_ext += e.q_ext;
d.q_sca += e.q_sca;
d.q_abs += e.q_abs;
d.q_back += e.q_back;
d.g += e.g;
}
d
}
}
pub(super) struct Solver {
mie: Mie<f64>,
layered: Layered<f64>,
xs: Vec<f64>,
ms: Vec<Complex<f64>>,
}
impl Solver {
pub(super) fn new() -> Self {
Self {
mie: Mie::new(),
layered: Layered::new(),
xs: Vec::new(),
ms: Vec::new(),
}
}
pub(super) fn solve<'a>(
&'a mut self,
optics: Optics<'a>,
x: f64,
opts: &MieOptions,
derivatives: bool,
) -> Result<Solved<'a>> {
match optics {
Optics::Sphere(m) => {
let s = if derivatives {
self.mie.compute_with_derivatives(x, m, opts)
} else {
self.mie.compute(x, m, opts)
}?;
Ok(Solved {
c: s.coefficients(),
groups: s.group().into_iter().collect(),
fractions: &[],
})
}
Optics::Layered { fractions, .. } => {
optics.layers(x, &mut self.xs, &mut self.ms);
let s = if derivatives {
self.layered
.compute_with_derivatives(&self.xs, &self.ms, opts)
} else {
self.layered.compute(&self.xs, &self.ms, opts)
}?;
Ok(Solved {
c: s.coefficients(),
groups: (0..self.xs.len()).filter_map(|l| s.group(l)).collect(),
fractions,
})
}
}
}
}