1#![allow(clippy::indexing_slicing)] use crate::{Complex, Error, Result};
14
15#[derive(Clone, Copy, Debug)]
23pub struct GmresOptions {
24 pub rtol: f64,
26 pub atol: f64,
28 pub restart: usize,
30 pub max_iterations: usize,
32}
33
34impl Default for GmresOptions {
35 fn default() -> Self {
36 Self {
37 rtol: 1e-10,
38 atol: 0.0,
39 restart: 30,
40 max_iterations: 300,
41 }
42 }
43}
44
45impl GmresOptions {
46 pub(crate) fn for_derivative(self) -> Self {
51 Self {
52 rtol: if self.rtol > 0.0 {
53 self.rtol
54 } else {
55 Self::default().rtol
56 },
57 atol: 0.0,
58 ..self
59 }
60 }
61
62 pub(crate) fn validate(self) -> Result<()> {
65 if !self.rtol.is_finite()
66 || !self.atol.is_finite()
67 || self.rtol < 0.0
68 || self.atol < 0.0
69 || self.rtol + self.atol == 0.0
70 || self.restart == 0
71 || self.max_iterations == 0
72 {
73 return Err(Error::InvalidInput(
74 "GMRES needs finite nonnegative tolerances, a positive tolerance, restart and iteration limit".into(),
75 ));
76 }
77 Ok(())
78 }
79}
80
81#[derive(Clone, Copy, Debug)]
86pub struct Convergence {
87 pub iterations: usize,
89 pub residual_norm: f64,
91 pub rhs_norm: f64,
93}
94
95pub(crate) fn norm(vector: &[Complex]) -> f64 {
97 vector.iter().fold(0.0_f64, |sum, z| sum.hypot(z.norm()))
98}
99
100pub(crate) fn gmres(
105 rhs: &[Complex],
106 options: GmresOptions,
107 apply: impl Fn(&[Complex]) -> Result<Vec<Complex>>,
108) -> Result<(Vec<Complex>, Convergence)> {
109 let (answer, reports) = gmres_batch(rhs, 1, options, |x, _| apply(x))?;
110 Ok((answer, reports[0]))
111}
112
113pub(crate) fn gmres_batch(
120 rhs: &[Complex],
121 columns: usize,
122 options: GmresOptions,
123 apply: impl Fn(&[Complex], usize) -> Result<Vec<Complex>>,
124) -> Result<(Vec<Complex>, Vec<Convergence>)> {
125 let dimension = rhs.len() / columns;
126 let restart = options.restart.min(dimension).min(options.max_iterations);
127 let mut states: Vec<_> = (0..columns)
128 .map(|column| Krylov::new(&rhs[column * dimension..][..dimension], options))
129 .collect();
130 loop {
131 let mut cycle = Vec::with_capacity(columns);
132 for (column, state) in states.iter_mut().enumerate() {
133 if !state.converged(options)? {
134 state.start(restart);
135 cycle.push(column);
136 }
137 }
138 if cycle.is_empty() {
139 break;
140 }
141 let mut extending = cycle.clone();
142 for j in 0..restart {
143 let mut product = apply_columns(
144 &extending,
145 |column| states[column].basis[j].as_slice(),
146 &apply,
147 )?;
148 let mut next = Vec::with_capacity(extending.len());
149 for (&column, w) in extending.iter().zip(product.chunks_exact_mut(dimension)) {
150 if states[column].step(w, j, restart, options)? {
151 next.push(column);
152 }
153 }
154 extending = next;
155 if extending.is_empty() {
156 break;
157 }
158 }
159 for &column in &cycle {
160 states[column].update(restart);
161 }
162 let product = apply_columns(&cycle, |column| states[column].answer.as_slice(), &apply)?;
164 for (&column, product) in cycle.iter().zip(product.chunks_exact(dimension)) {
165 states[column].residual = rhs[column * dimension..][..dimension]
166 .iter()
167 .zip(product)
168 .map(|(b, a)| b - a)
169 .collect();
170 }
171 }
172 let reports = states.iter().map(|state| state.report).collect();
173 let answer = if let [state] = states.as_mut_slice() {
174 std::mem::take(&mut state.answer)
175 } else {
176 states.into_iter().flat_map(|state| state.answer).collect()
177 };
178 Ok((answer, reports))
179}
180
181fn apply_columns<'a>(
184 columns: &[usize],
185 vector: impl Fn(usize) -> &'a [Complex],
186 apply: &impl Fn(&[Complex], usize) -> Result<Vec<Complex>>,
187) -> Result<Vec<Complex>> {
188 if let [column] = columns {
189 apply(vector(*column), 1)
190 } else {
191 let input: Vec<_> = columns
192 .iter()
193 .flat_map(|&column| vector(column).iter().copied())
194 .collect();
195 apply(&input, columns.len())
196 }
197}
198
199struct Krylov {
202 answer: Vec<Complex>,
203 residual: Vec<Complex>,
204 tolerance: f64,
205 report: Convergence,
206 basis: Vec<Vec<Complex>>,
207 h: Vec<Complex>,
208 rotations: Vec<(f64, Complex)>,
209 g: Vec<Complex>,
210 used: usize,
211}
212
213impl Krylov {
214 fn new(rhs: &[Complex], options: GmresOptions) -> Self {
215 let rhs_norm = norm(rhs);
216 Self {
217 answer: vec![Complex::default(); rhs.len()],
218 residual: rhs.to_vec(),
219 tolerance: options.atol.max(options.rtol * rhs_norm),
220 report: Convergence {
221 iterations: 0,
222 residual_norm: rhs_norm,
223 rhs_norm,
224 },
225 basis: Vec::new(),
226 h: Vec::new(),
227 rotations: Vec::new(),
228 g: Vec::new(),
229 used: 0,
230 }
231 }
232
233 fn converged(&mut self, options: GmresOptions) -> Result<bool> {
234 self.report.residual_norm = norm(&self.residual);
235 let Convergence {
236 residual_norm,
237 iterations,
238 ..
239 } = self.report;
240 if residual_norm.is_finite() && residual_norm <= self.tolerance {
241 return Ok(true);
242 }
243 if !residual_norm.is_finite() || iterations >= options.max_iterations {
244 return Err(Error::NotConverged(format!(
245 "GMRES did not converge after {iterations} iterations: residual {residual_norm:e}, tolerance {:e}",
246 self.tolerance
247 )));
248 }
249 Ok(false)
250 }
251
252 fn start(&mut self, restart: usize) {
253 self.basis.clear();
256 self.basis.reserve(restart + 1);
257 self.basis.push(
258 self.residual
259 .iter()
260 .map(|z| z / self.report.residual_norm)
261 .collect(),
262 );
263 self.h.clear();
264 self.h.resize((restart + 1) * restart, Complex::default());
265 self.rotations.clear();
266 self.rotations.reserve(restart);
267 self.g.clear();
268 self.g.resize(restart + 1, Complex::default());
269 self.g[0] = Complex::new(self.report.residual_norm, 0.0);
270 self.used = 0;
271 }
272
273 fn step(
275 &mut self,
276 w: &mut [Complex],
277 j: usize,
278 restart: usize,
279 options: GmresOptions,
280 ) -> Result<bool> {
281 for _ in 0..2 {
283 for i in 0..=j {
284 let dot: Complex = self.basis[i]
285 .iter()
286 .zip(&*w)
287 .map(|(v, w)| v.conj() * w)
288 .sum();
289 self.h[j * (restart + 1) + i] += dot;
290 for (w, v) in w.iter_mut().zip(&self.basis[i]) {
291 *w -= dot * v;
292 }
293 }
294 }
295 let next = norm(w);
296 self.h[j * (restart + 1) + j + 1] = Complex::new(next, 0.0);
297 for (i, &(c, s)) in self.rotations.iter().enumerate() {
298 let offset = j * (restart + 1) + i;
299 let a = self.h[offset];
300 let b = self.h[offset + 1];
301 self.h[offset] = c * a + s * b;
302 self.h[offset + 1] = -s.conj() * a + c * b;
303 }
304 let diagonal = j * (restart + 1) + j;
305 let a = self.h[diagonal];
306 let b = self.h[diagonal + 1];
307 let length = a.norm().hypot(b.norm());
308 if !length.is_finite() || length == 0.0 {
309 return Err(Error::NotConverged(
310 "GMRES Arnoldi breakdown before convergence".into(),
311 ));
312 }
313 let phase = if a.norm() == 0.0 {
314 Complex::new(1.0, 0.0)
315 } else {
316 a / a.norm()
317 };
318 let c = a.norm() / length;
319 let s = phase * b.conj() / length;
320 self.h[diagonal] = phase * length;
321 self.h[diagonal + 1] = Complex::default();
322 self.rotations.push((c, s));
323 self.g[j + 1] = -s.conj() * self.g[j];
324 self.g[j] *= c;
325 self.used = j + 1;
326 self.report.iterations += 1;
327 if self.g[j + 1].norm() <= self.tolerance
328 || next == 0.0
329 || self.report.iterations == options.max_iterations
330 {
331 return Ok(false);
332 }
333 self.basis.push(w.iter().map(|z| z / next).collect());
334 Ok(true)
335 }
336
337 fn update(&mut self, restart: usize) {
338 for i in (0..self.used).rev() {
339 let tail: Complex = ((i + 1)..self.used)
340 .map(|j| self.h[j * (restart + 1) + i] * self.g[j])
341 .sum();
342 self.g[i] = (self.g[i] - tail) / self.h[i * (restart + 1) + i];
343 for (x, v) in self.answer.iter_mut().zip(&self.basis[i]) {
344 *x += self.g[i] * v;
345 }
346 }
347 }
348}
349
350#[cfg(test)]
352mod tests {
353 use std::cell::RefCell;
354
355 use nalgebra::{DMatrix, DVector};
356 use proptest::{prelude::*, test_runner::TestCaseError};
357
358 use super::{GmresOptions, Krylov, gmres, gmres_batch, norm};
359 use crate::{
360 Complex,
361 test_support::{DEFAULT_CASES, complex_matrix, prop_assert_close},
362 };
363
364 fn options() -> GmresOptions {
365 GmresOptions {
366 rtol: 2e-12,
367 restart: 8,
368 max_iterations: 100,
369 ..GmresOptions::default()
370 }
371 }
372
373 proptest! {
374 #![proptest_config(ProptestConfig::with_cases(DEFAULT_CASES))]
375
376 #[test]
377 fn restarted_gmres_matches_a_direct_solve(
378 (m, rhs, restart) in (1_usize..=24).prop_flat_map(|n| {
379 (complex_matrix(n, n, 1.0), complex_matrix(n, 1, 1.0), 1..=n + 2)
380 }),
381 ) {
382 check_gmres(&m, rhs.as_slice(), restart)?;
383 }
384 }
385
386 fn check_gmres(
391 m: &DMatrix<Complex>,
392 rhs: &[Complex],
393 restart: usize,
394 ) -> Result<(), TestCaseError> {
395 let n = m.nrows();
396 let shrink = if m.norm() > 0.0 { 0.5 / m.norm() } else { 0.0 };
397 let a = DMatrix::identity(n, n) + m * Complex::from(shrink);
398 let options = GmresOptions {
399 rtol: 1e-10,
400 atol: 0.0,
401 restart,
402 max_iterations: 4000,
403 };
404 let apply = |x: &[Complex]| Ok((&a * DVector::from_column_slice(x)).as_slice().to_vec());
405 let (answer, report) = gmres(rhs, options, apply).unwrap();
406 let b = DVector::from_column_slice(rhs);
407 let direct = a.clone().lu().solve(&b).unwrap();
408 prop_assert_close!(
409 answer.as_slice(),
410 direct.as_slice(),
411 1e-8 * (1.0 + direct.norm())
412 );
413 prop_assert_close!(
414 report.rhs_norm,
415 b.norm(),
416 1e-14 * b.norm() + f64::MIN_POSITIVE
417 );
418 let residual = (&b - &a * DVector::from_column_slice(&answer)).norm();
419 prop_assert_close!(
420 report.residual_norm,
421 residual,
422 1e-14 * b.norm() + f64::MIN_POSITIVE
423 );
424 prop_assert!(report.residual_norm <= options.rtol * report.rhs_norm);
425 if restart >= n {
426 prop_assert!(report.iterations <= n, "{} iterations", report.iterations);
427 }
428 Ok(())
429 }
430
431 #[test]
432 fn gmres_zero_happy_breakdown_restarts_and_failure() {
433 let identity = |x: &[Complex]| Ok(x.to_vec());
434 let (zero, report) = gmres(&[Complex::default(); 3], options(), identity).unwrap();
435 assert_eq!(zero, vec![Complex::default(); 3]);
436 assert_eq!(report.iterations, 0);
437 let rhs = vec![Complex::new(0.2, -0.3); 3];
438 let (answer, report) = gmres(&rhs, options(), identity).unwrap();
439 assert!((norm(&answer) - norm(&rhs)).abs() < 1e-14);
440 assert_eq!(report.iterations, 1);
441 let diagonal = |x: &[Complex]| {
442 Ok(x.iter()
443 .enumerate()
444 .map(|(i, x)| {
445 Complex::new(1.0 + f64::from(u32::try_from(i).unwrap()) * 0.3, 0.1) * x
446 })
447 .collect())
448 };
449 let limited = GmresOptions {
450 restart: 1,
451 max_iterations: 1,
452 ..options()
453 };
454 assert!(gmres(&rhs, limited, diagonal).is_err());
455 let restarted = GmresOptions {
456 restart: 1,
457 ..options()
458 };
459 let (_, report) = gmres(&rhs, restarted, diagonal).unwrap();
460 assert!(report.iterations > 1);
461 assert!(report.residual_norm <= restarted.rtol * report.rhs_norm);
462 assert!(gmres(&rhs, options(), |_| Ok(vec![Complex::default(); 3])).is_err());
463 }
464
465 #[test]
468 fn batched_columns_keep_independent_convergence() {
469 let diagonal = [
470 Complex::new(1.0, 0.1),
471 Complex::new(1.3, 0.1),
472 Complex::new(1.6, 0.1),
473 ];
474 let apply = |input: &[Complex]| {
475 Ok(input
476 .iter()
477 .enumerate()
478 .map(|(i, x)| diagonal[i % 3] * x)
479 .collect::<Vec<_>>())
480 };
481 for (rtol, atol) in [(1e-10, 0.0), (0.0, 1e-10)] {
482 let options = GmresOptions {
483 rtol,
484 atol,
485 restart: 2,
486 max_iterations: 100,
487 };
488 let mut rhs = vec![Complex::default(); 18];
489 rhs[0] = Complex::new(1.0, 2.0);
490 for (column, scale) in [0.7, 1e-120, if rtol > 0.0 { 1e120 } else { 1e-3 }]
491 .into_iter()
492 .enumerate()
493 {
494 for (i, z) in [
495 Complex::new(0.2, -0.3),
496 Complex::new(0.5, 0.7),
497 Complex::new(0.6, -0.1),
498 ]
499 .into_iter()
500 .enumerate()
501 {
502 rhs[(column + 2) * 3 + i] = z * scale;
503 }
504 }
505 rhs[16] = Complex::new(1.0, -0.3);
506 let widths = RefCell::new(Vec::new());
507 let (answer, reports) = gmres_batch(&rhs, 6, options, |input, columns| {
508 assert_eq!(input.len(), columns * 3);
509 widths.borrow_mut().push(columns);
510 apply(input)
511 })
512 .unwrap();
513 for (column, right) in rhs.as_chunks::<3>().0.iter().enumerate() {
514 let (independent, report) = gmres(right, options, apply).unwrap();
515 assert_eq!(&answer[column * 3..][..3], independent.as_slice());
516 assert_eq!(reports[column].iterations, report.iterations);
517 assert_eq!(
518 reports[column].rhs_norm.to_bits(),
519 report.rhs_norm.to_bits()
520 );
521 assert_eq!(
522 reports[column].residual_norm.to_bits(),
523 report.residual_norm.to_bits()
524 );
525 assert!(report.residual_norm <= atol.max(rtol * report.rhs_norm));
526 }
527 assert_eq!(reports[1].iterations, 0);
528 assert_eq!(reports[0].iterations, 1);
529 assert!(reports[2].iterations > 2);
530 let widths = widths.into_inner();
531 assert!(widths[0] > 1);
532 assert!(widths.last().unwrap() < &widths[0]);
533 let independent_calls: usize = reports
534 .iter()
535 .map(|report| report.iterations + report.iterations.div_ceil(2))
536 .sum();
537 assert!(widths.len() < independent_calls);
538 }
539 }
540
541 #[test]
544 fn batched_eigenvectors_obey_each_columns_iteration_limit() {
545 let rhs = [1.0, 0.0, 0.0, 1.0].map(Complex::from);
546 let options = GmresOptions {
547 restart: 1,
548 max_iterations: 1,
549 ..options()
550 };
551 let (answer, reports) = gmres_batch(&rhs, 2, options, |input, _| {
552 Ok(input
553 .iter()
554 .enumerate()
555 .map(|(i, x)| x * if i % 2 == 0 { 2.0 } else { 3.0 })
556 .collect())
557 })
558 .unwrap();
559 assert_eq!(answer, [0.5, 0.0, 0.0, 1.0 / 3.0].map(Complex::from));
560 assert!(reports.iter().all(|report| report.iterations == 1));
561 let (answer, reports) = gmres_batch(&[Complex::default(); 8], 4, options, |_, _| {
562 panic!("zero right-hand sides must not apply the operator")
563 })
564 .unwrap();
565 assert_eq!(answer, vec![Complex::default(); 8]);
566 assert!(reports.iter().all(|report| report.iterations == 0));
567 }
568
569 #[test]
570 fn initially_converged_columns_allocate_no_krylov_workspace() {
571 let options = GmresOptions {
572 atol: 1e-12,
573 restart: usize::MAX,
574 max_iterations: usize::MAX,
575 ..options()
576 };
577 for scale in [0.0, 1e-14] {
578 let mut state = Krylov::new(&[Complex::from(scale); 3], options);
579 assert!(state.converged(options).unwrap());
580 assert_eq!(state.basis.capacity(), 0);
581 assert_eq!(state.h.capacity(), 0);
582 assert_eq!(state.rotations.capacity(), 0);
583 assert_eq!(state.g.capacity(), 0);
584 }
585 }
586}