1use crate::any_scalar::AnyScalar;
39use crate::TensorVectorSpace;
40use anyhow::Result;
41use num_complex::Complex64;
42use std::sync::atomic::{AtomicUsize, Ordering};
43use std::time::{Duration, Instant};
44use tensor4all_tensorbackend::{
45 hermitian_exponential_first_column, lowest_hermitian_eigenpair, Matrix,
46};
47
48#[derive(Debug, thiserror::Error)]
50pub enum KrylovError {
51 #[error("{solver}: invalid options: {reason}")]
53 InvalidOptions {
54 solver: &'static str,
56 reason: String,
58 },
59 #[error("{solver}: initial vector has norm below the breakdown tolerance; provide a nonzero initial vector")]
61 ZeroInitialVector {
62 solver: &'static str,
64 },
65 #[error("gmres_affine: at least one affine coefficient must be nonzero")]
67 NoAffineCoefficient,
68 #[error("{solver}: projected operator is not Hermitian: {source}")]
70 NonHermitian {
71 solver: &'static str,
73 #[source]
75 source: anyhow::Error,
76 },
77 #[error("{solver}: {source}")]
80 Operation {
81 solver: &'static str,
83 #[source]
85 source: anyhow::Error,
86 },
87}
88
89impl From<anyhow::Error> for KrylovError {
90 fn from(source: anyhow::Error) -> Self {
91 Self::Operation {
92 solver: "krylov",
93 source,
94 }
95 }
96}
97
98impl KrylovError {
99 fn from_anyhow_with_classification(source: anyhow::Error) -> Self {
100 match source.downcast::<KrylovError>() {
101 Ok(classified) => classified,
102 Err(source) => Self::from(source),
103 }
104 }
105}
106
107static GMRES_OP_PROFILE_COUNTER: AtomicUsize = AtomicUsize::new(0);
108
109#[derive(Debug, Clone)]
110struct GmresOpProfile {
111 started: Instant,
112 b_norm: Duration,
113 apply: Duration,
114 inner_product: Duration,
115 axpby: Duration,
116 norm: Duration,
117 scale: Duration,
118 triangular_solve: Duration,
119 solution_update: Duration,
120 apply_calls: usize,
121 inner_product_calls: usize,
122 axpby_calls: usize,
123 norm_calls: usize,
124 scale_calls: usize,
125 triangular_solve_calls: usize,
126 solution_update_calls: usize,
127}
128
129impl Default for GmresOpProfile {
130 fn default() -> Self {
131 Self {
132 started: Instant::now(),
133 b_norm: Duration::ZERO,
134 apply: Duration::ZERO,
135 inner_product: Duration::ZERO,
136 axpby: Duration::ZERO,
137 norm: Duration::ZERO,
138 scale: Duration::ZERO,
139 triangular_solve: Duration::ZERO,
140 solution_update: Duration::ZERO,
141 apply_calls: 0,
142 inner_product_calls: 0,
143 axpby_calls: 0,
144 norm_calls: 0,
145 scale_calls: 0,
146 triangular_solve_calls: 0,
147 solution_update_calls: 0,
148 }
149 }
150}
151
152impl GmresOpProfile {
153 fn measured(&self) -> Duration {
154 self.b_norm
155 + self.apply
156 + self.inner_product
157 + self.axpby
158 + self.norm
159 + self.scale
160 + self.triangular_solve
161 + self.solution_update
162 }
163
164 fn print(&self, id: usize, iterations: usize, residual_norm: f64, converged: bool) {
165 let total = self.started.elapsed();
166 let other = total.saturating_sub(self.measured());
167 eprintln!(
168 "T4A gmres_op_profile #{id}: iterations={iterations} residual={residual_norm:.6e} converged={converged} total_ms={:.3} apply_ms={:.3} apply_calls={} inner_ms={:.3} inner_calls={} axpby_ms={:.3} axpby_calls={} norm_ms={:.3} norm_calls={} scale_ms={:.3} scale_calls={} update_ms={:.3} update_calls={} triangular_ms={:.3} triangular_calls={} b_norm_ms={:.3} other_ms={:.3}",
169 total.as_secs_f64() * 1000.0,
170 self.apply.as_secs_f64() * 1000.0,
171 self.apply_calls,
172 self.inner_product.as_secs_f64() * 1000.0,
173 self.inner_product_calls,
174 self.axpby.as_secs_f64() * 1000.0,
175 self.axpby_calls,
176 self.norm.as_secs_f64() * 1000.0,
177 self.norm_calls,
178 self.scale.as_secs_f64() * 1000.0,
179 self.scale_calls,
180 self.solution_update.as_secs_f64() * 1000.0,
181 self.solution_update_calls,
182 self.triangular_solve.as_secs_f64() * 1000.0,
183 self.triangular_solve_calls,
184 self.b_norm.as_secs_f64() * 1000.0,
185 other.as_secs_f64() * 1000.0,
186 );
187 }
188}
189
190#[derive(Debug, Clone)]
205pub struct GmresOptions {
206 pub max_iter: usize,
209
210 pub rtol: f64,
214
215 pub max_restarts: usize,
219
220 pub verbose: bool,
223
224 pub check_true_residual: bool,
230}
231
232impl Default for GmresOptions {
233 fn default() -> Self {
234 Self {
235 max_iter: 100,
236 rtol: 1e-10,
237 max_restarts: 10,
238 verbose: false,
239 check_true_residual: false,
240 }
241 }
242}
243
244#[derive(Debug, Clone, Copy, PartialEq)]
245enum GmresTolerance {
246 Relative(f64),
247 Absolute(f64),
248}
249
250impl GmresTolerance {
251 fn residual_value(self, residual_norm: f64, b_norm: f64) -> f64 {
252 match self {
253 Self::Relative(_) => residual_norm / b_norm,
254 Self::Absolute(_) => residual_norm,
255 }
256 }
257
258 fn is_converged(self, residual_norm: f64, b_norm: f64) -> bool {
259 match self {
260 Self::Relative(rtol) => residual_norm / b_norm < rtol,
261 Self::Absolute(atol) => residual_norm < atol,
262 }
263 }
264}
265
266#[derive(Debug, Clone)]
281pub struct GmresResult<T> {
282 pub solution: T,
284
285 pub iterations: usize,
287
288 pub residual_norm: f64,
290
291 pub converged: bool,
293}
294
295#[derive(Debug, Clone)]
311pub struct HermitianLanczosOptions {
312 pub max_iter: usize,
317
318 pub rtol: f64,
323
324 pub atol: f64,
328
329 pub breakdown_tol: f64,
335
336 pub hermitian_tol: f64,
341
342 pub verbose: bool,
346}
347
348impl Default for HermitianLanczosOptions {
349 fn default() -> Self {
350 Self {
351 max_iter: 64,
352 rtol: 1.0e-10,
353 atol: 0.0,
354 breakdown_tol: 1.0e-14,
355 hermitian_tol: 1.0e-10,
356 verbose: false,
357 }
358 }
359}
360
361#[derive(Debug, Clone)]
378pub struct HermitianLanczosResult<T> {
379 pub eigenvalue: f64,
381
382 pub eigenvector: T,
384
385 pub iterations: usize,
387
388 pub residual_norm: f64,
390
391 pub converged: bool,
393}
394
395#[derive(Debug, Clone)]
414pub struct HermitianKrylovExpmOptions {
415 pub max_iter: usize,
417 pub max_time_splits: usize,
419 pub tol: f64,
421 pub breakdown_tol: f64,
423 pub hermitian_tol: f64,
425 pub verbose: bool,
427}
428
429impl Default for HermitianKrylovExpmOptions {
430 fn default() -> Self {
431 Self {
432 max_iter: 30,
433 max_time_splits: 100,
434 tol: 1.0e-12,
435 breakdown_tol: 1.0e-14,
436 hermitian_tol: 1.0e-10,
437 verbose: false,
438 }
439 }
440}
441
442#[derive(Debug, Clone)]
458pub struct HermitianKrylovExpmResult<T> {
459 pub output: T,
461 pub iterations: usize,
463 pub matvecs: usize,
465 pub error_estimate: f64,
467 pub converged: bool,
469 pub time_splits: usize,
471}
472
473#[derive(Debug, Clone)]
474struct HermitianRitzState {
475 eigenvalue: f64,
476 coefficients: Vec<Complex64>,
477 iterations: usize,
478 residual_estimate: f64,
479}
480
481pub fn hermitian_lanczos_lowest_eigenpair<T, F>(
518 apply_a: F,
519 initial: &T,
520 options: &HermitianLanczosOptions,
521) -> std::result::Result<HermitianLanczosResult<T>, KrylovError>
522where
523 T: TensorVectorSpace,
524 F: Fn(&T) -> Result<T>,
525{
526 hermitian_lanczos_lowest_eigenpair_impl(apply_a, initial, options)
527 .map_err(KrylovError::from_anyhow_with_classification)
528}
529
530fn hermitian_lanczos_lowest_eigenpair_impl<T, F>(
531 apply_a: F,
532 initial: &T,
533 options: &HermitianLanczosOptions,
534) -> Result<HermitianLanczosResult<T>>
535where
536 T: TensorVectorSpace,
537 F: Fn(&T) -> Result<T>,
538{
539 let basis_capacity = validate_hermitian_lanczos_options(options)?;
540 initial.validate()?;
541
542 let initial_norm = initial.norm()?;
543 if initial_norm <= options.breakdown_tol {
544 return Err(anyhow::Error::new(KrylovError::ZeroInitialVector {
545 solver: "hermitian_lanczos_lowest_eigenpair",
546 }));
547 }
548
549 let mut basis = Vec::with_capacity(basis_capacity);
550 basis.push(initial.scale(AnyScalar::new_real(1.0 / initial_norm))?);
551 let mut h_cols: Vec<Vec<Complex64>> = Vec::with_capacity(options.max_iter);
552 let mut last_ritz: Option<HermitianRitzState> = None;
553
554 for j in 0..options.max_iter {
555 let w = apply_a(&basis[j])?;
556 if j == 0 {
557 w.validate()?;
558 }
559
560 let mut h_col = Vec::with_capacity(j + 2);
561 let mut w_orth = w;
562
563 for v_i in basis.iter().take(j + 1) {
564 let h_ij = v_i.inner_product(&w_orth)?;
565 h_col.push(any_scalar_to_complex(&h_ij));
566 let neg_h_ij = AnyScalar::new_real(0.0) - h_ij;
567 w_orth = w_orth.axpby(AnyScalar::new_real(1.0), v_i, neg_h_ij)?;
568 }
569
570 for (i, v_i) in basis.iter().take(j + 1).enumerate() {
571 let correction = v_i.inner_product(&w_orth)?;
572 h_col[i] += any_scalar_to_complex(&correction);
573 let neg_correction = AnyScalar::new_real(0.0) - correction;
574 w_orth = w_orth.axpby(AnyScalar::new_real(1.0), v_i, neg_correction)?;
575 }
576
577 let beta = w_orth.norm()?;
578 h_col.push(Complex64::new(beta, 0.0));
579 h_cols.push(h_col);
580
581 let subspace_dim = j + 1;
582 let projected = projected_matrix_from_columns(&h_cols, subspace_dim)?;
583 let ritz =
584 lowest_hermitian_eigenpair(&projected, options.hermitian_tol).map_err(|err| {
585 anyhow::Error::new(KrylovError::NonHermitian {
586 solver: "hermitian_lanczos_lowest_eigenpair",
587 source: anyhow::anyhow!("projected operator is not Hermitian: {err}"),
588 })
589 })?;
590 let residual_estimate = beta * ritz.eigenvector[subspace_dim - 1].norm();
591 let threshold = hermitian_lanczos_threshold(ritz.eigenvalue, options);
592 let estimate_converged = residual_estimate <= threshold;
593
594 if options.verbose {
595 eprintln!(
596 "Hermitian Lanczos iter {}: eigenvalue={:.16e} residual_estimate={:.6e} threshold={:.6e}",
597 subspace_dim, ritz.eigenvalue, residual_estimate, threshold
598 );
599 }
600
601 let ritz_state = HermitianRitzState {
602 eigenvalue: ritz.eigenvalue,
603 iterations: subspace_dim,
604 residual_estimate,
605 coefficients: ritz.eigenvector,
606 };
607 if estimate_converged || beta <= options.breakdown_tol {
608 let result = finalize_hermitian_lanczos_result(
609 &apply_a,
610 &basis[..subspace_dim],
611 &ritz_state,
612 options,
613 )?;
614 if result.converged || beta <= options.breakdown_tol {
615 return Ok(result);
616 }
617 }
618 last_ritz = Some(ritz_state);
619 basis.push(w_orth.scale(AnyScalar::new_real(1.0 / beta))?);
620 }
621
622 let ritz_state = last_ritz.ok_or_else(|| {
623 anyhow::Error::new(KrylovError::InvalidOptions {
624 solver: "hermitian_lanczos_lowest_eigenpair",
625 reason: "max_iter must be greater than zero".to_string(),
626 })
627 })?;
628 finalize_hermitian_lanczos_result(
629 &apply_a,
630 &basis[..ritz_state.iterations],
631 &ritz_state,
632 options,
633 )
634}
635
636pub fn hermitian_krylov_expm_multiply<T, F>(
694 apply_a: F,
695 exponent: Complex64,
696 initial: &T,
697 options: &HermitianKrylovExpmOptions,
698) -> std::result::Result<HermitianKrylovExpmResult<T>, KrylovError>
699where
700 T: TensorVectorSpace,
701 F: FnMut(&T) -> Result<T>,
702{
703 hermitian_krylov_expm_multiply_impl(apply_a, exponent, initial, options)
704 .map_err(KrylovError::from_anyhow_with_classification)
705}
706
707fn hermitian_krylov_expm_multiply_impl<T, F>(
708 mut apply_a: F,
709 exponent: Complex64,
710 initial: &T,
711 options: &HermitianKrylovExpmOptions,
712) -> Result<HermitianKrylovExpmResult<T>>
713where
714 T: TensorVectorSpace,
715 F: FnMut(&T) -> Result<T>,
716{
717 validate_hermitian_krylov_expm_options(options)?;
718 initial.validate()?;
719
720 if exponent == Complex64::new(0.0, 0.0) {
721 return Ok(HermitianKrylovExpmResult {
722 output: initial.clone(),
723 iterations: 0,
724 matvecs: 0,
725 error_estimate: 0.0,
726 converged: true,
727 time_splits: 1,
728 });
729 }
730 if initial.norm()? <= options.breakdown_tol {
731 return Ok(HermitianKrylovExpmResult {
732 output: initial.clone(),
733 iterations: 0,
734 matvecs: 0,
735 error_estimate: 0.0,
736 converged: true,
737 time_splits: 1,
738 });
739 }
740
741 let mut splits = 1usize;
742 loop {
743 let step_exponent = exponent / splits as f64;
744 let mut output = initial.clone();
745 let mut iterations = 0usize;
746 let mut matvecs = 0usize;
747 let mut max_error = 0.0_f64;
748 let mut converged = true;
749
750 for _ in 0..splits {
751 let result =
752 hermitian_krylov_expm_multiply_once(&mut apply_a, step_exponent, &output, options)?;
753 iterations += result.iterations;
754 matvecs += result.matvecs;
755 max_error = max_error.max(result.error_estimate);
756 output = result.output;
757 if !result.converged {
758 converged = false;
759 break;
760 }
761 }
762
763 if converged {
764 return Ok(HermitianKrylovExpmResult {
765 output,
766 iterations,
767 matvecs,
768 error_estimate: max_error,
769 converged: true,
770 time_splits: splits,
771 });
772 }
773
774 if options.verbose {
775 eprintln!(
776 "Hermitian Krylov expm did not converge with {splits} time split(s); retrying"
777 );
778 }
779 if splits >= options.max_time_splits {
780 return Err(anyhow::anyhow!(
781 "hermitian_krylov_expm_multiply did not converge within max_time_splits={} and max_iter={}",
782 options.max_time_splits,
783 options.max_iter
784 ));
785 }
786 splits = splits.saturating_mul(2).min(options.max_time_splits);
787 }
788}
789
790fn hermitian_krylov_expm_multiply_once<T, F>(
791 apply_a: &mut F,
792 exponent: Complex64,
793 initial: &T,
794 options: &HermitianKrylovExpmOptions,
795) -> Result<HermitianKrylovExpmResult<T>>
796where
797 T: TensorVectorSpace,
798 F: FnMut(&T) -> Result<T>,
799{
800 let initial_norm = initial.norm()?;
801 if initial_norm <= options.breakdown_tol {
802 return Ok(HermitianKrylovExpmResult {
803 output: initial.clone(),
804 iterations: 0,
805 matvecs: 0,
806 error_estimate: 0.0,
807 converged: true,
808 time_splits: 1,
809 });
810 }
811
812 let basis_capacity =
813 checked_krylov_capacity(options.max_iter, "hermitian_krylov_expm_multiply")?;
814 let mut basis = Vec::with_capacity(basis_capacity);
815 basis.push(initial.scale(AnyScalar::new_real(1.0 / initial_norm))?);
816 let mut h_cols: Vec<Vec<Complex64>> = Vec::with_capacity(options.max_iter);
817 let mut last_coefficients: Option<Vec<Complex64>> = None;
818 let mut last_error_estimate = f64::INFINITY;
819 let mut last_subspace_dim = 0usize;
820 let threshold = options.tol * initial_norm.max(1.0);
821
822 for j in 0..options.max_iter {
823 let w = apply_a(&basis[j])?;
824 if j == 0 {
825 w.validate()?;
826 }
827 let mut w_orth = w;
828 let mut h_col = Vec::with_capacity(j + 2);
829
830 for v_i in basis.iter().take(j + 1) {
831 let h_ij = v_i.inner_product(&w_orth)?;
832 h_col.push(any_scalar_to_complex(&h_ij));
833 let neg_h_ij = AnyScalar::new_real(0.0) - h_ij;
834 w_orth = w_orth.axpby(AnyScalar::new_real(1.0), v_i, neg_h_ij)?;
835 }
836
837 for (i, v_i) in basis.iter().take(j + 1).enumerate() {
838 let correction = v_i.inner_product(&w_orth)?;
839 h_col[i] += any_scalar_to_complex(&correction);
840 let neg_correction = AnyScalar::new_real(0.0) - correction;
841 w_orth = w_orth.axpby(AnyScalar::new_real(1.0), v_i, neg_correction)?;
842 }
843
844 let beta_next = w_orth.norm()?;
845 h_col.push(Complex64::new(beta_next, 0.0));
846 h_cols.push(h_col);
847
848 let subspace_dim = j + 1;
849 let projected = projected_matrix_from_columns(&h_cols, subspace_dim)?;
850 let coefficients =
851 hermitian_exponential_first_column(&projected, exponent, options.hermitian_tol)
852 .map_err(|err| anyhow::anyhow!("projected operator is not Hermitian: {err}"))?;
853 let error_estimate = if beta_next <= options.breakdown_tol {
854 0.0
855 } else {
856 initial_norm * beta_next * coefficients[subspace_dim - 1].norm()
857 };
858 let converged = beta_next <= options.breakdown_tol || error_estimate <= threshold;
859
860 if options.verbose {
861 eprintln!(
862 "Hermitian Krylov expm iter {}: residual_estimate={:.6e} threshold={:.6e}",
863 subspace_dim, error_estimate, threshold
864 );
865 }
866
867 if converged {
868 let output = finalize_hermitian_krylov_expm_output(
869 &basis[..subspace_dim],
870 &coefficients,
871 initial_norm,
872 options.hermitian_tol,
873 )?;
874 return Ok(HermitianKrylovExpmResult {
875 output,
876 iterations: subspace_dim,
877 matvecs: subspace_dim,
878 error_estimate,
879 converged: true,
880 time_splits: 1,
881 });
882 }
883 last_coefficients = Some(coefficients);
884 last_error_estimate = error_estimate;
885 last_subspace_dim = subspace_dim;
886 basis.push(w_orth.scale(AnyScalar::new_real(1.0 / beta_next))?);
887 }
888
889 let coefficients = last_coefficients.ok_or_else(|| {
890 anyhow::anyhow!("hermitian_krylov_expm_multiply: max_iter must be greater than zero")
891 })?;
892 let output = finalize_hermitian_krylov_expm_output(
893 &basis[..last_subspace_dim],
894 &coefficients,
895 initial_norm,
896 options.hermitian_tol,
897 )?;
898 Ok(HermitianKrylovExpmResult {
899 output,
900 iterations: last_subspace_dim,
901 matvecs: last_subspace_dim,
902 error_estimate: last_error_estimate,
903 converged: false,
904 time_splits: 1,
905 })
906}
907
908fn finalize_hermitian_krylov_expm_output<T>(
909 basis: &[T],
910 coefficients: &[Complex64],
911 initial_norm: f64,
912 hermitian_tol: f64,
913) -> Result<T>
914where
915 T: TensorVectorSpace,
916{
917 let unit_output = combine_basis_with_complex_coefficients(
918 basis,
919 coefficients,
920 hermitian_tol,
921 "hermitian_krylov_expm_multiply",
922 )?;
923 unit_output
924 .scale(AnyScalar::new_real(initial_norm))
925 .map_err(anyhow::Error::new)
926}
927
928pub fn gmres<T, F>(
955 apply_a: F,
956 b: &T,
957 x0: &T,
958 options: &GmresOptions,
959) -> std::result::Result<GmresResult<T>, KrylovError>
960where
961 T: TensorVectorSpace,
962 F: Fn(&T) -> Result<T>,
963{
964 gmres_impl(
965 apply_a,
966 b,
967 x0,
968 options,
969 GmresTolerance::Relative(options.rtol),
970 None,
971 )
972 .map_err(KrylovError::from_anyhow_with_classification)
973}
974
975pub fn gmres_with_absolute_tolerance<T, F>(
988 apply_a: F,
989 b: &T,
990 x0: &T,
991 options: &GmresOptions,
992 atol: f64,
993) -> std::result::Result<GmresResult<T>, KrylovError>
994where
995 T: TensorVectorSpace,
996 F: Fn(&T) -> Result<T>,
997{
998 gmres_impl(
999 apply_a,
1000 b,
1001 x0,
1002 options,
1003 GmresTolerance::Absolute(atol),
1004 None,
1005 )
1006 .map_err(KrylovError::from_anyhow_with_classification)
1007}
1008
1009pub fn gmres_affine<T, F>(
1022 apply_a: F,
1023 b: &T,
1024 x0: &T,
1025 a0: AnyScalar,
1026 a1: AnyScalar,
1027 options: &GmresOptions,
1028) -> std::result::Result<GmresResult<T>, KrylovError>
1029where
1030 T: TensorVectorSpace,
1031 F: Fn(&T) -> Result<T>,
1032{
1033 gmres_affine_impl(
1034 apply_a,
1035 b,
1036 x0,
1037 a0,
1038 a1,
1039 options,
1040 GmresTolerance::Relative(options.rtol),
1041 )
1042 .map_err(KrylovError::from_anyhow_with_classification)
1043}
1044
1045pub fn gmres_affine_with_absolute_tolerance<T, F>(
1059 apply_a: F,
1060 b: &T,
1061 x0: &T,
1062 a0: AnyScalar,
1063 a1: AnyScalar,
1064 options: &GmresOptions,
1065 atol: f64,
1066) -> std::result::Result<GmresResult<T>, KrylovError>
1067where
1068 T: TensorVectorSpace,
1069 F: Fn(&T) -> Result<T>,
1070{
1071 gmres_affine_impl(
1072 apply_a,
1073 b,
1074 x0,
1075 a0,
1076 a1,
1077 options,
1078 GmresTolerance::Absolute(atol),
1079 )
1080 .map_err(KrylovError::from_anyhow_with_classification)
1081}
1082
1083fn gmres_affine_impl<T, F>(
1084 apply_a: F,
1085 b: &T,
1086 x0: &T,
1087 a0: AnyScalar,
1088 a1: AnyScalar,
1089 options: &GmresOptions,
1090 tolerance: GmresTolerance,
1091) -> Result<GmresResult<T>>
1092where
1093 T: TensorVectorSpace,
1094 F: Fn(&T) -> Result<T>,
1095{
1096 b.validate()?;
1097 x0.validate()?;
1098
1099 let profile_enabled = std::env::var_os("T4A_GMRES_OP_PROFILE").is_some();
1100 let profile_id = if profile_enabled {
1101 GMRES_OP_PROFILE_COUNTER.fetch_add(1, Ordering::Relaxed)
1102 } else {
1103 0
1104 };
1105 let mut profile = GmresOpProfile::default();
1106 let mut total_iters = 0usize;
1107
1108 macro_rules! finish {
1109 ($result:expr) => {{
1110 let result = $result;
1111 if profile_enabled {
1112 profile.print(
1113 profile_id,
1114 result.iterations,
1115 result.residual_norm,
1116 result.converged,
1117 );
1118 }
1119 return Ok(result);
1120 }};
1121 }
1122
1123 let started = Instant::now();
1124 let b_norm = b.norm()?;
1125 if profile_enabled {
1126 profile.b_norm += started.elapsed();
1127 }
1128 if b_norm < 1e-15 {
1129 finish!(GmresResult {
1130 solution: x0.clone(),
1131 iterations: 0,
1132 residual_norm: 0.0,
1133 converged: true,
1134 });
1135 }
1136 if a0.is_zero() && a1.is_zero() {
1137 return Err(anyhow::Error::new(KrylovError::NoAffineCoefficient));
1138 }
1139 if a1.is_zero() {
1140 let started = Instant::now();
1141 let solution = b.scale(AnyScalar::new_real(1.0) / a0)?;
1142 if profile_enabled {
1143 profile.scale += started.elapsed();
1144 profile.scale_calls += 1;
1145 }
1146 finish!(GmresResult {
1147 solution,
1148 iterations: 0,
1149 residual_norm: 0.0,
1150 converged: true,
1151 });
1152 }
1153
1154 let mut x = x0.clone();
1155
1156 for restart in 0..options.max_restarts {
1157 let started = Instant::now();
1158 let ax = apply_a(&x)?;
1159 if profile_enabled {
1160 profile.apply += started.elapsed();
1161 profile.apply_calls += 1;
1162 }
1163 if restart == 0 {
1164 ax.validate()?;
1165 }
1166 let started = Instant::now();
1167 let affine_x = x.axpby(a0.clone(), &ax, a1.clone())?;
1168 if profile_enabled {
1169 profile.axpby += started.elapsed();
1170 profile.axpby_calls += 1;
1171 }
1172 let started = Instant::now();
1173 let r = b.axpby(
1174 AnyScalar::new_real(1.0),
1175 &affine_x,
1176 AnyScalar::new_real(-1.0),
1177 )?;
1178 if profile_enabled {
1179 profile.axpby += started.elapsed();
1180 profile.axpby_calls += 1;
1181 }
1182 let started = Instant::now();
1183 let r_norm = r.norm()?;
1184 if profile_enabled {
1185 profile.norm += started.elapsed();
1186 profile.norm_calls += 1;
1187 }
1188 let residual_value = tolerance.residual_value(r_norm, b_norm);
1189 if options.verbose {
1190 eprintln!(
1191 "GMRES restart {}: initial residual = {:.6e}",
1192 restart, residual_value
1193 );
1194 }
1195 if tolerance.is_converged(r_norm, b_norm) {
1196 finish!(GmresResult {
1197 solution: x,
1198 iterations: total_iters,
1199 residual_norm: residual_value,
1200 converged: true,
1201 });
1202 }
1203
1204 let cycle_max_iter = options.max_iter;
1205 let mut v_basis: Vec<T> =
1206 Vec::with_capacity(checked_krylov_capacity(cycle_max_iter, "gmres")?);
1207 let started = Instant::now();
1208 v_basis.push(r.scale(AnyScalar::new_real(1.0 / r_norm))?);
1209 if profile_enabled {
1210 profile.scale += started.elapsed();
1211 profile.scale_calls += 1;
1212 }
1213
1214 let mut h_matrix: Vec<Vec<AnyScalar>> = Vec::with_capacity(cycle_max_iter);
1215 let mut cs: Vec<AnyScalar> = Vec::with_capacity(cycle_max_iter);
1216 let mut sn: Vec<AnyScalar> = Vec::with_capacity(cycle_max_iter);
1217 let mut g: Vec<AnyScalar> = vec![AnyScalar::new_real(r_norm)];
1218 let mut solution_already_updated = false;
1219
1220 for j in 0..cycle_max_iter {
1221 total_iters += 1;
1222
1223 let started = Instant::now();
1224 let w = apply_a(&v_basis[j])?;
1225 if profile_enabled {
1226 profile.apply += started.elapsed();
1227 profile.apply_calls += 1;
1228 }
1229 let mut h_a_col: Vec<AnyScalar> = Vec::with_capacity(j + 2);
1230 let mut w_orth = w;
1231
1232 for v_i in v_basis.iter().take(j + 1) {
1233 let started = Instant::now();
1234 let h_ij = v_i.inner_product(&w_orth)?;
1235 if profile_enabled {
1236 profile.inner_product += started.elapsed();
1237 profile.inner_product_calls += 1;
1238 }
1239 h_a_col.push(h_ij.clone());
1240 let neg_h_ij = AnyScalar::new_real(0.0) - h_ij;
1241 let started = Instant::now();
1242 w_orth = w_orth.axpby(AnyScalar::new_real(1.0), v_i, neg_h_ij)?;
1243 if profile_enabled {
1244 profile.axpby += started.elapsed();
1245 profile.axpby_calls += 1;
1246 }
1247 }
1248 for (i, v_i) in v_basis.iter().take(j + 1).enumerate() {
1249 let started = Instant::now();
1250 let correction = v_i.inner_product(&w_orth)?;
1251 if profile_enabled {
1252 profile.inner_product += started.elapsed();
1253 profile.inner_product_calls += 1;
1254 }
1255 h_a_col[i] = h_a_col[i].clone() + correction.clone();
1256 let neg_correction = AnyScalar::new_real(0.0) - correction;
1257 let started = Instant::now();
1258 w_orth = w_orth.axpby(AnyScalar::new_real(1.0), v_i, neg_correction)?;
1259 if profile_enabled {
1260 profile.axpby += started.elapsed();
1261 profile.axpby_calls += 1;
1262 }
1263 }
1264
1265 let started = Instant::now();
1266 let h_jp1_j_real = w_orth.norm()?;
1267 if profile_enabled {
1268 profile.norm += started.elapsed();
1269 profile.norm_calls += 1;
1270 }
1271 h_a_col.push(AnyScalar::new_real(h_jp1_j_real));
1272
1273 let mut h_col: Vec<AnyScalar> = Vec::with_capacity(j + 2);
1274 for h in h_a_col.iter().take(j) {
1275 h_col.push(a1.clone() * h.clone());
1276 }
1277 h_col.push(a0.clone() + a1.clone() * h_a_col[j].clone());
1278 h_col.push(a1.clone() * h_a_col[j + 1].clone());
1279
1280 #[allow(clippy::needless_range_loop)]
1281 for i in 0..j {
1282 let h_i = h_col[i].clone();
1283 let h_ip1 = h_col[i + 1].clone();
1284 let (new_hi, new_hip1) = apply_givens_rotation(&cs[i], &sn[i], &h_i, &h_ip1);
1285 h_col[i] = new_hi;
1286 h_col[i + 1] = new_hip1;
1287 }
1288
1289 let (c_j, s_j) = compute_givens_rotation(&h_col[j], &h_col[j + 1]);
1290 cs.push(c_j.clone());
1291 sn.push(s_j.clone());
1292
1293 let (new_hj, _) = apply_givens_rotation(&c_j, &s_j, &h_col[j], &h_col[j + 1]);
1294 h_col[j] = new_hj;
1295 h_col[j + 1] = AnyScalar::new_real(0.0);
1296
1297 let g_j = g[j].clone();
1298 let g_jp1 = AnyScalar::new_real(0.0);
1299 let (new_gj, new_gjp1) = apply_givens_rotation(&c_j, &s_j, &g_j, &g_jp1);
1300 g[j] = new_gj;
1301 let res_norm = new_gjp1.abs();
1302 g.push(new_gjp1);
1303
1304 h_matrix.push(h_col);
1305 let residual_value = tolerance.residual_value(res_norm, b_norm);
1306 if options.verbose {
1307 eprintln!("GMRES iter {}: residual = {:.6e}", j + 1, residual_value);
1308 }
1309
1310 if tolerance.is_converged(res_norm, b_norm) {
1311 let started = Instant::now();
1312 let y = solve_upper_triangular(&h_matrix, &g[..=j])?;
1313 if profile_enabled {
1314 profile.triangular_solve += started.elapsed();
1315 profile.triangular_solve_calls += 1;
1316 }
1317 let started = Instant::now();
1318 x = update_solution(&x, &v_basis[..=j], &y)?;
1319 if profile_enabled {
1320 profile.solution_update += started.elapsed();
1321 profile.solution_update_calls += 1;
1322 }
1323 if options.check_true_residual {
1324 let started = Instant::now();
1325 let ax_check = apply_a(&x)?;
1326 if profile_enabled {
1327 profile.apply += started.elapsed();
1328 profile.apply_calls += 1;
1329 }
1330 let started = Instant::now();
1331 let affine_check = x.axpby(a0.clone(), &ax_check, a1.clone())?;
1332 if profile_enabled {
1333 profile.axpby += started.elapsed();
1334 profile.axpby_calls += 1;
1335 }
1336 let started = Instant::now();
1337 let r_check = b.axpby(
1338 AnyScalar::new_real(1.0),
1339 &affine_check,
1340 AnyScalar::new_real(-1.0),
1341 )?;
1342 if profile_enabled {
1343 profile.axpby += started.elapsed();
1344 profile.axpby_calls += 1;
1345 }
1346 let started = Instant::now();
1347 let true_abs_res = r_check.norm()?;
1348 if profile_enabled {
1349 profile.norm += started.elapsed();
1350 profile.norm_calls += 1;
1351 }
1352 let true_residual_value = tolerance.residual_value(true_abs_res, b_norm);
1353 if options.verbose {
1354 eprintln!(
1355 "GMRES true residual check: hessenberg={:.6e}, checked={:.6e}",
1356 residual_value, true_residual_value
1357 );
1358 }
1359 if tolerance.is_converged(true_abs_res, b_norm) {
1360 finish!(GmresResult {
1361 solution: x,
1362 iterations: total_iters,
1363 residual_norm: true_residual_value,
1364 converged: true,
1365 });
1366 }
1367 solution_already_updated = true;
1368 break;
1369 } else {
1370 finish!(GmresResult {
1371 solution: x,
1372 iterations: total_iters,
1373 residual_norm: residual_value,
1374 converged: true,
1375 });
1376 }
1377 }
1378
1379 if h_jp1_j_real > 1e-14 {
1380 let started = Instant::now();
1381 v_basis.push(w_orth.scale(AnyScalar::new_real(1.0 / h_jp1_j_real))?);
1382 if profile_enabled {
1383 profile.scale += started.elapsed();
1384 profile.scale_calls += 1;
1385 }
1386 } else {
1387 let started = Instant::now();
1388 let y = solve_upper_triangular(&h_matrix, &g[..=j])?;
1389 if profile_enabled {
1390 profile.triangular_solve += started.elapsed();
1391 profile.triangular_solve_calls += 1;
1392 }
1393 let started = Instant::now();
1394 x = update_solution(&x, &v_basis[..=j], &y)?;
1395 if profile_enabled {
1396 profile.solution_update += started.elapsed();
1397 profile.solution_update_calls += 1;
1398 }
1399 let started = Instant::now();
1400 let ax_final = apply_a(&x)?;
1401 if profile_enabled {
1402 profile.apply += started.elapsed();
1403 profile.apply_calls += 1;
1404 }
1405 let started = Instant::now();
1406 let affine_final = x.axpby(a0.clone(), &ax_final, a1.clone())?;
1407 if profile_enabled {
1408 profile.axpby += started.elapsed();
1409 profile.axpby_calls += 1;
1410 }
1411 let started = Instant::now();
1412 let r_final = b.axpby(
1413 AnyScalar::new_real(1.0),
1414 &affine_final,
1415 AnyScalar::new_real(-1.0),
1416 )?;
1417 if profile_enabled {
1418 profile.axpby += started.elapsed();
1419 profile.axpby_calls += 1;
1420 }
1421 let started = Instant::now();
1422 let final_abs_res = r_final.norm()?;
1423 if profile_enabled {
1424 profile.norm += started.elapsed();
1425 profile.norm_calls += 1;
1426 }
1427 let final_res = tolerance.residual_value(final_abs_res, b_norm);
1428 finish!(GmresResult {
1429 solution: x,
1430 iterations: total_iters,
1431 residual_norm: final_res,
1432 converged: tolerance.is_converged(final_abs_res, b_norm),
1433 });
1434 }
1435 }
1436
1437 if !solution_already_updated {
1438 let actual_iters = h_matrix.len();
1439 let started = Instant::now();
1440 let y = solve_upper_triangular(&h_matrix, &g[..actual_iters])?;
1441 if profile_enabled {
1442 profile.triangular_solve += started.elapsed();
1443 profile.triangular_solve_calls += 1;
1444 }
1445 let started = Instant::now();
1446 x = update_solution(&x, &v_basis[..actual_iters], &y)?;
1447 if profile_enabled {
1448 profile.solution_update += started.elapsed();
1449 profile.solution_update_calls += 1;
1450 }
1451 }
1452 }
1453
1454 let started = Instant::now();
1455 let ax_final = apply_a(&x)?;
1456 if profile_enabled {
1457 profile.apply += started.elapsed();
1458 profile.apply_calls += 1;
1459 }
1460 let started = Instant::now();
1461 let affine_final = x.axpby(a0, &ax_final, a1)?;
1462 if profile_enabled {
1463 profile.axpby += started.elapsed();
1464 profile.axpby_calls += 1;
1465 }
1466 let started = Instant::now();
1467 let r_final = b.axpby(
1468 AnyScalar::new_real(1.0),
1469 &affine_final,
1470 AnyScalar::new_real(-1.0),
1471 )?;
1472 if profile_enabled {
1473 profile.axpby += started.elapsed();
1474 profile.axpby_calls += 1;
1475 }
1476 let started = Instant::now();
1477 let final_abs_res = r_final.norm()?;
1478 if profile_enabled {
1479 profile.norm += started.elapsed();
1480 profile.norm_calls += 1;
1481 }
1482 let final_res = tolerance.residual_value(final_abs_res, b_norm);
1483
1484 finish!(GmresResult {
1485 solution: x,
1486 iterations: total_iters,
1487 residual_norm: final_res,
1488 converged: tolerance.is_converged(final_abs_res, b_norm),
1489 })
1490}
1491
1492pub fn gmres_with_total_iteration_limit<T, F>(
1507 apply_a: F,
1508 b: &T,
1509 x0: &T,
1510 options: &GmresOptions,
1511 max_total_iter: usize,
1512) -> std::result::Result<GmresResult<T>, KrylovError>
1513where
1514 T: TensorVectorSpace,
1515 F: Fn(&T) -> Result<T>,
1516{
1517 gmres_impl(
1518 apply_a,
1519 b,
1520 x0,
1521 options,
1522 GmresTolerance::Relative(options.rtol),
1523 Some(max_total_iter),
1524 )
1525 .map_err(KrylovError::from_anyhow_with_classification)
1526}
1527
1528fn gmres_impl<T, F>(
1529 apply_a: F,
1530 b: &T,
1531 x0: &T,
1532 options: &GmresOptions,
1533 tolerance: GmresTolerance,
1534 max_total_iter: Option<usize>,
1535) -> Result<GmresResult<T>>
1536where
1537 T: TensorVectorSpace,
1538 F: Fn(&T) -> Result<T>,
1539{
1540 checked_krylov_capacity(options.max_iter, "gmres")?;
1541
1542 b.validate()?;
1544 x0.validate()?;
1545
1546 let b_norm = b.norm()?;
1547 if b_norm < 1e-15 {
1548 return Ok(GmresResult {
1550 solution: x0.clone(),
1551 iterations: 0,
1552 residual_norm: 0.0,
1553 converged: true,
1554 });
1555 }
1556
1557 let mut x = x0.clone();
1558 let mut total_iters = 0;
1559
1560 for _restart in 0..options.max_restarts {
1561 let cycle_max_iter = match max_total_iter {
1562 Some(limit) => {
1563 let remaining = limit.saturating_sub(total_iters);
1564 if remaining == 0 {
1565 break;
1566 }
1567 options.max_iter.min(remaining)
1568 }
1569 None => options.max_iter,
1570 };
1571 if cycle_max_iter == 0 {
1572 break;
1573 }
1574
1575 let ax = apply_a(&x)?;
1577 if _restart == 0 {
1579 ax.validate()?;
1580 }
1581 let r = b.axpby(AnyScalar::new_real(1.0), &ax, AnyScalar::new_real(-1.0))?;
1583 let r_norm = r.norm()?;
1584 let residual_value = tolerance.residual_value(r_norm, b_norm);
1585
1586 if options.verbose {
1587 eprintln!(
1588 "GMRES restart {}: initial residual = {:.6e}",
1589 _restart, residual_value
1590 );
1591 }
1592
1593 if tolerance.is_converged(r_norm, b_norm) {
1594 return Ok(GmresResult {
1595 solution: x,
1596 iterations: total_iters,
1597 residual_norm: residual_value,
1598 converged: true,
1599 });
1600 }
1601
1602 let mut v_basis: Vec<T> =
1604 Vec::with_capacity(checked_krylov_capacity(cycle_max_iter, "gmres")?);
1605 let mut h_matrix: Vec<Vec<AnyScalar>> = Vec::with_capacity(cycle_max_iter);
1606
1607 let v0 = r.scale(AnyScalar::new_real(1.0 / r_norm))?;
1609 v_basis.push(v0);
1610
1611 let mut cs: Vec<AnyScalar> = Vec::with_capacity(cycle_max_iter);
1613 let mut sn: Vec<AnyScalar> = Vec::with_capacity(cycle_max_iter);
1614 let mut g: Vec<AnyScalar> = vec![AnyScalar::new_real(r_norm)]; let mut solution_already_updated = false;
1616
1617 for j in 0..cycle_max_iter {
1618 total_iters += 1;
1619
1620 let w = apply_a(&v_basis[j])?;
1622
1623 let mut h_col: Vec<AnyScalar> = Vec::with_capacity(j + 2);
1625 let mut w_orth = w;
1626
1627 for v_i in v_basis.iter().take(j + 1) {
1628 let h_ij = v_i.inner_product(&w_orth)?;
1629 h_col.push(h_ij.clone());
1630 let neg_h_ij = AnyScalar::new_real(0.0) - h_ij;
1632 w_orth = w_orth.axpby(AnyScalar::new_real(1.0), v_i, neg_h_ij)?;
1633 }
1634
1635 for (i, v_i) in v_basis.iter().take(j + 1).enumerate() {
1639 let correction = v_i.inner_product(&w_orth)?;
1640 h_col[i] = h_col[i].clone() + correction.clone();
1641 let neg_correction = AnyScalar::new_real(0.0) - correction;
1642 w_orth = w_orth.axpby(AnyScalar::new_real(1.0), v_i, neg_correction)?;
1643 }
1644
1645 let h_jp1_j_real = w_orth.norm()?;
1646 let h_jp1_j = AnyScalar::new_real(h_jp1_j_real);
1647 h_col.push(h_jp1_j);
1648
1649 #[allow(clippy::needless_range_loop)]
1651 for i in 0..j {
1652 let h_i = h_col[i].clone();
1653 let h_ip1 = h_col[i + 1].clone();
1654 let (new_hi, new_hip1) = apply_givens_rotation(&cs[i], &sn[i], &h_i, &h_ip1);
1655 h_col[i] = new_hi;
1656 h_col[i + 1] = new_hip1;
1657 }
1658
1659 let (c_j, s_j) = compute_givens_rotation(&h_col[j], &h_col[j + 1]);
1661 cs.push(c_j.clone());
1662 sn.push(s_j.clone());
1663
1664 let (new_hj, _) = apply_givens_rotation(&c_j, &s_j, &h_col[j], &h_col[j + 1]);
1666 h_col[j] = new_hj;
1667 h_col[j + 1] = AnyScalar::new_real(0.0);
1668
1669 let g_j = g[j].clone();
1671 let g_jp1 = AnyScalar::new_real(0.0);
1672 let (new_gj, new_gjp1) = apply_givens_rotation(&c_j, &s_j, &g_j, &g_jp1);
1673 g[j] = new_gj;
1674 let res_norm = new_gjp1.abs();
1675 g.push(new_gjp1);
1676
1677 h_matrix.push(h_col);
1678
1679 let residual_value = tolerance.residual_value(res_norm, b_norm);
1681
1682 if options.verbose {
1683 eprintln!("GMRES iter {}: residual = {:.6e}", j + 1, residual_value);
1684 }
1685
1686 if tolerance.is_converged(res_norm, b_norm) {
1687 let y = solve_upper_triangular(&h_matrix, &g[..=j])?;
1689 x = update_solution(&x, &v_basis[..=j], &y)?;
1690 if options.check_true_residual {
1691 let ax_check = apply_a(&x)?;
1692 let r_check = b.axpby(
1693 AnyScalar::new_real(1.0),
1694 &ax_check,
1695 AnyScalar::new_real(-1.0),
1696 )?;
1697 let true_abs_res = r_check.norm()?;
1698 let true_residual_value = tolerance.residual_value(true_abs_res, b_norm);
1699
1700 if options.verbose {
1701 eprintln!(
1702 "GMRES true residual check: hessenberg={:.6e}, checked={:.6e}",
1703 residual_value, true_residual_value
1704 );
1705 }
1706
1707 if tolerance.is_converged(true_abs_res, b_norm) {
1708 return Ok(GmresResult {
1709 solution: x,
1710 iterations: total_iters,
1711 residual_norm: true_residual_value,
1712 converged: true,
1713 });
1714 }
1715 solution_already_updated = true;
1716 break;
1717 } else {
1718 return Ok(GmresResult {
1719 solution: x,
1720 iterations: total_iters,
1721 residual_norm: residual_value,
1722 converged: true,
1723 });
1724 }
1725 }
1726
1727 if h_jp1_j_real > 1e-14 {
1729 let v_jp1 = w_orth.scale(AnyScalar::new_real(1.0 / h_jp1_j_real))?;
1730 v_basis.push(v_jp1);
1731 } else {
1732 let y = solve_upper_triangular(&h_matrix, &g[..=j])?;
1734 x = update_solution(&x, &v_basis[..=j], &y)?;
1735 let ax_final = apply_a(&x)?;
1736 let r_final = b.axpby(
1737 AnyScalar::new_real(1.0),
1738 &ax_final,
1739 AnyScalar::new_real(-1.0),
1740 )?;
1741 let final_abs_res = r_final.norm()?;
1742 let final_res = tolerance.residual_value(final_abs_res, b_norm);
1743 return Ok(GmresResult {
1744 solution: x,
1745 iterations: total_iters,
1746 residual_norm: final_res,
1747 converged: tolerance.is_converged(final_abs_res, b_norm),
1748 });
1749 }
1750 }
1751
1752 if !solution_already_updated {
1754 let actual_iters = h_matrix.len();
1755 let y = solve_upper_triangular(&h_matrix, &g[..actual_iters])?;
1756 x = update_solution(&x, &v_basis[..actual_iters], &y)?;
1757 }
1758 }
1759
1760 let ax_final = apply_a(&x)?;
1762 let r_final = b.axpby(
1763 AnyScalar::new_real(1.0),
1764 &ax_final,
1765 AnyScalar::new_real(-1.0),
1766 )?;
1767 let final_abs_res = r_final.norm()?;
1768 let final_res = tolerance.residual_value(final_abs_res, b_norm);
1769
1770 Ok(GmresResult {
1771 solution: x,
1772 iterations: total_iters,
1773 residual_norm: final_res,
1774 converged: tolerance.is_converged(final_abs_res, b_norm),
1775 })
1776}
1777
1778pub fn gmres_with_truncation<T, F, Tr>(
1824 apply_a: F,
1825 b: &T,
1826 x0: &T,
1827 options: &GmresOptions,
1828 truncate: Tr,
1829) -> std::result::Result<GmresResult<T>, KrylovError>
1830where
1831 T: TensorVectorSpace,
1832 F: Fn(&T) -> Result<T>,
1833 Tr: Fn(&mut T) -> Result<()>,
1834{
1835 gmres_with_truncation_impl(apply_a, b, x0, options, truncate)
1836 .map_err(KrylovError::from_anyhow_with_classification)
1837}
1838
1839fn gmres_with_truncation_impl<T, F, Tr>(
1840 apply_a: F,
1841 b: &T,
1842 x0: &T,
1843 options: &GmresOptions,
1844 truncate: Tr,
1845) -> Result<GmresResult<T>>
1846where
1847 T: TensorVectorSpace,
1848 F: Fn(&T) -> Result<T>,
1849 Tr: Fn(&mut T) -> Result<()>,
1850{
1851 checked_krylov_capacity(options.max_iter, "gmres_with_truncation")?;
1852
1853 b.validate()?;
1855 x0.validate()?;
1856
1857 let b_norm = b.norm()?;
1858 if b_norm < 1e-15 {
1859 return Ok(GmresResult {
1860 solution: x0.clone(),
1861 iterations: 0,
1862 residual_norm: 0.0,
1863 converged: true,
1864 });
1865 }
1866
1867 let mut x = x0.clone();
1868 let mut total_iters = 0;
1869
1870 for _restart in 0..options.max_restarts {
1871 let ax = apply_a(&x)?;
1872 if _restart == 0 {
1874 ax.validate()?;
1875 }
1876 let mut r = b.axpby(AnyScalar::new_real(1.0), &ax, AnyScalar::new_real(-1.0))?;
1877 truncate(&mut r)?;
1878 let r_norm = r.norm()?;
1879 let rel_res = r_norm / b_norm;
1880
1881 if options.verbose {
1882 eprintln!(
1883 "GMRES restart {}: initial residual = {:.6e}",
1884 _restart, rel_res
1885 );
1886 }
1887
1888 if rel_res < options.rtol {
1889 return Ok(GmresResult {
1890 solution: x,
1891 iterations: total_iters,
1892 residual_norm: rel_res,
1893 converged: true,
1894 });
1895 }
1896
1897 let mut v_basis: Vec<T> = Vec::with_capacity(checked_krylov_capacity(
1898 options.max_iter,
1899 "gmres_with_truncation",
1900 )?);
1901 let mut h_matrix: Vec<Vec<AnyScalar>> = Vec::with_capacity(options.max_iter);
1902
1903 let mut v0 = r.scale(AnyScalar::new_real(1.0 / r_norm))?;
1904 truncate(&mut v0)?;
1905 let v0_norm = v0.norm()?;
1910 let effective_g0 = if v0_norm > 1e-15 {
1911 v0 = v0.scale(AnyScalar::new_real(1.0 / v0_norm))?;
1912 r_norm * v0_norm
1916 } else {
1917 r_norm
1918 };
1919 v_basis.push(v0);
1920
1921 let mut cs: Vec<AnyScalar> = Vec::with_capacity(options.max_iter);
1922 let mut sn: Vec<AnyScalar> = Vec::with_capacity(options.max_iter);
1923 let mut g: Vec<AnyScalar> = vec![AnyScalar::new_real(effective_g0)];
1924 let mut solution_already_updated = false;
1925
1926 for j in 0..options.max_iter {
1927 total_iters += 1;
1928
1929 let w = apply_a(&v_basis[j])?;
1930
1931 let mut h_col: Vec<AnyScalar> = Vec::with_capacity(j + 2);
1932 let mut w_orth = w;
1933
1934 for v_i in v_basis.iter().take(j + 1) {
1935 let h_ij = v_i.inner_product(&w_orth)?;
1936 h_col.push(h_ij.clone());
1937 let neg_h_ij = AnyScalar::new_real(0.0) - h_ij;
1938 w_orth = w_orth.axpby(AnyScalar::new_real(1.0), v_i, neg_h_ij)?;
1939 }
1940
1941 const REORTH_THRESHOLD: f64 = 1e-12;
1946 const MAX_REORTH_ITERS: usize = 10;
1947
1948 let mut reorth_iter_count = 0;
1949 for reorth_iter in 0..MAX_REORTH_ITERS {
1950 reorth_iter_count = reorth_iter + 1;
1951 let norm_before_truncate = w_orth.norm()?;
1952 truncate(&mut w_orth)?;
1953 let norm_after_truncate = w_orth.norm()?;
1954
1955 let mut max_correction = 0.0;
1956 for (i, v_i) in v_basis.iter().enumerate() {
1957 let correction = v_i.inner_product(&w_orth)?;
1958 let correction_abs = correction.abs();
1959 if correction_abs > max_correction {
1960 max_correction = correction_abs;
1961 }
1962 if correction_abs > REORTH_THRESHOLD {
1963 let neg_correction = AnyScalar::new_real(0.0) - correction.clone();
1964 w_orth = w_orth.axpby(AnyScalar::new_real(1.0), v_i, neg_correction)?;
1965 h_col[i] = h_col[i].clone() + correction;
1967 }
1968 }
1969
1970 if options.verbose {
1971 eprintln!(
1972 " reorth iter {}: norm {:.6e} -> {:.6e}, max_correction = {:.6e}",
1973 reorth_iter, norm_before_truncate, norm_after_truncate, max_correction
1974 );
1975 }
1976
1977 if max_correction < REORTH_THRESHOLD {
1979 break;
1980 }
1981 }
1982
1983 if options.verbose && reorth_iter_count > 1 {
1984 eprintln!(" (needed {} reorth iterations)", reorth_iter_count);
1985 }
1986
1987 let h_jp1_j_real = w_orth.norm()?;
1988 let h_jp1_j = AnyScalar::new_real(h_jp1_j_real);
1989 h_col.push(h_jp1_j);
1990
1991 #[allow(clippy::needless_range_loop)]
1992 for i in 0..j {
1993 let h_i = h_col[i].clone();
1994 let h_ip1 = h_col[i + 1].clone();
1995 let (new_hi, new_hip1) = apply_givens_rotation(&cs[i], &sn[i], &h_i, &h_ip1);
1996 h_col[i] = new_hi;
1997 h_col[i + 1] = new_hip1;
1998 }
1999
2000 let (c_j, s_j) = compute_givens_rotation(&h_col[j], &h_col[j + 1]);
2001 cs.push(c_j.clone());
2002 sn.push(s_j.clone());
2003
2004 let (new_hj, _) = apply_givens_rotation(&c_j, &s_j, &h_col[j], &h_col[j + 1]);
2005 h_col[j] = new_hj;
2006 h_col[j + 1] = AnyScalar::new_real(0.0);
2007
2008 let g_j = g[j].clone();
2009 let g_jp1 = AnyScalar::new_real(0.0);
2010 let (new_gj, new_gjp1) = apply_givens_rotation(&c_j, &s_j, &g_j, &g_jp1);
2011 g[j] = new_gj;
2012 let res_norm = new_gjp1.abs();
2013 g.push(new_gjp1);
2014
2015 h_matrix.push(h_col);
2016
2017 let rel_res = res_norm / b_norm;
2018
2019 if options.verbose {
2020 eprintln!("GMRES iter {}: residual = {:.6e}", j + 1, rel_res);
2021 }
2022
2023 if rel_res < options.rtol {
2024 let y = solve_upper_triangular(&h_matrix, &g[..=j])?;
2025 x = update_solution_truncated(&x, &v_basis[..=j], &y, &truncate)?;
2026
2027 if options.check_true_residual {
2028 let ax_check = apply_a(&x)?;
2030 let mut r_check = b.axpby(
2031 AnyScalar::new_real(1.0),
2032 &ax_check,
2033 AnyScalar::new_real(-1.0),
2034 )?;
2035 truncate(&mut r_check)?;
2036 let true_rel_res = r_check.norm()? / b_norm;
2037
2038 if options.verbose {
2039 eprintln!(
2040 "GMRES true residual check: hessenberg={:.6e}, checked={:.6e}",
2041 rel_res, true_rel_res
2042 );
2043 }
2044
2045 if true_rel_res < options.rtol {
2046 return Ok(GmresResult {
2047 solution: x,
2048 iterations: total_iters,
2049 residual_norm: true_rel_res,
2050 converged: true,
2051 });
2052 }
2053 solution_already_updated = true;
2056 break;
2057 } else {
2058 return Ok(GmresResult {
2059 solution: x,
2060 iterations: total_iters,
2061 residual_norm: rel_res,
2062 converged: true,
2063 });
2064 }
2065 }
2066
2067 if h_jp1_j_real > 1e-14 {
2068 let v_jp1 = w_orth.scale(AnyScalar::new_real(1.0 / h_jp1_j_real))?;
2072 v_basis.push(v_jp1);
2075 } else {
2076 let y = solve_upper_triangular(&h_matrix, &g[..=j])?;
2077 x = update_solution_truncated(&x, &v_basis[..=j], &y, &truncate)?;
2078 let ax_final = apply_a(&x)?;
2079 let r_final = b.axpby(
2080 AnyScalar::new_real(1.0),
2081 &ax_final,
2082 AnyScalar::new_real(-1.0),
2083 )?;
2084 let final_res = r_final.norm()? / b_norm;
2085 return Ok(GmresResult {
2086 solution: x,
2087 iterations: total_iters,
2088 residual_norm: final_res,
2089 converged: final_res < options.rtol,
2090 });
2091 }
2092 }
2093
2094 if !solution_already_updated {
2095 let actual_iters = v_basis.len().min(options.max_iter);
2096 let y = solve_upper_triangular(&h_matrix, &g[..actual_iters])?;
2097 x = update_solution_truncated(&x, &v_basis[..actual_iters], &y, &truncate)?;
2098 }
2099 }
2100
2101 let ax_final = apply_a(&x)?;
2102 let r_final = b.axpby(
2103 AnyScalar::new_real(1.0),
2104 &ax_final,
2105 AnyScalar::new_real(-1.0),
2106 )?;
2107 let final_res = r_final.norm()? / b_norm;
2108
2109 Ok(GmresResult {
2110 solution: x,
2111 iterations: total_iters,
2112 residual_norm: final_res,
2113 converged: final_res < options.rtol,
2114 })
2115}
2116
2117#[derive(Debug, Clone)]
2139pub struct RestartGmresOptions {
2140 pub max_outer_iters: usize,
2143
2144 pub rtol: f64,
2148
2149 pub inner_max_iter: usize,
2152
2153 pub inner_max_restarts: usize,
2156
2157 pub min_reduction: Option<f64>,
2163
2164 pub inner_rtol: Option<f64>,
2168
2169 pub verbose: bool,
2172}
2173
2174impl Default for RestartGmresOptions {
2175 fn default() -> Self {
2176 Self {
2177 max_outer_iters: 20,
2178 rtol: 1e-10,
2179 inner_max_iter: 10,
2180 inner_max_restarts: 0,
2181 min_reduction: None,
2182 inner_rtol: None,
2183 verbose: false,
2184 }
2185 }
2186}
2187
2188impl RestartGmresOptions {
2189 pub fn new() -> Self {
2191 Self::default()
2192 }
2193
2194 pub fn with_max_outer_iters(mut self, max_outer_iters: usize) -> Self {
2196 self.max_outer_iters = max_outer_iters;
2197 self
2198 }
2199
2200 pub fn with_rtol(mut self, rtol: f64) -> Self {
2202 self.rtol = rtol;
2203 self
2204 }
2205
2206 pub fn with_inner_max_iter(mut self, inner_max_iter: usize) -> Self {
2208 self.inner_max_iter = inner_max_iter;
2209 self
2210 }
2211
2212 pub fn with_inner_max_restarts(mut self, inner_max_restarts: usize) -> Self {
2214 self.inner_max_restarts = inner_max_restarts;
2215 self
2216 }
2217
2218 pub fn with_min_reduction(mut self, min_reduction: f64) -> Self {
2220 self.min_reduction = Some(min_reduction);
2221 self
2222 }
2223
2224 pub fn with_inner_rtol(mut self, inner_rtol: f64) -> Self {
2226 self.inner_rtol = Some(inner_rtol);
2227 self
2228 }
2229
2230 pub fn with_verbose(mut self, verbose: bool) -> Self {
2232 self.verbose = verbose;
2233 self
2234 }
2235}
2236
2237#[derive(Debug, Clone)]
2256pub struct RestartGmresResult<T> {
2257 pub solution: T,
2259
2260 pub iterations: usize,
2262
2263 pub outer_iterations: usize,
2265
2266 pub residual_norm: f64,
2268
2269 pub converged: bool,
2271}
2272
2273pub fn restart_gmres_with_truncation<T, F, Tr>(
2326 apply_a: F,
2327 b: &T,
2328 x0: Option<&T>,
2329 options: &RestartGmresOptions,
2330 truncate: Tr,
2331) -> std::result::Result<RestartGmresResult<T>, KrylovError>
2332where
2333 T: TensorVectorSpace,
2334 F: Fn(&T) -> Result<T>,
2335 Tr: Fn(&mut T) -> Result<()>,
2336{
2337 restart_gmres_with_truncation_impl(apply_a, b, x0, options, truncate)
2338 .map_err(KrylovError::from_anyhow_with_classification)
2339}
2340
2341fn restart_gmres_with_truncation_impl<T, F, Tr>(
2342 apply_a: F,
2343 b: &T,
2344 x0: Option<&T>,
2345 options: &RestartGmresOptions,
2346 truncate: Tr,
2347) -> Result<RestartGmresResult<T>>
2348where
2349 T: TensorVectorSpace,
2350 F: Fn(&T) -> Result<T>,
2351 Tr: Fn(&mut T) -> Result<()>,
2352{
2353 b.validate()?;
2355 if let Some(x) = x0 {
2356 x.validate()?;
2357 }
2358
2359 let b_norm = b.norm()?;
2360 if b_norm < 1e-15 {
2361 let solution = match x0 {
2363 Some(x) => x.clone(),
2364 None => b.scale(AnyScalar::new_real(0.0))?,
2365 };
2366 return Ok(RestartGmresResult {
2367 solution,
2368 iterations: 0,
2369 outer_iterations: 0,
2370 residual_norm: 0.0,
2371 converged: true,
2372 });
2373 }
2374
2375 let mut x_is_zero = x0.is_none();
2379 let mut x = match x0 {
2380 Some(x) => x.clone(),
2381 None => b.scale(AnyScalar::new_real(0.0))?,
2382 };
2383
2384 let mut total_inner_iters = 0;
2385 let mut prev_residual_norm = f64::INFINITY;
2386
2387 let inner_options = GmresOptions {
2389 max_iter: options.inner_max_iter,
2390 rtol: options.inner_rtol.unwrap_or(0.1), max_restarts: options.inner_max_restarts.checked_add(1).ok_or_else(|| {
2392 anyhow::Error::new(KrylovError::InvalidOptions {
2393 solver: "restart_gmres_with_truncation",
2394 reason: "inner_max_restarts + 1 overflows usize".to_string(),
2395 })
2396 })?, verbose: options.verbose,
2398 check_true_residual: true, };
2400
2401 for outer_iter in 0..options.max_outer_iters {
2402 let ax = apply_a(&x)?;
2404 if outer_iter == 0 {
2406 ax.validate()?;
2407 }
2408 let mut r = b.axpby(AnyScalar::new_real(1.0), &ax, AnyScalar::new_real(-1.0))?;
2409 truncate(&mut r)?;
2410
2411 let r_norm = r.norm()?;
2412 let rel_res = r_norm / b_norm;
2413
2414 if options.verbose {
2415 eprintln!(
2416 "Restart GMRES outer iter {}: true residual = {:.6e}",
2417 outer_iter, rel_res
2418 );
2419 }
2420
2421 if rel_res < options.rtol {
2423 return Ok(RestartGmresResult {
2424 solution: x,
2425 iterations: total_inner_iters,
2426 outer_iterations: outer_iter,
2427 residual_norm: rel_res,
2428 converged: true,
2429 });
2430 }
2431
2432 if let Some(min_reduction) = options.min_reduction {
2434 if outer_iter > 0 && rel_res > prev_residual_norm * min_reduction {
2435 if options.verbose {
2436 eprintln!(
2437 "Restart GMRES stagnated: residual ratio = {:.6e} > {:.6e}",
2438 rel_res / prev_residual_norm,
2439 min_reduction
2440 );
2441 }
2442 return Ok(RestartGmresResult {
2443 solution: x,
2444 iterations: total_inner_iters,
2445 outer_iterations: outer_iter,
2446 residual_norm: rel_res,
2447 converged: false,
2448 });
2449 }
2450 }
2451 prev_residual_norm = rel_res;
2452
2453 let zero = r.scale(AnyScalar::new_real(0.0))?;
2456 let inner_result =
2457 gmres_with_truncation_impl(&apply_a, &r, &zero, &inner_options, &truncate)?;
2458
2459 total_inner_iters += inner_result.iterations;
2460
2461 if options.verbose {
2462 eprintln!(
2463 " Inner GMRES: {} iterations, residual = {:.6e}, converged = {}",
2464 inner_result.iterations, inner_result.residual_norm, inner_result.converged
2465 );
2466 }
2467
2468 if x_is_zero {
2472 x = inner_result.solution;
2473 x_is_zero = false;
2474 } else {
2475 x = x.axpby(
2476 AnyScalar::new_real(1.0),
2477 &inner_result.solution,
2478 AnyScalar::new_real(1.0),
2479 )?;
2480 }
2481 truncate(&mut x)?;
2482 }
2483
2484 let ax = apply_a(&x)?;
2487 let mut r = b.axpby(AnyScalar::new_real(1.0), &ax, AnyScalar::new_real(-1.0))?;
2488 truncate(&mut r)?;
2489 let final_rel_res = r.norm()? / b_norm;
2490
2491 Ok(RestartGmresResult {
2492 solution: x,
2493 iterations: total_inner_iters,
2494 outer_iterations: options.max_outer_iters,
2495 residual_norm: final_rel_res,
2496 converged: false,
2497 })
2498}
2499
2500fn validate_hermitian_lanczos_options(options: &HermitianLanczosOptions) -> Result<usize> {
2501 if options.max_iter == 0 {
2502 return Err(anyhow::Error::new(KrylovError::InvalidOptions {
2503 solver: "hermitian_lanczos_lowest_eigenpair",
2504 reason: "max_iter must be greater than zero".to_string(),
2505 }));
2506 }
2507 let basis_capacity =
2508 checked_krylov_capacity(options.max_iter, "hermitian_lanczos_lowest_eigenpair")?;
2509 anyhow::ensure!(
2510 options.rtol.is_finite() && options.rtol >= 0.0,
2511 "hermitian_lanczos_lowest_eigenpair: rtol must be finite and non-negative"
2512 );
2513 anyhow::ensure!(
2514 options.atol.is_finite() && options.atol >= 0.0,
2515 "hermitian_lanczos_lowest_eigenpair: atol must be finite and non-negative"
2516 );
2517 anyhow::ensure!(
2518 options.breakdown_tol.is_finite() && options.breakdown_tol >= 0.0,
2519 "hermitian_lanczos_lowest_eigenpair: breakdown_tol must be finite and non-negative"
2520 );
2521 anyhow::ensure!(
2522 options.hermitian_tol.is_finite() && options.hermitian_tol >= 0.0,
2523 "hermitian_lanczos_lowest_eigenpair: hermitian_tol must be finite and non-negative"
2524 );
2525 Ok(basis_capacity)
2526}
2527
2528fn validate_hermitian_krylov_expm_options(options: &HermitianKrylovExpmOptions) -> Result<()> {
2529 checked_krylov_capacity(options.max_iter, "hermitian_krylov_expm_multiply")?;
2530 anyhow::ensure!(
2531 options.max_iter > 0,
2532 "hermitian_krylov_expm_multiply: max_iter must be greater than zero"
2533 );
2534 anyhow::ensure!(
2535 options.max_time_splits > 0,
2536 "hermitian_krylov_expm_multiply: max_time_splits must be greater than zero"
2537 );
2538 anyhow::ensure!(
2539 options.tol.is_finite() && options.tol >= 0.0,
2540 "hermitian_krylov_expm_multiply: tol must be finite and non-negative"
2541 );
2542 anyhow::ensure!(
2543 options.breakdown_tol.is_finite() && options.breakdown_tol >= 0.0,
2544 "hermitian_krylov_expm_multiply: breakdown_tol must be finite and non-negative"
2545 );
2546 anyhow::ensure!(
2547 options.hermitian_tol.is_finite() && options.hermitian_tol >= 0.0,
2548 "hermitian_krylov_expm_multiply: hermitian_tol must be finite and non-negative"
2549 );
2550 Ok(())
2551}
2552
2553fn projected_matrix_from_columns(
2554 h_cols: &[Vec<Complex64>],
2555 dim: usize,
2556) -> Result<Matrix<Complex64>> {
2557 let matrix_len = dim.checked_mul(dim).ok_or_else(|| {
2558 anyhow::anyhow!("Krylov projected matrix shape ({dim}, {dim}) overflows usize")
2559 })?;
2560 let mut data = vec![Complex64::new(0.0, 0.0); matrix_len];
2561 for col in 0..dim {
2562 for row in 0..dim {
2563 if let Some(value) = h_cols.get(col).and_then(|h_col| h_col.get(row)) {
2564 data[row + dim * col] = *value;
2565 }
2566 }
2567 }
2568 Ok(Matrix::from_col_major_vec(dim, dim, data))
2569}
2570
2571fn checked_krylov_capacity(max_iter: usize, solver: &'static str) -> Result<usize> {
2572 max_iter.checked_add(1).ok_or_else(|| {
2573 anyhow::Error::new(KrylovError::InvalidOptions {
2574 solver,
2575 reason: "max_iter + 1 overflows usize".to_string(),
2576 })
2577 })
2578}
2579
2580fn any_scalar_to_complex(value: &AnyScalar) -> Complex64 {
2581 value
2582 .as_c64()
2583 .unwrap_or_else(|| Complex64::new(value.real(), 0.0))
2584}
2585
2586fn any_scalar_from_complex(value: Complex64, tolerance: f64) -> Result<AnyScalar> {
2587 if value.im.abs() <= tolerance {
2588 Ok(AnyScalar::new_real(value.re))
2589 } else {
2590 Ok(AnyScalar::new_complex(value.re, value.im))
2591 }
2592}
2593
2594fn hermitian_lanczos_threshold(eigenvalue: f64, options: &HermitianLanczosOptions) -> f64 {
2595 options.atol.max(options.rtol * eigenvalue.abs().max(1.0))
2596}
2597
2598fn hermitian_true_residual_norm<T, F>(apply_a: &F, eigenvector: &T, eigenvalue: f64) -> Result<f64>
2599where
2600 T: TensorVectorSpace,
2601 F: Fn(&T) -> Result<T>,
2602{
2603 let av = apply_a(eigenvector)?;
2604 let lambda_v = eigenvector.scale(AnyScalar::new_real(eigenvalue))?;
2605 let residual = av.axpby(
2606 AnyScalar::new_real(1.0),
2607 &lambda_v,
2608 AnyScalar::new_real(-1.0),
2609 )?;
2610 Ok(residual.norm()?)
2611}
2612
2613fn finalize_hermitian_lanczos_result<T, F>(
2614 apply_a: &F,
2615 basis: &[T],
2616 ritz: &HermitianRitzState,
2617 options: &HermitianLanczosOptions,
2618) -> Result<HermitianLanczosResult<T>>
2619where
2620 T: TensorVectorSpace,
2621 F: Fn(&T) -> Result<T>,
2622{
2623 let eigenvector = combine_basis_with_coefficients(basis, &ritz.coefficients, options)?;
2624 let residual_norm = hermitian_true_residual_norm(apply_a, &eigenvector, ritz.eigenvalue)?;
2625 let threshold = hermitian_lanczos_threshold(ritz.eigenvalue, options);
2626 let converged = residual_norm <= threshold;
2627
2628 if options.verbose {
2629 eprintln!(
2630 "Hermitian Lanczos final check iter {}: residual_estimate={:.6e} true_residual={:.6e} threshold={:.6e}",
2631 ritz.iterations, ritz.residual_estimate, residual_norm, threshold
2632 );
2633 }
2634
2635 Ok(HermitianLanczosResult {
2636 eigenvalue: ritz.eigenvalue,
2637 eigenvector,
2638 iterations: ritz.iterations,
2639 residual_norm,
2640 converged,
2641 })
2642}
2643
2644fn combine_basis_with_coefficients<T>(
2645 basis: &[T],
2646 coefficients: &[Complex64],
2647 options: &HermitianLanczosOptions,
2648) -> Result<T>
2649where
2650 T: TensorVectorSpace,
2651{
2652 anyhow::ensure!(
2653 !basis.is_empty(),
2654 "hermitian_lanczos_lowest_eigenpair: empty Krylov basis"
2655 );
2656 anyhow::ensure!(
2657 basis.len() == coefficients.len(),
2658 "hermitian_lanczos_lowest_eigenpair: coefficient length {} does not match basis length {}",
2659 coefficients.len(),
2660 basis.len()
2661 );
2662
2663 let mut result = basis[0].scale(any_scalar_from_complex(
2664 coefficients[0],
2665 options.hermitian_tol,
2666 )?)?;
2667 for (basis_vector, coefficient) in basis.iter().zip(coefficients.iter()).skip(1) {
2668 result = result.axpby(
2669 AnyScalar::new_real(1.0),
2670 basis_vector,
2671 any_scalar_from_complex(*coefficient, options.hermitian_tol)?,
2672 )?;
2673 }
2674 Ok(result)
2675}
2676
2677fn combine_basis_with_complex_coefficients<T>(
2678 basis: &[T],
2679 coefficients: &[Complex64],
2680 hermitian_tol: f64,
2681 context: &'static str,
2682) -> Result<T>
2683where
2684 T: TensorVectorSpace,
2685{
2686 anyhow::ensure!(!basis.is_empty(), "{context}: empty Krylov basis");
2687 anyhow::ensure!(
2688 basis.len() == coefficients.len(),
2689 "{context}: coefficient length {} does not match basis length {}",
2690 coefficients.len(),
2691 basis.len()
2692 );
2693
2694 let mut result = basis[0].scale(any_scalar_from_complex(coefficients[0], hermitian_tol)?)?;
2695 for (basis_vector, coefficient) in basis.iter().zip(coefficients.iter()).skip(1) {
2696 result = result.axpby(
2697 AnyScalar::new_real(1.0),
2698 basis_vector,
2699 any_scalar_from_complex(*coefficient, hermitian_tol)?,
2700 )?;
2701 }
2702 Ok(result)
2703}
2704
2705fn compute_givens_rotation(a: &AnyScalar, b: &AnyScalar) -> (AnyScalar, AnyScalar) {
2709 let a_abs = a.abs();
2710 let b_abs = b.abs();
2711 let r = (a_abs * a_abs + b_abs * b_abs).sqrt();
2712 if r < 1e-15 {
2713 (AnyScalar::new_real(1.0), AnyScalar::new_real(0.0))
2714 } else if a_abs < 1e-15 {
2715 (
2716 AnyScalar::new_real(0.0),
2717 b.clone().conj() / AnyScalar::new_real(r),
2718 )
2719 } else {
2720 let phase = a.clone() / AnyScalar::new_real(a_abs);
2721 (
2722 AnyScalar::new_real(a_abs / r),
2723 phase * b.clone().conj() / AnyScalar::new_real(r),
2724 )
2725 }
2726}
2727
2728fn apply_givens_rotation(
2733 c: &AnyScalar,
2734 s: &AnyScalar,
2735 x: &AnyScalar,
2736 y: &AnyScalar,
2737) -> (AnyScalar, AnyScalar) {
2738 let new_x = c.clone() * x.clone() + s.clone() * y.clone();
2739 let new_y = -(s.clone().conj() * x.clone()) + c.clone() * y.clone();
2740 (new_x, new_y)
2741}
2742
2743fn solve_upper_triangular(h: &[Vec<AnyScalar>], g: &[AnyScalar]) -> Result<Vec<AnyScalar>> {
2745 let n = g.len();
2746 if n == 0 {
2747 return Ok(vec![]);
2748 }
2749
2750 let mut y = vec![AnyScalar::new_real(0.0); n];
2751
2752 for i in (0..n).rev() {
2753 let mut sum = g[i].clone();
2754
2755 for j in (i + 1)..n {
2756 let prod = h[j][i].clone() * y[j].clone();
2758 sum = sum - prod;
2759 }
2760
2761 let h_ii = &h[i][i];
2762 if h_ii.abs() < 1e-15 {
2763 return Err(anyhow::anyhow!(
2764 "Near-singular upper triangular matrix in GMRES"
2765 ));
2766 }
2767
2768 y[i] = sum / h_ii.clone();
2769 }
2770
2771 Ok(y)
2772}
2773
2774fn update_solution<T: TensorVectorSpace>(x: &T, v_basis: &[T], y: &[AnyScalar]) -> Result<T> {
2776 let mut result = x.clone();
2777
2778 for (vi, yi) in v_basis.iter().zip(y.iter()) {
2779 let scaled_vi = vi.scale(yi.clone())?;
2780 result = result.axpby(
2782 AnyScalar::new_real(1.0),
2783 &scaled_vi,
2784 AnyScalar::new_real(1.0),
2785 )?;
2786 }
2787
2788 Ok(result)
2789}
2790
2791fn update_solution_truncated<T, Tr>(
2793 x: &T,
2794 v_basis: &[T],
2795 y: &[AnyScalar],
2796 truncate: &Tr,
2797) -> Result<T>
2798where
2799 T: TensorVectorSpace,
2800 Tr: Fn(&mut T) -> Result<()>,
2801{
2802 let mut result = x.clone();
2803 let mut result_is_zero = x.norm()? == 0.0;
2808
2809 for (vi, yi) in v_basis.iter().zip(y.iter()) {
2810 let scaled_vi = vi.scale(yi.clone())?;
2811 if result_is_zero {
2812 result = scaled_vi;
2813 result_is_zero = false;
2814 } else {
2815 result = result.axpby(
2816 AnyScalar::new_real(1.0),
2817 &scaled_vi,
2818 AnyScalar::new_real(1.0),
2819 )?;
2820 }
2821 truncate(&mut result)?;
2823 }
2824
2825 Ok(result)
2826}
2827
2828#[cfg(test)]
2829mod tests;