1mod saved;
6
7use faer::MatRef;
8use nalgebra::DMatrix;
9
10use super::{Blocks, StoredBlock, any_nonfinite, checked_dimension, interface::port_boundary};
11use crate::{
12 Complex, Error, Result,
13 linalg::{product_adjoint_right, product_views, view},
14 numerics::{Jet, finite, label_bits},
15};
16
17const KS: usize = 0;
21const ZS: usize = 4;
23const Q: usize = 6;
25
26type PortWaves<const N: usize> = [[[[Jet<N>; 4]; 2]; 2]; 2];
29
30fn port_waves<const N: usize>(
33 ks: [[Complex; 2]; 2],
34 zs: [Complex; 2],
35 q: [f64; 2],
36 axis: usize,
37 helicity: bool,
38 fixed_q: bool,
39) -> Result<PortWaves<N>> {
40 let ks: [[Jet<N>; 2]; 2] = std::array::from_fn(|port| {
41 std::array::from_fn(|pol| Jet::variable(ks[port][pol], KS + 2 * port + pol))
42 });
43 let zs: [Jet<N>; 2] = std::array::from_fn(|port| Jet::variable(zs[port], ZS + port));
44 let q = std::array::from_fn(|j| {
45 if fixed_q {
46 Jet::constant(q[j])
47 } else {
48 Jet::variable(q[j], Q + j)
49 }
50 });
51 port_wave_inputs(ks, zs, q, axis, helicity)
52}
53
54fn port_wave_inputs<const N: usize>(
56 ks: [[Jet<N>; 2]; 2],
57 zs: [Jet<N>; 2],
58 q: [Jet<N>; 2],
59 axis: usize,
60 helicity: bool,
61) -> Result<PortWaves<N>> {
62 let mut waves = [
63 port_boundary(ks[0], zs[0], q, axis)?,
64 port_boundary(ks[1], zs[1], q, axis)?,
65 ];
66 if !helicity {
67 for port in &mut waves {
68 for polarizations in port {
69 let [minus, plus] = *polarizations;
70 *polarizations = [
71 std::array::from_fn(|j| (plus[j] - minus[j]) * std::f64::consts::FRAC_1_SQRT_2),
72 std::array::from_fn(|j| (plus[j] + minus[j]) * std::f64::consts::FRAC_1_SQRT_2),
73 ];
74 }
75 }
76 }
77 Ok(waves)
78}
79
80fn cross(e: [Complex; 4], h: [Complex; 4]) -> f64 {
83 0.5 * (e[0] * h[3].conj() - e[1] * h[2].conj()).re
84}
85
86fn cross_direction(e: [Jet<1>; 4], h: [Jet<1>; 4]) -> f64 {
88 cross(e.map(|v| v.derivative[0]), h.map(|v| v.value))
89 + cross(e.map(|v| v.value), h.map(|v| v.derivative[0]))
90}
91
92fn cross_pullback(e: [Complex; 4], h: [Complex; 4], weight: f64) -> ([Complex; 4], [Complex; 4]) {
94 (
95 [
96 0.5 * weight * h[3],
97 -0.5 * weight * h[2],
98 Complex::default(),
99 Complex::default(),
100 ],
101 [
102 Complex::default(),
103 Complex::default(),
104 -0.5 * weight * e[1],
105 0.5 * weight * e[0],
106 ],
107 )
108}
109
110fn aggregate<const N: usize>(
116 waves: &[PortWaves<N>],
117 modes: &[(usize, u8)],
118 amplitudes: impl Fn(usize) -> [Jet<N>; 3],
119 direction: usize,
120) -> Vec<[[Jet<N>; 4]; 3]> {
121 let transmission = direction;
122 let reflection = 1 - transmission;
123 let mut fields = vec![[[Jet::default(); 4]; 3]; waves.len()];
124 for (mode, &(group, pol)) in modes.iter().enumerate() {
125 for (which, ((port, wave_direction), amplitude)) in [
126 (reflection, transmission),
127 (transmission, transmission),
128 (reflection, reflection),
129 ]
130 .into_iter()
131 .zip(amplitudes(mode))
132 .enumerate()
133 {
134 for (j, field) in fields[group][which].iter_mut().enumerate() {
135 *field += waves[group][port][wave_direction][usize::from(pol)][j] * amplitude;
136 }
137 }
138 }
139 fields
140}
141
142fn amplitudes(
144 incident: &DMatrix<Complex>,
145 outgoing: &[DMatrix<Complex>; 2],
146 mode: usize,
147 illumination: usize,
148) -> [Complex; 3] {
149 [
150 incident[(mode, illumination)],
151 outgoing[0][(mode, illumination)],
152 outgoing[1][(mode, illumination)],
153 ]
154}
155
156#[derive(Clone, Debug)]
159pub struct TrPorts {
160 pub ks: [[Complex; 2]; 2],
162 pub zs: [Complex; 2],
164 pub q: Vec<[f64; 2]>,
166 pub modes: Vec<(usize, u8)>,
168 pub axis: usize,
170 pub helicity: bool,
172 pub direction: usize,
175}
176
177impl TrPorts {
178 #[allow(clippy::float_cmp)] fn validate(&self) -> Result<()> {
180 if self.axis > 2
181 || self.direction > 1
182 || self
183 .modes
184 .iter()
185 .any(|&(group, pol)| group >= self.q.len() || pol > 1)
186 || self.q.iter().flatten().any(|v| !v.is_finite())
187 || self
188 .ks
189 .iter()
190 .flatten()
191 .chain(&self.zs)
192 .any(|&v| !finite(v) || v == Complex::default())
193 {
194 return Err(Error::InvalidInput(INVALID_INPUTS.into()));
195 }
196 if !self.helicity && self.ks.iter().any(|k| k[0] != k[1]) {
197 return Err(Error::InvalidInput(
198 "parity power requires achiral port media".into(),
199 ));
200 }
201 let mut unique = std::collections::HashSet::new();
202 if self.q.iter().any(|q| !unique.insert(q.map(label_bits))) {
203 return Err(Error::InvalidInput(
204 "transverse groups must be distinct; place coherent modes in the same group".into(),
205 ));
206 }
207 let mut unique = std::collections::HashSet::new();
209 if self.modes.iter().any(|&mode| !unique.insert(mode)) {
210 return Err(Error::InvalidInput("plane modes must be distinct".into()));
211 }
212 Ok(())
213 }
214
215 fn waves<const N: usize>(&self, q: [f64; 2], fixed_q: bool) -> Result<PortWaves<N>> {
217 port_waves(self.ks, self.zs, q, self.axis, self.helicity, fixed_q)
218 }
219}
220
221const INVALID_INPUTS: &str = "require matching finite square S blocks, illumination columns, port media and valid plane modes";
222
223#[derive(Debug)]
227pub struct TrResidual {
228 matrices: [StoredBlock; 2],
229 incident: DMatrix<Complex>,
230 ports: TrPorts,
231 forward: TrForward,
232 fixed_q: bool,
233}
234
235#[derive(Debug)]
237pub struct TrGradient {
238 pub matrices: Blocks,
240 pub incident: DMatrix<Complex>,
242 pub ks: [[Complex; 2]; 2],
244 pub zs: [Complex; 2],
246 pub q: Vec<[f64; 2]>,
249}
250
251#[derive(Debug)]
253struct TrForward {
254 value: DMatrix<f64>,
256 flux: Vec<f64>,
257 outgoing: [DMatrix<Complex>; 2],
258 waves: Vec<PortWaves<0>>,
259}
260
261fn evaluate(
263 matrices: [MatRef<'_, Complex>; 2],
264 incident: &DMatrix<Complex>,
265 ports: &TrPorts,
266) -> Result<TrForward> {
267 let n = ports.modes.len();
268 if n == 0
269 || incident.nrows() != n
270 || incident.ncols() == 0
271 || matrices.iter().any(|m| m.nrows() != n || m.ncols() != n)
272 || incident.iter().any(|&z| !finite(z))
273 || matrices.iter().any(|&m| any_nonfinite(m))
274 {
275 return Err(Error::InvalidInput(INVALID_INPUTS.into()));
276 }
277 ports.validate()?;
278 let waves = ports
279 .q
280 .iter()
281 .map(|&q| ports.waves::<0>(q, true))
282 .collect::<Result<Vec<_>>>()?;
283 let outgoing = matrices.map(|m| product_views(m, view(incident)));
284 let sign = if ports.direction == 0 { 1.0 } else { -1.0 };
285 let mut flux = vec![0.0; incident.ncols()];
286 let mut value = DMatrix::zeros(2, incident.ncols());
287 for illumination in 0..incident.ncols() {
288 let mut transmitted = 0.0;
289 let mut reflected = 0.0;
290 for [input, trans, reflect] in aggregate(
291 &waves,
292 &ports.modes,
293 |mode| amplitudes(incident, &outgoing, mode, illumination).map(Jet::constant),
294 ports.direction,
295 ) {
296 let [input, trans, reflect] = [input, trans, reflect].map(|f| f.map(|v| v.value));
297 flux[illumination] +=
298 sign * (cross(input, input) + cross(input, reflect) + cross(reflect, input));
299 transmitted += sign * cross(trans, trans);
300 reflected -= sign * cross(reflect, reflect);
301 }
302 if !flux[illumination].is_finite() || flux[illumination] <= 0.0 {
303 return Err(Error::InvalidInput(
304 "transmittance requires positive finite incident power flux".into(),
305 ));
306 }
307 value[(0, illumination)] = transmitted / flux[illumination];
308 value[(1, illumination)] = reflected / flux[illumination];
309 }
310 if value.iter().any(|v| !v.is_finite()) {
311 return Err(Error::NonFinite(
312 "non-finite transmission or reflection".into(),
313 ));
314 }
315 Ok(TrForward {
316 value,
317 flux,
318 outgoing,
319 waves,
320 })
321}
322
323#[doc(alias = "transmittance")]
329#[doc(alias = "power")]
330pub fn tr_value(
331 matrices: [MatRef<'_, Complex>; 2],
332 incident: &DMatrix<Complex>,
333 ports: &TrPorts,
334) -> Result<DMatrix<f64>> {
335 Ok(evaluate(matrices, incident, ports)?.value)
336}
337
338#[doc(alias = "transmittance")]
348#[doc(alias = "power")]
349pub fn tr(
350 matrices: [MatRef<'_, Complex>; 2],
351 incident: DMatrix<Complex>,
352 ports: TrPorts,
353 fixed_q: bool,
354) -> Result<TrResidual> {
355 let forward = evaluate(matrices, &incident, &ports)?;
356 Ok(TrResidual {
357 matrices: matrices.map(StoredBlock::copy_view),
358 incident,
359 ports,
360 forward,
361 fixed_q,
362 })
363}
364
365impl TrResidual {
366 #[must_use]
369 pub const fn value(&self) -> &DMatrix<f64> {
370 &self.forward.value
371 }
372
373 #[must_use]
375 pub fn shape(&self) -> (usize, usize) {
376 self.forward.value.shape()
377 }
378
379 #[must_use]
381 pub fn input_shape(&self) -> (usize, usize, usize) {
382 (
383 self.incident.nrows(),
384 self.incident.ncols(),
385 self.ports.q.len(),
386 )
387 }
388
389 pub fn pushforward(
392 &self,
393 matrices: &Blocks,
394 incident: &DMatrix<Complex>,
395 ks: [[Complex; 2]; 2],
396 zs: [Complex; 2],
397 q: &[[f64; 2]],
398 ) -> Result<DMatrix<f64>> {
399 checked_dimension(
400 matrices,
401 self.incident.nrows(),
402 "power matrix tangent shape mismatch",
403 )?;
404 if incident.shape() != self.incident.shape()
405 || q.len() != self.ports.q.len()
406 || incident
407 .iter()
408 .chain(ks.iter().flatten())
409 .chain(&zs)
410 .any(|&v| !finite(v))
411 || q.iter().flatten().any(|v| !v.is_finite())
412 {
413 return Err(Error::InvalidInput(
414 "power tangents must be finite and match inputs".into(),
415 ));
416 }
417 let t = self.ports.direction;
418 let r = 1 - t;
419 let outgoing = std::array::from_fn(|which| {
420 let direction = if which == 0 { t } else { r };
421 product_views(view(&matrices[2 * direction + t]), view(&self.incident))
422 + product_views(self.matrices[which].view(), view(incident))
423 });
424 let fixed_media = ks
425 .iter()
426 .flatten()
427 .chain(&zs)
428 .all(|&v| v == Complex::default());
429 let ks = std::array::from_fn(|p| {
430 std::array::from_fn(|j| Jet {
431 value: self.ports.ks[p][j],
432 derivative: [ks[p][j]],
433 })
434 });
435 let zs = std::array::from_fn(|p| Jet {
436 value: self.ports.zs[p],
437 derivative: [zs[p]],
438 });
439 let waves = self
440 .ports
441 .q
442 .iter()
443 .zip(q)
444 .enumerate()
445 .map(|(group, (value, tangent))| {
446 if fixed_media && (self.fixed_q || tangent.iter().all(|&v| v == 0.0)) {
449 return Ok(self.forward.waves[group].map(|port| {
450 port.map(|direction| {
451 direction.map(|wave| wave.map(|v| Jet::constant(v.value)))
452 })
453 }));
454 }
455 port_wave_inputs(
456 ks,
457 zs,
458 std::array::from_fn(|j| Jet {
459 value: value[j].into(),
460 derivative: [if self.fixed_q {
461 Complex::default()
462 } else {
463 tangent[j].into()
464 }],
465 }),
466 self.ports.axis,
467 self.ports.helicity,
468 )
469 })
470 .collect::<Result<Vec<_>>>()?;
471 let sign = if t == 0 { 1.0 } else { -1.0 };
472 let mut tangent = DMatrix::zeros(2, incident.ncols());
473 for illumination in 0..incident.ncols() {
474 let fields = aggregate(
475 &waves,
476 &self.ports.modes,
477 |mode| {
478 let values =
479 amplitudes(&self.incident, &self.forward.outgoing, mode, illumination);
480 let directions = amplitudes(incident, &outgoing, mode, illumination);
481 std::array::from_fn(|i| Jet {
482 value: values[i],
483 derivative: [directions[i]],
484 })
485 },
486 t,
487 );
488 let mut dflux = 0.0;
489 let mut dtransmitted = 0.0;
490 let mut dreflected = 0.0;
491 for [input, trans, reflect] in fields {
492 dflux += sign
493 * (cross_direction(input, input)
494 + cross_direction(input, reflect)
495 + cross_direction(reflect, input));
496 dtransmitted += sign * cross_direction(trans, trans);
497 dreflected -= sign * cross_direction(reflect, reflect);
498 }
499 for (row, dpower) in [dtransmitted, dreflected].into_iter().enumerate() {
500 tangent[(row, illumination)] = (dpower
501 - self.forward.value[(row, illumination)] * dflux)
502 / self.forward.flux[illumination];
503 }
504 }
505 if tangent.iter().any(|v| !v.is_finite()) {
506 return Err(Error::NonFinite("non-finite power derivative".into()));
507 }
508 Ok(tangent)
509 }
510
511 pub fn pullback(&self, cotangent: &DMatrix<f64>) -> Result<TrGradient> {
513 if cotangent.shape() != self.shape() || cotangent.iter().any(|v| !v.is_finite()) {
514 return Err(Error::InvalidInput(
515 "power cotangent must be finite and match output".into(),
516 ));
517 }
518 let Self {
519 matrices: stored,
520 incident: input,
521 ports,
522 forward:
523 TrForward {
524 value,
525 flux,
526 outgoing: output,
527 waves,
528 },
529 fixed_q,
530 } = self;
531 let n = ports.modes.len();
532 let t = ports.direction;
533 let r = 1 - t;
534 let sign = if t == 0 { 1.0 } else { -1.0 };
535 let mut gwaves = vec![[[[[Complex::default(); 4]; 2]; 2]; 2]; ports.q.len()];
536 let mut incident = DMatrix::zeros(n, input.ncols());
537 let mut outgoing = [incident.clone(), incident.clone()];
538 for illumination in 0..input.ncols() {
539 let wt = cotangent[(0, illumination)] / flux[illumination];
540 let wr = cotangent[(1, illumination)] / flux[illumination];
541 let wi = -(cotangent[(0, illumination)] * value[(0, illumination)]
542 + cotangent[(1, illumination)] * value[(1, illumination)])
543 / flux[illumination];
544 let fields = aggregate(
545 waves,
546 &ports.modes,
547 |mode| amplitudes(input, output, mode, illumination).map(Jet::constant),
548 t,
549 );
550 let mut gradient = vec![[[Complex::default(); 4]; 3]; ports.q.len()];
551 for (group, f) in fields.iter().enumerate() {
552 let f = f.map(|field| field.map(|v| v.value));
553 for (e, h, weight) in [
554 (0, 0, sign * wi),
555 (0, 2, sign * wi),
556 (2, 0, sign * wi),
557 (1, 1, sign * wt),
558 (2, 2, -sign * wr),
559 ] {
560 let (ge, gh) = cross_pullback(f[e], f[h], weight);
561 for j in 0..4 {
562 gradient[group][e][j] += ge[j];
563 gradient[group][h][j] += gh[j];
564 }
565 }
566 }
567 for (mode, &(group, pol)) in ports.modes.iter().enumerate() {
568 let amplitudes = [
569 input[(mode, illumination)],
570 output[0][(mode, illumination)],
571 output[1][(mode, illumination)],
572 ];
573 for (which, (port, wave_direction)) in
574 [(r, t), (t, t), (r, r)].into_iter().enumerate()
575 {
576 let mut amplitude_gradient = Complex::default();
577 for j in 0..4 {
578 let g = gradient[group][which][j];
579 amplitude_gradient += waves[group][port][wave_direction][usize::from(pol)]
580 [j]
581 .value
582 .conj()
583 * g;
584 gwaves[group][port][wave_direction][usize::from(pol)][j] +=
585 amplitudes[which].conj() * g;
586 }
587 if which == 0 {
588 incident[(mode, illumination)] += amplitude_gradient;
589 } else {
590 outgoing[which - 1][(mode, illumination)] += amplitude_gradient;
591 }
592 }
593 }
594 }
595 let mut matrices = std::array::from_fn(|_| DMatrix::zeros(n, n));
596 for (which, direction) in [t, r].into_iter().enumerate() {
597 matrices[2 * direction + t] = product_adjoint_right(&outgoing[which], input);
598 incident += product_views(stored[which].view().adjoint(), view(&outgoing[which]));
599 }
600 let mut result = TrGradient {
601 matrices,
602 incident,
603 ks: [[Complex::default(); 2]; 2],
604 zs: [Complex::default(); 2],
605 q: vec![[0.0; 2]; ports.q.len()],
606 };
607 for (group, &q) in ports.q.iter().enumerate() {
608 if gwaves[group]
609 .iter()
610 .flatten()
611 .flatten()
612 .flatten()
613 .all(|&v| v == Complex::default())
614 {
615 continue;
616 }
617 let jets = ports.waves::<8>(q, *fixed_q)?;
618 let mut parameters = [Complex::default(); 8];
619 for (v, g) in jets
620 .iter()
621 .flatten()
622 .flatten()
623 .flatten()
624 .zip(gwaves[group].iter().flatten().flatten().flatten())
625 {
626 for (target, d) in parameters.iter_mut().zip(v.derivative) {
627 *target += g * d.conj();
628 }
629 }
630 for port in 0..2 {
631 for pol in 0..2 {
632 result.ks[port][pol] += parameters[KS + 2 * port + pol];
633 }
634 result.zs[port] += parameters[ZS + port];
635 }
636 result.q[group] = [parameters[Q].re, parameters[Q + 1].re];
637 }
638 Ok(result)
639 }
640}