1use scirs2_core::ndarray::{Array1, Array2, ArrayView1, ArrayView2, ScalarOperand};
24use scirs2_core::numeric::{Float, NumAssign};
25use std::fmt::Debug;
26use std::iter::Sum;
27
28use crate::error::{LinalgError, LinalgResult};
29
30#[derive(Debug, Clone)]
36pub struct RefinementConfig<F> {
37 pub max_iterations: usize,
39 pub tolerance: F,
41 pub stop_on_stagnation: bool,
43}
44
45impl<F: Float> Default for RefinementConfig<F> {
46 fn default() -> Self {
47 Self {
48 max_iterations: 10,
49 tolerance: F::from(1e-14).unwrap_or_else(|| F::epsilon()),
50 stop_on_stagnation: true,
51 }
52 }
53}
54
55#[derive(Debug, Clone)]
57pub struct IterativeRefinementResult<F> {
58 pub solution: Array1<F>,
60 pub iterations: usize,
62 pub residual_history: Vec<F>,
64 pub forward_error: F,
66 pub backward_error: F,
68 pub converged: bool,
70}
71
72pub fn lu_iterative_refinement<F>(
92 a: &ArrayView2<F>,
93 b: &ArrayView1<F>,
94 config: &RefinementConfig<F>,
95) -> LinalgResult<IterativeRefinementResult<F>>
96where
97 F: Float + NumAssign + Sum + Debug + ScalarOperand + Send + Sync + 'static,
98{
99 let (m, n) = a.dim();
100 if m != n {
101 return Err(LinalgError::DimensionError(
102 "LU refinement: matrix must be square".to_string(),
103 ));
104 }
105 if b.len() != n {
106 return Err(LinalgError::DimensionError(format!(
107 "LU refinement: b length ({}) != matrix dimension ({n})",
108 b.len()
109 )));
110 }
111
112 let (piv, l, u) = lu_factor_partial(a)?;
114
115 let pb = apply_perm_vec(b, &piv);
117 let y = forward_solve(&l, &pb.view())?;
118 let mut x = back_solve(&u, &y.view())?;
119
120 let b_norm = inf_norm_vec(b);
122 let a_norm = inf_norm_mat(a);
123
124 let mut residual_history = Vec::with_capacity(config.max_iterations + 1);
125 let mut converged = false;
126 let mut iterations = 0;
127 let mut prev_res_norm = F::infinity();
128
129 let r0 = compute_residual(a, &x.view(), b);
131 let r0_norm = inf_norm_arr(&r0);
132 residual_history.push(r0_norm);
133
134 if b_norm > F::epsilon() && r0_norm / b_norm < config.tolerance {
135 let x_norm_init = inf_norm_arr(&x);
136 let bw_err = r0_norm / (a_norm * x_norm_init + b_norm);
137 return Ok(IterativeRefinementResult {
138 solution: x,
139 iterations: 0,
140 residual_history,
141 forward_error: F::zero(),
142 backward_error: bw_err,
143 converged: true,
144 });
145 }
146
147 for _it in 0..config.max_iterations {
148 iterations += 1;
149
150 let r = compute_residual(a, &x.view(), b);
152 let r_norm = inf_norm_arr(&r);
153 residual_history.push(r_norm);
154
155 let x_norm = inf_norm_arr(&x);
157 let backward_err = if a_norm * x_norm + b_norm > F::epsilon() {
158 r_norm / (a_norm * x_norm + b_norm)
159 } else {
160 r_norm
161 };
162
163 if backward_err < config.tolerance {
164 converged = true;
165 break;
166 }
167
168 if config.stop_on_stagnation && r_norm >= prev_res_norm {
170 break;
171 }
172 prev_res_norm = r_norm;
173
174 let pr = apply_perm_vec(&r.view(), &piv);
176 let y_corr = forward_solve(&l, &pr.view())?;
177 let dx = back_solve(&u, &y_corr.view())?;
178
179 for i in 0..n {
181 x[i] += dx[i];
182 }
183 }
184
185 let final_r = compute_residual(a, &x.view(), b);
187 let final_r_norm = inf_norm_arr(&final_r);
188 let x_norm = inf_norm_arr(&x);
189 let forward_error = if x_norm > F::epsilon() {
190 if residual_history.len() >= 2 {
192 let last_res = residual_history[residual_history.len() - 1];
193 last_res / (a_norm * x_norm)
194 } else {
195 final_r_norm / (a_norm * x_norm)
196 }
197 } else {
198 final_r_norm
199 };
200 let backward_error = if a_norm * x_norm + b_norm > F::epsilon() {
201 final_r_norm / (a_norm * x_norm + b_norm)
202 } else {
203 final_r_norm
204 };
205
206 Ok(IterativeRefinementResult {
207 solution: x,
208 iterations,
209 residual_history,
210 forward_error,
211 backward_error,
212 converged,
213 })
214}
215
216pub fn qr_iterative_refinement<F>(
237 a: &ArrayView2<F>,
238 b: &ArrayView1<F>,
239 config: &RefinementConfig<F>,
240) -> LinalgResult<IterativeRefinementResult<F>>
241where
242 F: Float + NumAssign + Sum + Debug + ScalarOperand + Send + Sync + 'static,
243{
244 let (m, n) = a.dim();
245 if b.len() != m {
246 return Err(LinalgError::DimensionError(format!(
247 "QR refinement: b length ({}) != matrix rows ({m})",
248 b.len()
249 )));
250 }
251 if m < n {
252 return Err(LinalgError::DimensionError(
253 "QR refinement: requires m >= n (overdetermined or square)".to_string(),
254 ));
255 }
256
257 let (q, r_mat) = householder_qr_internal(a)?;
259
260 let qtb = q.t().dot(b); let qtb_n = qtb.slice(scirs2_core::ndarray::s![..n]).to_owned();
263 let mut x = back_solve_rect(&r_mat, &qtb_n.view(), n)?;
264
265 let b_norm = inf_norm_vec(b);
266 let a_norm = inf_norm_mat(a);
267
268 let mut residual_history = Vec::with_capacity(config.max_iterations + 1);
269 let mut converged = false;
270 let mut iterations = 0;
271 let mut prev_res_norm = F::infinity();
272
273 let r0 = compute_residual(a, &x.view(), b);
275 let r0_norm = inf_norm_arr(&r0);
276 residual_history.push(r0_norm);
277
278 if b_norm > F::epsilon() && r0_norm / b_norm < config.tolerance {
279 let x_norm_init = inf_norm_arr(&x);
280 let bw_err = r0_norm / (a_norm * x_norm_init + b_norm);
281 return Ok(IterativeRefinementResult {
282 solution: x,
283 iterations: 0,
284 residual_history,
285 forward_error: F::zero(),
286 backward_error: bw_err,
287 converged: true,
288 });
289 }
290
291 for _it in 0..config.max_iterations {
292 iterations += 1;
293
294 let r = compute_residual(a, &x.view(), b);
295 let r_norm = inf_norm_arr(&r);
296 residual_history.push(r_norm);
297
298 let x_norm = inf_norm_arr(&x);
299 let backward_err = if a_norm * x_norm + b_norm > F::epsilon() {
300 r_norm / (a_norm * x_norm + b_norm)
301 } else {
302 r_norm
303 };
304
305 if backward_err < config.tolerance {
306 converged = true;
307 break;
308 }
309
310 if config.stop_on_stagnation && r_norm >= prev_res_norm {
311 break;
312 }
313 prev_res_norm = r_norm;
314
315 let qt_r = q.t().dot(&r);
317 let qt_r_n = qt_r.slice(scirs2_core::ndarray::s![..n]).to_owned();
318 let dx = back_solve_rect(&r_mat, &qt_r_n.view(), n)?;
319
320 for i in 0..n {
321 x[i] += dx[i];
322 }
323 }
324
325 let final_r = compute_residual(a, &x.view(), b);
326 let final_r_norm = inf_norm_arr(&final_r);
327 let x_norm = inf_norm_arr(&x);
328 let forward_error = if x_norm > F::epsilon() {
329 final_r_norm / (a_norm * x_norm)
330 } else {
331 final_r_norm
332 };
333 let backward_error = if a_norm * x_norm + b_norm > F::epsilon() {
334 final_r_norm / (a_norm * x_norm + b_norm)
335 } else {
336 final_r_norm
337 };
338
339 Ok(IterativeRefinementResult {
340 solution: x,
341 iterations,
342 residual_history,
343 forward_error,
344 backward_error,
345 converged,
346 })
347}
348
349pub fn generic_iterative_refinement<F, S>(
366 a: &ArrayView2<F>,
367 b: &ArrayView1<F>,
368 mut solver: S,
369 config: &RefinementConfig<F>,
370) -> LinalgResult<IterativeRefinementResult<F>>
371where
372 F: Float + NumAssign + Sum + Debug + ScalarOperand + Send + Sync + 'static,
373 S: FnMut(&Array1<F>) -> LinalgResult<Array1<F>>,
374{
375 let (m, _n) = a.dim();
376 if b.len() != m {
377 return Err(LinalgError::DimensionError(format!(
378 "Generic refinement: b length ({}) != matrix rows ({m})",
379 b.len()
380 )));
381 }
382
383 let b_owned = b.to_owned();
385 let mut x = solver(&b_owned)?;
386
387 let b_norm = inf_norm_vec(b);
388 let a_norm = inf_norm_mat(a);
389
390 let mut residual_history = Vec::with_capacity(config.max_iterations + 1);
391 let mut converged = false;
392 let mut iterations = 0;
393 let mut prev_res_norm = F::infinity();
394
395 let r0 = compute_residual(a, &x.view(), b);
397 residual_history.push(inf_norm_arr(&r0));
398
399 for _it in 0..config.max_iterations {
400 iterations += 1;
401
402 let r = compute_residual(a, &x.view(), b);
403 let r_norm = inf_norm_arr(&r);
404 residual_history.push(r_norm);
405
406 let x_norm = inf_norm_arr(&x);
407 let backward_err = if a_norm * x_norm + b_norm > F::epsilon() {
408 r_norm / (a_norm * x_norm + b_norm)
409 } else {
410 r_norm
411 };
412
413 if backward_err < config.tolerance {
414 converged = true;
415 break;
416 }
417
418 if config.stop_on_stagnation && r_norm >= prev_res_norm {
419 break;
420 }
421 prev_res_norm = r_norm;
422
423 let dx = solver(&r)?;
424 let n = x.len();
425 for i in 0..n {
426 x[i] += dx[i];
427 }
428 }
429
430 let final_r = compute_residual(a, &x.view(), b);
431 let final_r_norm = inf_norm_arr(&final_r);
432 let x_norm = inf_norm_arr(&x);
433 let forward_error = if x_norm > F::epsilon() {
434 final_r_norm / (a_norm * x_norm)
435 } else {
436 final_r_norm
437 };
438 let backward_error = if a_norm * x_norm + b_norm > F::epsilon() {
439 final_r_norm / (a_norm * x_norm + b_norm)
440 } else {
441 final_r_norm
442 };
443
444 Ok(IterativeRefinementResult {
445 solution: x,
446 iterations,
447 residual_history,
448 forward_error,
449 backward_error,
450 converged,
451 })
452}
453
454fn compute_residual<F>(a: &ArrayView2<F>, x: &ArrayView1<F>, b: &ArrayView1<F>) -> Array1<F>
460where
461 F: Float + NumAssign + Sum + ScalarOperand,
462{
463 let ax = a.dot(x);
464 let n = b.len();
465 let mut r = Array1::<F>::zeros(n);
466 for i in 0..n {
467 r[i] = b[i] - ax[i];
468 }
469 r
470}
471
472fn inf_norm_vec<F: Float>(v: &ArrayView1<F>) -> F {
474 v.iter().fold(F::zero(), |acc, &x| acc.max(x.abs()))
475}
476
477fn inf_norm_arr<F: Float>(v: &Array1<F>) -> F {
479 v.iter().fold(F::zero(), |acc, &x| acc.max(x.abs()))
480}
481
482fn inf_norm_mat<F: Float + Sum>(a: &ArrayView2<F>) -> F {
484 let (m, n) = a.dim();
485 let mut max_row = F::zero();
486 for i in 0..m {
487 let mut row_sum = F::zero();
488 for j in 0..n {
489 row_sum = row_sum + a[[i, j]].abs();
490 }
491 if row_sum > max_row {
492 max_row = row_sum;
493 }
494 }
495 max_row
496}
497
498fn lu_factor_partial<F>(a: &ArrayView2<F>) -> LinalgResult<(Vec<usize>, Array2<F>, Array2<F>)>
502where
503 F: Float + NumAssign + Debug,
504{
505 let n = a.nrows();
506 let mut lu = a.to_owned();
507 let mut piv: Vec<usize> = (0..n).collect();
508
509 for k in 0..n {
510 let mut max_val = lu[[k, k]].abs();
512 let mut max_row = k;
513 for i in (k + 1)..n {
514 let v = lu[[i, k]].abs();
515 if v > max_val {
516 max_val = v;
517 max_row = i;
518 }
519 }
520
521 if max_val <= F::epsilon() {
522 return Err(LinalgError::SingularMatrixError(
523 "LU factorization: matrix is singular or nearly singular".to_string(),
524 ));
525 }
526
527 if max_row != k {
529 piv.swap(k, max_row);
530 for j in 0..n {
531 let tmp = lu[[k, j]];
532 lu[[k, j]] = lu[[max_row, j]];
533 lu[[max_row, j]] = tmp;
534 }
535 }
536
537 for i in (k + 1)..n {
539 lu[[i, k]] = lu[[i, k]] / lu[[k, k]];
540 for j in (k + 1)..n {
541 let lik = lu[[i, k]];
542 let ukj = lu[[k, j]];
543 lu[[i, j]] -= lik * ukj;
544 }
545 }
546 }
547
548 let mut l = Array2::<F>::eye(n);
550 let mut u = Array2::<F>::zeros((n, n));
551 for i in 0..n {
552 for j in 0..n {
553 if j < i {
554 l[[i, j]] = lu[[i, j]];
555 } else {
556 u[[i, j]] = lu[[i, j]];
557 }
558 }
559 }
560
561 Ok((piv, l, u))
562}
563
564fn apply_perm_vec<F: Float>(b: &ArrayView1<F>, piv: &[usize]) -> Array1<F> {
566 let n = b.len();
567 let mut result = Array1::<F>::zeros(n);
568 for i in 0..n {
569 result[i] = b[piv[i]];
570 }
571 result
572}
573
574fn forward_solve<F: Float + NumAssign>(
576 l: &Array2<F>,
577 b: &ArrayView1<F>,
578) -> LinalgResult<Array1<F>> {
579 let n = l.nrows();
580 let mut y = Array1::<F>::zeros(n);
581 for i in 0..n {
582 let mut s = b[i];
583 for j in 0..i {
584 s -= l[[i, j]] * y[j];
585 }
586 y[i] = s; }
588 Ok(y)
589}
590
591fn back_solve<F: Float + NumAssign + Debug>(
593 u: &Array2<F>,
594 y: &ArrayView1<F>,
595) -> LinalgResult<Array1<F>> {
596 let n = u.nrows();
597 let mut x = Array1::<F>::zeros(n);
598 for i in (0..n).rev() {
599 let mut s = y[i];
600 for j in (i + 1)..n {
601 s -= u[[i, j]] * x[j];
602 }
603 let diag = u[[i, i]];
604 if diag.abs() <= F::epsilon() {
605 return Err(LinalgError::SingularMatrixError(format!(
606 "Back substitution: zero diagonal at index {i}"
607 )));
608 }
609 x[i] = s / diag;
610 }
611 Ok(x)
612}
613
614fn back_solve_rect<F: Float + NumAssign + Debug>(
616 r: &Array2<F>,
617 y: &ArrayView1<F>,
618 k: usize,
619) -> LinalgResult<Array1<F>> {
620 let mut x = Array1::<F>::zeros(k);
621 for i in (0..k).rev() {
622 let mut s = y[i];
623 for j in (i + 1)..k {
624 s -= r[[i, j]] * x[j];
625 }
626 let diag = r[[i, i]];
627 if diag.abs() <= F::epsilon() {
628 return Err(LinalgError::SingularMatrixError(format!(
629 "Back substitution: zero diagonal at index {i}"
630 )));
631 }
632 x[i] = s / diag;
633 }
634 Ok(x)
635}
636
637fn householder_qr_internal<F>(a: &ArrayView2<F>) -> LinalgResult<(Array2<F>, Array2<F>)>
639where
640 F: Float + NumAssign + Sum + Debug + ScalarOperand + 'static,
641{
642 let (m, n) = a.dim();
643 let min_dim = m.min(n);
644 let mut r = a.to_owned();
645 let mut q = Array2::<F>::eye(m);
646 let two = F::from(2.0).unwrap_or_else(|| F::one() + F::one());
647
648 for k in 0..min_dim {
649 let mut x = Array1::<F>::zeros(m - k);
650 for i in k..m {
651 x[i - k] = r[[i, k]];
652 }
653 let x_norm = x.iter().fold(F::zero(), |acc, &v| acc + v * v).sqrt();
654 if x_norm <= F::epsilon() {
655 continue;
656 }
657 let alpha = if x[0] >= F::zero() { -x_norm } else { x_norm };
658 let mut v = x;
659 v[0] -= alpha;
660 let v_norm_sq = v.iter().fold(F::zero(), |acc, &val| acc + val * val);
661 if v_norm_sq <= F::epsilon() {
662 continue;
663 }
664 let beta = two / v_norm_sq;
665
666 for j in k..n {
667 let mut dot = F::zero();
668 for i in 0..(m - k) {
669 dot += v[i] * r[[i + k, j]];
670 }
671 for i in 0..(m - k) {
672 r[[i + k, j]] -= beta * v[i] * dot;
673 }
674 }
675 for row in 0..m {
676 let mut dot = F::zero();
677 for jj in 0..(m - k) {
678 dot += q[[row, jj + k]] * v[jj];
679 }
680 for jj in 0..(m - k) {
681 q[[row, jj + k]] -= beta * dot * v[jj];
682 }
683 }
684 }
685
686 Ok((q, r))
687}
688
689#[cfg(test)]
694mod tests {
695 use super::*;
696 use scirs2_core::ndarray::array;
697
698 #[test]
699 fn test_lu_refinement_well_conditioned() {
700 let a = array![[4.0, 1.0], [1.0, 3.0]];
701 let x_true = array![1.0, 2.0];
702 let b = a.dot(&x_true);
703
704 let config = RefinementConfig {
705 max_iterations: 10,
706 tolerance: 1e-14,
707 stop_on_stagnation: true,
708 };
709 let result =
710 lu_iterative_refinement(&a.view(), &b.view(), &config).expect("LU refinement failed");
711
712 assert!(
713 result.converged,
714 "should converge for well-conditioned system"
715 );
716 for i in 0..2 {
717 assert!(
718 (result.solution[i] - x_true[i]).abs() < 1e-12,
719 "solution[{i}] = {} != {}",
720 result.solution[i],
721 x_true[i]
722 );
723 }
724 }
725
726 #[test]
727 fn test_lu_refinement_ill_conditioned() {
728 let a = array![
730 [1.0, 0.5, 1.0 / 3.0],
731 [0.5, 1.0 / 3.0, 0.25],
732 [1.0 / 3.0, 0.25, 0.2]
733 ];
734 let x_true = array![1.0, 1.0, 1.0];
735 let b = a.dot(&x_true);
736
737 let config = RefinementConfig {
738 max_iterations: 20,
739 tolerance: 1e-12,
740 stop_on_stagnation: true,
741 };
742 let result =
743 lu_iterative_refinement(&a.view(), &b.view(), &config).expect("LU refinement failed");
744
745 let residual = &a.dot(&result.solution) - &b;
747 let res_norm: f64 = residual.iter().map(|&v| v.abs()).fold(0.0, f64::max);
748 assert!(
749 res_norm < 1e-10,
750 "residual should be small after refinement, got {res_norm}"
751 );
752 }
753
754 #[test]
755 fn test_lu_refinement_convergence_tracking() {
756 let a = array![[2.0, 1.0], [1.0, 3.0]];
757 let b = array![3.0, 4.0];
758
759 let config = RefinementConfig {
760 max_iterations: 5,
761 tolerance: 1e-14,
762 stop_on_stagnation: false,
763 };
764 let result =
765 lu_iterative_refinement(&a.view(), &b.view(), &config).expect("LU refinement failed");
766
767 assert!(
769 !result.residual_history.is_empty(),
770 "should have residual history"
771 );
772 assert!(result.forward_error.is_finite());
774 assert!(result.backward_error.is_finite());
775 }
776
777 #[test]
778 fn test_lu_refinement_singular_error() {
779 let a = array![[1.0, 2.0], [2.0, 4.0]]; let b = array![1.0, 2.0];
781
782 let config = RefinementConfig::default();
783 let result = lu_iterative_refinement(&a.view(), &b.view(), &config);
784 assert!(result.is_err(), "should fail on singular matrix");
785 }
786
787 #[test]
788 fn test_lu_refinement_dimension_mismatch() {
789 let a = array![[1.0, 2.0], [3.0, 4.0]];
790 let b = array![1.0, 2.0, 3.0]; let config = RefinementConfig::default();
793 assert!(lu_iterative_refinement(&a.view(), &b.view(), &config).is_err());
794 }
795
796 #[test]
797 fn test_qr_refinement_square() {
798 let a = array![[4.0, 1.0], [1.0, 3.0]];
799 let x_true = array![1.0, 2.0];
800 let b = a.dot(&x_true);
801
802 let config = RefinementConfig {
803 max_iterations: 10,
804 tolerance: 1e-14,
805 stop_on_stagnation: true,
806 };
807 let result =
808 qr_iterative_refinement(&a.view(), &b.view(), &config).expect("QR refinement failed");
809
810 for i in 0..2 {
811 assert!(
812 (result.solution[i] - x_true[i]).abs() < 1e-10,
813 "QR solution[{i}] = {} != {}",
814 result.solution[i],
815 x_true[i]
816 );
817 }
818 }
819
820 #[test]
821 fn test_qr_refinement_overdetermined() {
822 let a = array![[1.0, 1.0], [1.0, 2.0], [1.0, 3.0]];
824 let b = array![1.0, 2.0, 3.0]; let config = RefinementConfig {
827 max_iterations: 10,
828 tolerance: 1e-12,
829 stop_on_stagnation: true,
830 };
831 let result =
832 qr_iterative_refinement(&a.view(), &b.view(), &config).expect("QR refinement failed");
833
834 let res = &a.dot(&result.solution) - &b;
836 let res_norm: f64 = res.iter().map(|&v| v * v).sum::<f64>().sqrt();
837 assert!(res_norm < 1e-8, "overdetermined LS residual = {res_norm}");
838 }
839
840 #[test]
841 fn test_qr_refinement_dimension_error() {
842 let a = array![[1.0, 2.0], [3.0, 4.0]];
843 let b = array![1.0, 2.0, 3.0]; let config = RefinementConfig::default();
845 assert!(qr_iterative_refinement(&a.view(), &b.view(), &config).is_err());
846 }
847
848 #[test]
849 fn test_generic_refinement() {
850 let a = array![[4.0, 1.0], [1.0, 3.0]];
851 let x_true = array![1.0, 2.0];
852 let b = a.dot(&x_true);
853
854 let a_clone = a.clone();
855 let solver = move |rhs: &Array1<f64>| -> LinalgResult<Array1<f64>> {
856 let (piv, l, u) = lu_factor_partial(&a_clone.view())?;
858 let pb = apply_perm_vec(&rhs.view(), &piv);
859 let y = forward_solve(&l, &pb.view())?;
860 back_solve(&u, &y.view())
861 };
862
863 let config = RefinementConfig {
864 max_iterations: 5,
865 tolerance: 1e-14,
866 stop_on_stagnation: true,
867 };
868 let result = generic_iterative_refinement(&a.view(), &b.view(), solver, &config)
869 .expect("generic refinement failed");
870
871 for i in 0..2 {
872 assert!(
873 (result.solution[i] - x_true[i]).abs() < 1e-10,
874 "generic solution[{i}] wrong"
875 );
876 }
877 }
878
879 #[test]
880 fn test_refinement_config_default() {
881 let config = RefinementConfig::<f64>::default();
882 assert_eq!(config.max_iterations, 10);
883 assert!(config.tolerance < 1e-10);
884 assert!(config.stop_on_stagnation);
885 }
886
887 #[test]
888 fn test_lu_refinement_identity() {
889 let a = Array2::<f64>::eye(3);
890 let b = array![1.0, 2.0, 3.0];
891
892 let config = RefinementConfig::default();
893 let result =
894 lu_iterative_refinement(&a.view(), &b.view(), &config).expect("identity solve failed");
895
896 for i in 0..3 {
897 assert!(
898 (result.solution[i] - b[i]).abs() < 1e-14,
899 "identity system wrong at {i}"
900 );
901 }
902 assert!(result.converged);
903 }
904
905 #[test]
906 fn test_lu_refinement_non_square_error() {
907 let a = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]];
908 let b = array![1.0, 2.0];
909 let config = RefinementConfig::default();
910 assert!(lu_iterative_refinement(&a.view(), &b.view(), &config).is_err());
911 }
912}