1#![allow(clippy::indexing_slicing)] use nalgebra::DMatrix;
9
10use super::polarization::{Direction, polarization_jet, transverse_values, wavenumbers};
11use crate::{
12 Complex, Error, Result,
13 numerics::{
14 Jet, finite,
15 parallel::{PARALLEL_ITEMS, try_fill_chunks},
16 },
17 special::{check_pol, helicity_sign},
18};
19
20#[derive(Debug)]
26pub struct PermutationResidual {
27 pub(super) vectors: Vec<[Complex; 3]>,
28 pub(super) polarizations: Vec<u8>,
29 pub(super) turns: usize,
30 pub(super) helicity: bool,
31}
32
33fn permutation_pair<const N: usize>(vector: [Complex; 3], turns: usize) -> Result<[Jet<N>; 2]> {
52 if turns == 0 {
53 wavenumbers(vector)?;
54 return Ok([Jet::constant(1.0), Jet::default()]);
55 }
56 let scale = vector
59 .iter()
60 .map(|v| v.re.abs().max(v.im.abs()))
61 .fold(0.0, f64::max);
62 if (1e-70..=1e70).contains(&scale) && vector.iter().all(|&v| finite(v)) {
63 let [x, y, z] = std::array::from_fn(|i| Jet::<N>::variable(vector[i], i));
64 let transverse = (x * x + y * y).sqrt();
65 let destination = if turns == 1 {
66 (z * z + x * x).sqrt()
67 } else {
68 (y * y + z * z).sqrt()
69 };
70 let denominator = transverse * destination;
71 let k = (x * x + y * y + z * z).sqrt();
72 if (1e-140..=1e140).contains(&denominator.value.norm_sqr()) && k.value != Complex::default()
73 {
74 let inverse = Jet::constant(1.0) / denominator;
75 return Ok(if turns == 1 {
76 [-y * z * inverse, -Complex::i() * x * k * inverse]
77 } else {
78 [-x * z * inverse, Complex::i() * y * k * inverse]
79 });
80 }
81 }
82 let rotated = std::array::from_fn(|axis| vector[(axis + 3 - turns) % 3]);
83 let source_direction = Direction::<N>::new(vector)?;
84 let (transverse, xy) = transverse_values([rotated[0], rotated[1]])?;
85 let destination =
87 Direction::<N>::from_parts(rotated, source_direction.k.value, transverse, xy)?;
88 if source_direction.transverse.value != Complex::default()
89 && destination.transverse.value != Complex::default()
90 {
91 let unpermute = |mut jet: Jet<N>| {
92 jet.derivative = std::array::from_fn(|j| jet.derivative[(j + turns) % 3]);
93 jet
94 };
95 let pair = if turns == 1 {
96 (
97 -source_direction.xy[1] * unpermute(destination.xy[0]),
98 -Complex::i()
99 * source_direction.xy[0]
100 * (source_direction.k / unpermute(destination.transverse)),
101 )
102 } else {
103 (
104 -source_direction.xy[0] * unpermute(destination.xy[1]),
105 Complex::i()
106 * source_direction.xy[1]
107 * (source_direction.k / unpermute(destination.transverse)),
108 )
109 };
110 return Ok(pair.into());
111 }
112 let source = polarization_jet::<N>(vector, 0, false)?;
114 let mut result = [Jet::default(); 2];
115 for (p, output) in result.iter_mut().enumerate() {
116 let dual_pol = u8::from(p == 1);
118 let sign = if p == 0 { -1.0 } else { 1.0 };
119 let dual = polarization_jet::<N>(rotated, dual_pol, false)?;
120 for (axis, mut component) in dual.into_iter().enumerate() {
121 component.derivative = std::array::from_fn(|j| component.derivative[(j + turns) % 3]);
122 *output += sign * component * source[(axis + 3 - turns) % 3];
123 }
124 }
125 Ok(result)
126}
127
128fn permutation_polarization<const N: usize>(
129 pair: [Jet<N>; 2],
130 pol: u8,
131 helicity: bool,
132) -> [Jet<N>; 2] {
133 let [same, cross] = pair;
134 std::array::from_fn(|p| {
135 if helicity {
136 if p == usize::from(pol) {
137 same + helicity_sign(pol) * cross
138 } else {
139 Jet::default()
140 }
141 } else if p == usize::from(pol) {
142 same
143 } else {
144 cross
145 }
146 })
147}
148
149pub fn permutation(
157 vectors: Vec<[Complex; 3]>,
158 polarizations: Vec<u8>,
159 turns: usize,
160 helicity: bool,
161) -> Result<(DMatrix<Complex>, PermutationResidual)> {
162 if vectors.is_empty()
163 || polarizations.len() != vectors.len()
164 || polarizations.iter().any(|&p| p > 1)
165 {
166 return Err(Error::InvalidInput(
167 "require matching nonempty vectors and polarizations".into(),
168 ));
169 }
170 let turns = turns % 3;
171 let mut value = DMatrix::zeros(2, vectors.len());
172 let parallel = vectors.len() >= PARALLEL_ITEMS;
173 try_fill_chunks(value.as_mut_slice(), 4, parallel, |group, output| {
174 let first = 2 * group;
175 let pair = permutation_pair::<0>(vectors[first], turns)?;
176 for (offset, column) in output.chunks_mut(2).enumerate() {
177 let i = first + offset;
178 let pair = if offset == 0 || vectors[i] == vectors[first] {
179 pair
180 } else {
181 permutation_pair::<0>(vectors[i], turns)?
182 };
183 for (out, coefficient) in
184 column
185 .iter_mut()
186 .zip(permutation_polarization(pair, polarizations[i], helicity))
187 {
188 *out = coefficient.value;
189 }
190 }
191 Ok(())
192 })?;
193 if value.iter().any(|&z| !finite(z)) {
194 return Err(Error::NonFinite("plane permutation overflow".into()));
195 }
196 Ok((
197 value,
198 PermutationResidual {
199 vectors,
200 polarizations,
201 turns,
202 helicity,
203 },
204 ))
205}
206
207impl PermutationResidual {
208 #[must_use]
211 pub fn shape(&self) -> (usize, usize) {
212 (2, self.vectors.len())
213 }
214
215 pub fn validate_tangents(&self, vectors: &[[Complex; 3]]) -> Result<()> {
217 if vectors.len() != self.vectors.len() || vectors.iter().flatten().any(|&k| !finite(k)) {
218 return Err(Error::InvalidInput(
219 "invalid plane-permutation tangent".into(),
220 ));
221 }
222 Ok(())
223 }
224
225 pub fn pushforward(&self, vectors: &[[Complex; 3]]) -> Result<DMatrix<Complex>> {
227 self.validate_tangents(vectors)?;
228 let mut tangent = DMatrix::zeros(2, self.vectors.len());
229 if self.turns == 0 {
230 return Ok(tangent);
231 }
232 try_fill_chunks(
235 tangent.as_mut_slice(),
236 4,
237 self.vectors.len() >= PARALLEL_ITEMS,
238 |group, output| -> Result<()> {
239 let first = 2 * group;
240 let mut previous = None;
241 for (offset, column) in output.chunks_mut(2).enumerate() {
242 let i = first + offset;
243 if vectors[i].iter().all(|&v| v == Complex::default()) {
244 continue;
245 }
246 let pair = if let Some((vector, pair)) = previous
247 && vector == self.vectors[i]
248 {
249 pair
250 } else {
251 let pair = permutation_pair::<3>(self.vectors[i], self.turns)?;
252 previous = Some((self.vectors[i], pair));
253 pair
254 };
255 for (out, coefficient) in column.iter_mut().zip(permutation_polarization(
256 pair,
257 self.polarizations[i],
258 self.helicity,
259 )) {
260 *out = coefficient
261 .derivative
262 .iter()
263 .zip(vectors[i])
264 .map(|(&d, v)| d * v)
265 .sum();
266 }
267 }
268 Ok(())
269 },
270 )?;
271 Ok(tangent)
272 }
273
274 pub fn pullback(&self, cotangent: &DMatrix<Complex>) -> Result<Vec<[Complex; 3]>> {
277 if cotangent.shape() != self.shape() || cotangent.iter().any(|&z| !finite(z)) {
278 return Err(Error::InvalidInput(
279 "invalid plane-permutation cotangent".into(),
280 ));
281 }
282 let modes = self.vectors.len();
283 if self.turns == 0 {
284 return Ok(vec![[Complex::default(); 3]; modes]);
285 }
286 let mut gradient = vec![[Complex::default(); 3]; modes];
288 let parallel = modes >= PARALLEL_ITEMS;
289 try_fill_chunks(&mut gradient, 2, parallel, |group, output| {
290 let first = 2 * group;
291 let pair = permutation_pair::<3>(self.vectors[first], self.turns)?;
292 for (offset, gradient) in output.iter_mut().enumerate() {
293 let i = first + offset;
294 let pair = if offset == 0 || self.vectors[i] == self.vectors[first] {
295 pair
296 } else {
297 permutation_pair::<3>(self.vectors[i], self.turns)?
298 };
299 let coefficients =
300 permutation_polarization(pair, self.polarizations[i], self.helicity);
301 *gradient = std::array::from_fn(|axis| {
302 (0..2)
303 .map(|p| cotangent[(p, i)] * coefficients[p].derivative[axis].conj())
304 .sum()
305 });
306 }
307 Ok(())
308 })?;
309 Ok(gradient)
310 }
311}
312
313pub fn permute_xyz(
320 vector: [Complex; 3],
321 destination: u8,
322 source: u8,
323 turns: usize,
324 helicity: bool,
325) -> Result<Complex> {
326 check_pol(destination)?;
327 check_pol(source)?;
328 let value =
329 permutation_polarization(permutation_pair::<0>(vector, turns % 3)?, source, helicity)
330 [usize::from(destination)]
331 .value;
332 if !finite(value) {
333 return Err(Error::NonFinite("non-finite plane permutation".into()));
334 }
335 Ok(value)
336}