1#![allow(clippy::indexing_slicing)] use crate::{
7 Complex, Error, MAX_DEGREE, Result,
8 coeffs::{LayerGradient, Material, MieCylResidual, mie_cyl},
9 numerics::{self, finite, label_bits},
10};
11
12mod saved;
13
14#[derive(Debug)]
17pub struct CylinderResidual {
18 blocks: Vec<Block>,
19 solves: Vec<MieCylResidual>,
20 kz_count: usize,
21 boundaries: usize,
22}
23
24#[derive(Clone, Copy, Debug)]
28struct Block {
29 kz_index: usize,
30 solve: usize,
31 mirrored: bool,
32}
33
34#[derive(Debug)]
36pub struct CylinderGradient {
37 pub kzs: Vec<f64>,
39 pub k0: f64,
41 pub layers: LayerGradient,
43}
44
45pub fn cylinder(
55 kzs: &[f64],
56 mmax: u32,
57 k0: f64,
58 radii: &[f64],
59 materials: &[Material],
60) -> Result<(nalgebra::DMatrix<Complex>, CylinderResidual)> {
61 use rayon::prelude::*;
62 if kzs.is_empty() || kzs.iter().any(|k| !k.is_finite()) || mmax > MAX_DEGREE.unsigned_abs() {
63 return Err(Error::InvalidInput(
64 "require finite nonempty kzs and mmax <= 128".into(),
66 ));
67 }
68 let mut seen = std::collections::HashSet::new();
69 if kzs.iter().any(|&kz| !seen.insert(label_bits(kz))) {
70 return Err(Error::InvalidInput(
71 "axial wavenumbers must be unique".into(),
72 ));
73 }
74 let bound = i32::try_from(mmax).map_err(|_| Error::InvalidInput("invalid mmax".into()))?;
75 let mirrored =
76 |kz: f64, m: i32| (kz < 0.0 || (kz == 0.0 && m < 0)) && seen.contains(&label_bits(-kz));
77 let labels: Vec<_> = kzs
78 .iter()
79 .enumerate()
80 .flat_map(|(index, &kz)| (-bound..=bound).map(move |m| (index, kz, m)))
81 .collect();
82 let mut solved = Vec::new();
83 let mut solve = std::collections::HashMap::new();
84 for &(_, kz, m) in labels.iter().filter(|&&(_, kz, m)| !mirrored(kz, m)) {
85 solve.insert((label_bits(kz), m), solved.len());
86 solved.push((kz, m));
87 }
88 let solves = crate::threads::install(|| {
89 solved
90 .par_iter()
91 .map(|&(kz, m)| mie_cyl(kz, m, k0, radii, materials))
92 .collect::<Result<Vec<_>>>()
93 })?;
94 let blocks = labels
95 .iter()
96 .map(|&(index, kz, m)| {
97 let mirrored = mirrored(kz, m);
98 let (kz, m) = if mirrored { (-kz, -m) } else { (kz, m) };
99 Ok(Block {
100 kz_index: index,
101 solve: *solve
102 .get(&(label_bits(kz), m))
103 .ok_or_else(|| Error::InvalidInput("missing cylinder block".into()))?,
104 mirrored,
105 })
106 })
107 .collect::<Result<Vec<_>>>()?;
108 let mut value = numerics::zeros(2 * blocks.len(), 2 * blocks.len())?;
109 for (index, block) in blocks.iter().enumerate() {
110 value
111 .fixed_view_mut::<2, 2>(2 * index, 2 * index)
112 .copy_from(&crate::coeffs::to_mode_order(solves[block.solve].value()));
113 }
114 Ok((
115 value,
116 CylinderResidual {
117 blocks,
118 solves,
119 kz_count: kzs.len(),
120 boundaries: radii.len(),
121 },
122 ))
123}
124
125impl CylinderResidual {
126 #[must_use]
128 pub const fn input_counts(&self) -> (usize, usize) {
129 (self.kz_count, self.boundaries)
130 }
131
132 #[must_use]
134 pub fn shape(&self) -> (usize, usize) {
135 (2 * self.blocks.len(), 2 * self.blocks.len())
136 }
137
138 pub fn pushforward(
143 &self,
144 kzs: &[f64],
145 k0: f64,
146 radii: &[f64],
147 materials: &[Material],
148 ) -> Result<nalgebra::DMatrix<Complex>> {
149 use rayon::prelude::*;
150 crate::coeffs::validate_layer_tangents(self.boundaries, radii, materials)?;
151 if kzs.len() != self.kz_count || kzs.iter().any(|k| !k.is_finite()) || !k0.is_finite() {
152 return Err(Error::InvalidInput(
153 "wavenumber tangents must be finite and match the recorded inputs".into(),
154 ));
155 }
156 let blocks = crate::threads::install(|| {
157 self.blocks
158 .par_iter()
159 .map(|block| {
160 let kz = kzs[block.kz_index] * if block.mirrored { -1.0 } else { 1.0 };
161 self.solves[block.solve].pushforward(kz, k0, radii, materials)
162 })
163 .collect::<Result<Vec<_>>>()
164 })?;
165 let (rows, cols) = self.shape();
166 let mut tangent = numerics::zeros(rows, cols)?;
167 for (index, block) in blocks.iter().enumerate() {
168 tangent
169 .fixed_view_mut::<2, 2>(2 * index, 2 * index)
170 .copy_from(&crate::coeffs::to_mode_order(block));
171 }
172 Ok(tangent)
173 }
174
175 pub fn pullback(&self, cotangent: &nalgebra::DMatrix<Complex>) -> Result<CylinderGradient> {
181 use rayon::prelude::*;
182 if cotangent.shape() != self.shape() || cotangent.iter().any(|&g| !finite(g)) {
183 return Err(Error::InvalidInput(
184 "invalid cylinder T-matrix cotangent".into(),
185 ));
186 }
187 let solves = &self.solves;
188 let gradients = crate::threads::install(|| {
189 self.blocks
190 .par_iter()
191 .enumerate()
192 .map(|(index, block)| {
193 let g = cotangent
194 .fixed_view::<2, 2>(2 * index, 2 * index)
195 .into_owned();
196 let gradient =
197 solves[block.solve].pullback(&crate::coeffs::to_mode_order(&g))?;
198 let kz = if block.mirrored {
200 -gradient.kz
201 } else {
202 gradient.kz
203 };
204 Ok((kz, gradient))
205 })
206 .collect::<Result<Vec<_>>>()
207 })?;
208 let mut result = CylinderGradient {
209 kzs: vec![0.0; self.kz_count],
210 k0: 0.0,
211 layers: LayerGradient::zeros(self.boundaries),
212 };
213 for (block, (kz, gradient)) in self.blocks.iter().zip(gradients) {
214 result.kzs[block.kz_index] += kz;
215 result.k0 += gradient.k0;
216 result.layers.accumulate(&gradient.layers);
217 }
218 Ok(result)
219 }
220}