treams_core/coeffs/
material.rs1#![allow(clippy::indexing_slicing)] use std::ops::AddAssign;
7
8use crate::{Complex, Error, Result, numerics::finite};
9
10#[derive(Clone, Copy, Debug)]
16pub struct Material {
17 pub epsilon: Complex,
19 pub mu: Complex,
21 pub kappa: Complex,
23}
24
25impl Default for Material {
26 fn default() -> Self {
27 Self {
28 epsilon: Complex::new(1.0, 0.0),
29 mu: Complex::new(1.0, 0.0),
30 kappa: Complex::default(),
31 }
32 }
33}
34
35impl Material {
36 pub(super) fn pushforward(self, direction: Self) -> MaterialTangent {
39 MaterialTangent {
40 index: (self.mu * direction.epsilon + self.epsilon * direction.mu)
41 / (2.0 * self.index()),
42 impedance: self.impedance()
43 * (direction.mu / self.mu - direction.epsilon / self.epsilon)
44 / 2.0,
45 kappa: direction.kappa,
46 }
47 }
48
49 #[must_use]
51 pub fn index(self) -> Complex {
52 crate::numerics::complex_sqrt(self.epsilon * self.mu)
53 }
54 #[must_use]
59 pub fn indices(self) -> [Complex; 2] {
60 let n = self.index();
61 [n - self.kappa, n + self.kappa].map(|v| if v.im < 0.0 { -v } else { v })
62 }
63 #[must_use]
67 pub fn impedance(self) -> Complex {
68 crate::numerics::complex_sqrt(self.mu / self.epsilon)
69 }
70 pub(super) fn tangents(self) -> [MaterialTangent; 3] {
73 let (n, impedance) = (self.index(), self.impedance());
74 let zero = Complex::default();
75 [
76 MaterialTangent {
77 index: self.mu / (2.0 * n),
78 impedance: -impedance / (2.0 * self.epsilon),
79 kappa: zero,
80 },
81 MaterialTangent {
82 index: self.epsilon / (2.0 * n),
83 impedance: impedance / (2.0 * self.mu),
84 kappa: zero,
85 },
86 MaterialTangent {
87 index: zero,
88 impedance: zero,
89 kappa: Complex::new(1.0, 0.0),
90 },
91 ]
92 }
93}
94
95#[inline]
103#[must_use]
104pub fn real_refractive_indices(epsilon: f64, mu: f64, kappa: f64) -> [f64; 2] {
105 let n = (epsilon * mu).sqrt();
106 [n - kappa, n + kappa]
107}
108
109#[derive(Clone, Debug)]
111pub struct LayerGradient {
112 pub radii: Vec<f64>,
114 pub epsilon: Vec<Complex>,
116 pub mu: Vec<Complex>,
118 pub kappa: Vec<Complex>,
120}
121
122impl LayerGradient {
123 pub(crate) fn zeros(boundaries: usize) -> Self {
125 let media = vec![Complex::default(); boundaries + 1];
126 Self {
127 radii: vec![0.0; boundaries],
128 epsilon: media.clone(),
129 mu: media.clone(),
130 kappa: media,
131 }
132 }
133
134 pub(crate) fn accumulate(&mut self, other: &Self) {
136 add_entries(&mut self.radii, &other.radii);
137 add_entries(&mut self.epsilon, &other.epsilon);
138 add_entries(&mut self.mu, &other.mu);
139 add_entries(&mut self.kappa, &other.kappa);
140 }
141}
142
143pub(super) fn add_entries<T: Copy + AddAssign>(sums: &mut [T], values: &[T]) {
145 for (sum, &value) in sums.iter_mut().zip(values) {
146 *sum += value;
147 }
148}
149
150#[derive(Clone, Copy, Debug)]
152pub(super) struct MaterialTangent {
153 pub(super) index: Complex,
154 pub(super) impedance: Complex,
155 pub(super) kappa: Complex,
156}
157
158pub(crate) fn validate_layers(sizes: &[f64], materials: &[Material]) -> Result<()> {
160 if sizes.is_empty() || materials.len() != sizes.len() + 1 {
161 return Err(Error::InvalidInput(
162 "require one more material than radii".into(),
163 ));
164 }
165 if sizes.iter().any(|x| !x.is_finite() || *x <= 0.0) || sizes.windows(2).any(|x| x[0] >= x[1]) {
166 return Err(Error::InvalidInput(
167 "radii and size parameters must be finite, positive and strictly increasing".into(),
168 ));
169 }
170 for material in materials {
171 if !finite(material.epsilon)
172 || !finite(material.mu)
173 || !finite(material.kappa)
174 || material.epsilon.norm_sqr() == 0.0
175 || material.mu.norm_sqr() == 0.0
176 {
177 return Err(Error::InvalidInput(
178 "require finite materials with nonzero epsilon and mu".into(),
179 ));
180 }
181 }
182 Ok(())
183}
184
185pub(crate) fn validate_layer_tangents(
188 boundaries: usize,
189 sizes: &[f64],
190 materials: &[Material],
191) -> Result<()> {
192 if sizes.len() != boundaries
193 || materials.len() != boundaries + 1
194 || sizes.iter().any(|x| !x.is_finite())
195 || materials
196 .iter()
197 .any(|m| !finite(m.epsilon) || !finite(m.mu) || !finite(m.kappa))
198 {
199 return Err(Error::InvalidInput(
200 "layer tangents must be finite and match the recorded inputs".into(),
201 ));
202 }
203 Ok(())
204}