1#![allow(clippy::indexing_slicing)] mod saved;
9
10use nalgebra::DMatrix;
11
12use super::{AddGradient, AddResidual, Blocks, InterfaceGradient, InterfaceResidual};
13use crate::{
14 Complex, Error, Result,
15 linalg::product,
16 numerics::{
17 finite,
18 parallel::{try_fold_ordered, try_map},
19 },
20 pw::wave_vector_z,
21};
22
23#[derive(Debug)]
25struct Step {
26 normal: [Complex; 2],
28 phase: [Complex; 2],
30 below: [DMatrix<Complex>; 3],
32 boundary: AddResidual,
33 interface: InterfaceResidual,
34}
35
36#[derive(Debug)]
39struct Channel {
40 initial: InterfaceResidual,
41 steps: Vec<Step>,
42}
43
44#[derive(Debug)]
52pub struct LayerStackResidual {
53 ks: Vec<[Complex; 2]>,
54 q: Vec<[f64; 2]>,
55 thickness: Vec<f64>,
56 channels: Vec<Channel>,
57 fixed_q: bool,
58}
59
60#[derive(Debug)]
62pub struct LayerStackGradient {
63 pub ks: Vec<[Complex; 2]>,
66 pub zs: Vec<Complex>,
68 pub q: Vec<[f64; 2]>,
71 pub thickness: Vec<f64>,
73}
74
75fn propagate(below: Blocks, phase: [Complex; 2]) -> (Blocks, [DMatrix<Complex>; 3]) {
80 let [v0, v1, v2, v3] = below;
81 let diagonal = DMatrix::from_fn(
85 2,
86 2,
87 |i, j| {
88 if i == j { phase[i] } else { Complex::default() }
89 },
90 );
91 let spaced = [
92 product(&diagonal, &v0),
93 product(&diagonal, &product(&v1, &diagonal)),
94 v2,
95 product(&v3, &diagonal),
96 ];
97 (spaced, [v0, v1, v3])
98}
99
100fn propagate_pullback(
102 g: Blocks,
103 below: &[DMatrix<Complex>; 3],
104 phase: [Complex; 2],
105) -> (Blocks, [Complex; 2]) {
106 let [v0, v1, v3] = below;
107 let mut gradient = [Complex::default(); 2];
108 for i in 0..2 {
109 for j in 0..2 {
110 gradient[i] +=
111 g[0][(i, j)] * v0[(i, j)].conj() + g[1][(i, j)] * (v1[(i, j)] * phase[j]).conj();
112 gradient[j] +=
113 g[1][(i, j)] * (phase[i] * v1[(i, j)]).conj() + g[3][(i, j)] * v3[(i, j)].conj();
114 }
115 }
116 let [g0, g1, g2, g3] = g;
117 let conjugate = phase.map(|p| p.conj());
118 let previous = [
119 DMatrix::from_fn(2, 2, |i, j| conjugate[i] * g0[(i, j)]),
120 DMatrix::from_fn(2, 2, |i, j| conjugate[i] * g1[(i, j)] * conjugate[j]),
121 g2,
122 DMatrix::from_fn(2, 2, |i, j| g3[(i, j)] * conjugate[j]),
123 ];
124 (previous, gradient)
125}
126
127pub fn layer_stack(
140 ks: Vec<[Complex; 2]>,
141 zs: &[Complex],
142 q: Vec<[f64; 2]>,
143 thickness: &[f64],
144 axis: usize,
145 fixed_q: bool,
146) -> Result<(Vec<Blocks>, LayerStackResidual)> {
147 if ks.len() < 2
148 || ks.len() != zs.len()
149 || ks.len() != thickness.len() + 2
150 || q.is_empty()
151 || thickness.iter().any(|&d| !d.is_finite() || d < 0.0)
152 {
153 return Err(Error::InvalidInput("layers require at least two media, matching impedances, nonempty transverse channels and nonnegative interior thicknesses".into()));
154 }
155 let results = try_map(q.len(), q.len() > 1, |channel| -> Result<_> {
156 let q = q[channel];
157 let mut steps = Vec::with_capacity(thickness.len());
158 let (mut value, initial) =
159 super::interface([ks[0], ks[1]], [zs[0], zs[1]], q, axis, fixed_q)?;
160 for (layer, &d) in thickness.iter().enumerate() {
161 let medium = layer + 1;
162 let normal = ks[medium].map(|k| wave_vector_z(q[0].into(), q[1].into(), k));
164 let phase = normal.map(|kz| (Complex::i() * (kz * d)).exp());
165 let (spaced, below) = propagate(value, phase);
166 let (matching, interface) = super::interface(
167 [ks[medium], ks[medium + 1]],
168 [zs[medium], zs[medium + 1]],
169 q,
170 axis,
171 fixed_q,
172 )?;
173 let boundary;
174 (value, boundary) = super::add(spaced, matching)?;
175 steps.push(Step {
176 normal,
177 phase,
178 below,
179 boundary,
180 interface,
181 });
182 }
183 Ok((value, Channel { initial, steps }))
184 })?;
185 let (values, channels) = results.into_iter().unzip();
186 Ok((
187 values,
188 LayerStackResidual {
189 ks,
190 q,
191 thickness: thickness.to_vec(),
192 channels,
193 fixed_q,
194 },
195 ))
196}
197
198impl LayerStackGradient {
199 fn add(&mut self, other: &Self) {
201 for (a, b) in self
202 .ks
203 .iter_mut()
204 .flatten()
205 .chain(&mut self.zs)
206 .zip(other.ks.iter().flatten().chain(&other.zs))
207 {
208 *a += b;
209 }
210 for (a, b) in self
211 .q
212 .iter_mut()
213 .flatten()
214 .chain(&mut self.thickness)
215 .zip(other.q.iter().flatten().chain(&other.thickness))
216 {
217 *a += b;
218 }
219 }
220
221 fn add_interface(&mut self, medium: usize, q: &mut [f64; 2], gradient: InterfaceGradient) {
224 let InterfaceGradient {
225 ks: gk,
226 z: gz,
227 q: gq,
228 } = gradient;
229 for side in 0..2 {
230 for (target, g) in self.ks[medium + side].iter_mut().zip(gk[side]) {
231 *target += g;
232 }
233 self.zs[medium + side] += gz[side];
234 }
235 for (target, g) in q.iter_mut().zip(gq) {
236 *target += g;
237 }
238 }
239}
240
241impl LayerStackResidual {
242 #[must_use]
244 pub fn medium_count(&self) -> usize {
245 self.ks.len()
246 }
247
248 #[must_use]
250 pub fn channel_count(&self) -> usize {
251 self.q.len()
252 }
253
254 pub fn pushforward(
257 &self,
258 ks: &[[Complex; 2]],
259 zs: &[Complex],
260 q: &[[f64; 2]],
261 thickness: &[f64],
262 ) -> Result<Vec<Blocks>> {
263 if ks.len() != self.ks.len()
264 || zs.len() != self.ks.len()
265 || q.len() != self.q.len()
266 || thickness.len() != self.thickness.len()
267 || ks.iter().flatten().chain(zs).any(|&v| !finite(v))
268 || q.iter().flatten().chain(thickness).any(|v| !v.is_finite())
269 {
270 return Err(Error::InvalidInput("invalid layer-stack tangents".into()));
271 }
272 try_map(self.channels.len(), self.channels.len() > 1, |i| {
273 let channel = &self.channels[i];
274 let dq = if self.fixed_q { [0.0; 2] } else { q[i] };
275 let mut tangent = channel
276 .initial
277 .pushforward([ks[0], ks[1]], [zs[0], zs[1]], dq)?;
278 for (layer, step) in channel.steps.iter().enumerate() {
279 let medium = layer + 1;
280 let phase_tangent: [Complex; 2] = std::array::from_fn(|pol| {
281 let normal_tangent = (self.ks[medium][pol] * ks[medium][pol]
282 - self.q[i][0] * dq[0]
283 - self.q[i][1] * dq[1])
284 / step.normal[pol];
285 Complex::i()
286 * step.phase[pol]
287 * (self.thickness[layer] * normal_tangent
288 + step.normal[pol] * thickness[layer])
289 });
290 let [dv0, dv1, dv2, dv3] = tangent;
291 let [v0, v1, v3] = &step.below;
292 let phase = step.phase;
293 let spaced = [
294 DMatrix::from_fn(2, 2, |i, j| {
295 phase_tangent[i] * v0[(i, j)] + phase[i] * dv0[(i, j)]
296 }),
297 DMatrix::from_fn(2, 2, |i, j| {
298 phase_tangent[i] * v1[(i, j)] * phase[j]
299 + phase[i] * dv1[(i, j)] * phase[j]
300 + phase[i] * v1[(i, j)] * phase_tangent[j]
301 }),
302 dv2,
303 DMatrix::from_fn(2, 2, |i, j| {
304 dv3[(i, j)] * phase[j] + v3[(i, j)] * phase_tangent[j]
305 }),
306 ];
307 let boundary = step.interface.pushforward(
308 [ks[medium], ks[medium + 1]],
309 [zs[medium], zs[medium + 1]],
310 dq,
311 )?;
312 tangent = step.boundary.pushforward(&spaced, &boundary)?;
313 }
314 Ok(tangent)
315 })
316 }
317
318 pub fn pullback(&self, cotangent: Vec<Blocks>) -> Result<LayerStackGradient> {
320 if cotangent.len() != self.q.len()
321 || cotangent
322 .iter()
323 .flatten()
324 .any(|b| b.shape() != (2, 2) || b.iter().any(|&v| !finite(v)))
325 {
326 return Err(Error::InvalidInput(
327 "invalid compact layer-stack cotangent".into(),
328 ));
329 }
330 let Self {
331 ks,
332 q,
333 thickness,
334 channels,
335 fixed_q,
336 } = self;
337 let mut q_gradients = vec![[0.0; 2]; q.len()];
340 let items: Vec<_> = channels
341 .iter()
342 .zip(cotangent)
343 .zip(&mut q_gradients)
344 .collect();
345 let mut result = try_fold_ordered(
346 items,
347 true,
348 || LayerStackGradient {
349 ks: vec![[Complex::default(); 2]; ks.len()],
350 zs: vec![Complex::default(); ks.len()],
351 q: Vec::new(),
352 thickness: vec![0.0; ks.len() - 2],
353 },
354 |mut result, i, ((channel, mut cotangent), q_gradient)| -> Result<_> {
355 let wavevector = q[i];
356 for (layer, step) in channel.steps.iter().enumerate().rev() {
357 let medium = layer + 1;
358 let AddGradient {
359 lower: spaced_g,
360 upper: interface_g,
361 } = step.boundary.pullback(&cotangent)?;
362 let interface_g = step.interface.pullback(&interface_g)?;
363 result.add_interface(medium, q_gradient, interface_g);
364 let (previous, phase_g) = propagate_pullback(spaced_g, &step.below, step.phase);
365 let d = thickness[layer];
366 for (pol, (&phase, &normal)) in step.phase.iter().zip(&step.normal).enumerate()
367 {
368 let phase_g = phase_g[pol];
369 let slope = Complex::i() * phase;
371 result.thickness[layer] += (phase_g.conj() * slope * normal).re;
372 let normal_g = phase_g * (slope * d).conj();
373 result.ks[medium][pol] += normal_g * (ks[medium][pol] / normal).conj();
375 if !fixed_q {
376 for (target, &component) in q_gradient.iter_mut().zip(&wavevector) {
377 *target -= (normal_g.conj() * component / normal).re;
378 }
379 }
380 }
381 cotangent = previous;
382 }
383 let initial_g = channel.initial.pullback(&cotangent)?;
384 result.add_interface(0, q_gradient, initial_g);
385 Ok(result)
386 },
387 |mut total, partial| {
388 total.add(&partial);
389 total
390 },
391 )?;
392 result.q = q_gradients;
393 Ok(result)
394 }
395}
396
397#[cfg(test)]
398mod tests {
399 use super::{Blocks, layer_stack};
400 use crate::{
401 Complex,
402 test_support::{assert_same_bits_on_pools, bits, patterned},
403 };
404
405 #[test]
409 fn pullback_does_not_depend_on_the_thread_count() {
410 let c = Complex::new;
411 let ks = vec![
412 [c(1.0, 0.0); 2],
413 [c(1.7, 0.02), c(1.8, 0.01)],
414 [c(1.3, 0.0), c(1.35, 0.0)],
415 [c(2.1, 0.05); 2],
416 [c(1.0, 0.0); 2],
417 ];
418 let zs = [
419 c(1.0, 0.0),
420 c(0.7, 0.01),
421 c(0.8, 0.0),
422 c(0.6, 0.02),
423 c(1.0, 0.0),
424 ];
425 let thickness = [0.3, 0.45, 0.2];
426 let channels = 100_u32;
427 let q: Vec<[f64; 2]> = (0..channels)
428 .map(|j| {
429 let t = f64::from(j);
430 [0.9 * (0.7 * t).sin(), 0.8 * (1.3 * t).cos()]
431 })
432 .collect();
433 let g: Vec<Blocks> = (0..channels)
434 .map(|j| [0.0, 0.25, 0.5, 0.75].map(|seed| patterned(2, 2, f64::from(j) + seed)))
435 .collect();
436 assert_same_bits_on_pools(|| {
437 let (_, residual) =
438 layer_stack(ks.clone(), &zs, q.clone(), &thickness, 2, false).unwrap();
439 let g = residual.pullback(g.clone()).unwrap();
440 bits(&[&g.ks, &g.zs, &g.q, &g.thickness])
441 });
442 }
443}