treams_core/tmatrix/
sphere.rs1#![allow(clippy::indexing_slicing)] use nalgebra::DMatrix;
7
8use crate::coeffs::{
9 LayerGradient, Material, Matrix2, MieGradient, MieResidual, mie, to_mode_order,
10};
11use crate::sw;
12use crate::{
13 Complex, Error, Result,
14 numerics::{self, finite},
15};
16
17mod saved;
18
19#[derive(Clone, Debug)]
21pub struct SphereResidual {
22 k0: f64,
23 radii: Vec<f64>,
24 degrees: Vec<MieResidual>,
25}
26
27#[derive(Clone, Debug)]
29pub struct SphereGradient {
30 pub k0: f64,
32 pub layers: LayerGradient,
34}
35
36pub fn sphere(
44 lmax: u32,
45 k0: f64,
46 radii: &[f64],
47 materials: &[Material],
48) -> Result<(DMatrix<Complex>, SphereResidual)> {
49 if !k0.is_finite() || k0 <= 0.0 {
50 return Err(Error::InvalidInput("k0 must be finite and positive".into()));
51 }
52 let dimension = sw::modes(lmax)?.len();
53 let sizes: Vec<_> = radii.iter().map(|r| k0 * r).collect();
54 let degrees = (1..=lmax)
55 .map(|l| mie(l, &sizes, materials))
56 .collect::<Result<Vec<_>>>()?;
57 let mut value = numerics::zeros(dimension, dimension)?;
58 for (block, degree) in block_degrees(degrees.len()).enumerate() {
59 value
60 .fixed_view_mut::<2, 2>(2 * block, 2 * block)
61 .copy_from(&to_mode_order(degrees[degree].value()));
62 }
63 Ok((
64 value,
65 SphereResidual {
66 k0,
67 radii: radii.to_vec(),
68 degrees,
69 },
70 ))
71}
72
73pub(crate) fn block_degrees(count: usize) -> impl Iterator<Item = usize> {
77 (0..count).flat_map(|degree| std::iter::repeat_n(degree, 2 * degree + 3))
78}
79
80impl SphereResidual {
81 #[must_use]
83 pub fn boundaries(&self) -> usize {
84 self.radii.len()
85 }
86
87 #[must_use]
89 pub fn shape(&self) -> (usize, usize) {
90 let lmax = self.degrees.len();
91 (2 * lmax * (lmax + 2), 2 * lmax * (lmax + 2))
92 }
93
94 pub fn pushforward(
98 &self,
99 k0: f64,
100 radii: &[f64],
101 materials: &[Material],
102 ) -> Result<DMatrix<Complex>> {
103 crate::coeffs::validate_layer_tangents(self.radii.len(), radii, materials)?;
104 if !k0.is_finite() {
105 return Err(Error::InvalidInput("k0 tangent must be finite".into()));
106 }
107 let sizes: Vec<_> = self
108 .radii
109 .iter()
110 .zip(radii)
111 .map(|(r, dr)| k0 * r + self.k0 * dr)
112 .collect();
113 let degrees = self
114 .degrees
115 .iter()
116 .map(|degree| degree.pushforward(&sizes, materials))
117 .collect::<Result<Vec<_>>>()?;
118 let (rows, cols) = self.shape();
119 let mut tangent = numerics::zeros(rows, cols)?;
120 for (block, degree) in block_degrees(degrees.len()).enumerate() {
121 tangent
122 .fixed_view_mut::<2, 2>(2 * block, 2 * block)
123 .copy_from(&to_mode_order(°rees[degree]));
124 }
125 Ok(tangent)
126 }
127
128 pub fn pullback(&self, cotangent: &DMatrix<Complex>) -> Result<SphereGradient> {
134 if cotangent.shape() != self.shape() || cotangent.iter().any(|z| !finite(*z)) {
135 return Err(Error::InvalidInput(
136 "invalid spherical T-matrix cotangent".into(),
137 ));
138 }
139 let mut blocks = vec![Matrix2::zeros(); self.degrees.len()];
140 for (block, degree) in block_degrees(self.degrees.len()).enumerate() {
141 let g = cotangent
142 .fixed_view::<2, 2>(2 * block, 2 * block)
143 .into_owned();
144 blocks[degree] += to_mode_order(&g);
145 }
146 let mut sum = MieGradient::zeros(self.radii.len());
147 for (residual, g) in self.degrees.iter().zip(blocks) {
148 sum.accumulate(&residual.pullback(&g)?);
149 }
150 let k0 = sum.sizes.iter().zip(&self.radii).map(|(g, r)| g * r).sum();
154 let radii = sum.sizes.iter().map(|g| self.k0 * g).collect();
155 Ok(SphereGradient {
156 k0,
157 layers: LayerGradient {
158 radii,
159 epsilon: sum.epsilon,
160 mu: sum.mu,
161 kappa: sum.kappa,
162 },
163 })
164 }
165}