1#![allow(clippy::indexing_slicing)] use super::{Basis, Mode};
9use crate::{
10 Complex, Error, Result,
11 basis::{ExpansionGradient, ModeLabel, add_pair_gradient, validate_wavenumbers},
12 cw::{self, coefficient},
13 numerics::{Jet, finite, parallel::try_fold_ordered},
14 special::Radial,
15};
16use nalgebra::DMatrix;
17use rayon::prelude::*;
18
19#[allow(clippy::float_cmp)] fn periodic_entry<const N: usize>(
23 to: cw::Mode,
24 from: Mode,
25 k: Complex,
26 r: [f64; 3],
27 period: f64,
28 helicity: bool,
29) -> Result<cw::CartesianTranslation> {
30 if (helicity && to.pol != from.pol)
31 || (r[0] == 0.0 && r[1] == 0.0 && (to.m - from.m).unsigned_abs() > u32::from(N != 0))
32 {
33 return Ok(cw::CartesianTranslation::default());
34 }
35 let wave = Jet::<N>::variable(k, 0);
36 let kz = Jet::variable(to.kz, 1);
37 let sign = if (from.l - from.m) % 2 == 0 {
40 1.0
41 } else {
42 -1.0
43 };
44 let beta = coefficient(from, kz, wave, to.pol, helicity)? * (0.25 * sign / period) / wave;
45 if N == 0 && r == [0.0; 3] {
47 return Ok(cw::CartesianTranslation {
48 value: beta.value,
49 ..cw::CartesianTranslation::default()
50 });
51 }
52 let translated =
53 cw::cartesian_translation(to, cw::Mode { m: from.m, ..to }, k, r, Radial::Regular)?;
54 Ok(cw::CartesianTranslation {
55 value: beta.value * translated.value,
56 position: translated.position.map(|g| beta.value * g),
57 k: beta.derivative.first().copied().unwrap_or_default() * translated.value
58 + beta.value * translated.k,
59 kz: beta.derivative.get(1).copied().unwrap_or_default() * translated.value
60 + beta.value * translated.kz,
61 })
62}
63
64pub fn periodic_to_cw(
71 to: cw::Mode,
72 from: Mode,
73 k: Complex,
74 period: f64,
75 helicity: bool,
76) -> Result<Complex> {
77 to.validate()?;
78 from.validate()?;
79 if !finite(k) || k == Complex::default() || !period.is_finite() || period == 0.0 {
80 return Err(Error::InvalidInput(
81 "finite nonzero wavenumber and period required".into(),
82 ));
83 }
84 Ok(periodic_entry::<0>(to, from, k, [0.0; 3], period.abs(), helicity)?.value)
85}
86
87#[derive(Debug)]
94pub struct PeriodicToCwResidual {
95 destination: cw::Basis,
96 source: Basis,
97 ks: [Complex; 2],
98 period: f64,
99 helicity: bool,
100}
101
102#[derive(Debug)]
104pub struct PeriodicToCwGradient {
105 pub expansion: ExpansionGradient,
107 pub kz: Vec<f64>,
109 pub period: f64,
111}
112
113pub fn periodic_to_cw_matrix(
121 destination: cw::Basis,
122 source: Basis,
123 ks: [Complex; 2],
124 period: f64,
125 helicity: bool,
126) -> Result<(DMatrix<Complex>, PeriodicToCwResidual)> {
127 destination.validate()?;
128 source.validate()?;
129 if !period.is_finite() || period <= 0.0 {
130 return Err(Error::InvalidInput(
131 "period must be positive and finite".into(),
132 ));
133 }
134 validate_wavenumbers(ks, helicity, false)?;
135 let mut value = crate::numerics::zeros(destination.modes.len(), source.modes.len())?;
136 crate::threads::install(|| {
137 value
138 .as_mut_slice()
139 .par_chunks_mut(destination.modes.len())
140 .enumerate()
141 .try_for_each(|(j, column)| -> Result<()> {
142 let (q, from) = source.modes[j];
143 for (out, &(p, to)) in column.iter_mut().zip(&destination.modes) {
144 let r = std::array::from_fn(|a| {
145 destination.positions[p][a] - source.positions[q][a]
146 });
147 *out = periodic_entry::<0>(
148 to,
149 from,
150 ks[usize::from(from.pol)],
151 r,
152 period,
153 helicity,
154 )?
155 .value;
156 }
157 Ok(())
158 })
159 })?;
160 Ok((
161 value,
162 PeriodicToCwResidual {
163 destination,
164 source,
165 ks,
166 period,
167 helicity,
168 },
169 ))
170}
171
172impl PeriodicToCwResidual {
173 pub fn state_size(
175 destination_modes: usize,
176 destination_positions: usize,
177 source_modes: usize,
178 source_positions: usize,
179 ) -> Result<usize> {
180 let destination = crate::saved::cw_basis_size(destination_modes, destination_positions)?;
181 let source = crate::saved::sw_basis_size(source_modes, source_positions)?;
182 destination
183 .checked_add(source)
184 .and_then(|n| n.checked_add(41))
185 .ok_or_else(crate::saved::invalid)
186 }
187 #[must_use]
189 pub fn shape(&self) -> (usize, usize) {
190 (self.destination.modes.len(), self.source.modes.len())
191 }
192
193 pub fn pushforward(
196 &self,
197 destination: &[[f64; 3]],
198 source: &[[f64; 3]],
199 ks: [Complex; 2],
200 kz: &[f64],
201 period: f64,
202 ) -> Result<DMatrix<Complex>> {
203 crate::basis::validate_expansion_tangent(
204 destination,
205 source,
206 ks,
207 (
208 self.destination.positions.len(),
209 self.source.positions.len(),
210 ),
211 )?;
212 if kz.len() != self.destination.modes.len()
213 || kz.iter().any(|k| !k.is_finite())
214 || !period.is_finite()
215 {
216 return Err(Error::InvalidInput(
217 "axial and period tangents must be finite and match inputs".into(),
218 ));
219 }
220 let mut value = crate::numerics::zeros(self.shape().0, self.shape().1)?;
221 crate::threads::install(|| {
222 value
223 .as_mut_slice()
224 .par_chunks_mut(self.shape().0)
225 .enumerate()
226 .try_for_each(|(j, column)| -> Result<()> {
227 let (q, from) = self.source.modes[j];
228 let pol = usize::from(from.pol);
229 for (i, (out, &(p, to))) in
230 column.iter_mut().zip(&self.destination.modes).enumerate()
231 {
232 let displacement = std::array::from_fn(|axis| {
233 self.destination.positions[p][axis] - self.source.positions[q][axis]
234 });
235 let entry = periodic_entry::<2>(
236 to,
237 from,
238 self.ks[pol],
239 displacement,
240 self.period,
241 self.helicity,
242 )?;
243 *out = crate::basis::pair_tangent(
244 destination,
245 source,
246 ks,
247 [p, q, pol],
248 entry.position,
249 entry.k,
250 ) + entry.kz * kz[i]
251 - entry.value * (period / self.period);
252 }
253 Ok(())
254 })
255 })?;
256 Ok(value)
257 }
258
259 pub fn pullback(&self, cotangent: &DMatrix<Complex>) -> Result<PeriodicToCwGradient> {
261 if cotangent.shape() != self.shape() || cotangent.iter().any(|&v| !finite(v)) {
262 return Err(Error::InvalidInput(
263 "invalid periodic conversion cotangent".into(),
264 ));
265 }
266 try_fold_ordered(
267 self.source.modes.iter().collect(),
268 true,
269 || PeriodicToCwGradient {
270 expansion: ExpansionGradient::zeros(
271 self.destination.positions.len(),
272 self.source.positions.len(),
273 ),
274 kz: vec![0.0; self.destination.modes.len()],
275 period: 0.0,
276 },
277 |mut result, j, &(q, from)| -> Result<_> {
278 for (i, &(p, to)) in self.destination.modes.iter().enumerate() {
279 let cot = cotangent[(i, j)];
280 if cot == Complex::default() {
281 continue;
282 }
283 let r = std::array::from_fn(|a| {
284 self.destination.positions[p][a] - self.source.positions[q][a]
285 });
286 let pol = usize::from(from.pol);
287 let entry =
288 periodic_entry::<2>(to, from, self.ks[pol], r, self.period, self.helicity)?;
289 add_pair_gradient(
290 &mut result.expansion,
291 [p, q, pol],
292 cot,
293 entry.position,
294 entry.k,
295 );
296 result.kz[i] += (cot.conj() * entry.kz).re;
297 result.period -= (cot.conj() * entry.value).re / self.period;
298 }
299 Ok(result)
300 },
301 |mut total, partial| {
302 total.expansion.accumulate(&partial.expansion);
303 for (a, b) in total.kz.iter_mut().zip(&partial.kz) {
304 *a += b;
305 }
306 total.period += partial.period;
307 total
308 },
309 )
310 }
311}
312
313impl crate::saved::SavedState for PeriodicToCwResidual {
314 fn save_state(&self) -> Result<Vec<u8>> {
315 let mut writer = crate::saved::Writer::new(Self::state_size(
316 self.destination.modes.len(),
317 self.destination.positions.len(),
318 self.source.modes.len(),
319 self.source.positions.len(),
320 )?);
321 crate::saved::write_cw_basis(&mut writer, &self.destination);
322 crate::saved::write_sw_basis(&mut writer, &self.source);
323 for k in self.ks {
324 writer.complex(k);
325 }
326 writer.f64(self.period);
327 writer.byte(u8::from(self.helicity));
328 Ok(writer.finish())
329 }
330
331 fn from_state(bytes: &[u8]) -> Result<Self> {
332 let mut shape = crate::saved::Reader::new(bytes);
333 let (dm, dp) = crate::saved::read_cw_basis_dimensions(&mut shape)?;
334 let (sm, sp) = crate::saved::read_sw_basis_dimensions(&mut shape)?;
335 if bytes.len() != Self::state_size(dm, dp, sm, sp)? {
336 return Err(crate::saved::invalid());
337 }
338 let mut reader = crate::saved::Reader::new(bytes);
339 let destination = crate::saved::read_cw_basis(&mut reader)?;
340 let source = crate::saved::read_sw_basis(&mut reader)?;
341 let ks = [reader.complex()?, reader.complex()?];
342 let period = reader.f64()?;
343 let helicity = match reader.byte()? {
344 0 => false,
345 1 => true,
346 _ => return Err(crate::saved::invalid()),
347 };
348 reader.finish()?;
349 validate_wavenumbers(ks, helicity, false)?;
350 if !period.is_finite() || period <= 0.0 {
351 return Err(crate::saved::invalid());
352 }
353 Ok(Self {
354 destination,
355 source,
356 ks,
357 period,
358 helicity,
359 })
360 }
361}
362
363#[cfg(test)]
364mod tests {
365 use super::periodic_to_cw_matrix;
366 use crate::{
367 Complex, cw, sw,
368 test_support::{assert_same_bits_on_pools, bits, patterned},
369 };
370
371 #[test]
374 fn pullback_does_not_depend_on_the_thread_count() {
375 let modes = sw::modes(3).unwrap();
376 let source = sw::Basis {
377 modes: (0..3)
378 .flat_map(|p| modes.iter().map(move |&mode| (p, mode)))
379 .collect(),
380 positions: vec![[0.1, -0.2, 0.3], [-0.3, 0.2, -0.1], [0.2, 0.25, 0.0]],
381 };
382 let destination = cw::Basis {
383 modes: (0..2)
384 .flat_map(|p| {
385 [0.2, -0.35].into_iter().flat_map(move |kz| {
386 (-3..=3).flat_map(move |m| [1, 0].map(|pol| (p, cw::Mode { kz, m, pol })))
387 })
388 })
389 .collect(),
390 positions: vec![[1.1, 0.4, 0.0], [-0.9, -0.7, 0.3]],
391 };
392 let ks = [Complex::new(1.2, 0.05); 2];
393 let g = patterned(destination.modes.len(), source.modes.len(), 0.2);
394 assert_same_bits_on_pools(|| {
395 let (_, residual) =
396 periodic_to_cw_matrix(destination.clone(), source.clone(), ks, 1.3, true).unwrap();
397 let g = residual.pullback(&g).unwrap();
398 let expansion = &g.expansion;
399 bits(&[
400 &expansion.destination,
401 &expansion.source,
402 &expansion.ks,
403 &g.kz,
404 &g.period,
405 ])
406 });
407 }
408}