treams_core/sw/mod.rs
1//! Spherical vector waves: mode labels, translation coefficients of mode pairs, expansion
2//! matrices between bases and over lattices, and the cylindrical waves radiated by a
3//! z-periodic chain of spheres.
4//!
5//! Upstream: `treams.sw`, and `treams.special.tl_vsw_A`, `tl_vsw_B`, `tl_vsw_rA` and
6//! `tl_vsw_rB`.
7//!
8//! | File | Contents | Upstream |
9//! |---|---|---|
10//! | `coupling.rs` | degree terms `(p, weight)` of one mode pair, from Wigner 3j symbols | `special._tl_vsw_helper`, the sums of `tl_vsw_A` and `tl_vsw_B` |
11//! | `polar.rs` | [`PolarTranslation`] and [`polar_translation_array`]: coefficients at polar arguments `(kr, theta, phi)` | `sw.translate`, `special.tl_vsw_*` |
12//! | `cartesian.rs` | [`cartesian_translation`]: one coefficient at a Cartesian displacement, with its derivatives | treams-rs extension |
13//! | `plan.rs` | the couplings of whole mode blocks, shared by every pair of positions | the loops of `sw.translate` over a basis |
14//! | `expansion.rs` | [`expansion()`], [`lattice_expansion`] and [`lattice_expansion_from_table`] | `Expand`, `ExpandLattice`, `sw.translate_periodic` |
15//! | `periodic_to_cw.rs` | [`periodic_to_cw()`] and [`periodic_to_cw_matrix`] | `sw.periodic_to_cw`, `ExpandLattice` to a cylindrical basis |
16//!
17//! Conventions:
18//!
19//! - A translation from a source position to a destination position takes the
20//! displacement `destination - source`. Lattice sums run over `source - destination`,
21//! the argument of [`crate::lattice::sum`]; the lattice paths negate the displacement
22//! where they call it.
23//! - `pol` is the polarization index, 0 or 1. With `helicity` true, 1 is positive and 0
24//! negative helicity; with `helicity` false, 1 is TM (the N multipoles) and 0 TE (the M
25//! multipoles). Bases list pol 1 before pol 0, as treams does.
26//! - [`Radial::Regular`](crate::special::Radial::Regular) and
27//! [`Radial::Singular`](crate::special::Radial::Singular) are the treams `modetype`
28//! values 'regular' and 'singular'; singular waves use the outgoing Hankel function.
29//! - In a derivative struct such as [`CartesianTranslation`], every field other than
30//! `value` holds the derivative of `value` with respect to the input it is named after;
31//! `position` holds the derivatives with respect to the displacement components.
32//!
33//! The inline `tests` modules check how each file computes its results; the physical
34//! and adjoint identities of translations and expansions are in `properties/waves.rs`.
35
36mod cartesian;
37mod coupling;
38mod expansion;
39mod periodic_to_cw;
40mod plan;
41mod polar;
42
43#[cfg(test)]
44pub(crate) use cartesian::harmonic;
45pub use cartesian::{CartesianTranslation, cartesian_translation};
46pub(crate) use coupling::terms;
47pub use expansion::{
48 ExpansionResidual, LatticeExpansionFromTableResidual, LatticeExpansionResidual, expansion,
49 lattice_expansion, lattice_expansion_from_table,
50};
51pub use periodic_to_cw::{
52 PeriodicToCwGradient, PeriodicToCwResidual, periodic_to_cw, periodic_to_cw_matrix,
53};
54pub(crate) use plan::TranslationPlan;
55pub use polar::{PolarTranslation, PolarTranslationResidual, polar_translation_array};
56
57use std::collections::BTreeMap;
58
59use crate::{Error, MAX_DEGREE, Result, basis::ModeLabel};
60
61/// One spherical-wave mode: degree `l`, order `m` and polarization index `pol`.
62///
63/// Upstream: the `(l, m, pol)` of one entry of `treams.SphericalWaveBasis`; [`Basis`]
64/// adds the position index `pidx`.
65#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
66pub struct Mode {
67 /// Multipole degree, `1 <= l <= MAX_DEGREE`.
68 pub l: i32,
69 /// Azimuthal order, `|m| <= l`.
70 pub m: i32,
71 /// Polarization index: 1 or 0, as in treams.
72 pub pol: u8,
73}
74
75impl ModeLabel for Mode {
76 /// Require `1 <= l <= MAX_DEGREE`, `|m| <= l` and `pol` 0 or 1.
77 fn validate(self) -> Result<()> {
78 if !(1..=MAX_DEGREE).contains(&self.l)
79 || self.m.unsigned_abs() > self.l.unsigned_abs()
80 || self.pol > 1
81 {
82 return Err(Error::InvalidInput("invalid spherical mode".into()));
83 }
84 Ok(())
85 }
86
87 fn pol(self) -> u8 {
88 self.pol
89 }
90}
91
92/// Spherical modes at Cartesian expansion positions.
93///
94/// Upstream: `treams.SphericalWaveBasis`.
95pub type Basis = crate::basis::Basis<Mode>;
96
97impl crate::basis::Basis<Mode> {
98 /// The basis entries and modes at each position index, in basis order.
99 pub(crate) fn groups(&self) -> BTreeMap<usize, (Vec<usize>, Vec<Mode>)> {
100 let mut groups = BTreeMap::<usize, (Vec<usize>, Vec<Mode>)>::new();
101 for (index, &(pidx, mode)) in self.modes.iter().enumerate() {
102 let group = groups.entry(pidx).or_default();
103 group.0.push(index);
104 group.1.push(mode);
105 }
106 groups
107 }
108}
109
110/// The modes of every degree up to `lmax`, ordered by degree, then order, then pol 1
111/// before pol 0.
112///
113/// Upstream: the modes of `treams.SphericalWaveBasis.default(lmax)` at one position.
114pub fn modes(lmax: u32) -> Result<Vec<Mode>> {
115 if !(1..=MAX_DEGREE.unsigned_abs()).contains(&lmax) {
116 return Err(Error::InvalidInput(
117 // 128 is MAX_DEGREE.
118 "require 1 <= lmax <= 128".into(),
119 ));
120 }
121 let lmax = i32::try_from(lmax).map_err(|_| Error::InvalidInput("invalid lmax".into()))?;
122 Ok((1..=lmax)
123 .flat_map(|l| (-l..=l).flat_map(move |m| [Mode { l, m, pol: 1 }, Mode { l, m, pol: 0 }]))
124 .collect())
125}