treams_core/cluster/
interaction_state.rs

1//! Array-owned interaction factors and fields, a treams-rs extension.
2
3use std::sync::Arc;
4
5use nalgebra::DMatrix;
6
7use super::{IlluminateResidual, InteractionFactor, InteractionResidual, LocalMatrix};
8use crate::{
9    Complex, Error, Result,
10    linalg::Lu,
11    numerics,
12    saved::{Reader, SavedState, Writer, write_matrix},
13};
14
15fn invalid() -> Error {
16    Error::InvalidInput("invalid cluster derivative state".into())
17}
18
19fn add(a: usize, b: usize) -> Result<usize> {
20    a.checked_add(b).ok_or_else(invalid)
21}
22
23fn mul(a: usize, b: usize) -> Result<usize> {
24    a.checked_mul(b).ok_or_else(invalid)
25}
26
27fn matrix_size(rows: usize, columns: usize) -> Result<usize> {
28    mul(16, mul(rows, columns)?)
29}
30
31fn dimensions(sizes: &[usize]) -> Result<(usize, usize)> {
32    if sizes.is_empty() || sizes.contains(&0) {
33        return Err(invalid());
34    }
35    sizes
36        .iter()
37        .try_fold((0, 0), |(dimension, entries), &size| {
38            Ok((add(dimension, size)?, add(entries, mul(size, size)?)?))
39        })
40}
41
42fn factor_size(blocks: usize, dimension: usize, entries: usize) -> Result<usize> {
43    let header = add(8, mul(8, blocks)?)?;
44    let matrices = add(mul(16, entries)?, matrix_size(dimension, dimension)?)?;
45    add(add(header, matrices)?, Lu::state_size(dimension)?)
46}
47
48fn read_matrix(reader: &mut Reader<'_>, rows: usize, columns: usize) -> Result<DMatrix<Complex>> {
49    let mut matrix = numerics::zeros(rows, columns)?;
50    for value in matrix.as_mut_slice() {
51        *value = reader.complex()?;
52    }
53    Ok(matrix)
54}
55
56/// Read only the dimension header. The count is bounded before allocating the
57/// short block-size vector; no matrix or factor allocation precedes the size check.
58fn read_sizes(reader: &mut Reader<'_>) -> Result<Vec<usize>> {
59    let count = reader.usize()?;
60    if count == 0 || count > reader.remaining_len() / 8 {
61        return Err(invalid());
62    }
63    (0..count).map(|_| reader.usize()).collect()
64}
65
66impl InteractionFactor {
67    fn write_state(&self, writer: &mut Writer) {
68        writer.usize(self.local.blocks.len());
69        for block in &self.local.blocks {
70            writer.usize(block.nrows());
71        }
72        for block in &self.local.blocks {
73            write_matrix(writer, block);
74        }
75        write_matrix(writer, &self.coupling);
76        self.lu.write_state(writer);
77    }
78
79    fn read_body(reader: &mut Reader<'_>, sizes: &[usize], dimension: usize) -> Result<Self> {
80        let blocks = sizes
81            .iter()
82            .map(|&size| read_matrix(reader, size, size))
83            .collect::<Result<Vec<_>>>()?;
84        let coupling = read_matrix(reader, dimension, dimension)?;
85        let lu = Lu::read_state(reader, dimension)?;
86        Ok(Self {
87            local: LocalMatrix { blocks },
88            coupling,
89            lu,
90        })
91    }
92}
93
94impl InteractionResidual {
95    /// Bytes needed to save local blocks, coupling, LU and interacting T-matrix.
96    pub fn state_size(local_sizes: &[usize]) -> Result<usize> {
97        let (dimension, entries) = dimensions(local_sizes)?;
98        add(
99            factor_size(local_sizes.len(), dimension, entries)?,
100            matrix_size(dimension, dimension)?,
101        )
102    }
103
104    /// State size for equally sized particle blocks, without a temporary size list.
105    pub(crate) fn state_size_uniform(block_size: usize, particles: usize) -> Result<usize> {
106        if block_size == 0 || particles == 0 {
107            return Err(invalid());
108        }
109        let dimension = mul(block_size, particles)?;
110        let entries = mul(mul(block_size, block_size)?, particles)?;
111        add(
112            factor_size(particles, dimension, entries)?,
113            matrix_size(dimension, dimension)?,
114        )
115    }
116
117    pub(crate) fn write_state(&self, writer: &mut Writer) {
118        self.factor.write_state(writer);
119        write_matrix(writer, &self.value);
120    }
121
122    pub(crate) fn read_state(reader: &mut Reader<'_>) -> Result<Self> {
123        let sizes = read_sizes(reader)?;
124        let header = add(8, mul(8, sizes.len())?)?;
125        let expected = Self::state_size(&sizes)?;
126        if expected - header > reader.remaining_len() {
127            return Err(invalid());
128        }
129        let (dimension, _) = dimensions(&sizes)?;
130        let factor = InteractionFactor::read_body(reader, &sizes, dimension)?;
131        let value = read_matrix(reader, dimension, dimension)?;
132        Ok(Self { factor, value })
133    }
134}
135
136impl SavedState for InteractionResidual {
137    fn save_state(&self) -> Result<Vec<u8>> {
138        let sizes: Vec<_> = self.local_shapes().map(|(rows, _)| rows).collect();
139        let mut writer = Writer::new(Self::state_size(&sizes)?);
140        self.write_state(&mut writer);
141        Ok(writer.finish())
142    }
143
144    fn from_state(bytes: &[u8]) -> Result<Self> {
145        let mut reader = Reader::new(bytes);
146        let residual = Self::read_state(&mut reader)?;
147        reader.finish()?;
148        Ok(residual)
149    }
150}
151
152impl IlluminateResidual {
153    /// Bytes needed to save local blocks, coupling, LU, response and scattered fields.
154    pub fn state_size(local_sizes: &[usize], columns: usize) -> Result<usize> {
155        if columns == 0 {
156            return Err(invalid());
157        }
158        let (dimension, entries) = dimensions(local_sizes)?;
159        add(
160            add(8, factor_size(local_sizes.len(), dimension, entries)?)?,
161            mul(2, matrix_size(dimension, columns)?)?,
162        )
163    }
164}
165
166impl SavedState for IlluminateResidual {
167    fn save_state(&self) -> Result<Vec<u8>> {
168        let sizes: Vec<_> = self.local_shapes().map(|(rows, _)| rows).collect();
169        let mut writer = Writer::new(Self::state_size(&sizes, self.value.ncols())?);
170        writer.usize(self.value.ncols());
171        self.factor.write_state(&mut writer);
172        write_matrix(&mut writer, &self.response);
173        write_matrix(&mut writer, &self.value);
174        Ok(writer.finish())
175    }
176
177    fn from_state(bytes: &[u8]) -> Result<Self> {
178        let mut reader = Reader::new(bytes);
179        let columns = reader.usize()?;
180        let sizes = read_sizes(&mut reader)?;
181        if Self::state_size(&sizes, columns)? != bytes.len() {
182            return Err(invalid());
183        }
184        let (dimension, _) = dimensions(&sizes)?;
185        let factor = Arc::new(InteractionFactor::read_body(
186            &mut reader,
187            &sizes,
188            dimension,
189        )?);
190        let response = read_matrix(&mut reader, dimension, columns)?;
191        let value = read_matrix(&mut reader, dimension, columns)?;
192        reader.finish()?;
193        Ok(Self {
194            factor,
195            response,
196            value,
197        })
198    }
199}
200
201#[cfg(test)]
202mod tests {
203    use super::*;
204    use crate::{cluster::interaction_blocks, test_support::patterned};
205
206    #[test]
207    fn saved_interactions_retain_blocks_factors_and_both_derivatives() {
208        let blocks = vec![patterned(1, 1, 0.2), patterned(2, 2, 0.4)];
209        let coupling = patterned(3, 3, 0.6) * Complex::from(0.02);
210        let direction = patterned(3, 3, 0.8);
211        let residual = interaction_blocks(blocks.clone(), coupling.clone()).unwrap();
212        let bytes = residual.save_state().unwrap();
213        assert_eq!(
214            bytes.len(),
215            InteractionResidual::state_size(&[1, 2]).unwrap()
216        );
217        let restored = InteractionResidual::from_state(&bytes).unwrap();
218        assert_eq!(restored.save_state().unwrap(), bytes);
219        assert_eq!(restored.value(), residual.value());
220        assert_eq!(
221            restored.pushforward_blocks(&blocks, &direction).unwrap(),
222            residual.pushforward_blocks(&blocks, &direction).unwrap()
223        );
224        let actual = restored.pullback_blocks(&direction).unwrap();
225        let expected = residual.pullback_blocks(&direction).unwrap();
226        assert_eq!(actual.local, expected.local);
227        assert_eq!(actual.coupling, expected.coupling);
228        assert!(InteractionResidual::from_state(&bytes[..bytes.len() - 1]).is_err());
229        let mut malformed = bytes;
230        malformed.extend_from_slice(&[0]);
231        assert!(InteractionResidual::from_state(&malformed).is_err());
232        malformed[..8].fill(255);
233        assert!(InteractionResidual::from_state(&malformed).is_err());
234
235        let factor = Arc::new(InteractionFactor::from_blocks(blocks.clone(), coupling).unwrap());
236        let incident = patterned(3, 2, 1.0);
237        let residual = factor.record(incident.clone()).unwrap();
238        let bytes = residual.save_state().unwrap();
239        assert_eq!(
240            bytes.len(),
241            IlluminateResidual::state_size(&[1, 2], 2).unwrap()
242        );
243        let restored = IlluminateResidual::from_state(&bytes).unwrap();
244        assert_eq!(restored.save_state().unwrap(), bytes);
245        assert_eq!(restored.value(), residual.value());
246        assert_eq!(
247            restored
248                .pushforward(&blocks, &direction, &incident)
249                .unwrap(),
250            residual
251                .pushforward(&blocks, &direction, &incident)
252                .unwrap()
253        );
254        let actual = restored.pullback(&incident).unwrap();
255        let expected = residual.pullback(&incident).unwrap();
256        assert_eq!(actual.local, expected.local);
257        assert_eq!(actual.coupling, expected.coupling);
258        assert_eq!(actual.incident, expected.incident);
259        assert!(IlluminateResidual::from_state(&bytes[..bytes.len() - 1]).is_err());
260        let mut malformed = bytes;
261        malformed[..8].fill(255);
262        assert!(IlluminateResidual::from_state(&malformed).is_err());
263    }
264}