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}