1use faer::linalg::matmul::matmul;
10use faer::sparse::SparseColMat;
11use faer::{Accum, Col, Mat, Par};
12
13use crate::error::SparseError;
14use crate::io::reference::{DBlock, Inertia, ReferenceFactorization};
15
16pub fn validate_permutation(perm: &[usize], n: usize) -> Result<(), SparseError> {
26 if perm.len() != n {
27 return Err(SparseError::InvalidInput {
28 reason: format!(
29 "permutation length ({}) does not match expected size ({})",
30 perm.len(),
31 n
32 ),
33 });
34 }
35 let mut seen = vec![false; n];
36 for (i, &p) in perm.iter().enumerate() {
37 if p >= n {
38 return Err(SparseError::InvalidInput {
39 reason: format!("permutation[{}] = {} is out of bounds (n = {})", i, p, n),
40 });
41 }
42 if seen[p] {
43 return Err(SparseError::InvalidInput {
44 reason: format!("permutation has duplicate index {} at position {}", p, i),
45 });
46 }
47 seen[p] = true;
48 }
49 Ok(())
50}
51
52pub fn dense_matvec(a: &SparseColMat<usize, f64>, x: &Col<f64>) -> Col<f64> {
62 let a_dense = a.to_dense();
63 let n = a.nrows();
64 let x_mat = x.as_mat();
65 let mut result = Mat::<f64>::zeros(n, 1);
66 matmul(
67 result.as_mut(),
68 Accum::Replace,
69 a_dense.as_ref(),
70 x_mat,
71 1.0,
72 Par::Seq,
73 );
74 Col::from_fn(n, |i| result[(i, 0)])
75}
76
77pub fn reconstruction_error(
85 a: &SparseColMat<usize, f64>,
86 reference: &ReferenceFactorization,
87) -> f64 {
88 let n = a.nrows();
89 let a_dense = a.to_dense();
90 let a_norm = a_dense.norm_l2();
92 if a_norm == 0.0 {
93 return 0.0;
94 }
95
96 let perm = &reference.permutation;
98 let pap = Mat::<f64>::from_fn(n, n, |i, j| a_dense[(perm[i], perm[j])]);
99
100 let mut l_dense = Mat::<f64>::identity(n, n);
102 for entry in &reference.l_entries {
103 l_dense[(entry.row, entry.col)] = entry.value;
104 }
105
106 let mut d_dense = Mat::<f64>::zeros(n, n);
108 let mut row_offset = 0;
109 for block in &reference.d_blocks {
110 match block {
111 DBlock::OneByOne { value } => {
112 d_dense[(row_offset, row_offset)] = *value;
113 row_offset += 1;
114 }
115 DBlock::TwoByTwo { values } => {
116 d_dense[(row_offset, row_offset)] = values[0][0];
117 d_dense[(row_offset, row_offset + 1)] = values[0][1];
118 d_dense[(row_offset + 1, row_offset)] = values[1][0];
119 d_dense[(row_offset + 1, row_offset + 1)] = values[1][1];
120 row_offset += 2;
121 }
122 }
123 }
124
125 let mut ld = Mat::<f64>::zeros(n, n);
127 matmul(
128 ld.as_mut(),
129 Accum::Replace,
130 l_dense.as_ref(),
131 d_dense.as_ref(),
132 1.0,
133 Par::Seq,
134 );
135
136 let mut ldlt = Mat::<f64>::zeros(n, n);
138 matmul(
139 ldlt.as_mut(),
140 Accum::Replace,
141 ld.as_ref(),
142 l_dense.as_ref().transpose(),
143 1.0,
144 Par::Seq,
145 );
146
147 let diff = &pap - &ldlt;
149 diff.norm_l2() / a_norm
150}
151
152pub fn backward_error(a: &SparseColMat<usize, f64>, x: &Col<f64>, b: &Col<f64>) -> f64 {
165 let n = a.nrows();
166 let a_dense = a.to_dense();
167
168 let x_mat = x.as_mat();
170 let mut ax = Mat::<f64>::zeros(n, 1);
171 matmul(
172 ax.as_mut(),
173 Accum::Replace,
174 a_dense.as_ref(),
175 x_mat,
176 1.0,
177 Par::Seq,
178 );
179
180 let r = Col::<f64>::from_fn(n, |i| ax[(i, 0)] - b[i]);
182
183 let r_norm = r.norm_l2();
184 let a_norm = a_dense.norm_l2();
185 let x_norm = x.norm_l2();
186 let b_norm = b.norm_l2();
187
188 let denom = a_norm * x_norm + b_norm;
191 if denom == 0.0 {
192 return 0.0;
193 }
194
195 r_norm / denom
196}
197
198pub fn sparse_backward_error(a: &SparseColMat<usize, f64>, x: &Col<f64>, b: &Col<f64>) -> f64 {
210 let n = a.nrows();
211
212 let symbolic = a.symbolic();
215 let col_ptrs = symbolic.col_ptr();
216 let row_indices = symbolic.row_idx();
217 let values = a.val();
218
219 let mut ax = vec![0.0f64; n];
220 for j in 0..n {
221 for idx in col_ptrs[j]..col_ptrs[j + 1] {
222 let i = row_indices[idx];
223 let v = values[idx];
224 ax[i] += v * x[j];
225 }
226 }
227
228 let mut r_norm_sq = 0.0;
230 for k in 0..n {
231 let r = ax[k] - b[k];
232 r_norm_sq += r * r;
233 }
234 let r_norm = r_norm_sq.sqrt();
235 let x_norm = x.norm_l2();
236 let b_norm = b.norm_l2();
237
238 let mut a_norm_sq = 0.0;
242 for j in 0..n {
243 for &v in &values[col_ptrs[j]..col_ptrs[j + 1]] {
244 a_norm_sq += v * v;
245 }
246 }
247 let a_norm = a_norm_sq.sqrt();
248
249 let denom = a_norm * x_norm + b_norm;
250 if denom == 0.0 {
251 return 0.0;
252 }
253
254 r_norm / denom
255}
256
257pub fn check_inertia(computed: &Inertia, expected: &Inertia) -> bool {
262 computed.dimension() == expected.dimension()
263 && computed.positive == expected.positive
264 && computed.negative == expected.negative
265 && computed.zero == expected.zero
266}
267
268#[cfg(test)]
269mod tests {
270 use super::*;
271 use crate::io::registry;
272
273 #[test]
276 fn reconstruction_error_arrow_5_pd() {
277 let test = registry::load_test_matrix("arrow-5-pd")
278 .expect("registry error")
279 .expect("matrix should exist");
280 let refdata = test.reference.expect("reference should exist");
281 let err = reconstruction_error(&test.matrix, &refdata);
282 assert!(
283 err < 1e-12,
284 "reconstruction error for arrow-5-pd: {:.2e} (expected < 1e-12)",
285 err
286 );
287 }
288
289 #[test]
290 fn reconstruction_error_stress_delayed_pivots() {
291 let test = registry::load_test_matrix("stress-delayed-pivots")
292 .expect("registry error")
293 .expect("matrix should exist");
294 let refdata = test.reference.expect("reference should exist");
295 let err = reconstruction_error(&test.matrix, &refdata);
296 assert!(
297 err < 1e-12,
298 "reconstruction error for stress-delayed-pivots: {:.2e} (expected < 1e-12)",
299 err
300 );
301 }
302
303 #[test]
304 fn reconstruction_error_with_perturbed_l() {
305 let test = registry::load_test_matrix("arrow-5-pd")
306 .expect("registry error")
307 .expect("matrix should exist");
308 let mut refdata = test.reference.expect("reference should exist");
309 if !refdata.l_entries.is_empty() {
311 refdata.l_entries[0].value += 10.0;
312 }
313 let err = reconstruction_error(&test.matrix, &refdata);
314 assert!(
315 err > 0.01,
316 "perturbed reconstruction error should be large: {:.2e}",
317 err
318 );
319 }
320
321 #[test]
324 fn backward_error_exact_solution() {
325 let test = registry::load_test_matrix("arrow-5-pd")
326 .expect("registry error")
327 .expect("matrix should exist");
328 let a = &test.matrix;
329 let n = a.nrows();
330
331 let x_exact = Col::<f64>::from_fn(n, |i| (i + 1) as f64);
333
334 let b = dense_matvec(a, &x_exact);
336
337 let err = backward_error(a, &x_exact, &b);
338 assert!(
339 err < 1e-14,
340 "backward error for exact solution: {:.2e} (expected < 1e-14)",
341 err
342 );
343 }
344
345 #[test]
346 fn backward_error_perturbed_solution() {
347 let test = registry::load_test_matrix("arrow-5-pd")
348 .expect("registry error")
349 .expect("matrix should exist");
350 let a = &test.matrix;
351 let n = a.nrows();
352
353 let x_exact = Col::<f64>::from_fn(n, |i| (i + 1) as f64);
355 let b = dense_matvec(a, &x_exact);
356
357 let x_perturbed = Col::<f64>::from_fn(n, |i| (i + 1) as f64 + 0.1);
359 let err = backward_error(a, &x_perturbed, &b);
360 assert!(
361 err > 1e-4,
362 "backward error for perturbed solution should be measurable: {:.2e}",
363 err
364 );
365 }
366
367 #[test]
370 fn check_inertia_matching() {
371 let a = Inertia {
372 positive: 5,
373 negative: 3,
374 zero: 0,
375 };
376 let b = Inertia {
377 positive: 5,
378 negative: 3,
379 zero: 0,
380 };
381 assert!(check_inertia(&a, &b));
382 }
383
384 #[test]
385 fn check_inertia_mismatched() {
386 let a = Inertia {
387 positive: 5,
388 negative: 3,
389 zero: 0,
390 };
391 let b = Inertia {
392 positive: 4,
393 negative: 3,
394 zero: 1,
395 };
396 assert!(!check_inertia(&a, &b));
397 }
398
399 #[test]
402 fn validate_permutation_valid() {
403 assert!(validate_permutation(&[2, 0, 1], 3).is_ok());
404 assert!(validate_permutation(&[0], 1).is_ok());
405 assert!(validate_permutation(&[], 0).is_ok());
406 }
407
408 #[test]
409 fn validate_permutation_wrong_length() {
410 let result = validate_permutation(&[0, 1], 3);
411 assert!(result.is_err());
412 }
413
414 #[test]
415 fn validate_permutation_out_of_bounds() {
416 let result = validate_permutation(&[0, 5, 2], 3);
417 assert!(result.is_err());
418 }
419
420 #[test]
421 fn validate_permutation_duplicate() {
422 let result = validate_permutation(&[0, 1, 1], 3);
423 assert!(result.is_err());
424 }
425
426 #[test]
429 fn dense_matvec_matches_manual() {
430 let test = registry::load_test_matrix("arrow-5-pd")
431 .expect("registry error")
432 .expect("matrix should exist");
433 let n = test.matrix.nrows();
434 let x = Col::<f64>::from_fn(n, |i| (i + 1) as f64);
435 let b = dense_matvec(&test.matrix, &x);
436 assert_eq!(b.nrows(), n);
437
438 let err = backward_error(&test.matrix, &x, &b);
440 assert!(err < 1e-14);
441 }
442}