1#![allow(clippy::indexing_slicing)] mod saved;
7
8use super::{LayerGradient, Material, Matrix2, Matrix4, Matrix42};
9use crate::{
10 Complex, Error, MAX_DEGREE, Result,
11 numerics::finite,
12 special::{Radial, RadialJet, cylindrical_radial},
13};
14
15#[derive(Clone, Debug)]
17struct Interface {
18 inside: Side,
20 outside: Side,
22 inverse_outer: Matrix4,
24 transfer: Matrix4,
27 before: Matrix42,
30}
31
32#[derive(Clone, Debug)]
35pub struct MieCylResidual {
36 order: i32,
37 kz: f64,
38 k0: f64,
39 radii: Vec<f64>,
40 materials: Vec<Material>,
41 interfaces: Vec<Interface>,
42 inverse: Matrix2,
43 value: Matrix2,
44}
45
46#[derive(Clone, Debug)]
48pub struct MieCylGradient {
49 pub kz: f64,
51 pub k0: f64,
53 pub layers: LayerGradient,
55}
56
57#[derive(Clone, Copy, Debug, Default)]
60struct Direction {
61 ks: [Complex; 2],
62 kz: Complex,
63 radius: Complex,
64 impedance: Complex,
65}
66
67#[derive(Clone, Copy, Debug)]
71struct Side {
72 ks: [Complex; 2],
73 impedance: Complex,
74 kr: [Complex; 2],
75 x: [Complex; 2],
76 radial: [[Option<RadialJet>; 2]; 2],
77}
78
79impl Side {
80 fn new(
81 order: i32,
82 kz: f64,
83 ks: [Complex; 2],
84 radius: f64,
85 impedance: Complex,
86 regular_only: bool,
87 ) -> Result<Self> {
88 let mut side = Self {
89 ks,
90 impedance,
91 kr: [Complex::default(); 2],
92 x: [Complex::default(); 2],
93 radial: [[None; 2]; 2],
94 };
95 for (pol, &k) in ks.iter().enumerate() {
96 let kr = (k * k - kz * kz).sqrt();
97 if k.norm_sqr() == 0.0 || kr.norm_sqr() == 0.0 {
98 return Err(Error::InvalidInput(
99 "cylindrical cutoff k_rho=0 is not supported".into(),
100 ));
101 }
102 let x = kr * radius;
103 (side.kr[pol], side.x[pol]) = (kr, x);
104 let kinds: &[Radial] = if regular_only {
105 &[Radial::Regular]
106 } else {
107 &[Radial::Regular, Radial::Singular]
108 };
109 for (kind, &radial) in kinds.iter().enumerate() {
110 side.radial[pol][kind] = Some(cylindrical_radial(order, x, radial)?);
111 }
112 }
113 Ok(side)
114 }
115
116 fn columns(&self) -> impl Iterator<Item = (usize, usize, RadialJet)> + '_ {
118 (0..2).flat_map(move |kind| {
119 (0..2).filter_map(move |pol| self.radial[pol][kind].map(|f| (pol, kind, f)))
120 })
121 }
122
123 fn matrix(&self, order: i32, kz: f64) -> Matrix4 {
125 let mut value = Matrix4::zeros();
126 for (pol, kind, f) in self.columns() {
127 let (k, kr, x) = (self.ks[pol], self.kr[pol], self.x[pol]);
128 let phi = -kr * f.value / k;
129 let sign = if pol == 0 { 1.0 } else { -1.0 };
130 let z = -kz * f64::from(order) * f.value / (k * x) + sign * f.first;
131 let col = pol + 2 * kind;
132 value[(0, col)] = phi;
133 value[(1, col)] = z;
134 value[(2, col)] = -sign * phi / self.impedance;
135 value[(3, col)] = -sign * z / self.impedance;
136 }
137 value
138 }
139
140 fn derivative(&self, order: i32, kz: f64, radius: f64, direction: Direction) -> Matrix4 {
142 let mut derivative = Matrix4::zeros();
143 for (pol, kind, f) in self.columns() {
144 let (k, kr, x) = (self.ks[pol], self.kr[pol], self.x[pol]);
145 let dk = direction.ks[pol];
146 let dkr = (k * dk - kz * direction.kz) / kr;
147 let dx = dkr * radius + kr * direction.radius;
148 let phi = -kr * f.value / k;
149 let dphi = -(dkr * f.value + kr * f.first * dx) / k - phi * dk / k;
150 let sign = if pol == 0 { 1.0 } else { -1.0 };
151 let z = -kz * f64::from(order) * f.value / (k * x) + sign * f.first;
152 let dz = -f64::from(order) / (k * x)
153 * (direction.kz * f.value + kz * f.first * dx - kz * f.value * (dk / k + dx / x))
154 + sign * f.second * dx;
155 let (impedance, dimpedance) = (self.impedance, direction.impedance);
156 let col = pol + 2 * kind;
157 derivative[(0, col)] = dphi;
158 derivative[(1, col)] = dz;
159 derivative[(2, col)] = -sign * (dphi - phi * dimpedance / impedance) / impedance;
160 derivative[(3, col)] = -sign * (dz - z * dimpedance / impedance) / impedance;
161 }
162 derivative
163 }
164}
165
166pub fn mie_cyl(
174 kz: f64,
175 order: i32,
176 k0: f64,
177 radii: &[f64],
178 materials: &[Material],
179) -> Result<MieCylResidual> {
180 crate::coeffs::validate_layers(radii, materials)?;
181 if !kz.is_finite()
182 || !k0.is_finite()
183 || k0 <= 0.0
184 || order.unsigned_abs() > MAX_DEGREE.unsigned_abs()
185 {
186 return Err(Error::InvalidInput(
187 "require finite kz, positive k0 and |m| <= 128".into(),
189 ));
190 }
191 let mut q = Matrix42::zeros();
192 q[(0, 0)] = Complex::new(1.0, 0.0);
193 q[(1, 1)] = Complex::new(1.0, 0.0);
194 let mut interfaces = Vec::with_capacity(radii.len());
195 for (i, &radius) in radii.iter().enumerate() {
196 let side = |m: Material, regular_only| {
197 let ks = [k0 * (m.index() - m.kappa), k0 * (m.index() + m.kappa)];
198 Side::new(order, kz, ks, radius, m.impedance(), regular_only)
199 };
200 let inside = side(materials[i], i == 0)?;
201 let outside = side(materials[i + 1], false)?;
202 let inverse_outer = outside
203 .matrix(order, kz)
204 .try_inverse()
205 .ok_or(Error::Singular)?;
206 let transfer = inverse_outer * inside.matrix(order, kz);
207 interfaces.push(Interface {
208 inside,
209 outside,
210 inverse_outer,
211 transfer,
212 before: q,
213 });
214 q = transfer * q;
215 }
216 let inverse = q
217 .fixed_rows::<2>(0)
218 .into_owned()
219 .try_inverse()
220 .ok_or(Error::Singular)?;
221 let value = q.fixed_rows::<2>(2) * inverse;
222 if value.iter().any(|&z| !finite(z)) {
223 return Err(Error::Singular);
224 }
225 Ok(MieCylResidual {
226 order,
227 kz,
228 k0,
229 radii: radii.to_vec(),
230 materials: materials.to_vec(),
231 interfaces,
232 inverse,
233 value,
234 })
235}
236impl MieCylResidual {
237 #[must_use]
239 pub fn boundaries(&self) -> usize {
240 self.radii.len()
241 }
242
243 #[must_use]
245 pub const fn value(&self) -> &Matrix2 {
246 &self.value
247 }
248
249 pub fn pushforward(
254 &self,
255 kz: f64,
256 k0: f64,
257 radii: &[f64],
258 materials: &[Material],
259 ) -> Result<Matrix2> {
260 super::validate_layer_tangents(self.radii.len(), radii, materials)?;
261 if !kz.is_finite() || !k0.is_finite() {
262 return Err(Error::InvalidInput(
263 "wavenumber tangents must be finite".into(),
264 ));
265 }
266 let optical: Vec<_> = self
267 .materials
268 .iter()
269 .zip(materials)
270 .map(|(&material, &direction)| material.pushforward(direction))
271 .collect();
272 let mut dq = Matrix42::zeros();
273 for (i, interface) in self.interfaces.iter().enumerate() {
274 let derivative = |side: usize, boundary: &Side| {
275 let tangent = optical[i + side];
276 let dn = [tangent.index - tangent.kappa, tangent.index + tangent.kappa];
277 boundary.derivative(
278 self.order,
279 self.kz,
280 self.radii[i],
281 Direction {
282 ks: std::array::from_fn(|pol| {
283 boundary.ks[pol] * (k0 / self.k0) + self.k0 * dn[pol]
284 }),
285 kz: kz.into(),
286 radius: radii[i].into(),
287 impedance: tangent.impedance,
288 },
289 )
290 };
291 let inner = derivative(0, &interface.inside);
292 let outer = derivative(1, &interface.outside);
293 let transfer = interface.inverse_outer * (inner - outer * interface.transfer);
294 dq = transfer * interface.before + interface.transfer * dq;
295 }
296 Ok((dq.fixed_rows::<2>(2) - self.value * dq.fixed_rows::<2>(0)) * self.inverse)
297 }
298
299 pub fn pullback(&self, cotangent: &Matrix2) -> Result<MieCylGradient> {
305 if cotangent.iter().any(|&z| !finite(z)) {
306 return Err(Error::InvalidInput("cotangent must be finite".into()));
307 }
308 let mut result = MieCylGradient {
309 kz: 0.0,
310 k0: 0.0,
311 layers: LayerGradient::zeros(self.radii.len()),
312 };
313 let lower = cotangent * self.inverse.adjoint();
314 let mut gq = Matrix42::zeros();
315 gq.fixed_rows_mut::<2>(0)
316 .copy_from(&(-self.value.adjoint() * lower));
317 gq.fixed_rows_mut::<2>(2).copy_from(&lower);
318 for (i, interface) in self.interfaces.iter().enumerate().rev() {
319 let gf = gq * interface.before.adjoint();
320 let inner = interface.inverse_outer.adjoint() * gf;
321 let outer = -inner * interface.transfer.adjoint();
322 let sides = [(&interface.inside, inner), (&interface.outside, outer)];
323 for (side, (boundary, g)) in sides.into_iter().enumerate() {
324 let material = self.materials[i + side];
325 let n = material.index();
326 let contract = |direction: Direction| -> Complex {
327 let df = boundary.derivative(self.order, self.kz, self.radii[i], direction);
328 g.dotc(&df).conj()
329 };
330 let one = Complex::new(1.0, 0.0);
331 result.layers.radii[i] += contract(Direction {
332 radius: one,
333 ..Direction::default()
334 })
335 .re;
336 result.kz += contract(Direction {
337 kz: one,
338 ..Direction::default()
339 })
340 .re;
341 result.k0 += contract(Direction {
342 ks: [n - material.kappa, n + material.kappa],
343 ..Direction::default()
344 })
345 .re;
346 let layers = &mut result.layers;
347 let targets = [&mut layers.epsilon, &mut layers.mu, &mut layers.kappa];
348 for (tangent, target) in material.tangents().into_iter().zip(targets) {
349 target[i + side] += contract(Direction {
350 ks: [
351 self.k0 * (tangent.index - tangent.kappa),
352 self.k0 * (tangent.index + tangent.kappa),
353 ],
354 impedance: tangent.impedance,
355 ..Direction::default()
356 });
357 }
358 }
359 gq = interface.transfer.adjoint() * gq;
360 }
361 Ok(result)
362 }
363}