1#![allow(clippy::indexing_slicing)] use crate::{Complex, Error, Result, numerics::finite};
13use nalgebra::DMatrix;
14
15pub trait ModeLabel: Copy + std::fmt::Debug {
17 fn validate(self) -> Result<()>;
19 fn pol(self) -> u8;
21}
22
23#[derive(Clone, Debug)]
28pub struct Basis<M> {
29 pub modes: Vec<(usize, M)>,
31 pub positions: Vec<[f64; 3]>,
33}
34
35impl<M: ModeLabel> Basis<M> {
36 pub(crate) fn validate(&self) -> Result<()> {
37 if self.modes.is_empty() || self.positions.iter().flatten().any(|v| !v.is_finite()) {
38 return Err(Error::InvalidInput(
39 "basis must be nonempty with finite positions".into(),
40 ));
41 }
42 for &(pidx, mode) in &self.modes {
43 mode.validate()?;
44 if pidx >= self.positions.len() {
45 return Err(Error::InvalidInput(
46 "basis position index outside positions".into(),
47 ));
48 }
49 }
50 Ok(())
51 }
52
53 pub(crate) fn position_pol(&self, i: usize) -> (usize, usize) {
55 let (pidx, mode) = self.modes[i];
56 (pidx, usize::from(mode.pol()))
57 }
58}
59
60pub(crate) fn validate_wavenumbers(
65 ks: [Complex; 2],
66 helicity: bool,
67 allow_zero: bool,
68) -> Result<()> {
69 if ks
70 .iter()
71 .any(|&k| !finite(k) || (!allow_zero && k == Complex::default()))
72 {
73 return Err(Error::InvalidInput(
74 if allow_zero {
75 "medium wavenumbers must be finite"
76 } else {
77 "medium wavenumbers must be finite and nonzero"
78 }
79 .into(),
80 ));
81 }
82 if !helicity && ks[0] != ks[1] {
83 return Err(Error::InvalidInput(
84 "parity polarization requires equal wavenumbers (an achiral medium)".into(),
85 ));
86 }
87 Ok(())
88}
89
90#[derive(Clone, Debug)]
93pub enum MultipoleBasis {
94 Spherical(crate::sw::Basis),
96 Cylindrical(crate::cw::Basis),
98}
99
100impl From<crate::sw::Basis> for MultipoleBasis {
101 fn from(value: crate::sw::Basis) -> Self {
102 Self::Spherical(value)
103 }
104}
105
106impl From<crate::cw::Basis> for MultipoleBasis {
107 fn from(value: crate::cw::Basis) -> Self {
108 Self::Cylindrical(value)
109 }
110}
111
112impl MultipoleBasis {
113 pub(crate) fn validate(&self) -> Result<()> {
114 match self {
115 Self::Spherical(b) => b.validate(),
116 Self::Cylindrical(b) => b.validate(),
117 }
118 }
119
120 pub(crate) fn positions(&self) -> &[[f64; 3]] {
121 match self {
122 Self::Spherical(b) => &b.positions,
123 Self::Cylindrical(b) => &b.positions,
124 }
125 }
126
127 pub(crate) fn len(&self) -> usize {
128 match self {
129 Self::Spherical(b) => b.modes.len(),
130 Self::Cylindrical(b) => b.modes.len(),
131 }
132 }
133
134 pub(crate) fn position_pol(&self, i: usize) -> (usize, usize) {
135 match self {
136 Self::Spherical(b) => b.position_pol(i),
137 Self::Cylindrical(b) => b.position_pol(i),
138 }
139 }
140}
141
142#[derive(Debug)]
144pub struct LatticeExpansionGradient {
145 pub expansion: ExpansionGradient,
147 pub kpar: Vec<f64>,
149 pub vectors: DMatrix<f64>,
151}
152
153impl LatticeExpansionGradient {
154 pub(crate) fn zeros(destination: usize, source: usize, dim: usize) -> Self {
157 Self {
158 expansion: ExpansionGradient::zeros(destination, source),
159 kpar: vec![0.0; dim],
160 vectors: DMatrix::zeros(dim, dim),
161 }
162 }
163
164 pub(crate) fn add_lattice(&mut self, g: &crate::lattice::SumGradient) {
166 for (j, value) in self.kpar.iter_mut().enumerate() {
167 *value += g.kpar[j];
168 for i in 0..self.vectors.nrows() {
169 self.vectors[(i, j)] += g.vectors[i][j];
170 }
171 }
172 }
173}
174
175#[derive(Clone, Debug)]
177pub struct ExpansionGradient {
178 pub destination: Vec<[f64; 3]>,
180 pub source: Vec<[f64; 3]>,
182 pub ks: [Complex; 2],
184}
185
186impl ExpansionGradient {
187 pub(crate) fn zeros(destination: usize, source: usize) -> Self {
189 Self {
190 destination: vec![[0.0; 3]; destination],
191 source: vec![[0.0; 3]; source],
192 ks: [Complex::default(); 2],
193 }
194 }
195
196 pub(crate) fn accumulate(&mut self, other: &Self) {
198 for (a, b) in self
199 .destination
200 .iter_mut()
201 .chain(&mut self.source)
202 .flatten()
203 .zip(other.destination.iter().chain(&other.source).flatten())
204 {
205 *a += b;
206 }
207 for (a, b) in self.ks.iter_mut().zip(other.ks) {
208 *a += b;
209 }
210 }
211}
212
213pub(crate) fn validate_expansion_tangent(
215 destination: &[[f64; 3]],
216 source: &[[f64; 3]],
217 ks: [Complex; 2],
218 counts: (usize, usize),
219) -> Result<()> {
220 if (destination.len(), source.len()) != counts
221 || destination
222 .iter()
223 .chain(source)
224 .flatten()
225 .any(|x| !x.is_finite())
226 || ks.into_iter().any(|x| !finite(x))
227 {
228 return Err(Error::InvalidInput(
229 "expansion tangents must be finite and match inputs".into(),
230 ));
231 }
232 Ok(())
233}
234
235pub(crate) fn pair_tangent(
237 destination: &[[f64; 3]],
238 source: &[[f64; 3]],
239 ks: [Complex; 2],
240 [p, q, pol]: [usize; 3],
241 position: [Complex; 3],
242 k: Complex,
243) -> Complex {
244 k * ks[pol]
245 + position
246 .into_iter()
247 .enumerate()
248 .map(|(axis, derivative)| derivative * (destination[p][axis] - source[q][axis]))
249 .sum::<Complex>()
250}
251
252pub(crate) fn add_pair_gradient(
256 result: &mut ExpansionGradient,
257 [p, q, pol]: [usize; 3],
258 cot: Complex,
259 position: [Complex; 3],
260 k: Complex,
261) {
262 result.ks[pol] += cot * k.conj();
263 for (axis, derivative) in position.into_iter().enumerate() {
264 let gradient = (cot.conj() * derivative).re;
265 result.destination[p][axis] += gradient;
266 result.source[q][axis] -= gradient;
267 }
268}