treams_core/cluster/iterative/
saved.rs1use std::sync::Arc;
8
9use nalgebra::DMatrix;
10
11use super::{IterativeResidual, IterativeSolution, IterativeSphereCluster};
12use crate::{
13 Complex, MAX_DEGREE, Result,
14 coeffs::MieResidual,
15 linalg::{Convergence, GmresOptions},
16 saved::{Reader, SavedState, Writer, invalid},
17 sw::{self, TranslationPlan},
18};
19
20impl IterativeResidual {
21 pub fn state_size(lmax: u32, particles: usize, columns: usize) -> Result<usize> {
25 if !(1..=MAX_DEGREE.unsigned_abs()).contains(&lmax) || particles == 0 || columns == 0 {
26 return Err(invalid());
27 }
28 let lmax = usize::try_from(lmax).map_err(|_| invalid())?;
29 let mie = lmax
30 .checked_mul(MieResidual::state_size(1)?)
31 .ok_or_else(invalid)?;
32 let rows = lmax
33 .checked_add(2)
34 .and_then(|n| n.checked_mul(lmax))
35 .and_then(|n| n.checked_mul(2))
36 .and_then(|n| n.checked_mul(particles))
37 .ok_or_else(invalid)?;
38 let coefficients = rows
39 .checked_mul(columns)
40 .and_then(|n| n.checked_mul(32))
41 .ok_or_else(invalid)?;
42 particles
45 .checked_mul(mie.checked_add(32).ok_or_else(invalid)?)
46 .and_then(|n| n.checked_add(coefficients))
47 .and_then(|n| columns.checked_mul(24).and_then(|c| n.checked_add(c)))
48 .and_then(|n| n.checked_add(64))
49 .ok_or_else(invalid)
50 }
51}
52
53impl SavedState for IterativeResidual {
54 fn save_state(&self) -> Result<Vec<u8>> {
55 let operator = &self.operator;
56 let lmax = operator.degrees.first().map_or(0, Vec::len);
57 let particles = operator.radii.len();
58 let columns = self.incident.ncols();
59 let size = Self::state_size(
60 u32::try_from(lmax).map_err(|_| invalid())?,
61 particles,
62 columns,
63 )?;
64 let mut writer = Writer::new(size);
65 writer.usize(lmax);
66 writer.usize(particles);
67 writer.usize(columns);
68 writer.f64(operator.k0);
69 writer.f64(self.options.rtol);
70 writer.f64(self.options.atol);
71 writer.usize(self.options.restart);
72 writer.usize(self.options.max_iterations);
73 for &radius in &operator.radii {
74 writer.f64(radius);
75 }
76 for &position in operator.positions.iter().flatten() {
77 writer.f64(position);
78 }
79 for residual in operator.degrees.iter().flatten() {
80 residual.write_state(&mut writer);
81 }
82 for &value in self.incident.iter().chain(self.solution.value.iter()) {
83 writer.complex(value);
84 }
85 for report in &self.solution.convergence {
86 writer.usize(report.iterations);
87 writer.f64(report.residual_norm);
88 writer.f64(report.rhs_norm);
89 }
90 Ok(writer.finish())
91 }
92
93 fn from_state(bytes: &[u8]) -> Result<Self> {
94 let mut reader = Reader::new(bytes);
95 let lmax = u32::try_from(reader.usize()?).map_err(|_| invalid())?;
96 let particles = reader.usize()?;
97 let columns = reader.usize()?;
98 if bytes.len() != Self::state_size(lmax, particles, columns)? {
99 return Err(invalid());
100 }
101 let k0 = reader.f64()?;
102 let options = GmresOptions {
103 rtol: reader.f64()?,
104 atol: reader.f64()?,
105 restart: reader.usize()?,
106 max_iterations: reader.usize()?,
107 };
108 options.validate()?;
109 let radii = (0..particles)
110 .map(|_| reader.f64())
111 .collect::<Result<Vec<_>>>()?;
112 let positions = (0..particles)
113 .map(|_| Ok([reader.f64()?, reader.f64()?, reader.f64()?]))
114 .collect::<Result<Vec<_>>>()?;
115 let mie_size = MieResidual::state_size(1)?;
116 let degrees = (0..particles)
117 .map(|_| {
118 (0..lmax)
119 .map(|_| MieResidual::from_state(reader.raw(mie_size)?))
120 .collect::<Result<Vec<_>>>()
121 })
122 .collect::<Result<Vec<_>>>()?;
123 let modes = sw::modes(lmax)?;
124 let dimension = modes.len() * particles;
126 let incident = read_matrix(&mut reader, dimension, columns)?;
127 let value = read_matrix(&mut reader, dimension, columns)?;
128 let convergence = (0..columns)
129 .map(|_| {
130 Ok(Convergence {
131 iterations: reader.usize()?,
132 residual_norm: reader.f64()?,
133 rhs_norm: reader.f64()?,
134 })
135 })
136 .collect::<Result<Vec<_>>>()?;
137 reader.finish()?;
138 let count = usize::try_from(lmax).map_err(|_| invalid())?;
139 Ok(Self {
140 operator: Arc::new(IterativeSphereCluster {
141 k0,
142 radii,
143 positions,
144 degrees,
145 mode_degrees: crate::tmatrix::block_degrees(count).collect(),
146 plan: TranslationPlan::between(&modes, &modes, true)?,
147 dimension,
148 }),
149 incident,
150 options,
151 solution: IterativeSolution { value, convergence },
152 })
153 }
154}
155
156fn read_matrix(reader: &mut Reader<'_>, rows: usize, columns: usize) -> Result<DMatrix<Complex>> {
157 let data = (0..rows * columns)
158 .map(|_| reader.complex())
159 .collect::<Result<Vec<_>>>()?;
160 Ok(DMatrix::from_vec(rows, columns, data))
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166 use crate::test_support::patterned;
167
168 fn residual() -> IterativeResidual {
169 Arc::new(
170 IterativeSphereCluster::new(
171 2,
172 1.3,
173 &[0.2, 0.24],
174 &[Complex::new(2.3, 0.04), Complex::new(3.1, 0.02)],
175 &[[0.1, 0.0, -0.1], [1.2, 0.2, 0.4]],
176 )
177 .unwrap(),
178 )
179 .record(
180 patterned(32, 3, 0.3),
181 GmresOptions {
182 rtol: 2e-12,
183 ..GmresOptions::default()
184 },
185 )
186 .unwrap()
187 }
188
189 #[test]
190 fn saved_state_preserves_solution_certificates_and_reusable_derivatives() {
191 let residual = residual();
192 let state = residual.save_state().unwrap();
193 assert_eq!(state.len(), IterativeResidual::state_size(2, 2, 3).unwrap());
194 let restored = IterativeResidual::from_state(&state).unwrap();
195 assert_eq!(restored.solution.value, residual.solution.value);
196 for (actual, expected) in restored
197 .solution
198 .convergence
199 .iter()
200 .zip(&residual.solution.convergence)
201 {
202 assert_eq!(actual.iterations, expected.iterations);
203 assert_eq!(
204 actual.residual_norm.to_bits(),
205 expected.residual_norm.to_bits()
206 );
207 assert_eq!(actual.rhs_norm.to_bits(), expected.rhs_norm.to_bits());
208 }
209 let dr = [0.03, -0.02];
210 let de = [Complex::new(0.2, -0.03), Complex::new(-0.1, 0.04)];
211 let dp = [[0.03, 0.02, -0.04], [-0.02, 0.01, 0.03]];
212 let db = patterned(32, 3, -0.2);
213 let tangent = residual.pushforward(0.07, &dr, &de, &dp, &db).unwrap();
214 assert_eq!(
215 restored
216 .pushforward(0.07, &dr, &de, &dp, &db)
217 .unwrap()
218 .value,
219 tangent.value
220 );
221 let cotangent = patterned(32, 3, 0.7);
222 let actual = restored.pullback(&cotangent).unwrap();
223 let expected = residual.pullback(&cotangent).unwrap();
224 assert_eq!(actual.cluster.k0.to_bits(), expected.cluster.k0.to_bits());
225 assert_eq!(actual.cluster.radii, expected.cluster.radii);
226 assert_eq!(actual.cluster.epsilon, expected.cluster.epsilon);
227 assert_eq!(actual.cluster.positions, expected.cluster.positions);
228 assert_eq!(actual.incident, expected.incident);
229 assert_eq!(
230 restored
231 .pushforward(0.07, &dr, &de, &dp, &db)
232 .unwrap()
233 .value,
234 tangent.value
235 );
236 assert_eq!(restored.save_state().unwrap(), state);
237 }
238
239 #[test]
240 fn saved_state_checks_exact_dimensions_before_allocating() {
241 let state = residual().save_state().unwrap();
242 for end in [0, 8, 16, state.len() - 1] {
243 assert!(IterativeResidual::from_state(&state[..end]).is_err());
244 }
245 let mut extra = state.clone();
246 extra.push(0);
247 assert!(IterativeResidual::from_state(&extra).is_err());
248 for index in 0..3 {
249 let mut malformed = state.clone();
250 malformed[index * 8..(index + 1) * 8].copy_from_slice(&u64::MAX.to_le_bytes());
251 assert!(IterativeResidual::from_state(&malformed).is_err());
252 }
253 let mut nested = state;
254 nested[128..136].copy_from_slice(&2_u64.to_le_bytes());
256 assert!(IterativeResidual::from_state(&nested).is_err());
257 assert!(IterativeResidual::state_size(2, usize::MAX, 1).is_err());
258 assert!(IterativeResidual::state_size(2, 1, 0).is_err());
259 }
260}