1use std::sync::OnceLock;
6
7use faer::{Mat, MatMut, MatRef, perm::Perm};
8
9use super::{EigResidual, Equilibration, Lu, SolveResidual, SvdvalsResidual, faer_zeros, view};
10use crate::{
11 Complex, Error, Result, numerics,
12 saved::{Reader, SavedState, Writer, invalid, read_complex, read_matrix, total},
13};
14
15fn matrix_size(rows: usize, columns: usize) -> Result<usize> {
16 if rows == 0 || columns == 0 {
17 return Err(invalid());
18 }
19 rows.checked_mul(columns)
20 .and_then(|count| count.checked_mul(16))
21 .ok_or_else(invalid)
22}
23
24impl Lu {
25 pub(crate) fn state_size(n: usize) -> Result<usize> {
27 total(&[matrix_size(n, n)?, n.checked_mul(24).ok_or_else(invalid)?])
28 }
29
30 pub(crate) fn write_state(&self, writer: &mut Writer) {
32 write_lu(writer, self);
33 }
34
35 pub(crate) fn read_state(reader: &mut Reader<'_>, n: usize) -> Result<Self> {
37 read_lu(reader, n)
38 }
39}
40
41fn write_matrix(writer: &mut Writer, matrix: MatRef<'_, Complex>) {
42 for j in 0..matrix.ncols() {
43 for i in 0..matrix.nrows() {
44 writer.complex(matrix[(i, j)]);
45 }
46 }
47}
48
49fn read_values(reader: &mut Reader<'_>, mut matrix: MatMut<'_, Complex>) -> Result<()> {
50 for j in 0..matrix.ncols() {
51 for i in 0..matrix.nrows() {
52 matrix[(i, j)] = read_complex(reader)?;
53 }
54 }
55 Ok(())
56}
57
58fn read_faer(reader: &mut Reader<'_>, rows: usize, columns: usize) -> Result<Mat<Complex>> {
59 let mut matrix = faer_zeros(rows, columns)?;
60 read_values(reader, matrix.as_mut())?;
61 Ok(matrix)
62}
63
64fn write_lu(writer: &mut Writer, lu: &Lu) {
67 write_matrix(writer, view(&lu.factors));
68 for &index in lu.permutation.arrays().0 {
69 writer.usize(index);
70 }
71 let n = lu.factors.nrows();
72 for column in [false, true] {
73 for i in 0..n {
74 writer.f64(lu.equilibration.as_ref().map_or(1.0, |scales| {
75 if column {
76 scales.column[i]
77 } else {
78 scales.row[i]
79 }
80 }));
81 }
82 }
83}
84
85fn read_lu(reader: &mut Reader<'_>, n: usize) -> Result<Lu> {
86 let factors = read_matrix(reader, n, n)?;
87 if factors
88 .diagonal()
89 .iter()
90 .any(|&value| value == Complex::default())
91 {
92 return Err(invalid());
93 }
94 let mut forward = numerics::filled(n, 0_usize)?;
95 let mut inverse = numerics::filled(n, usize::MAX)?;
96 for (i, index) in forward.iter_mut().enumerate() {
97 *index = reader.usize()?;
98 let entry = inverse.get_mut(*index).ok_or_else(invalid)?;
99 if *entry != usize::MAX {
100 return Err(invalid());
101 }
102 *entry = i;
103 }
104 let mut row = numerics::filled(n, 1.0)?;
105 let mut column = numerics::filled(n, 1.0)?;
106 for value in row.iter_mut().chain(column.iter_mut()) {
107 *value = reader.f64()?;
108 if !value.is_finite() || *value <= 0.0 {
109 return Err(invalid());
110 }
111 }
112 let equilibration = if row
113 .iter()
114 .chain(&column)
115 .all(|&value| value.to_bits() == 1.0_f64.to_bits())
116 {
117 None
118 } else {
119 Some(Equilibration { row, column })
120 };
121 Ok(Lu {
122 factors,
123 permutation: Perm::new_checked(forward.into_boxed_slice(), inverse.into_boxed_slice(), n),
124 equilibration,
125 })
126}
127
128impl SolveResidual {
129 pub fn state_size(n: usize, rhs: usize) -> Result<usize> {
131 total(&[16, Lu::state_size(n)?, matrix_size(n, rhs)?])
132 }
133}
134
135impl SavedState for SolveResidual {
136 fn save_state(&self) -> Result<Vec<u8>> {
137 let (n, rhs) = self.shape();
138 let mut writer = Writer::new(Self::state_size(n, rhs)?);
139 writer.usize(n);
140 writer.usize(rhs);
141 self.lu.write_state(&mut writer);
142 write_matrix(&mut writer, view(&self.value));
143 Ok(writer.finish())
144 }
145
146 fn from_state(bytes: &[u8]) -> Result<Self> {
147 let mut reader = Reader::new(bytes);
148 let (n, rhs) = (reader.usize()?, reader.usize()?);
149 if bytes.len() != Self::state_size(n, rhs)? {
150 return Err(invalid());
151 }
152 let lu = Lu::read_state(&mut reader, n)?;
153 let value = read_matrix(&mut reader, n, rhs)?;
154 reader.finish()?;
155 Ok(Self { lu, value })
156 }
157}
158
159impl SvdvalsResidual {
160 pub fn state_size(m: usize, n: usize) -> Result<usize> {
162 let k = m.min(n);
163 total(&[
164 16,
165 matrix_size(m, k)?,
166 matrix_size(n, k)?,
167 k.checked_mul(8).ok_or_else(invalid)?,
168 ])
169 }
170}
171
172impl SavedState for SvdvalsResidual {
173 fn save_state(&self) -> Result<Vec<u8>> {
174 let (m, n) = self.shape();
175 let mut writer = Writer::new(Self::state_size(m, n)?);
176 writer.usize(m);
177 writer.usize(n);
178 write_matrix(&mut writer, self.u.as_ref());
179 write_matrix(&mut writer, self.v.as_ref());
180 for &value in &self.values {
181 writer.f64(value);
182 }
183 Ok(writer.finish())
184 }
185
186 fn from_state(bytes: &[u8]) -> Result<Self> {
187 let mut reader = Reader::new(bytes);
188 let (m, n) = (reader.usize()?, reader.usize()?);
189 if bytes.len() != Self::state_size(m, n)? {
190 return Err(invalid());
191 }
192 let k = m.min(n);
193 let u = read_faer(&mut reader, m, k)?;
194 let v = read_faer(&mut reader, n, k)?;
195 let mut values = numerics::filled(k, 0.0)?;
196 for value in &mut values {
197 *value = reader.f64()?;
198 if !value.is_finite() || *value < 0.0 {
199 return Err(invalid());
200 }
201 }
202 reader.finish()?;
203 Ok(Self { u, v, values })
204 }
205}
206
207impl EigResidual {
208 pub fn state_size(n: usize) -> Result<usize> {
210 total(&[
211 17,
212 matrix_size(n, n)?,
213 n.checked_mul(24).ok_or_else(invalid)?,
214 Lu::state_size(n)?,
215 ])
216 }
217}
218
219impl SavedState for EigResidual {
220 fn save_state(&self) -> Result<Vec<u8>> {
221 let n = self.values.len();
222 let mut writer = Writer::new(Self::state_size(n)?);
223 writer.usize(n);
224 for &value in &self.values {
225 writer.complex(value);
226 }
227 write_matrix(&mut writer, view(&self.vectors));
228 for &pivot in &self.pivots {
229 writer.usize(pivot);
230 }
231 writer.f64(self.scale);
232 match self.vectors_lu() {
233 Ok(lu) => {
234 writer.byte(0);
235 lu.write_state(&mut writer);
236 }
237 Err(Error::Singular) => {
238 writer.byte(1);
241 for j in 0..n {
242 for i in 0..n {
243 writer.complex(if i == j {
244 Complex::from(1.0)
245 } else {
246 Complex::default()
247 });
248 }
249 }
250 for i in 0..n {
251 writer.usize(i);
252 }
253 for _ in 0..2 * n {
254 writer.f64(1.0);
255 }
256 }
257 Err(error) => return Err(error),
258 }
259 Ok(writer.finish())
260 }
261
262 fn from_state(bytes: &[u8]) -> Result<Self> {
263 let mut reader = Reader::new(bytes);
264 let n = reader.usize()?;
265 if bytes.len() != Self::state_size(n)? {
266 return Err(invalid());
267 }
268 let mut values = numerics::filled(n, Complex::default())?;
269 for value in &mut values {
270 *value = read_complex(&mut reader)?;
271 }
272 let vectors = read_matrix(&mut reader, n, n)?;
273 let mut pivots = numerics::filled(n, 0_usize)?;
274 for pivot in &mut pivots {
275 *pivot = reader.usize()?;
276 if *pivot >= n {
277 return Err(invalid());
278 }
279 }
280 let scale = reader.f64()?;
281 if !scale.is_finite() || scale <= 0.0 {
282 return Err(invalid());
283 }
284 let status = reader.byte()?;
285 let lu = Lu::read_state(&mut reader, n)?;
286 let lu = match status {
287 0 => Ok(lu),
288 1 => Err(Error::Singular),
289 _ => return Err(invalid()),
290 };
291 reader.finish()?;
292 Ok(Self {
293 values,
294 vectors,
295 pivots,
296 scale,
297 vectors_lu: OnceLock::from(lu),
298 })
299 }
300}
301
302#[cfg(test)]
303mod tests {
304 use nalgebra::DMatrix;
305
306 use super::*;
307 use crate::linalg::{eig, solve_owned, svdvals};
308
309 #[test]
310 fn solve_state_preserves_factors_scales_and_both_derivatives() {
311 for scaled in [false, true] {
312 let operator = DMatrix::from_fn(3, 3, |i, j| {
313 let value = Complex::new(if (i + 1) % 3 == j { 3.0 } else { 0.2 }, 0.1);
314 let exponent = if scaled {
315 [-64, 0, 64][i] + [-40, 0, 40][j]
316 } else {
317 0
318 };
319 value * 2.0_f64.powi(exponent)
320 });
321 let rhs = &operator * DMatrix::from_element(3, 2, Complex::new(0.3, -0.1));
322 let residual = solve_owned(operator.clone(), rhs.clone()).unwrap();
323 assert_eq!(residual.lu.equilibration.is_some(), scaled);
324 assert_ne!(residual.lu.permutation.arrays().0, &[0, 1, 2]);
325 if let Some(scales) = &residual.lu.equilibration {
326 assert_ne!(scales.row, vec![1.0; 3]);
327 assert_ne!(scales.column, vec![1.0; 3]);
328 }
329 let bytes = residual.save_state().unwrap();
330 assert_eq!(bytes.len(), SolveResidual::state_size(3, 2).unwrap());
331 let restored = SolveResidual::from_state(&bytes).unwrap();
332 assert_eq!(restored.save_state().unwrap(), bytes);
333 assert_eq!(restored.value(), residual.value());
334 assert_eq!(restored.lu.factors, residual.lu.factors);
335 assert_eq!(
336 restored.lu.permutation.arrays(),
337 residual.lu.permutation.arrays()
338 );
339 let operator_direction = operator * Complex::from(0.2);
340 let rhs_direction = rhs * Complex::from(0.3);
341 assert_eq!(
342 restored
343 .pushforward(&operator_direction, rhs_direction.clone())
344 .unwrap(),
345 residual
346 .pushforward(&operator_direction, rhs_direction)
347 .unwrap()
348 );
349 let cotangent = DMatrix::from_element(3, 2, Complex::new(0.5, 0.2));
350 let expected = residual.pullback(cotangent.clone()).unwrap();
351 let actual = restored.pullback(cotangent).unwrap();
352 assert_eq!(actual.operator, expected.operator);
353 assert_eq!(actual.rhs, expected.rhs);
354 }
355 }
356
357 #[test]
358 fn eig_state_carries_the_cached_derivative_factorization() {
359 let operator = DMatrix::from_row_slice(
360 2,
361 2,
362 &[
363 Complex::new(2.0, 0.1),
364 Complex::new(0.2, -0.1),
365 Complex::default(),
366 Complex::new(3.0, -0.2),
367 ],
368 );
369 let residual = eig(&operator).unwrap();
370 assert!(residual.vectors_lu.get().is_none());
371 let bytes = residual.save_state().unwrap();
372 assert_eq!(bytes.len(), EigResidual::state_size(2).unwrap());
373 let restored = EigResidual::from_state(&bytes).unwrap();
374 assert!(restored.vectors_lu.get().unwrap().is_ok());
375 assert_eq!(restored.save_state().unwrap(), bytes);
376 assert_eq!(
377 restored.vectors_lu().unwrap().factors,
378 residual.vectors_lu().unwrap().factors
379 );
380 let direction = DMatrix::from_element(2, 2, Complex::new(0.2, 0.1));
381 assert_eq!(
382 restored.pushforward(&direction).unwrap(),
383 residual.pushforward(&direction).unwrap()
384 );
385 let values = [Complex::new(0.2, 0.3), Complex::new(-0.4, 0.1)];
386 let vectors = DMatrix::from_element(2, 2, Complex::new(-0.2, 0.3));
387 assert_eq!(
388 restored.pullback(&values, vectors.clone()).unwrap(),
389 residual.pullback(&values, vectors).unwrap()
390 );
391 }
392
393 #[test]
394 fn eig_state_preserves_trace_pullbacks_with_a_singular_eigenbasis() {
395 let residual = EigResidual {
398 values: vec![Complex::from(1.0); 2],
399 vectors: DMatrix::from_row_slice(
400 2,
401 2,
402 &[
403 Complex::from(1.0),
404 Complex::from(1.0),
405 Complex::default(),
406 Complex::default(),
407 ],
408 ),
409 pivots: vec![0; 2],
410 scale: 1.0,
411 vectors_lu: OnceLock::new(),
412 };
413 let bytes = residual.save_state().unwrap();
414 assert_eq!(bytes.len(), EigResidual::state_size(2).unwrap());
415 let restored = EigResidual::from_state(&bytes).unwrap();
416 assert!(matches!(
417 restored.vectors_lu.get(),
418 Some(Err(Error::Singular))
419 ));
420 assert_eq!(restored.save_state().unwrap(), bytes);
421 assert_eq!(
422 restored
423 .pullback(&[Complex::from(1.0); 2], DMatrix::zeros(2, 2))
424 .unwrap(),
425 DMatrix::identity(2, 2)
426 );
427 assert!(restored.pushforward(&DMatrix::identity(2, 2)).is_err());
428 }
429
430 #[test]
431 fn rectangular_svd_state_preserves_both_derivatives() {
432 let tall = DMatrix::from_fn(3, 2, |i, j| {
433 Complex::new(if i == j { [2.0, 3.0, 4.0][i] } else { 0.2 }, 0.1)
434 });
435 for operator in [tall.clone(), tall.transpose()] {
436 let residual = svdvals(&operator).unwrap();
437 let bytes = residual.save_state().unwrap();
438 assert_eq!(
439 bytes.len(),
440 SvdvalsResidual::state_size(operator.nrows(), operator.ncols()).unwrap()
441 );
442 let restored = SvdvalsResidual::from_state(&bytes).unwrap();
443 assert_eq!(restored.save_state().unwrap(), bytes);
444 assert_eq!(restored.values(), residual.values());
445 let direction =
446 DMatrix::from_element(operator.nrows(), operator.ncols(), Complex::new(-0.2, 0.1));
447 assert_eq!(
448 restored.pushforward(&direction).unwrap(),
449 residual.pushforward(&direction).unwrap()
450 );
451 assert_eq!(
452 restored.pullback(&[0.2, -0.4]).unwrap(),
453 residual.pullback(&[0.2, -0.4]).unwrap()
454 );
455 }
456 }
457
458 #[test]
459 fn solve_restore_rejects_wrong_lengths_and_invalid_permutations() {
460 let residual = solve_owned(DMatrix::identity(2, 2), DMatrix::identity(2, 2)).unwrap();
461 let bytes = residual.save_state().unwrap();
462 assert!(SolveResidual::from_state(&bytes[..bytes.len() - 1]).is_err());
463 let mut extended = bytes.clone();
464 extended.push(0);
465 assert!(SolveResidual::from_state(&extended).is_err());
466 let mut invalid_permutation = bytes.clone();
467 let permutation_start = 16 + 16 * 2 * 2;
468 invalid_permutation[permutation_start + 8..permutation_start + 16]
469 .copy_from_slice(&0_u64.to_le_bytes());
470 assert!(SolveResidual::from_state(&invalid_permutation).is_err());
471 let mut invalid_size = bytes;
472 invalid_size[..8].copy_from_slice(&u64::MAX.to_le_bytes());
473 assert!(SolveResidual::from_state(&invalid_size).is_err());
474 }
475}