use num_complex::Complex;
use super::{CoefficientDerivatives, Coefficients, Group, MieSolution};
use crate::complex::times_i;
use crate::scalar::Real;
#[derive(Clone, Copy, Debug, PartialEq)]
#[non_exhaustive]
pub struct Efficiencies<T> {
pub q_ext: T,
pub q_sca: T,
pub q_abs: T,
pub q_back: T,
pub g: T,
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[non_exhaustive]
pub struct EfficiencyJacobian<T> {
pub x: Efficiencies<T>,
pub m_re: Efficiencies<T>,
pub m_im: Efficiencies<T>,
}
fn re_mul_conj<T: Real>(u: Complex<T>, v: Complex<T>) -> T {
u.re * v.re + u.im * v.im
}
fn int<T: Real>(k: usize) -> T {
T::from_f64(k as f64)
}
struct Sums<T> {
sca: T,
back: Complex<T>,
g: T,
}
fn value_sums<T: Real>(a: &[Complex<T>], b: &[Complex<T>]) -> Sums<T> {
let n = a.len();
let mut sca = T::zero();
let mut back = Complex::new(T::zero(), T::zero());
let mut g = T::zero();
for k in 1..=n {
let (ak, bk) = (a[k - 1], b[k - 1]);
let w: T = int(2 * k + 1);
sca += w * (ak.norm_sqr() + bk.norm_sqr());
let t = (ak - bk).scale(w);
back = if k % 2 == 0 { back + t } else { back - t };
let kf: T = int(k);
if k < n {
let (a1, b1) = (a[k], b[k]);
g += kf * (kf + T::from_f64(2.0)) / (kf + T::one())
* (re_mul_conj(ak, a1) + re_mul_conj(bk, b1));
}
g += w / (kf * (kf + T::one())) * re_mul_conj(ak, bk);
}
Sums { sca, back, g }
}
fn derivative_sums<T: Real>(
a: &[Complex<T>],
b: &[Complex<T>],
d: &CoefficientDerivatives<'_, T>,
) -> [Sums<T>; 3] {
let n = a.len();
let (b, da_dx, da_dm, db_dx, db_dm) = (
&b[..n],
&d.da_dx[..n],
&d.da_dm[..n],
&d.db_dx[..n],
&d.db_dm[..n],
);
let two = T::from_f64(2.0);
let zero = Complex::new(T::zero(), T::zero());
let mut sca = [T::zero(); 3];
let mut back = [zero; 3];
let mut g = [T::zero(); 3];
let derivs = |i: usize| {
(
[da_dx[i], da_dm[i], times_i(da_dm[i])],
[db_dx[i], db_dm[i], times_i(db_dm[i])],
)
};
let mut current = derivs(0);
for k in 1..=n {
let (ak, bk) = (a[k - 1], b[k - 1]);
let (dak, dbk) = current;
let w: T = int(2 * k + 1);
let kf: T = int(k);
let next = (k < n).then(|| derivs(k));
let pair = kf * (kf + two) / (kf + T::one());
let own = w / (kf * (kf + T::one()));
for p in 0..3 {
sca[p] += w * two * (re_mul_conj(dak[p], ak) + re_mul_conj(dbk[p], bk));
let t = (dak[p] - dbk[p]).scale(w);
back[p] = if k % 2 == 0 { back[p] + t } else { back[p] - t };
if let Some((da1, db1)) = next {
let (a1, b1) = (a[k], b[k]);
g[p] += pair
* (re_mul_conj(dak[p], a1)
+ re_mul_conj(ak, da1[p])
+ re_mul_conj(dbk[p], b1)
+ re_mul_conj(bk, db1[p]));
}
g[p] += own * (re_mul_conj(dak[p], bk) + re_mul_conj(ak, dbk[p]));
}
if let Some(nx) = next {
current = nx;
}
}
[0, 1, 2].map(|p| Sums {
sca: sca[p],
back: back[p],
g: g[p],
})
}
impl<T: Real> MieSolution<'_, T> {
pub fn efficiencies(&self) -> Efficiencies<T> {
self.coefficients().efficiencies()
}
pub fn efficiencies_jacobian(&self) -> Option<EfficiencyJacobian<T>> {
let g = self.group()?;
Some(self.coefficients().efficiencies_jacobian(&g))
}
}
impl<T: Real> Coefficients<'_, T> {
pub(crate) fn efficiencies(&self) -> Efficiencies<T> {
self.efficiencies_from(&value_sums(self.a, self.b))
}
fn efficiencies_from(&self, sums: &Sums<T>) -> Efficiencies<T> {
let inv_x = self.x.recip();
let inv_x2 = inv_x * inv_x;
let two_x2 = T::from_f64(2.0) * inv_x2;
let q_sca = two_x2 * sums.sca;
let q_abs = two_x2 * self.abs_sum;
let g = if sums.sca == T::zero() {
T::zero()
} else {
T::from_f64(2.0) * sums.g / sums.sca
};
Efficiencies {
q_ext: q_sca + q_abs,
q_sca,
q_abs,
q_back: sums.back.norm_sqr() * inv_x2,
g,
}
}
pub(crate) fn efficiencies_jacobian(&self, group: &Group<'_, T>) -> EfficiencyJacobian<T> {
let (a, b, d) = (self.a, self.b, &group.d);
let two = T::from_f64(2.0);
let inv_x = self.x.recip();
let inv_x2 = inv_x * inv_x;
let two_x2 = two * inv_x2;
let values = value_sums(a, b);
let q = self.efficiencies_from(&values);
let all = derivative_sums(a, b, d);
let mut out = [q; 3];
for (p, (o, sums)) in out.iter_mut().zip(&all).enumerate() {
let x_term = |v: T| {
if p == 0 && group.outer {
two * inv_x * v
} else {
T::zero()
}
};
let q_sca = two_x2 * sums.sca - x_term(q.q_sca);
let q_abs = two_x2 * group.abs_sum[p] - x_term(q.q_abs);
let q_back = two * re_mul_conj(sums.back, values.back) * inv_x2 - x_term(q.q_back);
let g = if values.sca == T::zero() {
T::zero()
} else {
(two * sums.g - q.g * sums.sca) / values.sca
};
*o = Efficiencies {
q_ext: q_sca + q_abs,
q_sca,
q_abs,
q_back,
g,
};
}
let [x, m_re, m_im] = out;
EfficiencyJacobian { x, m_re, m_im }
}
}