1#![allow(clippy::indexing_slicing)] use nalgebra::DMatrix;
30use rayon::prelude::*;
31
32use crate::{
33 Complex, Error, Result,
34 basis::ModeLabel,
35 numerics::{Jet, finite, parallel::try_fold_ordered},
36 saved::{self, Reader, SavedState, Writer},
37 special::polarized_angular,
38 sw::{Basis, Mode},
39};
40
41const POSITION: usize = 0;
46const K: usize = 3;
48const Q: usize = 4;
50const SPHERICAL_MEASURE: usize = 6;
52const SPHERICAL_SLOTS: usize = SPHERICAL_MEASURE + 1;
54const CYLINDRICAL_MEASURE: usize = 5;
56const CYLINDRICAL_SLOTS: usize = CYLINDRICAL_MEASURE + 1;
58
59fn channel_matrix<I: Iterator<Item = [Complex; 2]>>(
62 multipoles: usize,
63 planes: usize,
64 entries: impl Fn(usize, usize) -> Result<I> + Sync,
65) -> Result<DMatrix<Complex>> {
66 let mut value = crate::numerics::zeros(4 * multipoles, planes)?;
67 crate::threads::install(|| {
68 value
69 .as_mut_slice()
70 .par_chunks_mut(4 * multipoles)
71 .enumerate()
72 .try_for_each(|(j, column)| -> Result<()> {
73 for side in 0..2 {
74 for (i, [incident, radiated]) in entries(j, side)?.enumerate() {
75 column[side * multipoles + i] = incident;
76 column[(2 + side) * multipoles + i] = radiated;
77 }
78 }
79 Ok(())
80 })
81 })?;
82 Ok(value)
83}
84
85struct SphericalChannel<const N: usize> {
89 vector: [Jet<N>; 3],
91 k: Jet<N>,
93 kz: Jet<N>,
96 sine: Jet<N>,
98 cosine: Jet<N>,
100 azimuth: Jet<N>,
102 area: Jet<N>,
104}
105impl<const N: usize> SphericalChannel<N> {
106 fn new(k: Complex, q: [f64; 2], side: usize, area: f64, fixed_q: bool) -> Result<Self> {
115 let input = Jet::variable(k, K);
116 let xy: [Jet<N>; 2] = std::array::from_fn(|i| {
117 if fixed_q {
118 Jet::constant(q[i])
119 } else {
120 Jet::variable(q[i], Q + i)
121 }
122 });
123 let k = (input * input).sqrt();
124 let mut kz = (input * input - xy[0] * xy[0] - xy[1] * xy[1]).sqrt();
125 if kz.value == Complex::default() {
126 return Err(Error::InvalidInput(
127 "plane-wave channel is at a diffraction threshold".into(),
128 ));
129 }
130 if kz.value.im < 0.0 || (kz.value.im == 0.0 && kz.value.re < 0.0) {
131 kz = -kz;
132 }
133 let scale = q[0].abs().max(q[1].abs());
134 let (mut sine, azimuth) = if scale == 0.0 {
135 if N > 0 && !fixed_q {
136 return Err(Error::InvalidInput("normal-incidence azimuth is undefined; hold the transverse wavevector fixed (fixed_q) to differentiate at normal incidence".into()));
137 }
138 (Jet::default(), Jet::constant(1.0))
139 } else {
140 let x = xy[0] / scale;
141 let y = xy[1] / scale;
142 let norm = (x * x + y * y).sqrt();
143 (norm * scale / k, (x + Complex::i() * y) / norm)
144 };
145 if sine.value.re < 0.0 {
146 sine = -sine;
147 }
148 let axial = if side == 0 { kz } else { -kz };
149 Ok(Self {
150 vector: [xy[0], xy[1], axial],
151 k,
152 kz,
153 sine,
154 cosine: axial / k,
155 azimuth,
156 area: Jet::variable(area, SPHERICAL_MEASURE),
157 })
158 }
159
160 fn entry(&self, mode: Mode, pol: u8, position: [f64; 3], helicity: bool) -> [Jet<N>; 2] {
163 if helicity && mode.pol != pol {
164 return [Jet::default(); 2];
165 }
166 let (l, m) = (mode.l, mode.m);
167 let angular = polarized_angular(l, m, [self.cosine, self.sine], [mode.pol, pol], helicity);
168 let normalization = (std::f64::consts::PI * f64::from(2 * l + 1) / f64::from(l * (l + 1)))
169 .sqrt()
170 * crate::special::factorial_ratio_sqrt(l, m);
171 let phase = (Complex::i()
172 * self
173 .vector
174 .into_iter()
175 .enumerate()
176 .map(|(i, k)| k * Jet::variable(position[i], POSITION + i))
177 .sum::<Jet<N>>())
178 .exp();
179 let incident =
180 2.0 * Complex::i().powi(l) * normalization * self.azimuth.powi(-m) * angular * phase;
181 let outgoing = (-Complex::i()).powi(l) * normalization * self.azimuth.powi(m) * angular
182 / (self.area * self.k * self.kz * phase);
183 [incident, outgoing]
184 }
185}
186
187pub fn sw_periodic_to_pw(
196 mode: Mode,
197 vector: [Complex; 3],
198 pol: u8,
199 area: f64,
200 helicity: bool,
201) -> Result<Complex> {
202 mode.validate()?;
203 if pol > 1
204 || vector.iter().any(|&v| !finite(v))
205 || vector[0].im != 0.0
206 || vector[1].im != 0.0
207 || !area.is_finite()
208 || area == 0.0
209 {
210 return Err(Error::InvalidInput("real transverse wavevector, finite axial component, polarization 0/1 and nonzero area required".into()));
211 }
212 let side = usize::from(vector[2].im < 0.0 || (vector[2].im == 0.0 && vector[2].re < 0.0));
213 let k = crate::numerics::complex_sqrt(vector.iter().map(|v| v * v).sum());
214 let channel =
215 SphericalChannel::<0>::new(k, [vector[0].re, vector[1].re], side, area.abs(), true)?;
216 let value = channel.entry(mode, pol, [0.0; 3], helicity)[1].value;
217 if !finite(value) {
218 return Err(Error::NonFinite(
219 "non-finite plane radiation coefficient".into(),
220 ));
221 }
222 Ok(value)
223}
224
225#[derive(Clone, Debug)]
235pub struct SphericalChannelsResidual {
236 basis: Basis,
237 ks: [Complex; 2],
238 q: Vec<[f64; 2]>,
239 polarizations: Vec<u8>,
240 area: f64,
241 helicity: bool,
242 fixed_q: bool,
243}
244
245fn validate_channels(
247 ks: [Complex; 2],
248 q: &[[f64; 2]],
249 polarizations: &[u8],
250 measure: f64,
251 helicity: bool,
252) -> Result<()> {
253 if q.is_empty()
254 || q.len() != polarizations.len()
255 || q.iter().flatten().any(|v| !v.is_finite())
256 || polarizations.iter().any(|&p| p > 1)
257 || ks.iter().any(|&k| !finite(k) || k == Complex::default())
258 || !measure.is_finite()
259 || measure <= 0.0
260 {
261 return Err(Error::InvalidInput("channels require finite transverse vectors, polarizations 0/1, nonzero wavenumbers and positive unit-cell measure".into()));
262 }
263 if !helicity && ks[0] != ks[1] {
264 return Err(Error::InvalidInput(
265 "parity channels require an achiral medium".into(),
266 ));
267 }
268 Ok(())
269}
270
271pub fn spherical_channels(
283 basis: Basis,
284 ks: [Complex; 2],
285 q: Vec<[f64; 2]>,
286 polarizations: Vec<u8>,
287 area: f64,
288 helicity: bool,
289 fixed_q: bool,
290) -> Result<(DMatrix<Complex>, SphericalChannelsResidual)> {
291 basis.validate()?;
292 validate_channels(ks, &q, &polarizations, area, helicity)?;
293 let positions = &basis.positions;
295 let value = channel_matrix(basis.modes.len(), q.len(), |j, side| {
296 let pol = polarizations[j];
297 let channel = SphericalChannel::<0>::new(ks[usize::from(pol)], q[j], side, area, true)?;
298 Ok(basis.modes.iter().map(move |&(p, mode)| {
299 channel
300 .entry(mode, pol, positions[p], helicity)
301 .map(|v| v.value)
302 }))
303 })?;
304 if value.iter().any(|&v| !finite(v)) {
305 return Err(Error::NonFinite("non-finite plane-wave channel".into()));
306 }
307 Ok((
308 value,
309 SphericalChannelsResidual {
310 basis,
311 ks,
312 q,
313 polarizations,
314 area,
315 helicity,
316 fixed_q,
317 },
318 ))
319}
320
321#[derive(Clone, Debug)]
324pub struct ChannelGradient {
325 pub positions: Vec<[f64; 3]>,
327 pub ks: [Complex; 2],
329 pub q: Vec<[f64; 2]>,
331 pub measure: f64,
334}
335impl ChannelGradient {
336 fn zeros(positions: usize) -> Self {
337 Self {
338 positions: vec![[0.0; 3]; positions],
339 ks: [Complex::default(); 2],
340 q: Vec::new(),
341 measure: 0.0,
342 }
343 }
344 fn add(&mut self, other: Self) {
346 for (a, b) in self.positions.iter_mut().zip(other.positions) {
347 for (a, b) in a.iter_mut().zip(b) {
348 *a += b;
349 }
350 }
351 for (a, b) in self.ks.iter_mut().zip(other.ks) {
352 *a += b;
353 }
354 self.measure += other.measure;
355 }
356}
357fn channel_pullback<M: Copy + Sync, const N: usize, E: Fn(M, [f64; 3]) -> [Jet<N>; 2]>(
362 basis: &crate::basis::Basis<M>,
363 polarizations: &[u8],
364 cotangent: &DMatrix<Complex>,
365 entries: impl Fn(usize, usize) -> Result<E> + Sync,
366 add_q: impl Fn(&mut [f64; 2], &[Complex; N]) + Sync,
367) -> Result<ChannelGradient> {
368 let d = basis.modes.len();
369 let mut q_gradients = vec![[0.0; 2]; polarizations.len()];
370 let columns: Vec<_> = q_gradients.iter_mut().collect();
371 let mut result = try_fold_ordered(
372 columns,
373 true,
374 || ChannelGradient::zeros(basis.positions.len()),
375 |mut result, j, q_gradient| -> Result<_> {
376 let pol = usize::from(polarizations[j]);
377 for side in 0..2 {
378 let entry = entries(j, side)?;
379 for (i, &(p, mode)) in basis.modes.iter().enumerate() {
380 let weights = [
381 cotangent[(side * d + i, j)],
382 cotangent[((2 + side) * d + i, j)],
383 ];
384 if weights.iter().all(|&z| z == Complex::default()) {
385 continue;
386 }
387 let pair = entry(mode, basis.positions[p]);
388 let gradient: [Complex; N] = std::array::from_fn(|a| {
389 weights[0] * pair[0].derivative[a].conj()
390 + weights[1] * pair[1].derivative[a].conj()
391 });
392 for (a, g) in result.positions[p]
393 .iter_mut()
394 .zip(&gradient[POSITION..POSITION + 3])
395 {
396 *a += g.re;
397 }
398 result.ks[pol] += gradient[K];
399 add_q(q_gradient, &gradient);
400 result.measure += gradient[N - 1].re;
401 }
402 }
403 Ok(result)
404 },
405 |mut total, partial| {
406 total.add(partial);
407 total
408 },
409 )?;
410 result.q = q_gradients;
411 Ok(result)
412}
413
414impl SphericalChannelsResidual {
415 pub fn state_size(multipoles: usize, positions: usize, channels: usize) -> Result<usize> {
417 channel_state_size(saved::sw_basis_size(multipoles, positions)?, channels)
418 }
419
420 #[must_use]
422 pub fn shape(&self) -> (usize, usize) {
423 (4 * self.basis.modes.len(), self.q.len())
424 }
425
426 pub fn validate_tangent(
428 &self,
429 positions: &[[f64; 3]],
430 ks: [Complex; 2],
431 q: &[[f64; 2]],
432 measure: f64,
433 ) -> Result<()> {
434 validate_channel_tangent(
435 positions,
436 ks,
437 q,
438 measure,
439 self.basis.positions.len(),
440 self.q.len(),
441 )
442 }
443
444 pub fn pushforward(
448 &self,
449 positions: &[[f64; 3]],
450 ks: [Complex; 2],
451 q: &[[f64; 2]],
452 measure: f64,
453 ) -> Result<DMatrix<Complex>> {
454 self.validate_tangent(positions, ks, q, measure)?;
455 channel_matrix(self.basis.modes.len(), self.q.len(), |j, side| {
456 let pol = self.polarizations[j];
457 let fixed_q = self.fixed_q || q[j].iter().all(|&v| v == 0.0);
460 let channel = SphericalChannel::<SPHERICAL_SLOTS>::new(
461 self.ks[usize::from(pol)],
462 self.q[j],
463 side,
464 self.area,
465 fixed_q,
466 )?;
467 Ok(self.basis.modes.iter().map(move |&(p, mode)| {
468 channel
469 .entry(mode, pol, self.basis.positions[p], self.helicity)
470 .map(|entry| {
471 channel_direction(entry, positions[p], ks[usize::from(pol)], &q[j], measure)
472 })
473 }))
474 })
475 }
476
477 pub fn pullback(&self, cotangent: &DMatrix<Complex>) -> Result<ChannelGradient> {
481 if cotangent.shape() != self.shape() || cotangent.iter().any(|&z| !finite(z)) {
482 return Err(Error::InvalidInput("invalid channel cotangent".into()));
483 }
484 channel_pullback(
485 &self.basis,
486 &self.polarizations,
487 cotangent,
488 |j, side| {
489 let pol = self.polarizations[j];
490 let channel = SphericalChannel::<SPHERICAL_SLOTS>::new(
491 self.ks[usize::from(pol)],
492 self.q[j],
493 side,
494 self.area,
495 self.fixed_q,
496 )?;
497 Ok(move |mode, position| channel.entry(mode, pol, position, self.helicity))
498 },
499 |q_gradient, gradient| {
500 if !self.fixed_q {
501 for (a, g) in q_gradient.iter_mut().zip(&gradient[Q..Q + 2]) {
502 *a += g.re;
503 }
504 }
505 },
506 )
507 }
508}
509
510impl SavedState for SphericalChannelsResidual {
511 fn save_state(&self) -> Result<Vec<u8>> {
512 let mut writer = Writer::new(Self::state_size(
513 self.basis.modes.len(),
514 self.basis.positions.len(),
515 self.q.len(),
516 )?);
517 saved::write_sw_basis(&mut writer, &self.basis);
518 write_channel_state(
519 &mut writer,
520 self.ks,
521 &self.q,
522 &self.polarizations,
523 self.area,
524 );
525 writer.byte(u8::from(self.helicity) | (u8::from(self.fixed_q) << 1));
526 Ok(writer.finish())
527 }
528
529 fn from_state(bytes: &[u8]) -> Result<Self> {
530 let mut reader = Reader::new(bytes);
531 let basis = saved::read_sw_basis(&mut reader)?;
532 let (ks, q, polarizations, area) = read_channel_state(&mut reader)?;
533 let flags = reader.byte()?;
534 if flags > 3 {
535 return Err(saved::invalid());
536 }
537 reader.finish()?;
538 let helicity = flags & 1 != 0;
539 validate_channels(ks, &q, &polarizations, area, helicity)?;
540 Ok(Self {
541 basis,
542 ks,
543 q,
544 polarizations,
545 area,
546 helicity,
547 fixed_q: flags & 2 != 0,
548 })
549 }
550}
551
552fn channel_state_size(basis: usize, channels: usize) -> Result<usize> {
554 channels
555 .checked_mul(17)
556 .and_then(|n| n.checked_add(49))
557 .and_then(|n| n.checked_add(basis))
558 .ok_or_else(saved::invalid)
559}
560
561fn write_channel_state(
562 writer: &mut Writer,
563 ks: [Complex; 2],
564 q: &[[f64; 2]],
565 polarizations: &[u8],
566 measure: f64,
567) {
568 for k in ks {
569 writer.complex(k);
570 }
571 writer.usize(q.len());
572 for (&[x, y], &pol) in q.iter().zip(polarizations) {
573 writer.f64(x);
574 writer.f64(y);
575 writer.byte(pol);
576 }
577 writer.f64(measure);
578}
579
580type ChannelState = ([Complex; 2], Vec<[f64; 2]>, Vec<u8>, f64);
581
582fn read_channel_state(reader: &mut Reader<'_>) -> Result<ChannelState> {
583 let ks = [reader.complex()?, reader.complex()?];
584 let count = reader.count(17)?;
585 if reader.remaining_len() != channel_state_size(0, count)? - 40 {
588 return Err(saved::invalid());
589 }
590 let mut q = Vec::with_capacity(count);
591 let mut polarizations = Vec::with_capacity(count);
592 for _ in 0..count {
593 q.push([reader.f64()?, reader.f64()?]);
594 polarizations.push(reader.byte()?);
595 }
596 Ok((ks, q, polarizations, reader.f64()?))
597}
598
599fn channel_direction<const N: usize>(
603 entry: Jet<N>,
604 position: [f64; 3],
605 k: Complex,
606 q: &[f64],
607 measure: f64,
608) -> Complex {
609 position
610 .iter()
611 .zip(&entry.derivative[POSITION..K])
612 .map(|(v, d)| *v * *d)
613 .sum::<Complex>()
614 + k * entry.derivative[K]
615 + q.iter()
616 .zip(&entry.derivative[Q..N - 1])
617 .map(|(v, d)| *v * *d)
618 .sum::<Complex>()
619 + measure * entry.derivative[N - 1]
620}
621
622fn validate_channel_tangent(
623 positions: &[[f64; 3]],
624 ks: [Complex; 2],
625 q: &[[f64; 2]],
626 measure: f64,
627 position_count: usize,
628 plane_count: usize,
629) -> Result<()> {
630 if positions.len() != position_count
631 || q.len() != plane_count
632 || positions.iter().flatten().any(|v| !v.is_finite())
633 || ks.iter().any(|&v| !finite(v))
634 || q.iter().flatten().any(|v| !v.is_finite())
635 || !measure.is_finite()
636 {
637 return Err(Error::InvalidInput(
638 "channel tangents must be finite and match the input shapes".into(),
639 ));
640 }
641 Ok(())
642}
643
644struct CylindricalChannel<const N: usize> {
649 vector: [Jet<N>; 3],
651 transverse: Jet<N>,
654 normal: Jet<N>,
657 period: Jet<N>,
659}
660impl<const N: usize> CylindricalChannel<N> {
661 fn new(k: Complex, q: [f64; 2], side: usize, period: f64, fixed_q: bool) -> Result<Self> {
666 let k = Jet::variable(k, K);
667 let kz = Jet::constant(q[0]);
668 let kx = if fixed_q {
669 Jet::constant(q[1])
670 } else {
671 Jet::variable(q[1], Q)
672 };
673 let mut transverse = (k * k - kz * kz).sqrt();
674 let normal = (k * k - kz * kz - kx * kx).sqrt();
675 if transverse.value == Complex::default() {
676 return Err(Error::InvalidInput(
677 "cylindrical plane channel is at a cutoff or diffraction threshold".into(),
678 ));
679 }
680 if transverse.value.im < 0.0 {
681 transverse = -transverse;
682 }
683 let mut channel = Self::from_vector([kx, normal, kz], transverse, period)?;
684 channel.vector[1] = if side == 0 {
685 channel.normal
686 } else {
687 -channel.normal
688 };
689 Ok(channel)
690 }
691
692 fn from_vector(vector: [Jet<N>; 3], transverse: Jet<N>, period: f64) -> Result<Self> {
696 let mut normal = vector[1];
697 if normal.value == Complex::default() {
698 return Err(Error::InvalidInput(
699 "plane channel is at a diffraction threshold".into(),
700 ));
701 }
702 if normal.value.im < 0.0 || (normal.value.im == 0.0 && normal.value.re < 0.0) {
703 normal = -normal;
704 }
705 Ok(Self {
706 vector,
707 transverse,
708 normal,
709 period: Jet::variable(period, CYLINDRICAL_MEASURE),
710 })
711 }
712 fn entry(&self, mode: crate::cw::Mode, pol: u8, position: [f64; 3]) -> [Jet<N>; 2] {
715 if !crate::pw::cylindrical_mode_matches(mode, self.vector[2].value, pol) {
716 return [Jet::default(); 2];
717 }
718 let phase = (Complex::i()
719 * self
720 .vector
721 .iter()
722 .enumerate()
723 .map(|(a, &k)| k * Jet::variable(position[a], POSITION + a))
724 .sum::<Jet<N>>())
725 .exp();
726 let (incoming_angle, outgoing_angle) = if self.transverse.value == Complex::default() {
727 (
728 Jet::constant(Complex::i().powi(mode.m)),
729 Jet::constant((-Complex::i()).powi(mode.m)),
730 )
731 } else {
732 (
733 ((Complex::i() * self.vector[0] + self.vector[1]) / self.transverse).powi(mode.m),
734 ((-Complex::i() * self.vector[0] + self.vector[1]) / self.transverse).powi(mode.m),
735 )
736 };
737 let incident = incoming_angle * phase;
738 let outgoing = 2.0 * outgoing_angle / (self.period * self.normal * phase);
739 [incident, outgoing]
740 }
741}
742
743#[allow(clippy::float_cmp)] pub fn cw_periodic_to_pw(
755 mode: crate::cw::Mode,
756 vector: [Complex; 3],
757 pol: u8,
758 period: f64,
759) -> Result<Complex> {
760 mode.validate()?;
761 if pol > 1
762 || vector.iter().any(|&v| !finite(v))
763 || vector[0].im != 0.0
764 || vector[2].im != 0.0
765 || !period.is_finite()
766 || period == 0.0
767 {
768 return Err(Error::InvalidInput(
769 "finite vector with real kx/kz, polarization 0/1 and nonzero period required".into(),
770 ));
771 }
772 if mode.kz != vector[2].re || mode.pol != pol {
773 return Ok(Complex::default());
774 }
775 let channel = CylindricalChannel::<0>::from_vector(
776 vector.map(Jet::constant),
777 Jet::constant(crate::numerics::complex_sqrt(
778 vector[0] * vector[0] + vector[1] * vector[1],
779 )),
780 period.abs(),
781 )?;
782 let value = channel.entry(mode, pol, [0.0; 3])[1].value;
783 if !finite(value) {
784 return Err(Error::NonFinite(
785 "non-finite plane radiation coefficient".into(),
786 ));
787 }
788 Ok(value)
789}
790
791#[derive(Clone, Debug)]
800pub struct CylindricalChannelsResidual {
801 basis: crate::cw::Basis,
802 ks: [Complex; 2],
803 q: Vec<[f64; 2]>,
804 polarizations: Vec<u8>,
805 period: f64,
806 fixed_q: bool,
807}
808pub fn cylindrical_channels(
822 basis: crate::cw::Basis,
823 ks: [Complex; 2],
824 q: Vec<[f64; 2]>,
825 polarizations: Vec<u8>,
826 period: f64,
827 helicity: bool,
828 fixed_q: bool,
829) -> Result<(DMatrix<Complex>, CylindricalChannelsResidual)> {
830 basis.validate()?;
831 validate_channels(ks, &q, &polarizations, period, helicity)?;
832 let positions = &basis.positions;
834 let value = channel_matrix(basis.modes.len(), q.len(), |j, side| {
835 let pol = polarizations[j];
836 let channel = CylindricalChannel::<0>::new(ks[usize::from(pol)], q[j], side, period, true)?;
837 Ok(basis
838 .modes
839 .iter()
840 .map(move |&(p, mode)| channel.entry(mode, pol, positions[p]).map(|v| v.value)))
841 })?;
842 if value.iter().any(|&v| !finite(v)) {
843 return Err(Error::NonFinite(
844 "non-finite cylindrical plane-wave channel".into(),
845 ));
846 }
847 Ok((
848 value,
849 CylindricalChannelsResidual {
850 basis,
851 ks,
852 q,
853 polarizations,
854 period,
855 fixed_q,
856 },
857 ))
858}
859impl CylindricalChannelsResidual {
860 pub fn state_size(multipoles: usize, positions: usize, channels: usize) -> Result<usize> {
862 channel_state_size(saved::cw_basis_size(multipoles, positions)?, channels)
863 }
864
865 #[must_use]
868 pub fn shape(&self) -> (usize, usize) {
869 (4 * self.basis.modes.len(), self.q.len())
870 }
871
872 pub fn validate_tangent(
874 &self,
875 positions: &[[f64; 3]],
876 ks: [Complex; 2],
877 q: &[[f64; 2]],
878 measure: f64,
879 ) -> Result<()> {
880 validate_channel_tangent(
881 positions,
882 ks,
883 q,
884 measure,
885 self.basis.positions.len(),
886 self.q.len(),
887 )
888 }
889
890 pub fn pushforward(
893 &self,
894 positions: &[[f64; 3]],
895 ks: [Complex; 2],
896 q: &[[f64; 2]],
897 measure: f64,
898 ) -> Result<DMatrix<Complex>> {
899 self.validate_tangent(positions, ks, q, measure)?;
900 channel_matrix(self.basis.modes.len(), self.q.len(), |j, side| {
901 let pol = self.polarizations[j];
902 let channel = CylindricalChannel::<CYLINDRICAL_SLOTS>::new(
903 self.ks[usize::from(pol)],
904 self.q[j],
905 side,
906 self.period,
907 self.fixed_q,
908 )?;
909 Ok(self.basis.modes.iter().map(move |&(p, mode)| {
910 channel
911 .entry(mode, pol, self.basis.positions[p])
912 .map(|entry| {
913 channel_direction(
914 entry,
915 positions[p],
916 ks[usize::from(pol)],
917 &q[j][1..],
918 measure,
919 )
920 })
921 }))
922 })
923 }
924
925 pub fn pullback(&self, cotangent: &DMatrix<Complex>) -> Result<ChannelGradient> {
930 if cotangent.shape() != self.shape() || cotangent.iter().any(|&v| !finite(v)) {
931 return Err(Error::InvalidInput(
932 "invalid cylindrical channel cotangent".into(),
933 ));
934 }
935 channel_pullback(
936 &self.basis,
937 &self.polarizations,
938 cotangent,
939 |j, side| {
940 let pol = self.polarizations[j];
941 let channel = CylindricalChannel::<CYLINDRICAL_SLOTS>::new(
942 self.ks[usize::from(pol)],
943 self.q[j],
944 side,
945 self.period,
946 self.fixed_q,
947 )?;
948 Ok(move |mode, position| channel.entry(mode, pol, position))
949 },
950 |q_gradient, gradient| q_gradient[1] += gradient[Q].re,
951 )
952 }
953}
954
955impl SavedState for CylindricalChannelsResidual {
956 fn save_state(&self) -> Result<Vec<u8>> {
957 let mut writer = Writer::new(Self::state_size(
958 self.basis.modes.len(),
959 self.basis.positions.len(),
960 self.q.len(),
961 )?);
962 saved::write_cw_basis(&mut writer, &self.basis);
963 write_channel_state(
964 &mut writer,
965 self.ks,
966 &self.q,
967 &self.polarizations,
968 self.period,
969 );
970 writer.byte(u8::from(self.fixed_q));
971 Ok(writer.finish())
972 }
973
974 fn from_state(bytes: &[u8]) -> Result<Self> {
975 let mut reader = Reader::new(bytes);
976 let basis = saved::read_cw_basis(&mut reader)?;
977 let (ks, q, polarizations, period) = read_channel_state(&mut reader)?;
978 let fixed_q = match reader.byte()? {
979 0 => false,
980 1 => true,
981 _ => return Err(saved::invalid()),
982 };
983 reader.finish()?;
984 validate_channels(ks, &q, &polarizations, period, true)?;
987 Ok(Self {
988 basis,
989 ks,
990 q,
991 polarizations,
992 period,
993 fixed_q,
994 })
995 }
996}
997
998#[cfg(test)]
999mod tests {
1000 use super::*;
1001 use crate::test_support::{assert_same_bits_on_pools, bits, cylindrical_basis, patterned};
1002
1003 #[test]
1006 fn cylindrical_scalar_channels_preserve_the_supplied_vector() {
1007 let mode = crate::cw::Mode {
1008 kz: 0.2,
1009 m: 2,
1010 pol: 1,
1011 };
1012 for normal in [Complex::i(), -Complex::i()] {
1013 let vector = [Complex::from(1.0), normal, Complex::from(mode.kz)];
1014 let channel = CylindricalChannel::<0>::from_vector(
1015 vector.map(Jet::constant),
1016 Jet::default(),
1017 2.0,
1018 )
1019 .unwrap();
1020 assert_eq!(channel.vector.map(|v| v.value), vector);
1021 assert_eq!(channel.normal.value, Complex::i());
1022 assert_eq!(
1023 cw_periodic_to_pw(mode, vector, 1, -2.0).unwrap(),
1024 Complex::i()
1025 );
1026 }
1027 let vector = [
1028 Complex::from(1.0),
1029 Complex::default(),
1030 Complex::from(mode.kz),
1031 ];
1032 assert!(matches!(
1033 cw_periodic_to_pw(mode, vector, 1, 2.0),
1034 Err(Error::InvalidInput(_))
1035 ));
1036 assert_eq!(
1038 cw_periodic_to_pw(mode, vector, 0, 2.0).unwrap(),
1039 Complex::default()
1040 );
1041 }
1042
1043 #[test]
1046 fn channel_pullbacks_skip_zero_weight_entries_after_channel_validation() {
1047 use std::sync::atomic::{AtomicUsize, Ordering};
1048 let basis = cylindrical_basis(1, 0.2, [0.0; 3]);
1049 let weights = DMatrix::zeros(4 * basis.modes.len(), 1);
1050 let visits = AtomicUsize::new(0);
1051 let gradient = channel_pullback(
1052 &basis,
1053 &[1],
1054 &weights,
1055 |_, _| {
1056 visits.fetch_add(1, Ordering::Relaxed);
1057 Ok(|_, _| -> [Jet<CYLINDRICAL_SLOTS>; 2] { panic!("unused entry evaluated") })
1058 },
1059 |_, _| panic!("unused gradient accumulated"),
1060 )
1061 .unwrap();
1062 assert_eq!(visits.load(Ordering::Relaxed), 2);
1063 assert!(
1064 bits(&[
1065 &gradient.positions,
1066 &gradient.ks,
1067 &gradient.q,
1068 &gradient.measure,
1069 ])
1070 .iter()
1071 .all(|&bit| bit == 0)
1072 );
1073 }
1074
1075 fn roundtrip<R: SavedState>(residual: &R, expected_size: usize) -> R {
1076 let mut bytes = residual.save_state().unwrap();
1077 assert_eq!(bytes.len(), expected_size);
1078 let restored = R::from_state(&bytes).unwrap();
1079 assert_eq!(bytes, restored.save_state().unwrap());
1080 assert!(R::from_state(&bytes[..bytes.len() - 1]).is_err());
1081 bytes.push(0);
1082 assert!(R::from_state(&bytes).is_err());
1083 bytes.pop();
1084 bytes[..8].copy_from_slice(&u64::MAX.to_le_bytes());
1085 assert!(R::from_state(&bytes).is_err());
1086 restored
1087 }
1088
1089 fn assert_channel_gradient_same(left: &ChannelGradient, right: &ChannelGradient) {
1090 assert_eq!(
1091 bits(&[&left.positions, &left.ks, &left.q, &left.measure]),
1092 bits(&[&right.positions, &right.ks, &right.q, &right.measure]),
1093 );
1094 }
1095
1096 #[test]
1099 fn saved_channels_preserve_both_derivatives() {
1100 let ks = [Complex::new(1.3, 0.05); 2];
1101 let dp = [[0.1, -0.2, 0.3]];
1102 let dk = [Complex::new(0.04, -0.02); 2];
1103 let dq = [[0.03, -0.02], [-0.01, 0.04]];
1104 for helicity in [false, true] {
1105 for fixed_q in [false, true] {
1106 let basis = crate::test_support::spherical_basis(1, [0.1, -0.2, 0.05]);
1107 let size = SphericalChannelsResidual::state_size(basis.modes.len(), 1, 2).unwrap();
1108 let (value, residual) = spherical_channels(
1109 basis,
1110 ks,
1111 vec![[0.3, 0.2], [-0.4, 0.1]],
1112 vec![1, 0],
1113 2.0,
1114 helicity,
1115 fixed_q,
1116 )
1117 .unwrap();
1118 let restored = roundtrip(&residual, size);
1119 let g = patterned(value.nrows(), value.ncols(), 0.3);
1120 assert_channel_gradient_same(
1121 &residual.pullback(&g).unwrap(),
1122 &restored.pullback(&g).unwrap(),
1123 );
1124 assert_eq!(
1125 residual.pushforward(&dp, dk, &dq, 0.07).unwrap(),
1126 restored.pushforward(&dp, dk, &dq, 0.07).unwrap(),
1127 );
1128 assert_channel_gradient_same(
1129 &residual.pullback(&g).unwrap(),
1130 &restored.pullback(&g).unwrap(),
1131 );
1132
1133 let basis = cylindrical_basis(1, 0.2, [0.1, -0.2, 0.05]);
1134 let size =
1135 CylindricalChannelsResidual::state_size(basis.modes.len(), 1, 2).unwrap();
1136 let (value, residual) = cylindrical_channels(
1137 basis,
1138 ks,
1139 vec![[0.2, 0.3], [0.2, -0.4]],
1140 vec![1, 0],
1141 2.0,
1142 helicity,
1143 fixed_q,
1144 )
1145 .unwrap();
1146 let restored = roundtrip(&residual, size);
1147 let g = patterned(value.nrows(), value.ncols(), 0.3);
1148 assert_channel_gradient_same(
1149 &residual.pullback(&g).unwrap(),
1150 &restored.pullback(&g).unwrap(),
1151 );
1152 assert_eq!(
1153 residual.pushforward(&dp, dk, &dq, 0.07).unwrap(),
1154 restored.pushforward(&dp, dk, &dq, 0.07).unwrap(),
1155 );
1156 assert_channel_gradient_same(
1157 &residual.pullback(&g).unwrap(),
1158 &restored.pullback(&g).unwrap(),
1159 );
1160 }
1161 }
1162 }
1163
1164 #[test]
1167 fn nonfinite_channels_are_rejected() {
1168 let k = [Complex::new(1.3, 0.0); 2];
1169 let cylinders = cylindrical_basis(1, 0.2, [0.0, 20.0, 0.0]);
1170 let result =
1171 cylindrical_channels(cylinders, k, vec![[0.2, 50.0]], vec![1], 2.0, true, false);
1172 assert!(matches!(result, Err(Error::NonFinite(_))), "{result:?}");
1173 let spheres = crate::test_support::spherical_basis(1, [0.0, 0.0, 20.0]);
1174 let result = spherical_channels(spheres, k, vec![[50.0, 0.0]], vec![1], 2.0, true, false);
1175 assert!(matches!(result, Err(Error::NonFinite(_))), "{result:?}");
1176 }
1177
1178 #[test]
1182 fn pullbacks_do_not_depend_on_the_thread_count() {
1183 let ks = [Complex::new(1.2, 0.05), Complex::new(1.4, 0.03)];
1184 let columns = 150_u32;
1185 let q: Vec<[f64; 2]> = (0..columns)
1186 .map(|j| {
1187 let t = f64::from(j);
1188 [0.9 * (0.7 * t).sin(), 0.8 * (1.3 * t).cos()]
1189 })
1190 .collect();
1191 let polarizations: Vec<u8> = (0..columns).map(|j| u8::from(j % 3 != 0)).collect();
1192 let positions = vec![[0.1, -0.2, 0.05], [-0.15, 0.1, -0.1]];
1193 let modes = crate::sw::modes(2).unwrap();
1194 let spheres = Basis {
1195 modes: (0..2)
1196 .flat_map(|p| modes.iter().map(move |&mode| (p, mode)))
1197 .collect(),
1198 positions: positions.clone(),
1199 };
1200 let g = patterned(4 * spheres.modes.len(), q.len(), 0.3);
1201 assert_same_bits_on_pools(|| {
1202 let (_, residual) = spherical_channels(
1203 spheres.clone(),
1204 ks,
1205 q.clone(),
1206 polarizations.clone(),
1207 2.0,
1208 true,
1209 false,
1210 )
1211 .unwrap();
1212 let g = residual.pullback(&g).unwrap();
1213 bits(&[&g.positions, &g.ks, &g.q, &g.measure])
1214 });
1215 let cylinders = crate::cw::Basis {
1216 modes: (0..2)
1217 .flat_map(|p| {
1218 (-3..=3).flat_map(move |m| {
1219 [1, 0].map(|pol| (p, crate::cw::Mode { kz: 0.2, m, pol }))
1220 })
1221 })
1222 .collect(),
1223 positions,
1224 };
1225 let q: Vec<_> = q.iter().map(|&[kx, _]| [0.2, kx]).collect();
1226 let g = patterned(4 * cylinders.modes.len(), q.len(), 0.6);
1227 assert_same_bits_on_pools(|| {
1228 let (_, residual) = cylindrical_channels(
1229 cylinders.clone(),
1230 ks,
1231 q.clone(),
1232 polarizations.clone(),
1233 2.0,
1234 true,
1235 false,
1236 )
1237 .unwrap();
1238 let g = residual.pullback(&g).unwrap();
1239 bits(&[&g.positions, &g.ks, &g.q, &g.measure])
1240 });
1241 }
1242}