treams_core/coeffs/mie/
saved.rs

1//! Exact-size numerical state for the Mie derivative callbacks.
2//!
3//! treams-rs extension.
4
5use super::{Interface, Material, MieResidual, Riccati};
6use crate::{
7    Complex, Error, Result,
8    saved::{Reader, SavedState, Writer},
9};
10
11fn invalid() -> Error {
12    Error::InvalidInput("invalid Mie derivative state".into())
13}
14
15impl MieResidual {
16    /// Exact byte count of the saved Mie state for `boundaries` layers.
17    /// The layout contains one count, the sizes, three material parameters, 62
18    /// complex entries per interface, and two coefficient matrices.
19    pub fn state_size(boundaries: usize) -> Result<usize> {
20        if boundaries == 0 {
21            return Err(invalid());
22        }
23        boundaries
24            .checked_mul(1048)
25            .and_then(|size| size.checked_add(184))
26            .ok_or_else(invalid)
27    }
28
29    pub(crate) fn write_state(&self, writer: &mut Writer) {
30        writer.usize(self.sizes.len());
31        for &size in &self.sizes {
32            writer.f64(size);
33        }
34        for material in &self.materials {
35            writer.complex(material.epsilon);
36            writer.complex(material.mu);
37            writer.complex(material.kappa);
38        }
39        for interface in &self.interfaces {
40            for &value in interface.x.iter().flatten().chain(&interface.z) {
41                writer.complex(value);
42            }
43            for radial in interface.radials.iter().flatten() {
44                for &(value, derivative) in radial.waves.iter().chain(&radial.slopes) {
45                    writer.complex(value);
46                    writer.complex(derivative);
47                }
48            }
49            for &value in interface.matrix.iter().chain(interface.before.iter()) {
50                writer.complex(value);
51            }
52        }
53        for &value in self.inverse.iter().chain(self.value.iter()) {
54            writer.complex(value);
55        }
56    }
57
58    pub(crate) fn read_state(reader: &mut Reader<'_>) -> Result<Self> {
59        let boundaries = reader.usize()?;
60        let expected = Self::state_size(boundaries)?;
61        if reader.remaining_len() < expected - 8 {
62            return Err(invalid());
63        }
64        let sizes = (0..boundaries)
65            .map(|_| reader.f64())
66            .collect::<Result<Vec<_>>>()?;
67        let materials = (0..=boundaries)
68            .map(|_| {
69                Ok(Material {
70                    epsilon: reader.complex()?,
71                    mu: reader.complex()?,
72                    kappa: reader.complex()?,
73                })
74            })
75            .collect::<Result<Vec<_>>>()?;
76        let mut interfaces = Vec::with_capacity(boundaries);
77        for _ in 0..boundaries {
78            let mut x = [[Complex::default(); 2]; 2];
79            let mut z = [Complex::default(); 2];
80            for value in x.iter_mut().flatten().chain(&mut z) {
81                *value = reader.complex()?;
82            }
83            let mut radials = [[Riccati {
84                waves: [(Complex::default(), Complex::default()); 2],
85                slopes: [(Complex::default(), Complex::default()); 2],
86            }; 2]; 2];
87            for radial in radials.iter_mut().flatten() {
88                for (value, derivative) in radial.waves.iter_mut().chain(&mut radial.slopes) {
89                    *value = reader.complex()?;
90                    *derivative = reader.complex()?;
91                }
92            }
93            interfaces.push(Interface {
94                x,
95                z,
96                radials,
97                matrix: read_matrix(reader)?,
98                before: read_matrix(reader)?,
99            });
100        }
101        Ok(Self {
102            sizes,
103            materials,
104            interfaces,
105            inverse: read_matrix(reader)?,
106            value: read_matrix(reader)?,
107        })
108    }
109}
110
111fn read_matrix<const R: usize, const C: usize>(
112    reader: &mut Reader<'_>,
113) -> Result<nalgebra::SMatrix<Complex, R, C>> {
114    let mut matrix = nalgebra::SMatrix::<Complex, R, C>::zeros();
115    for value in matrix.iter_mut() {
116        *value = reader.complex()?;
117    }
118    Ok(matrix)
119}
120
121impl SavedState for MieResidual {
122    fn save_state(&self) -> Result<Vec<u8>> {
123        let mut writer = Writer::new(Self::state_size(self.sizes.len())?);
124        self.write_state(&mut writer);
125        Ok(writer.finish())
126    }
127
128    fn from_state(bytes: &[u8]) -> Result<Self> {
129        let mut reader = Reader::new(bytes);
130        let result = Self::read_state(&mut reader)?;
131        reader.finish()?;
132        Ok(result)
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139    use crate::coeffs::{Matrix2, mie};
140
141    #[test]
142    fn state_roundtrip_preserves_value_and_both_derivatives() {
143        let materials = [
144            Material {
145                epsilon: Complex::new(2.7, 0.2),
146                mu: Complex::new(1.1, 0.03),
147                kappa: Complex::new(0.12, 0.02),
148            },
149            Material {
150                epsilon: Complex::new(1.8, 0.1),
151                ..Material::default()
152            },
153            Material::default(),
154        ];
155        let residual = mie(3, &[0.4, 0.7], &materials).unwrap();
156        let state = residual.save_state().unwrap();
157        assert_eq!(state.len(), MieResidual::state_size(2).unwrap());
158        let restored = MieResidual::from_state(&state).unwrap();
159        assert_eq!(restored.value(), residual.value());
160        let directions = [Material {
161            epsilon: Complex::new(0.1, -0.02),
162            mu: Complex::new(-0.03, 0.02),
163            kappa: Complex::new(0.02, 0.01),
164        }; 3];
165        assert_eq!(
166            restored.pushforward(&[0.1, -0.2], &directions).unwrap(),
167            residual.pushforward(&[0.1, -0.2], &directions).unwrap()
168        );
169        let cotangent = Matrix2::repeat(Complex::new(0.2, -0.3));
170        let actual = restored.pullback(&cotangent).unwrap();
171        let expected = residual.pullback(&cotangent).unwrap();
172        assert_eq!(actual.sizes, expected.sizes);
173        assert_eq!(actual.epsilon, expected.epsilon);
174        assert_eq!(actual.mu, expected.mu);
175        assert_eq!(actual.kappa, expected.kappa);
176        assert_eq!(restored.save_state().unwrap(), state);
177    }
178
179    #[test]
180    fn state_rejects_inconsistent_dimensions_before_allocation() {
181        let residual = mie(1, &[0.5], &[Material::default(); 2]).unwrap();
182        let mut state = residual.save_state().unwrap();
183        assert!(MieResidual::from_state(&state[..state.len() - 1]).is_err());
184        state[..8].copy_from_slice(&u64::MAX.to_le_bytes());
185        assert!(MieResidual::from_state(&state).is_err());
186        assert!(MieResidual::state_size(usize::MAX).is_err());
187        assert!(MieResidual::state_size(0).is_err());
188    }
189}