1use scirs2_core::ndarray::{Array1, Array2, ArrayView1, ArrayView2};
33use scirs2_core::numeric::{Float, NumAssign, One, Zero};
34use std::fmt::Debug;
35use std::iter::Sum;
36
37use crate::error::{LinalgError, LinalgResult};
38
39#[derive(Debug, Clone, Copy, PartialEq)]
45pub enum MatrixFunctionType {
46 Exponential,
48 SquareRoot,
50 Logarithm,
52 Sign,
54 ExponentialScaled(f64),
56 Cosine,
58 Sine,
60}
61
62#[derive(Debug, Clone)]
64pub struct MatrixFunctionParams {
65 pub function: MatrixFunctionType,
67 pub tol: f64,
69 pub max_krylov_dim: usize,
71 pub use_lanczos: bool,
73 pub restart_dim: Option<usize>,
75}
76
77impl Default for MatrixFunctionParams {
78 fn default() -> Self {
79 Self {
80 function: MatrixFunctionType::Exponential,
81 tol: 1e-10,
82 max_krylov_dim: 50,
83 use_lanczos: false,
84 restart_dim: None,
85 }
86 }
87}
88
89impl MatrixFunctionParams {
90 pub fn new(function: MatrixFunctionType) -> Self {
92 Self {
93 function,
94 ..Self::default()
95 }
96 }
97
98 pub fn with_tol(mut self, tol: f64) -> Self {
100 self.tol = tol;
101 self
102 }
103
104 pub fn with_max_krylov_dim(mut self, dim: usize) -> Self {
106 self.max_krylov_dim = dim;
107 self
108 }
109
110 pub fn with_lanczos(mut self) -> Self {
112 self.use_lanczos = true;
113 self
114 }
115
116 pub fn with_restart(mut self, restart_dim: usize) -> Self {
118 self.restart_dim = Some(restart_dim);
119 self
120 }
121}
122
123#[derive(Debug, Clone)]
129pub struct ArnoldiResult {
130 pub v: Array2<f64>,
132 pub h: Array2<f64>,
134 pub m: usize,
136 pub happy_breakdown: bool,
138 pub residual_norm: f64,
140}
141
142pub struct ArnoldiIteration;
149
150impl ArnoldiIteration {
151 pub fn run(
160 a: &ArrayView2<f64>,
161 v0: &ArrayView1<f64>,
162 max_dim: usize,
163 tol: Option<f64>,
164 ) -> LinalgResult<ArnoldiResult> {
165 let n = a.nrows();
166 if a.ncols() != n {
167 return Err(LinalgError::ShapeError(
168 "ArnoldiIteration: A must be square".to_string(),
169 ));
170 }
171 if v0.len() != n {
172 return Err(LinalgError::DimensionError(format!(
173 "ArnoldiIteration: v0 has length {} but A is {}×{}",
174 v0.len(),
175 n,
176 n
177 )));
178 }
179 if max_dim == 0 {
180 return Err(LinalgError::ShapeError(
181 "ArnoldiIteration: max_dim must be positive".to_string(),
182 ));
183 }
184
185 let tolerance = tol.unwrap_or(1e-14);
186 let m = max_dim.min(n);
187
188 let v0_norm = v0.iter().map(|&v| v * v).sum::<f64>().sqrt();
190 if v0_norm < tolerance {
191 return Err(LinalgError::InvalidInputError(
192 "ArnoldiIteration: starting vector is zero".to_string(),
193 ));
194 }
195
196 let mut v_mat = Array2::zeros((n, m + 1));
198 for i in 0..n {
199 v_mat[[i, 0]] = v0[i] / v0_norm;
200 }
201
202 let mut h_mat = Array2::zeros((m + 1, m));
204
205 let mut actual_m = 0;
206 let mut happy = false;
207 let mut res_norm = 0.0;
208
209 for j in 0..m {
210 let mut w = Array1::zeros(n);
212 for i in 0..n {
213 let mut val = 0.0f64;
214 for k in 0..n {
215 val += a[[i, k]] * v_mat[[k, j]];
216 }
217 w[i] = val;
218 }
219
220 for i in 0..=j {
222 let h_ij = (0..n).map(|k| w[k] * v_mat[[k, i]]).sum::<f64>();
223 h_mat[[i, j]] = h_ij;
224 for k in 0..n {
225 w[k] -= h_ij * v_mat[[k, i]];
226 }
227 }
228
229 for i in 0..=j {
231 let correction = (0..n).map(|k| w[k] * v_mat[[k, i]]).sum::<f64>();
232 h_mat[[i, j]] += correction;
233 for k in 0..n {
234 w[k] -= correction * v_mat[[k, i]];
235 }
236 }
237
238 let h_next = w.iter().map(|&v| v * v).sum::<f64>().sqrt();
239 h_mat[[j + 1, j]] = h_next;
240 actual_m = j + 1;
241
242 if h_next < tolerance {
243 happy = true;
245 res_norm = 0.0;
246 break;
247 }
248
249 res_norm = h_next;
250
251 if j + 1 < m {
252 for k in 0..n {
253 v_mat[[k, j + 1]] = w[k] / h_next;
254 }
255 }
256 }
257
258 let v_out = v_mat
260 .slice(scirs2_core::ndarray::s![.., ..actual_m])
261 .to_owned();
262 let h_out = h_mat
263 .slice(scirs2_core::ndarray::s![..actual_m + 1, ..actual_m])
264 .to_owned();
265
266 Ok(ArnoldiResult {
267 v: v_out,
268 h: h_out,
269 m: actual_m,
270 happy_breakdown: happy,
271 residual_norm: res_norm,
272 })
273 }
274}
275
276#[derive(Debug, Clone)]
282pub struct LanczosResult {
283 pub v: Array2<f64>,
285 pub alpha: Array1<f64>,
287 pub beta: Array1<f64>,
289 pub m: usize,
291 pub breakdown: bool,
293}
294
295pub struct LanczosIteration;
303
304impl LanczosIteration {
305 pub fn run(
314 a: &ArrayView2<f64>,
315 v0: &ArrayView1<f64>,
316 max_dim: usize,
317 tol: Option<f64>,
318 ) -> LinalgResult<LanczosResult> {
319 let n = a.nrows();
320 if a.ncols() != n {
321 return Err(LinalgError::ShapeError(
322 "LanczosIteration: A must be square".to_string(),
323 ));
324 }
325 if v0.len() != n {
326 return Err(LinalgError::DimensionError(format!(
327 "LanczosIteration: v0 has length {} but n={}",
328 v0.len(),
329 n
330 )));
331 }
332
333 let tolerance = tol.unwrap_or(1e-14);
334 let m = max_dim.min(n);
335
336 let v0_norm = v0.iter().map(|&v| v * v).sum::<f64>().sqrt();
337 if v0_norm < tolerance {
338 return Err(LinalgError::InvalidInputError(
339 "LanczosIteration: starting vector is zero".to_string(),
340 ));
341 }
342
343 let mut v_mat = Array2::zeros((n, m + 1));
344 for i in 0..n {
345 v_mat[[i, 0]] = v0[i] / v0_norm;
346 }
347
348 let mut alpha_vec = vec![0.0f64; m];
349 let mut beta_vec = vec![0.0f64; m];
350
351 let mut beta_prev = 0.0f64;
352 let mut actual_m = 0;
353 let mut breakdown = false;
354
355 for j in 0..m {
356 let mut w = Array1::zeros(n);
358 for i in 0..n {
359 let mut val = 0.0f64;
360 for k in 0..n {
361 val += a[[i, k]] * v_mat[[k, j]];
362 }
363 w[i] = val;
364 }
365
366 if j > 0 {
367 for k in 0..n {
368 w[k] -= beta_prev * v_mat[[k, j - 1]];
369 }
370 }
371
372 let alpha_j = (0..n).map(|k| v_mat[[k, j]] * w[k]).sum::<f64>();
374 alpha_vec[j] = alpha_j;
375
376 for k in 0..n {
378 w[k] -= alpha_j * v_mat[[k, j]];
379 }
380
381 for i in 0..=j {
383 let corr = (0..n).map(|k| w[k] * v_mat[[k, i]]).sum::<f64>();
384 for k in 0..n {
385 w[k] -= corr * v_mat[[k, i]];
386 }
387 }
388
389 let beta_j = w.iter().map(|&v| v * v).sum::<f64>().sqrt();
390 actual_m = j + 1;
391
392 if beta_j < tolerance {
393 breakdown = true;
394 break;
395 }
396
397 beta_vec[j] = beta_j;
398 beta_prev = beta_j;
399
400 if j + 1 < m {
401 for k in 0..n {
402 v_mat[[k, j + 1]] = w[k] / beta_j;
403 }
404 }
405 }
406
407 let v_out = v_mat
408 .slice(scirs2_core::ndarray::s![.., ..actual_m])
409 .to_owned();
410 let alpha_out = Array1::from_vec(alpha_vec[..actual_m].to_vec());
411 let beta_out = if actual_m > 1 {
412 Array1::from_vec(beta_vec[..actual_m - 1].to_vec())
413 } else {
414 Array1::zeros(0)
415 };
416
417 Ok(LanczosResult {
418 v: v_out,
419 alpha: alpha_out,
420 beta: beta_out,
421 m: actual_m,
422 breakdown,
423 })
424 }
425
426 pub fn tridiagonal_matrix(result: &LanczosResult) -> Array2<f64> {
428 let m = result.m;
429 let mut t = Array2::zeros((m, m));
430
431 for i in 0..m {
432 t[[i, i]] = result.alpha[i];
433 if i + 1 < m {
434 let beta = result.beta[i];
435 t[[i, i + 1]] = beta;
436 t[[i + 1, i]] = beta;
437 }
438 }
439
440 t
441 }
442}
443
444pub struct MatrixFunctionInterpolation;
456
457impl MatrixFunctionInterpolation {
458 pub fn apply(
470 a: &ArrayView2<f64>,
471 v: &ArrayView1<f64>,
472 params: &MatrixFunctionParams,
473 ) -> LinalgResult<Array1<f64>> {
474 let n = a.nrows();
475 if a.ncols() != n {
476 return Err(LinalgError::ShapeError(
477 "MatrixFunctionInterpolation: A must be square".to_string(),
478 ));
479 }
480 if v.len() != n {
481 return Err(LinalgError::DimensionError(format!(
482 "MatrixFunctionInterpolation: v has length {} but n={}",
483 v.len(),
484 n
485 )));
486 }
487
488 let v_norm = v.iter().map(|&vi| vi * vi).sum::<f64>().sqrt();
489 if v_norm < 1e-300 {
490 return Ok(Array1::zeros(n));
491 }
492
493 if params.use_lanczos {
494 Self::apply_lanczos(a, v, v_norm, params)
495 } else {
496 Self::apply_arnoldi(a, v, v_norm, params)
497 }
498 }
499
500 fn apply_arnoldi(
502 a: &ArrayView2<f64>,
503 v: &ArrayView1<f64>,
504 v_norm: f64,
505 params: &MatrixFunctionParams,
506 ) -> LinalgResult<Array1<f64>> {
507 let arnoldi_result = ArnoldiIteration::run(a, v, params.max_krylov_dim, Some(1e-14))?;
508 let m = arnoldi_result.m;
509
510 let hm = arnoldi_result
512 .h
513 .slice(scirs2_core::ndarray::s![..m, ..m])
514 .to_owned();
515
516 let fhm = apply_dense_function(&hm.view(), params)?;
518
519 let mut result = Array1::zeros(v.len());
521 for i in 0..v.len() {
522 let mut val = 0.0f64;
523 for j in 0..m {
524 val += arnoldi_result.v[[i, j]] * fhm[[j, 0]];
525 }
526 result[i] = v_norm * val;
527 }
528
529 Ok(result)
530 }
531
532 fn apply_lanczos(
534 a: &ArrayView2<f64>,
535 v: &ArrayView1<f64>,
536 v_norm: f64,
537 params: &MatrixFunctionParams,
538 ) -> LinalgResult<Array1<f64>> {
539 let lanczos_result = LanczosIteration::run(a, v, params.max_krylov_dim, Some(1e-14))?;
540 let m = lanczos_result.m;
541
542 let tm = LanczosIteration::tridiagonal_matrix(&lanczos_result);
544
545 let ftm = apply_dense_function(&tm.view(), params)?;
547
548 let mut result = Array1::zeros(v.len());
550 for i in 0..v.len() {
551 let mut val = 0.0f64;
552 for j in 0..m {
553 val += lanczos_result.v[[i, j]] * ftm[[j, 0]];
554 }
555 result[i] = v_norm * val;
556 }
557
558 Ok(result)
559 }
560}
561
562pub struct MatrixExpKrylov;
576
577impl MatrixExpKrylov {
578 pub fn apply(
592 a: &ArrayView2<f64>,
593 v: &ArrayView1<f64>,
594 t: f64,
595 max_krylov_dim: Option<usize>,
596 tol: Option<f64>,
597 ) -> LinalgResult<Array1<f64>> {
598 let n = a.nrows();
599 if a.ncols() != n {
600 return Err(LinalgError::ShapeError(
601 "MatrixExpKrylov: A must be square".to_string(),
602 ));
603 }
604 if v.len() != n {
605 return Err(LinalgError::DimensionError(format!(
606 "MatrixExpKrylov: v has length {} but n={}",
607 v.len(),
608 n
609 )));
610 }
611
612 let m = max_krylov_dim.unwrap_or_else(|| 50.min(n));
613 let tolerance = tol.unwrap_or(1e-10);
614
615 let v_norm = v.iter().map(|&vi| vi * vi).sum::<f64>().sqrt();
616 if v_norm < 1e-300 {
617 return Ok(Array1::zeros(n));
618 }
619
620 if t == 0.0 {
621 return Ok(v.to_owned());
622 }
623
624 if n <= m + 5 {
626 let params = MatrixFunctionParams::new(MatrixFunctionType::ExponentialScaled(t))
627 .with_max_krylov_dim(n.min(m))
628 .with_tol(tolerance);
629 return MatrixFunctionInterpolation::apply(a, v, ¶ms);
630 }
631
632 let arnoldi_result = ArnoldiIteration::run(a, v, m, Some(1e-14))?;
634 let m_actual = arnoldi_result.m;
635
636 let h_core = arnoldi_result
640 .h
641 .slice(scirs2_core::ndarray::s![..m_actual, ..m_actual])
642 .to_owned();
643
644 let th = h_core.mapv(|v| v * t);
646
647 let exp_th = pade_expm(&th.view())?;
649
650 let mut result = Array1::zeros(n);
652 for i in 0..n {
653 let mut val = 0.0f64;
654 for j in 0..m_actual {
655 val += arnoldi_result.v[[i, j]] * exp_th[[j, 0]];
656 }
657 result[i] = v_norm * val;
658 }
659
660 let err_est = if m_actual >= 1 {
663 let h_residual = arnoldi_result.residual_norm;
664 let exp_last = exp_th[[m_actual - 1, 0]].abs();
665 v_norm * h_residual * exp_last * t.abs()
666 } else {
667 0.0
668 };
669
670 if err_est > tolerance && m_actual < n {
672 let m2 = (m_actual * 2).min(n);
673 let params = MatrixFunctionParams::new(MatrixFunctionType::ExponentialScaled(t))
674 .with_max_krylov_dim(m2)
675 .with_tol(tolerance);
676 return MatrixFunctionInterpolation::apply(a, v, ¶ms);
677 }
678
679 Ok(result)
680 }
681}
682
683fn apply_dense_function(
692 h: &ArrayView2<f64>,
693 params: &MatrixFunctionParams,
694) -> LinalgResult<Array2<f64>> {
695 match params.function {
696 MatrixFunctionType::Exponential => pade_expm(h),
697 MatrixFunctionType::ExponentialScaled(t) => {
698 let ht = h.mapv(|v| v * t);
699 pade_expm(&ht.view())
700 }
701 MatrixFunctionType::SquareRoot => dense_sqrtm(h),
702 MatrixFunctionType::Logarithm => dense_logm(h),
703 MatrixFunctionType::Sign => dense_signm(h),
704 MatrixFunctionType::Cosine => dense_cosm(h),
705 MatrixFunctionType::Sine => dense_sinm(h),
706 }
707}
708
709pub fn pade_expm(a: &ArrayView2<f64>) -> LinalgResult<Array2<f64>> {
711 let n = a.nrows();
712 if n == 0 {
713 return Ok(Array2::zeros((0, 0)));
714 }
715 if n == 1 {
716 let mut r = Array2::zeros((1, 1));
717 r[[0, 0]] = a[[0, 0]].exp();
718 return Ok(r);
719 }
720
721 let a_norm = matrix_1norm(a);
723
724 let theta = 5.371920351148152; let mut s = 0i32;
727 let mut scale = 1.0f64;
728 if a_norm > theta {
729 s = (a_norm / theta).log2().ceil() as i32;
730 scale = 2.0f64.powi(-s);
731 }
732
733 let a_scaled = if scale != 1.0 {
735 a.mapv(|v| v * scale)
736 } else {
737 a.to_owned()
738 };
739
740 let c = [
742 1.0_f64,
743 0.5_f64,
744 12.0_f64.recip(),
745 120.0_f64.recip(),
746 1.0 / 720.0,
748 1.0 / 30240.0,
749 1.0 / 1209600.0,
750 ];
751
752 let ident = Array2::eye(n);
753
754 let a2 = matmul_dense(&a_scaled.view(), &a_scaled.view());
756 let a4 = matmul_dense(&a2.view(), &a2.view());
757 let a6 = matmul_dense(&a4.view(), &a2.view());
758
759 let u = &(c[6] * &a6 + c[4] * &a4 + c[2] * &a2 + c[0] * &ident);
761 let u = matmul_dense(&a_scaled.view(), &u.view());
762
763 let v = c[5] * &a6 + c[3] * &a4 + c[1] * &a2 + &ident;
764
765 let vpu = &v + &u;
767 let vmu = &v - &u;
768
769 let r = solve_dense(&vmu.view(), &vpu.view())?;
771
772 let mut result = r;
774 for _ in 0..s {
775 result = matmul_dense(&result.view(), &result.view());
776 }
777
778 Ok(result)
779}
780
781fn dense_sqrtm(a: &ArrayView2<f64>) -> LinalgResult<Array2<f64>> {
783 let n = a.nrows();
784 let mut x = a.to_owned();
786 let mut y = Array2::eye(n);
787
788 for _ in 0..50 {
789 let x_inv = solve_dense(&x.view(), &Array2::eye(n).view())?;
790 let y_inv = solve_dense(&y.view(), &Array2::eye(n).view())?;
791
792 let x_new = 0.5 * (&x + &y_inv);
793 let y_new = 0.5 * (&y + &x_inv);
794
795 let diff = (&x_new - &x).mapv(|v: f64| v.abs());
797 let err = diff.sum();
798 let x_norm = x_new.mapv(|v: f64| v.abs()).sum();
799
800 x = x_new;
801 y = y_new;
802
803 if err < 1e-14 * x_norm {
804 break;
805 }
806 }
807
808 Ok(x)
809}
810
811fn dense_logm(a: &ArrayView2<f64>) -> LinalgResult<Array2<f64>> {
813 let n = a.nrows();
814
815 let mut a_scaled = a.to_owned();
818 let mut s = 0i32;
819
820 for _ in 0..50 {
822 let diff: f64 = (0..n)
823 .map(|i| {
824 (0..n)
825 .map(|j| {
826 let v = a_scaled[[i, j]] - if i == j { 1.0 } else { 0.0 };
827 v.abs()
828 })
829 .sum::<f64>()
830 })
831 .fold(0.0, f64::max);
832
833 if diff <= 0.5 {
834 break;
835 }
836
837 a_scaled = dense_sqrtm(&a_scaled.view())?;
838 s += 1;
839 }
840
841 let ident = Array2::<f64>::eye(n);
843 let b = &a_scaled - &ident; let b_norm = matrix_1norm(&b.view());
848
849 let log_scaled = if b_norm < 0.1 {
850 let mut log_a = b.clone();
852 let mut bpow = b.clone();
853 for k in 2..=20usize {
854 bpow = matmul_dense(&bpow.view(), &b.view());
855 let sign = if k % 2 == 0 { -1.0 } else { 1.0 };
856 log_a = log_a + (sign / k as f64) * &bpow;
857
858 let bpow_norm = matrix_1norm(&bpow.view());
859 if bpow_norm / (k as f64) < 1e-15 {
860 break;
861 }
862 }
863 log_a
864 } else {
865 let two_plus_b = 2.0 * &ident + &b;
868 let z = solve_dense(&two_plus_b.view(), &b.view())?;
869 let mut atanh_z = z.clone();
871 let mut zpow = z.clone();
872 for k in 1..=20usize {
873 zpow = matmul_dense(&zpow.view(), &matmul_dense(&z.view(), &z.view()).view());
874 let coeff = 1.0 / (2 * k + 1) as f64;
875 atanh_z = atanh_z + coeff * &zpow;
876 let err = matrix_1norm(&zpow.view()) * coeff;
877 if err < 1e-15 {
878 break;
879 }
880 }
881 2.0 * atanh_z
882 };
883
884 Ok(2.0f64.powi(s) * log_scaled)
886}
887
888fn dense_signm(a: &ArrayView2<f64>) -> LinalgResult<Array2<f64>> {
890 let n = a.nrows();
891 let mut x = a.to_owned();
892
893 for _ in 0..50 {
894 let x_inv = solve_dense(&x.view(), &Array2::eye(n).view())?;
895 let x_new = 0.5 * (&x + &x_inv);
896
897 let diff_norm = (&x_new - &x).mapv(|v: f64| v.abs()).sum();
898 let x_norm = x_new.mapv(|v: f64| v.abs()).sum().max(1e-300);
899
900 x = x_new;
901
902 if diff_norm / x_norm < 1e-14 {
903 break;
904 }
905 }
906
907 Ok(x)
908}
909
910fn dense_cosm(a: &ArrayView2<f64>) -> LinalgResult<Array2<f64>> {
912 let n = a.nrows();
913 let ident = Array2::<f64>::eye(n);
914
915 let a2 = matmul_dense(&a.view(), &a.view());
917 let mut result = ident.clone();
918 let mut apow = ident.clone();
919 let mut sign = 1.0_f64;
920
921 for k in 1..=16usize {
922 let two_k = 2 * k;
923 apow = matmul_dense(&apow.view(), &a2.view());
924 let mut fact = 1.0f64;
926 for i in 1..=(two_k as u64) {
927 fact *= i as f64;
928 }
929 sign = -sign;
930 result = result + (sign / fact) * &apow;
931
932 let apow_norm = matrix_1norm(&apow.view());
933 if apow_norm / fact < 1e-15 {
934 break;
935 }
936 }
937
938 Ok(result)
939}
940
941fn dense_sinm(a: &ArrayView2<f64>) -> LinalgResult<Array2<f64>> {
943 let n = a.nrows();
944 let a2 = matmul_dense(&a.view(), &a.view());
945
946 let mut result = a.to_owned();
948 let mut apow = a.to_owned();
949 let mut sign = 1.0_f64;
950
951 for k in 1..=16usize {
952 let two_k_plus_1 = 2 * k + 1;
953 apow = matmul_dense(
954 &matmul_dense(&apow.view(), &a2.view()).view(),
955 &Array2::eye(n).view(),
956 );
957 apow = matmul_dense(&apow.view(), &a2.view());
958 let mut fact = 1.0f64;
959 for i in 1..=(two_k_plus_1 as u64) {
960 fact *= i as f64;
961 }
962 sign = -sign;
963 result = result + (sign / fact) * &apow;
964
965 let apow_norm = matrix_1norm(&apow.view());
966 if apow_norm / fact < 1e-15 {
967 break;
968 }
969 }
970
971 Ok(result)
972}
973
974fn matrix_1norm(a: &ArrayView2<f64>) -> f64 {
980 let (m, n) = a.dim();
981 let mut max_col = 0.0f64;
982 for j in 0..n {
983 let col_sum: f64 = (0..m).map(|i| a[[i, j]].abs()).sum();
984 if col_sum > max_col {
985 max_col = col_sum;
986 }
987 }
988 max_col
989}
990
991fn matmul_dense(a: &ArrayView2<f64>, b: &ArrayView2<f64>) -> Array2<f64> {
993 let (m, k) = a.dim();
994 let (_, n) = b.dim();
995 let mut c = Array2::zeros((m, n));
996 for i in 0..m {
997 for l in 0..k {
998 let aval = a[[i, l]];
999 for j in 0..n {
1000 c[[i, j]] += aval * b[[l, j]];
1001 }
1002 }
1003 }
1004 c
1005}
1006
1007fn solve_dense(a: &ArrayView2<f64>, b: &ArrayView2<f64>) -> LinalgResult<Array2<f64>> {
1009 let n = a.nrows();
1010 let nb = b.ncols();
1011
1012 let mut lu = a.to_owned();
1014 let mut pivot = vec![0usize; n];
1015
1016 for k in 0..n {
1017 let mut max_val = lu[[k, k]].abs();
1019 let mut max_row = k;
1020 for i in (k + 1)..n {
1021 let v = lu[[i, k]].abs();
1022 if v > max_val {
1023 max_val = v;
1024 max_row = i;
1025 }
1026 }
1027 pivot[k] = max_row;
1028
1029 if max_row != k {
1031 for j in 0..n {
1032 let tmp = lu[[k, j]];
1033 lu[[k, j]] = lu[[max_row, j]];
1034 lu[[max_row, j]] = tmp;
1035 }
1036 }
1037
1038 let diag = lu[[k, k]];
1039 if diag.abs() < 1e-300 {
1040 return Err(LinalgError::SingularMatrixError(format!(
1041 "solve_dense: matrix is singular at column {k}"
1042 )));
1043 }
1044
1045 for i in (k + 1)..n {
1046 lu[[i, k]] /= diag;
1047 for j in (k + 1)..n {
1048 let luk = lu[[i, k]];
1049 let lukj = lu[[k, j]];
1050 lu[[i, j]] -= luk * lukj;
1051 }
1052 }
1053 }
1054
1055 let mut x = b.to_owned();
1057
1058 for k in 0..n {
1060 let p = pivot[k];
1061 if p != k {
1062 for j in 0..nb {
1063 let tmp = x[[k, j]];
1064 x[[k, j]] = x[[p, j]];
1065 x[[p, j]] = tmp;
1066 }
1067 }
1068 }
1069
1070 for k in 0..n {
1072 for i in (k + 1)..n {
1073 let lk = lu[[i, k]];
1074 for j in 0..nb {
1075 let xkj = x[[k, j]];
1076 x[[i, j]] -= lk * xkj;
1077 }
1078 }
1079 }
1080
1081 for k in (0..n).rev() {
1083 let diag = lu[[k, k]];
1084 for j in 0..nb {
1085 x[[k, j]] /= diag;
1086 }
1087 for i in 0..k {
1088 let uk = lu[[i, k]];
1089 for j in 0..nb {
1090 let xkj = x[[k, j]];
1091 x[[i, j]] -= uk * xkj;
1092 }
1093 }
1094 }
1095
1096 Ok(x)
1097}
1098
1099#[cfg(test)]
1104mod tests {
1105 use super::*;
1106 use scirs2_core::ndarray::array;
1107
1108 #[test]
1109 fn test_arnoldi_iteration() {
1110 let a = array![
1112 [2.0, 1.0, 0.0, 0.0],
1113 [0.0, 2.0, 1.0, 0.0],
1114 [0.0, 0.0, 2.0, 1.0],
1115 [0.0, 0.0, 0.0, 2.0],
1116 ];
1117 let v = array![1.0, 0.0, 0.0, 0.0];
1118
1119 let result = ArnoldiIteration::run(&a.view(), &v.view(), 3, None).expect("Arnoldi failed");
1120
1121 let vt_v = matmul_dense(&result.v.t(), &result.v.view());
1123 for i in 0..result.m {
1124 for j in 0..result.m {
1125 let expected = if i == j { 1.0 } else { 0.0 };
1126 assert!(
1127 (vt_v[[i, j]] - expected).abs() < 1e-12,
1128 "V^T V not identity at ({},{}) = {}",
1129 i,
1130 j,
1131 vt_v[[i, j]]
1132 );
1133 }
1134 }
1135 }
1136
1137 #[test]
1138 fn test_lanczos_iteration() {
1139 let a = array![
1141 [4.0, 1.0, 0.0, 0.0],
1142 [1.0, 3.0, 1.0, 0.0],
1143 [0.0, 1.0, 2.0, 1.0],
1144 [0.0, 0.0, 1.0, 1.0],
1145 ];
1146 let v = array![1.0, 0.0, 0.0, 0.0];
1147
1148 let result = LanczosIteration::run(&a.view(), &v.view(), 3, None).expect("Lanczos failed");
1149
1150 let vt_v = matmul_dense(&result.v.t(), &result.v.view());
1152 for i in 0..result.m {
1153 for j in 0..result.m {
1154 let expected = if i == j { 1.0 } else { 0.0 };
1155 assert!(
1156 (vt_v[[i, j]] - expected).abs() < 1e-10,
1157 "Lanczos V^T V not identity at ({},{}) = {}",
1158 i,
1159 j,
1160 vt_v[[i, j]]
1161 );
1162 }
1163 }
1164 }
1165
1166 #[test]
1167 fn test_matrix_exp_krylov_scalar() {
1168 let a = array![[2.0_f64]];
1170 let v = array![3.0_f64];
1171 let t = 0.5;
1172 let result = MatrixExpKrylov::apply(&a.view(), &v.view(), t, None, None)
1173 .expect("MatrixExpKrylov failed");
1174
1175 let expected = 3.0 * (2.0_f64 * 0.5).exp();
1176 assert!(
1177 (result[0] - expected).abs() < 1e-8,
1178 "MatrixExpKrylov scalar: {} vs {}",
1179 result[0],
1180 expected
1181 );
1182 }
1183
1184 #[test]
1185 fn test_matrix_function_interpolation_identity() {
1186 let n = 4;
1188 let a = Array2::eye(n);
1189 let v: Array1<f64> = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0]);
1190
1191 let params = MatrixFunctionParams::new(MatrixFunctionType::Exponential)
1192 .with_max_krylov_dim(n)
1193 .with_tol(1e-10);
1194
1195 let result = MatrixFunctionInterpolation::apply(&a.view(), &v.view(), ¶ms)
1196 .expect("MatrixFunctionInterpolation failed");
1197
1198 let expected_scale = 1.0_f64.exp();
1199 for i in 0..n {
1200 let expected = expected_scale * v[i];
1201 assert!(
1202 (result[i] - expected).abs() < 1e-6,
1203 "exp(I) * v failed at {i}: {} vs {}",
1204 result[i],
1205 expected
1206 );
1207 }
1208 }
1209
1210 #[test]
1211 fn test_pade_expm_zero() {
1212 let a = Array2::<f64>::zeros((3, 3));
1213 let result = pade_expm(&a.view()).expect("pade_expm of zero failed");
1214 for i in 0..3 {
1216 for j in 0..3 {
1217 let expected = if i == j { 1.0 } else { 0.0 };
1218 assert!(
1219 (result[[i, j]] - expected).abs() < 1e-12,
1220 "pade_expm(0) failed at ({},{})={}",
1221 i,
1222 j,
1223 result[[i, j]]
1224 );
1225 }
1226 }
1227 }
1228
1229 #[test]
1230 fn test_lanczos_tridiagonal() {
1231 let a = array![[3.0, 1.0, 0.0], [1.0, 3.0, 1.0], [0.0, 1.0, 3.0],];
1232 let v = array![1.0, 1.0, 1.0];
1233
1234 let result = LanczosIteration::run(&a.view(), &v.view(), 3, None).expect("Lanczos failed");
1235 let t = LanczosIteration::tridiagonal_matrix(&result);
1236
1237 for i in 0..result.m {
1239 for j in 0..result.m {
1240 assert!((t[[i, j]] - t[[j, i]]).abs() < 1e-12);
1241 }
1242 }
1243 }
1244}