1mod saved;
8
9use nalgebra::DMatrix;
10
11use super::{Blocks, checked_dimension, dimension, identity_blocks};
12use crate::{
13 Complex, Error, Result,
14 numerics::{Jet, finite},
15};
16
17fn fresnel_values<const N: usize>(
24 ks: [[Jet<N>; 2]; 2],
25 kzs: [[Jet<N>; 2]; 2],
26 zs: [Jet<N>; 2],
27) -> [[[[Jet<N>; 2]; 2]; 2]; 2] {
28 let ap = std::array::from_fn::<_, 2, _>(|i| ks[i][0] * kzs[i][1] + ks[i][1] * kzs[i][0]);
29 let am = std::array::from_fn::<_, 2, _>(|i| ks[i][0] * kzs[i][1] - ks[i][1] * kzs[i][0]);
30 let bp = ks[0][1] * kzs[1][1] + ks[1][1] * kzs[0][1];
31 let cp = ks[0][0] * kzs[1][0] + ks[1][0] * kzs[0][0];
32 let zs_diff = zs[0] - zs[1];
33 let zs_prod = 4.0 * zs[0] * zs[1];
34 let pref = 1.0 / (zs_diff * zs_diff * ap[0] * ap[1] + zs_prod * bp * cp);
35 let mut res = [[[[Jet::default(); 2]; 2]; 2]; 2];
36 for i in 0..2 {
37 let j = 1 - i;
38 res[i][i][0][0] = (zs[0] + zs[1]) * ks[j][0] * kzs[i][0] * bp * zs[j] * 4.0 * pref;
39 res[i][i][0][1] = -(zs[i] - zs[j])
40 * ks[j][0]
41 * kzs[i][1]
42 * (ks[j][1] * kzs[i][0] - ks[i][0] * kzs[j][1])
43 * zs[j]
44 * 4.0
45 * pref;
46 res[i][i][1][0] = -(zs[i] - zs[j])
47 * ks[j][1]
48 * kzs[i][0]
49 * (ks[j][0] * kzs[i][1] - ks[i][1] * kzs[j][0])
50 * zs[j]
51 * 4.0
52 * pref;
53 res[i][i][1][1] = (zs[0] + zs[1]) * ks[j][1] * kzs[i][1] * cp * zs[j] * 4.0 * pref;
54 res[j][i][0][0] = (-zs_diff * zs_diff * am[i] * ap[j]
55 - zs_prod * bp * (ks[i][0] * kzs[j][0] - ks[j][0] * kzs[i][0]))
56 * pref;
57 res[j][i][0][1] =
58 -2.0 * (zs[j] * zs[j] - zs[i] * zs[i]) * ks[i][0] * kzs[i][1] * ap[j] * pref;
59 res[j][i][1][0] =
60 -2.0 * (zs[j] * zs[j] - zs[i] * zs[i]) * ks[i][1] * kzs[i][0] * ap[j] * pref;
61 res[j][i][1][1] = (zs_diff * zs_diff * am[i] * ap[j]
62 - zs_prod * cp * (ks[i][1] * kzs[j][1] - ks[j][1] * kzs[i][1]))
63 * pref;
64 }
65 res
66}
67
68#[derive(Debug)]
72pub struct FresnelResidual {
73 ks: [[Complex; 2]; 2],
74 kzs: [[Complex; 2]; 2],
75 zs: [Complex; 2],
76}
77
78pub fn fresnel(
85 ks: [[Complex; 2]; 2],
86 kzs: [[Complex; 2]; 2],
87 zs: [Complex; 2],
88) -> Result<(Blocks, FresnelResidual)> {
89 if ks
90 .iter()
91 .flatten()
92 .chain(kzs.iter().flatten())
93 .chain(zs.iter())
94 .any(|&v| !finite(v))
95 {
96 return Err(Error::InvalidInput("Fresnel inputs must be finite".into()));
97 }
98 if ks[0] == ks[1]
99 && kzs[0] == kzs[1]
100 && zs[0] == zs[1]
101 && zs[0] != Complex::default()
102 && ks[0].iter().all(|&k| k != Complex::default())
103 && kzs[0].contains(&Complex::default())
104 {
105 return Ok((identity_blocks(2), FresnelResidual { ks, kzs, zs }));
108 }
109 let values = fresnel_values::<0>(
110 ks.map(|r| r.map(Jet::constant)),
111 kzs.map(|r| r.map(Jet::constant)),
112 zs.map(Jet::constant),
113 );
114 let value =
115 std::array::from_fn(|b| DMatrix::from_fn(2, 2, |i, j| values[b / 2][b % 2][i][j].value));
116 dimension(&value).map_err(|_| Error::Singular)?;
117 Ok((value, FresnelResidual { ks, kzs, zs }))
118}
119
120#[derive(Clone, Copy, Debug)]
123pub struct FresnelGradient {
124 pub ks: [[Complex; 2]; 2],
126 pub kz: [[Complex; 2]; 2],
128 pub z: [Complex; 2],
130}
131
132impl FresnelResidual {
133 pub fn pushforward(
135 &self,
136 ks: [[Complex; 2]; 2],
137 kz: [[Complex; 2]; 2],
138 z: [Complex; 2],
139 ) -> Result<Blocks> {
140 if ks
141 .iter()
142 .flatten()
143 .chain(kz.iter().flatten())
144 .chain(&z)
145 .any(|&v| !finite(v))
146 {
147 return Err(Error::InvalidInput(
148 "Fresnel tangents must be finite".into(),
149 ));
150 }
151 let values = fresnel_values(
152 std::array::from_fn(|i| {
153 std::array::from_fn(|j| Jet {
154 value: self.ks[i][j],
155 derivative: [ks[i][j]],
156 })
157 }),
158 std::array::from_fn(|i| {
159 std::array::from_fn(|j| Jet {
160 value: self.kzs[i][j],
161 derivative: [kz[i][j]],
162 })
163 }),
164 std::array::from_fn(|i| Jet {
165 value: self.zs[i],
166 derivative: [z[i]],
167 }),
168 );
169 if values
170 .iter()
171 .flatten()
172 .flatten()
173 .flatten()
174 .any(|v| !v.finite())
175 {
176 return Err(Error::InvalidInput(
177 "Fresnel derivative is undefined for this grazing-channel limit".into(),
178 ));
179 }
180 Ok(std::array::from_fn(|b| {
181 DMatrix::from_fn(2, 2, |i, j| values[b / 2][b % 2][i][j].derivative[0])
182 }))
183 }
184
185 pub fn pullback(&self, cotangent: &Blocks) -> Result<FresnelGradient> {
187 const KS: usize = 0;
190 const KZS: usize = 4;
191 const ZS: usize = 8;
192 checked_dimension(cotangent, 2, "Fresnel cotangent blocks must be 2 by 2")?;
193 let ks = std::array::from_fn(|i| {
194 std::array::from_fn(|j| Jet::<10>::variable(self.ks[i][j], KS + 2 * i + j))
195 });
196 let kzs = std::array::from_fn(|i| {
197 std::array::from_fn(|j| Jet::variable(self.kzs[i][j], KZS + 2 * i + j))
198 });
199 let zs = std::array::from_fn(|i| Jet::variable(self.zs[i], ZS + i));
200 let values = fresnel_values(ks, kzs, zs);
201 if values
202 .iter()
203 .flatten()
204 .flatten()
205 .flatten()
206 .any(|v| !v.finite())
207 {
208 return Err(Error::InvalidInput(
209 "Fresnel derivative is undefined for this grazing-channel limit".into(),
210 ));
211 }
212 let mut result = [Complex::default(); 10];
213 for (b, block) in cotangent.iter().enumerate() {
214 for i in 0..2 {
215 for j in 0..2 {
216 for (gradient, d) in
217 result.iter_mut().zip(values[b / 2][b % 2][i][j].derivative)
218 {
219 *gradient += block[(i, j)] * d.conj();
220 }
221 }
222 }
223 }
224 Ok(FresnelGradient {
225 ks: std::array::from_fn(|i| std::array::from_fn(|j| result[KS + 2 * i + j])),
226 kz: std::array::from_fn(|i| std::array::from_fn(|j| result[KZS + 2 * i + j])),
227 z: [result[ZS], result[ZS + 1]],
228 })
229 }
230}
231
232type InterfaceMatrix = nalgebra::SMatrix<Complex, 4, 4>;
234
235type BoundaryJets<const N: usize> = [[Jet<N>; 4]; 4];
237
238fn normal_component<const N: usize>(k: Jet<N>, q: [Jet<N>; 2]) -> Result<Jet<N>> {
241 let mut normal = (k * k - q[0] * q[0] - q[1] * q[1]).sqrt();
242 if N > 0 && normal.value == Complex::default() {
245 return Err(Error::InvalidInput(
246 "interface derivative is undefined at an exact diffraction threshold".into(),
247 ));
248 }
249 if normal.value.im < 0.0 {
251 normal = -normal;
252 }
253 Ok(normal)
254}
255
256pub(super) fn port_boundary<const N: usize>(
260 ks: [Jet<N>; 2],
261 z: Jet<N>,
262 q: [Jet<N>; 2],
263 axis: usize,
264) -> Result<[[[Jet<N>; 4]; 2]; 2]> {
265 let mut waves = [[[Jet::default(); 4]; 2]; 2];
266 let a = (axis + 1) % 3;
267 let b = (axis + 2) % 3;
268 for (pol, &wave_number) in ks.iter().enumerate() {
269 let normal = normal_component(wave_number, q)?;
270 for (direction, polarizations) in waves.iter_mut().enumerate() {
271 let mut vector = [Jet::default(); 3];
272 vector[a] = q[0];
273 vector[b] = q[1];
274 vector[axis] = if direction == 0 { normal } else { -normal };
275 let e = crate::pw::polarization_from_inputs(vector, u8::from(pol != 0))?;
276 let impedance = -Complex::i() * (if pol == 0 { -1.0 } else { 1.0 }) / z;
277 polarizations[pol] = [e[a], e[b], impedance * e[a], impedance * e[b]];
278 }
279 }
280 Ok(waves)
281}
282
283fn interface_boundary<const N: usize>(
287 ks: [[Jet<N>; 2]; 2],
288 zs: [Jet<N>; 2],
289 q: [Jet<N>; 2],
290 axis: usize,
291) -> Result<(BoundaryJets<N>, BoundaryJets<N>)> {
292 let waves = [
293 port_boundary(ks[0], zs[0], q, axis)?,
294 port_boundary(ks[1], zs[1], q, axis)?,
295 ];
296 let lhs = std::array::from_fn(|row| {
297 std::array::from_fn(|col| {
298 if col < 2 {
299 waves[1][0][col][row]
300 } else {
301 -waves[0][1][col - 2][row]
302 }
303 })
304 });
305 let rhs = std::array::from_fn(|row| {
306 std::array::from_fn(|col| {
307 if col < 2 {
308 waves[0][0][col][row]
309 } else {
310 -waves[1][1][col - 2][row]
311 }
312 })
313 });
314 Ok((lhs, rhs))
315}
316
317#[derive(Debug)]
320pub struct InterfaceResidual {
321 ks: [[Complex; 2]; 2],
322 zs: [Complex; 2],
323 q: [f64; 2],
324 axis: usize,
325 inverse: Option<InterfaceMatrix>,
326 result: InterfaceMatrix,
328 fixed_q: bool,
329}
330
331fn interface_matrix(blocks: &Blocks) -> InterfaceMatrix {
333 InterfaceMatrix::from_fn(|i, j| blocks[2 * (i / 2) + j / 2][(i % 2, j % 2)])
334}
335
336fn interface_blocks(matrix: &InterfaceMatrix) -> Blocks {
338 std::array::from_fn(|block| {
339 DMatrix::from_fn(2, 2, |i, j| {
340 matrix[(2 * (block / 2) + i, 2 * (block % 2) + j)]
341 })
342 })
343}
344
345pub fn interface(
354 ks: [[Complex; 2]; 2],
355 zs: [Complex; 2],
356 q: [f64; 2],
357 axis: usize,
358 fixed_q: bool,
359) -> Result<(Blocks, InterfaceResidual)> {
360 if axis > 2
361 || q.iter().any(|x| !x.is_finite())
362 || ks
363 .iter()
364 .flatten()
365 .chain(&zs)
366 .any(|&v| !finite(v) || v == Complex::default())
367 {
368 return Err(Error::InvalidInput("interface requires finite transverse components, nonzero finite wavenumbers/impedances, and a Cartesian normal".into()));
369 }
370 let (lhs, rhs) = interface_boundary::<0>(
371 ks.map(|k| k.map(Jet::constant)),
372 zs.map(Jet::constant),
373 q.map(Jet::constant),
374 axis,
375 )?;
376 let matrix = InterfaceMatrix::from_fn(|i, j| lhs[i][j].value);
377 let identical = ks[0] == ks[1] && zs[0] == zs[1];
378 let matched_grazing = identical
379 && ks[0]
380 .iter()
381 .any(|&k| k * k - q[0] * q[0] - q[1] * q[1] == Complex::default());
382 let inverse = if matched_grazing {
383 None
384 } else {
385 Some(matrix.lu().try_inverse().ok_or(Error::Singular)?)
386 };
387 let result = match inverse {
392 Some(inverse) if !identical => inverse * InterfaceMatrix::from_fn(|i, j| rhs[i][j].value),
393 _ => InterfaceMatrix::identity(),
394 };
395 let value = interface_blocks(&result);
396 dimension(&value).map_err(|_| Error::Singular)?;
397 Ok((
398 value,
399 InterfaceResidual {
400 ks,
401 zs,
402 q,
403 axis,
404 inverse,
405 result,
406 fixed_q,
407 },
408 ))
409}
410
411#[derive(Clone, Copy, Debug)]
414pub struct InterfaceGradient {
415 pub ks: [[Complex; 2]; 2],
417 pub z: [Complex; 2],
419 pub q: [f64; 2],
422}
423
424impl InterfaceResidual {
425 pub fn pushforward(
428 &self,
429 ks: [[Complex; 2]; 2],
430 z: [Complex; 2],
431 q: [f64; 2],
432 ) -> Result<Blocks> {
433 if ks.iter().flatten().chain(&z).any(|&v| !finite(v)) || q.iter().any(|v| !v.is_finite()) {
434 return Err(Error::InvalidInput(
435 "interface tangents must be finite".into(),
436 ));
437 }
438 let inverse = self.inverse.ok_or_else(|| {
439 Error::InvalidInput(
440 "interface derivative is undefined at an exact diffraction threshold".into(),
441 )
442 })?;
443 let fixed_q = self.fixed_q || (self.axis == 2 && self.q.iter().all(|&v| v == 0.0));
444 let (lhs, rhs) = interface_boundary(
445 std::array::from_fn(|i| {
446 std::array::from_fn(|j| Jet {
447 value: self.ks[i][j],
448 derivative: [ks[i][j]],
449 })
450 }),
451 std::array::from_fn(|i| Jet {
452 value: self.zs[i],
453 derivative: [z[i]],
454 }),
455 std::array::from_fn(|i| Jet {
456 value: self.q[i].into(),
457 derivative: [if fixed_q {
458 Complex::default()
459 } else {
460 q[i].into()
461 }],
462 }),
463 self.axis,
464 )?;
465 let lhs = InterfaceMatrix::from_fn(|i, j| lhs[i][j].derivative[0]);
466 let rhs = InterfaceMatrix::from_fn(|i, j| rhs[i][j].derivative[0]);
467 let tangent = interface_blocks(&(inverse * (rhs - lhs * self.result)));
468 dimension(&tangent)?;
469 Ok(tangent)
470 }
471
472 pub fn pullback(&self, cotangent: &Blocks) -> Result<InterfaceGradient> {
474 const KS: usize = 0;
477 const ZS: usize = 4;
478 const Q: usize = 6;
479 checked_dimension(cotangent, 2, "interface cotangent blocks must be 2 by 2")?;
480 let cotangent = interface_matrix(cotangent);
481 let inverse = self.inverse.ok_or_else(|| {
482 Error::InvalidInput(
483 "interface derivative is undefined at an exact diffraction threshold".into(),
484 )
485 })?;
486 let adjoint = inverse.adjoint() * cotangent;
487 let operator = -adjoint * self.result.adjoint();
488 let ks = std::array::from_fn(|i| {
489 std::array::from_fn(|j| Jet::<8>::variable(self.ks[i][j], KS + 2 * i + j))
490 });
491 let zs = std::array::from_fn(|i| Jet::variable(self.zs[i], ZS + i));
492 let fixed_q = self.fixed_q || (self.axis == 2 && self.q.iter().all(|&q| q == 0.0));
496 let q = std::array::from_fn(|i| {
497 if fixed_q {
498 Jet::constant(self.q[i])
499 } else {
500 Jet::variable(self.q[i], Q + i)
501 }
502 });
503 let (lhs, rhs) = interface_boundary(ks, zs, q, self.axis)?;
504 let mut result = [Complex::default(); 8];
505 for i in 0..4 {
506 for j in 0..4 {
507 for (a, g) in result.iter_mut().enumerate() {
508 *g += operator[(i, j)] * lhs[i][j].derivative[a].conj()
509 + adjoint[(i, j)] * rhs[i][j].derivative[a].conj();
510 }
511 }
512 }
513 Ok(InterfaceGradient {
514 ks: std::array::from_fn(|i| std::array::from_fn(|j| result[KS + 2 * i + j])),
515 z: [result[ZS], result[ZS + 1]],
516 q: [result[Q].re, result[Q + 1].re],
517 })
518 }
519}
520
521#[derive(Debug)]
524pub struct PropagationResidual {
525 vectors: Vec<[Complex; 3]>,
526 distance: [f64; 3],
527 phases: Vec<[Complex; 2]>,
529}
530
531pub fn propagation(
537 vectors: Vec<[Complex; 3]>,
538 distance: [f64; 3],
539) -> Result<(Blocks, PropagationResidual)> {
540 if vectors.is_empty()
541 || vectors.iter().flatten().any(|&v| !finite(v))
542 || distance.iter().any(|r| !r.is_finite())
543 {
544 return Err(Error::InvalidInput(
545 "propagation requires finite nonempty wavevectors and displacement".into(),
546 ));
547 }
548 let phases = vectors
549 .iter()
550 .map(|k| {
551 [1.0, -1.0].map(|sign| {
552 (Complex::i()
553 * (sign * (k[0] * distance[0] + k[1] * distance[1]) + k[2] * distance[2]))
554 .exp()
555 })
556 })
557 .collect::<Vec<_>>();
558 if phases.iter().flatten().any(|&phase| !finite(phase)) {
560 return Err(Error::NonFinite("non-finite propagation phase".into()));
561 }
562 let n = vectors.len();
563 let mut value: Blocks = std::array::from_fn(|_| DMatrix::zeros(n, n));
564 for (i, phase) in phases.iter().enumerate() {
565 value[0][(i, i)] = phase[0];
566 value[3][(i, i)] = phase[1];
567 }
568 Ok((
569 value,
570 PropagationResidual {
571 vectors,
572 distance,
573 phases,
574 },
575 ))
576}
577
578#[derive(Clone, Debug)]
580pub struct PropagationGradient {
581 pub vectors: Vec<[Complex; 3]>,
583 pub distance: [f64; 3],
585}
586
587impl PropagationResidual {
588 #[must_use]
590 pub fn shape(&self) -> (usize, usize) {
591 (self.vectors.len(), self.vectors.len())
592 }
593
594 pub fn pushforward(&self, vectors: &[[Complex; 3]], distance: [f64; 3]) -> Result<Blocks> {
596 if vectors.len() != self.vectors.len()
597 || vectors.iter().flatten().any(|&v| !finite(v))
598 || distance.iter().any(|v| !v.is_finite())
599 {
600 return Err(Error::InvalidInput(
601 "propagation tangents must be finite and match inputs".into(),
602 ));
603 }
604 let n = self.vectors.len();
605 let mut tangent: Blocks = std::array::from_fn(|_| DMatrix::zeros(n, n));
606 for (i, ((k, dk), phase)) in self
607 .vectors
608 .iter()
609 .zip(vectors)
610 .zip(&self.phases)
611 .enumerate()
612 {
613 let dot: [Complex; 3] =
614 std::array::from_fn(|a| dk[a] * self.distance[a] + k[a] * distance[a]);
615 for ((block, sign), phase) in [(0, 1.0), (3, -1.0)].into_iter().zip(phase) {
616 tangent[block][(i, i)] = Complex::i() * phase * (sign * (dot[0] + dot[1]) + dot[2]);
617 }
618 }
619 dimension(&tangent)?;
620 Ok(tangent)
621 }
622
623 pub fn pullback(&self, cotangent: &Blocks) -> Result<PropagationGradient> {
625 let n = checked_dimension(
626 cotangent,
627 self.vectors.len(),
628 "propagation cotangent shape mismatch",
629 )?;
630 let mut vectors = vec![[Complex::default(); 3]; n];
631 let mut distance = [0.0; 3];
632 for (i, (k, phase)) in self.vectors.iter().zip(&self.phases).enumerate() {
633 for ((block, sign), phase) in [(0, 1.0), (3, -1.0)].into_iter().zip(phase) {
634 let weight = cotangent[block][(i, i)].conj() * Complex::i() * phase;
635 for (a, s) in [sign, sign, 1.0].into_iter().enumerate() {
636 vectors[i][a] += (weight * s * self.distance[a]).conj();
637 distance[a] += (weight * s * k[a]).re;
638 }
639 }
640 }
641 Ok(PropagationGradient { vectors, distance })
642 }
643}