1mod saved;
18
19use nalgebra::DMatrix;
20
21use crate::{
22 Complex, Error, Result,
23 numerics::{
24 Jet, finite,
25 parallel::{PARALLEL_ITEMS, try_fill_chunks, try_map},
26 },
27 special::helicity_sign,
28};
29
30const KS: usize = 0;
35const Q: usize = 0;
37const KZS: usize = 2;
39const INTERVAL: usize = 4;
41
42#[derive(Debug)]
45pub struct ChiralityDensityResidual {
46 ks: Vec<Complex>,
47 normal: Vec<Complex>,
48 interval: [f64; 2],
49}
50
51#[derive(Debug)]
53pub struct ChiralityDensityGradient {
54 pub ks: Vec<Complex>,
56 pub normal: Vec<Complex>,
58 pub interval: [f64; 2],
60}
61
62fn mean_exp<const N: usize>(slope: Jet<N>, interval: [Jet<N>; 2]) -> Jet<N> {
65 let width = slope * (interval[1] - interval[0]);
66 if width.value.norm() < 0.1 {
67 let square = (width * 0.5).powi(2);
68 let mut term = Jet::constant(1.0);
69 let mut sum = term;
70 for j in 1..=6 {
71 term *= square / f64::from(2 * j * (2 * j + 1));
72 sum += term;
73 }
74 (slope * (interval[0] + interval[1]) * 0.5).exp() * sum
75 } else {
76 ((slope * interval[1]).exp() - (slope * interval[0]).exp()) / width
77 }
78}
79
80fn chirality_mode<const N: usize>(k: Complex, normal: Complex, z: [f64; 2]) -> [Jet<N>; 3] {
82 chirality_forms(
83 [Jet::variable(k.re, KS), Jet::variable(k.im, KS + 1)],
84 [
85 Jet::variable(normal.re, KZS),
86 Jet::variable(normal.im, KZS + 1),
87 ],
88 [
89 Jet::variable(z[0], INTERVAL),
90 Jet::variable(z[1], INTERVAL + 1),
91 ],
92 )
93}
94
95fn chirality_forms<const N: usize>(
97 k: [Jet<N>; 2],
98 normal: [Jet<N>; 2],
99 z: [Jet<N>; 2],
100) -> [Jet<N>; 3] {
101 let scale = k[0].value.re.hypot(k[1].value.re);
104 let [kr, ki] = k.map(|v| v / scale);
105 let [nr, ni] = normal;
106 let denominator = kr.powi(2) + ki.powi(2);
107 let same = 2.0 * (kr.powi(2) + (ni / scale).powi(2)) / denominator;
108 let cross = 2.0 * (kr.powi(2) - (nr / scale).powi(2)) / denominator;
109 [
110 same * mean_exp(-2.0 * ni, z),
111 same * mean_exp(2.0 * ni, z),
112 2.0 * cross * mean_exp(2.0 * Complex::i() * nr, z),
113 ]
114}
115
116pub fn chirality_density(
124 ks: Vec<Complex>,
125 normal: Vec<Complex>,
126 interval: [f64; 2],
127) -> Result<(DMatrix<Complex>, ChiralityDensityResidual)> {
128 if ks.is_empty()
129 || normal.len() != ks.len()
130 || ks.iter().any(|&k| !finite(k) || k == Complex::default())
131 || normal.iter().any(|&k| !finite(k))
132 || interval.iter().any(|z| !z.is_finite())
133 {
134 return Err(Error::InvalidInput(
135 "chirality requires matching finite wavenumbers, nonzero full k and finite interval"
136 .into(),
137 ));
138 }
139 let value = mode_forms(
140 ks.len(),
141 |j| Ok(chirality_mode::<0>(ks[j], normal[j], interval)),
142 |v| v.value,
143 )?;
144 Ok((
145 value,
146 ChiralityDensityResidual {
147 ks,
148 normal,
149 interval,
150 },
151 ))
152}
153
154fn mode_forms<const N: usize>(
156 modes: usize,
157 forms: impl Fn(usize) -> Result<[Jet<N>; 3]> + Sync,
158 component: impl Fn(Jet<N>) -> Complex + Sync,
159) -> Result<DMatrix<Complex>> {
160 let mut value = DMatrix::zeros(3, modes);
161 try_fill_chunks(
162 value.as_mut_slice(),
163 3,
164 modes >= PARALLEL_ITEMS,
165 |j, column| {
166 for (out, form) in column.iter_mut().zip(forms(j)?) {
167 *out = component(form);
168 if !finite(*out) {
169 return Err(Error::NonFinite("chirality density overflow".into()));
170 }
171 }
172 Ok(())
173 },
174 )?;
175 Ok(value)
176}
177
178fn mode_cotangents(
181 cotangent: &DMatrix<Complex>,
182 modes: usize,
183 forms: impl Fn(usize) -> Result<[Jet<6>; 3]> + Sync,
184) -> Result<Vec<[f64; 6]>> {
185 if cotangent.shape() != (3, modes) || cotangent.iter().any(|&g| !finite(g)) {
186 return Err(Error::InvalidInput("invalid chirality cotangent".into()));
187 }
188 try_map(modes, modes >= PARALLEL_ITEMS, |j| {
189 let forms = forms(j)?;
190 let g: [f64; 6] = std::array::from_fn(|p| {
191 forms
192 .iter()
193 .enumerate()
194 .map(|(i, c)| (cotangent[(i, j)].conj() * c.derivative[p]).re)
195 .sum()
196 });
197 if g.iter().any(|v| !v.is_finite()) {
198 return Err(Error::NonFinite("chirality derivative overflow".into()));
199 }
200 Ok(g)
201 })
202}
203
204impl ChiralityDensityResidual {
205 #[must_use]
207 pub fn shape(&self) -> (usize, usize) {
208 (3, self.ks.len())
209 }
210
211 pub fn pushforward(
213 &self,
214 ks: &[Complex],
215 normal: &[Complex],
216 interval: [f64; 2],
217 ) -> Result<DMatrix<Complex>> {
218 if ks.len() != self.ks.len()
219 || normal.len() != self.normal.len()
220 || ks.iter().chain(normal).any(|&v| !finite(v))
221 || interval.iter().any(|v| !v.is_finite())
222 {
223 return Err(Error::InvalidInput(
224 "chirality tangents must be finite and match inputs".into(),
225 ));
226 }
227 let interval = std::array::from_fn(|j| Jet {
228 value: self.interval[j].into(),
229 derivative: [interval[j].into()],
230 });
231 mode_forms(
232 self.ks.len(),
233 |j| {
234 Ok(chirality_forms(
235 real_direction(self.ks[j], ks[j]),
236 real_direction(self.normal[j], normal[j]),
237 interval,
238 ))
239 },
240 |v| v.derivative[0],
241 )
242 }
243
244 pub fn pullback(&self, cotangent: &DMatrix<Complex>) -> Result<ChiralityDensityGradient> {
246 let local = mode_cotangents(cotangent, self.ks.len(), |j| {
247 Ok(chirality_mode(self.ks[j], self.normal[j], self.interval))
248 })?;
249 let mut gradient = ChiralityDensityGradient {
250 ks: Vec::with_capacity(local.len()),
251 normal: Vec::with_capacity(local.len()),
252 interval: [0.0; 2],
253 };
254 for g in local {
255 gradient.ks.push(Complex::new(g[KS], g[KS + 1]));
256 gradient.normal.push(Complex::new(g[KZS], g[KZS + 1]));
257 gradient.interval[0] += g[INTERVAL];
258 gradient.interval[1] += g[INTERVAL + 1];
259 }
260 Ok(gradient)
261 }
262}
263
264#[derive(Debug)]
267pub struct OrientedChiralityResidual {
268 transverse: Vec<[f64; 2]>,
269 normal: Vec<Complex>,
270 polarizations: Vec<u8>,
271 axis: usize,
272 interval: [f64; 2],
273}
274
275#[derive(Debug)]
277pub struct OrientedChiralityGradient {
278 pub transverse: Vec<[f64; 2]>,
280 pub normal: Vec<Complex>,
282 pub interval: [f64; 2],
284}
285
286fn oriented_chirality_mode<const N: usize>(
288 transverse: [f64; 2],
289 normal: Complex,
290 pol: u8,
291 axis: usize,
292 interval: [f64; 2],
293) -> Result<[Jet<N>; 3]> {
294 oriented_chirality_forms(
295 [
296 Jet::variable(transverse[0], Q),
297 Jet::variable(transverse[1], Q + 1),
298 ],
299 [
300 Jet::variable(normal.re, KZS),
301 Jet::variable(normal.im, KZS + 1),
302 ],
303 pol,
304 axis,
305 [
306 Jet::variable(interval[0], INTERVAL),
307 Jet::variable(interval[1], INTERVAL + 1),
308 ],
309 )
310}
311
312fn oriented_chirality_forms<const N: usize>(
314 transverse: [Jet<N>; 2],
315 normal: [Jet<N>; 2],
316 pol: u8,
317 axis: usize,
318 z: [Jet<N>; 2],
319) -> Result<[Jet<N>; 3]> {
320 let [nr, ni] = normal;
321 let sign = helicity_sign(pol);
322 if transverse.iter().all(|q| q.value == Complex::default()) {
324 if normal.iter().all(|v| v.value == Complex::default()) {
325 return Err(Error::InvalidInput("wavevector must be nonzero".into()));
326 }
327 return Ok([
328 2.0 * sign * mean_exp(-2.0 * ni, z),
329 2.0 * sign * mean_exp(2.0 * ni, z),
330 Jet::default(),
331 ]);
332 }
333 let mut vector = [Jet::default(); 3];
334 vector[axis] = nr + Complex::i() * ni;
335 vector[(axis + 1) % 3] = transverse[0];
336 vector[(axis + 2) % 3] = transverse[1];
337 let up = crate::pw::polarization_from_inputs(vector, pol)?;
338 vector[axis] = -vector[axis];
339 let down = crate::pw::polarization_from_inputs(vector, pol)?;
340 let inner = |a: [Jet<N>; 3], b: [Jet<N>; 3]| -> Jet<N> {
342 a.into_iter()
343 .zip(b)
344 .map(|(a, b)| {
345 Jet {
346 value: a.value.conj(),
347 derivative: a.derivative.map(|d| d.conj()),
348 } * b
349 })
350 .sum()
351 };
352 Ok([
353 2.0 * sign * inner(up, up) * mean_exp(-2.0 * ni, z),
354 2.0 * sign * inner(down, down) * mean_exp(2.0 * ni, z),
355 4.0 * sign * inner(down, up) * mean_exp(2.0 * Complex::i() * nr, z),
356 ])
357}
358
359pub fn oriented_chirality(
368 transverse: Vec<[f64; 2]>,
369 normal: Vec<Complex>,
370 polarizations: Vec<u8>,
371 axis: usize,
372 interval: [f64; 2],
373) -> Result<(DMatrix<Complex>, OrientedChiralityResidual)> {
374 if transverse.is_empty()
375 || transverse.len() != normal.len()
376 || transverse.len() != polarizations.len()
377 || axis > 2
378 || transverse.iter().flatten().any(|q| !q.is_finite())
379 || normal.iter().any(|&k| !finite(k))
380 || polarizations.iter().any(|&p| p > 1)
381 || interval.iter().any(|z| !z.is_finite())
382 {
383 return Err(Error::InvalidInput(
384 "chirality requires matching finite geometry, polarization 0/1 and axis 0/1/2".into(),
385 ));
386 }
387 let value = mode_forms(
388 normal.len(),
389 |j| {
390 oriented_chirality_mode::<0>(transverse[j], normal[j], polarizations[j], axis, interval)
391 },
392 |v| v.value,
393 )?;
394 Ok((
395 value,
396 OrientedChiralityResidual {
397 transverse,
398 normal,
399 polarizations,
400 axis,
401 interval,
402 },
403 ))
404}
405
406impl OrientedChiralityResidual {
407 #[must_use]
409 pub fn shape(&self) -> (usize, usize) {
410 (3, self.normal.len())
411 }
412
413 pub fn pushforward(
415 &self,
416 transverse: &[[f64; 2]],
417 normal: &[Complex],
418 interval: [f64; 2],
419 ) -> Result<DMatrix<Complex>> {
420 if transverse.len() != self.transverse.len()
421 || normal.len() != self.normal.len()
422 || transverse.iter().flatten().any(|v| !v.is_finite())
423 || normal.iter().any(|&v| !finite(v))
424 || interval.iter().any(|v| !v.is_finite())
425 {
426 return Err(Error::InvalidInput(
427 "chirality tangents must be finite and match inputs".into(),
428 ));
429 }
430 let interval = std::array::from_fn(|j| Jet {
431 value: self.interval[j].into(),
432 derivative: [interval[j].into()],
433 });
434 mode_forms(
435 self.normal.len(),
436 |j| {
437 oriented_chirality_forms(
438 std::array::from_fn(|a| Jet {
439 value: self.transverse[j][a].into(),
440 derivative: [transverse[j][a].into()],
441 }),
442 real_direction(self.normal[j], normal[j]),
443 self.polarizations[j],
444 self.axis,
445 interval,
446 )
447 },
448 |v| v.derivative[0],
449 )
450 }
451
452 pub fn pullback(&self, cotangent: &DMatrix<Complex>) -> Result<OrientedChiralityGradient> {
454 let local = mode_cotangents(cotangent, self.normal.len(), |j| {
455 oriented_chirality_mode(
456 self.transverse[j],
457 self.normal[j],
458 self.polarizations[j],
459 self.axis,
460 self.interval,
461 )
462 })?;
463 let mut gradient = OrientedChiralityGradient {
464 transverse: Vec::with_capacity(local.len()),
465 normal: Vec::with_capacity(local.len()),
466 interval: [0.0; 2],
467 };
468 for g in local {
469 gradient.transverse.push([g[Q], g[Q + 1]]);
470 gradient.normal.push(Complex::new(g[KZS], g[KZS + 1]));
471 gradient.interval[0] += g[INTERVAL];
472 gradient.interval[1] += g[INTERVAL + 1];
473 }
474 Ok(gradient)
475 }
476}
477
478fn real_direction(value: Complex, tangent: Complex) -> [Jet<1>; 2] {
480 [
481 Jet {
482 value: value.re.into(),
483 derivative: [tangent.re.into()],
484 },
485 Jet {
486 value: value.im.into(),
487 derivative: [tangent.im.into()],
488 },
489 ]
490}