treams_core/smatrix/tr/
saved.rs

1//! Numeric port fields and powers preserved across an array-only callback.
2//! treams-rs extension.
3
4use super::super::saved::{
5    invalid, product, read_complex, read_f64, read_matrix, read_stored, total, write_matrix,
6    write_stored,
7};
8use super::{PortWaves, TrForward, TrPorts, TrResidual};
9use crate::{
10    Complex, Result,
11    numerics::Jet,
12    saved::{Reader, SavedState, Writer},
13};
14use nalgebra::DMatrix;
15
16impl TrResidual {
17    /// Byte length for mode count, illumination columns and distinct port groups.
18    pub fn state_size(n: usize, columns: usize, groups: usize) -> Result<usize> {
19        if n == 0 || columns == 0 || groups == 0 {
20            return Err(invalid());
21        }
22        total(&[
23            140,
24            product(&[32, n, n])?,
25            product(&[48, n, columns])?,
26            product(&[528, groups])?,
27            product(&[9, n])?,
28            product(&[24, columns])?,
29        ])
30    }
31}
32
33impl SavedState for TrResidual {
34    fn save_state(&self) -> Result<Vec<u8>> {
35        let (n, columns, groups) = self.input_shape();
36        let mut writer = Writer::new(Self::state_size(n, columns, groups)?);
37        for dimension in [n, columns, groups, self.ports.axis, self.ports.direction] {
38            writer.usize(dimension);
39        }
40        writer.byte(u8::from(self.ports.helicity));
41        writer.byte(u8::from(self.fixed_q));
42        for matrix in &self.matrices {
43            write_stored(&mut writer, matrix);
44        }
45        write_matrix(&mut writer, &self.incident);
46        for &value in self.ports.ks.iter().flatten().chain(&self.ports.zs) {
47            writer.complex(value);
48        }
49        for &value in self.ports.q.iter().flatten() {
50            writer.f64(value);
51        }
52        for &(group, pol) in &self.ports.modes {
53            writer.usize(group);
54            writer.byte(pol);
55        }
56        for &value in self.forward.value.iter().chain(&self.forward.flux) {
57            writer.f64(value);
58        }
59        for matrix in &self.forward.outgoing {
60            write_matrix(&mut writer, matrix);
61        }
62        for value in self
63            .forward
64            .waves
65            .iter()
66            .flatten()
67            .flatten()
68            .flatten()
69            .flatten()
70        {
71            writer.complex(value.value);
72        }
73        Ok(writer.finish())
74    }
75
76    fn from_state(bytes: &[u8]) -> Result<Self> {
77        let mut reader = Reader::new(bytes);
78        let n = reader.usize()?;
79        let columns = reader.usize()?;
80        let groups = reader.usize()?;
81        let axis = reader.usize()?;
82        let direction = reader.usize()?;
83        let helicity = reader.byte()?;
84        let fixed_q = reader.byte()?;
85        if axis > 2
86            || direction > 1
87            || helicity > 1
88            || fixed_q > 1
89            || bytes.len() != Self::state_size(n, columns, groups)?
90        {
91            return Err(invalid());
92        }
93        let matrices = [read_stored(&mut reader, n)?, read_stored(&mut reader, n)?];
94        let incident = read_matrix(&mut reader, n, columns)?;
95        let mut ks = [[Complex::default(); 2]; 2];
96        let mut zs = [Complex::default(); 2];
97        for value in ks.iter_mut().flatten().chain(&mut zs) {
98            *value = read_complex(&mut reader)?;
99        }
100        let q = (0..groups)
101            .map(|_| Ok([read_f64(&mut reader)?, read_f64(&mut reader)?]))
102            .collect::<Result<Vec<_>>>()?;
103        let modes = (0..n)
104            .map(|_| Ok((reader.usize()?, reader.byte()?)))
105            .collect::<Result<Vec<_>>>()?;
106        let ports = TrPorts {
107            ks,
108            zs,
109            q,
110            modes,
111            axis,
112            direction,
113            helicity: helicity != 0,
114        };
115        ports.validate()?;
116        let mut value = DMatrix::zeros(2, columns);
117        for v in value.iter_mut() {
118            *v = read_f64(&mut reader)?;
119        }
120        let flux = (0..columns)
121            .map(|_| {
122                let flux = read_f64(&mut reader)?;
123                if flux > 0.0 { Ok(flux) } else { Err(invalid()) }
124            })
125            .collect::<Result<Vec<_>>>()?;
126        let outgoing = [
127            read_matrix(&mut reader, n, columns)?,
128            read_matrix(&mut reader, n, columns)?,
129        ];
130        let mut waves: Vec<PortWaves<0>> = vec![Default::default(); groups];
131        for value in waves.iter_mut().flatten().flatten().flatten().flatten() {
132            *value = Jet::constant(read_complex(&mut reader)?);
133        }
134        reader.finish()?;
135        Ok(Self {
136            matrices,
137            incident,
138            ports,
139            forward: TrForward {
140                value,
141                flux,
142                outgoing,
143                waves,
144            },
145            fixed_q: fixed_q != 0,
146        })
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153    use crate::{
154        linalg::view,
155        smatrix::{self, Blocks},
156    };
157
158    #[test]
159    fn tr_roundtrip_preserves_saved_fields_derivatives_and_matrix_layout() {
160        let n = 2;
161        let matrix = DMatrix::from_row_slice(
162            n,
163            n,
164            &[
165                Complex::new(0.8, 0.1),
166                Complex::new(0.1, 0.03),
167                Complex::new(-0.03, 0.07),
168                Complex::new(0.7, 0.12),
169            ],
170        );
171        let reflected = &matrix * Complex::from(0.1);
172        let incident = DMatrix::from_element(n, 3, Complex::new(0.3, -0.1));
173        let ports = TrPorts {
174            ks: [[Complex::new(1.3, 0.03); 2], [Complex::new(1.2, 0.04); 2]],
175            zs: [Complex::new(0.9, 0.01), Complex::new(1.1, -0.01)],
176            q: vec![[0.2, -0.1]],
177            modes: vec![(0, 0), (0, 1)],
178            axis: 2,
179            helicity: true,
180            direction: 0,
181        };
182        // The transposed view exercises preservation of row-major saved storage.
183        let original = smatrix::tr(
184            [view(&matrix).transpose(), view(&reflected)],
185            incident.clone(),
186            ports,
187            false,
188        )
189        .unwrap();
190        let mut bytes = original.save_state().unwrap();
191        assert_eq!(bytes.len(), TrResidual::state_size(n, 3, 1).unwrap());
192        assert!(TrResidual::from_state(&bytes[..bytes.len() - 1]).is_err());
193        let mut extra = bytes.clone();
194        extra.push(0);
195        assert!(TrResidual::from_state(&extra).is_err());
196        let restored = TrResidual::from_state(&bytes).unwrap();
197        assert!(restored.matrices[0].row_major);
198        assert!(!restored.matrices[1].row_major);
199        assert_eq!(original.value(), restored.value());
200        let matrices: Blocks = std::array::from_fn(|_| &matrix * Complex::from(0.03));
201        let ks = [[Complex::new(0.1, -0.02); 2]; 2];
202        let zs = [Complex::new(0.03, -0.01); 2];
203        let q = [[0.02, -0.01]];
204        assert_eq!(
205            original
206                .pushforward(&matrices, &incident, ks, zs, &q)
207                .unwrap(),
208            restored
209                .pushforward(&matrices, &incident, ks, zs, &q)
210                .unwrap()
211        );
212        let cotangent = DMatrix::from_element(2, 3, 0.4);
213        let (a, b) = (
214            original.pullback(&cotangent).unwrap(),
215            restored.pullback(&cotangent).unwrap(),
216        );
217        assert_eq!(a.matrices, b.matrices);
218        assert_eq!(a.incident, b.incident);
219        assert_eq!(a.ks, b.ks);
220        assert_eq!(a.zs, b.zs);
221        assert_eq!(a.q, b.q);
222        bytes[..8].copy_from_slice(&u64::MAX.to_le_bytes());
223        assert!(TrResidual::from_state(&bytes).is_err());
224    }
225}