iterative_solvers/cg.rs
1//! Conjugate Gradient (CG) method.
2//!
3//! # Copyright
4//!
5//! Portions of this implementation are derived from [IterativeSolvers.jl](https://github.com/JuliaLinearAlgebra/IterativeSolvers.jl).
6//!
7//! Copyright (c) 2013--2016 The Julia Language.
8//!
9//! Licensed under the MIT License.
10
11use std::ops::Mul;
12
13use super::MatrixOp;
14#[cfg(not(feature = "ndarray"))]
15use crate::utils::is_vector;
16use crate::{
17 IterSolverError, IterSolverResult,
18 ops::Vector,
19 utils::{axpy, dot, norm_l2, zeros},
20};
21
22/// Conjugate Gradient (CG) method for solving linear systems Ax = b.
23///
24/// The Conjugate Gradient method is an iterative algorithm for solving systems of linear
25/// equations where the coefficient matrix A is symmetric and positive definite. It is
26/// particularly effective for large sparse systems.
27///
28/// # Algorithm Overview
29///
30/// The CG method generates a sequence of approximations to the solution by minimizing
31/// the quadratic form associated with the linear system. At each iteration, it computes
32/// a search direction that is conjugate to all previous search directions with respect
33/// to the matrix A.
34///
35/// # Convergence
36///
37/// For a symmetric positive definite matrix, the CG method is guaranteed to converge
38/// to the exact solution in at most n iterations (where n is the size of the matrix),
39/// though in practice it often converges much faster, especially for well-conditioned
40/// systems.
41///
42/// # Examples
43///
44/// **With nalgebra feature:**
45///
46/// ```rust
47/// # #[cfg(feature = "nalgebra")]
48/// # {
49/// use nalgebra::DVector;
50/// use iterative_solvers::CG;
51///
52/// let n = 1024;
53/// let h = 1.0 / (n as f64);
54/// let a = vec![2.0 / (h * h); n - 1];
55/// let b = vec![-1.0 / (h * h); n - 2];
56/// let mat = symmetric_tridiagonal(&a, &b).unwrap();
57/// let rhs: Vec<_> = (1..n)
58/// .map(|i| PI * PI * (i as f64 * h * PI).sin())
59/// .collect();
60/// let rhs = DVector::from_vec(rhs);
61/// let abstol = 1e-10;
62/// let reltol = 1e-8;
63///
64/// let mut cg = CG::new(&mat, &rhs, abstol, reltol).unwrap();
65/// let solution = cg.solve();
66/// # }
67/// ```
68///
69/// **With faer feature:**
70///
71/// ```rust
72/// # #[cfg(feature = "faer")]
73/// # {
74/// use faer::Mat;
75/// use iterative_solvers::CG;
76/// use iterative_solvers::utils::dense::symmetric_tridiagonal;
77/// use std::f64::consts::PI;
78///
79/// let n = 1024;
80/// let h = 1.0 / (n as f64);
81/// let a = vec![2.0 / (h * h); n - 1];
82/// let b = vec![-1.0 / (h * h); n - 2];
83/// let mat = symmetric_tridiagonal(&a, &b).unwrap();
84/// let rhs: Vec<_> = (1..n)
85/// .map(|i| PI * PI * (i as f64 * h * PI).sin())
86/// .collect();
87/// let rhs = Mat::from_fn(n - 1, 1, |i, _| rhs[i]);
88/// let abstol = 1e-10;
89/// let reltol = 1e-8;
90///
91/// let mut cg = CG::new(&mat, &rhs, abstol, reltol).unwrap();
92/// let solution = cg.solve();
93/// # }
94/// ```
95///
96/// **With ndarray feature:**
97///
98/// ```rust
99/// # #[cfg(feature = "ndarray")]
100/// # {
101/// use ndarray::arr1;
102/// use iterative_solvers::CG;
103/// use iterative_solvers::utils::dense::symmetric_tridiagonal;
104/// use std::f64::consts::PI;
105///
106/// let n = 1024;
107/// let h = 1.0 / (n as f64);
108/// let a = vec![2.0 / (h * h); n - 1];
109/// let b = vec![-1.0 / (h * h); n - 2];
110/// let mat = symmetric_tridiagonal(&a, &b).unwrap();
111/// let rhs: Vec<_> = (1..n)
112/// .map(|i| PI * PI * (i as f64 * h * PI).sin())
113/// .collect();
114/// let rhs = arr1(&rhs);
115/// let abstol = 1e-10;
116/// let reltol = 1e-8;
117///
118/// let mut cg = CG::new(&mat, &rhs, abstol, reltol).unwrap();
119/// let solution = cg.solve();
120/// # }
121/// ```
122#[derive(Debug, Clone)]
123pub struct CG<'mat, Mat: MatrixOp> {
124 mat: &'mat Mat,
125 solution: Vector<f64>,
126 residual: f64,
127 iteration: usize,
128 r: Vector<f64>,
129 c: Vector<f64>,
130 u: Vector<f64>,
131 tol: f64,
132 prev_residual: f64,
133}
134
135impl<'mat, Mat: MatrixOp> CG<'mat, Mat> {
136 /// Create a new `CG` solver with the given matrix, right-hand side, and tolerance.
137 ///
138 /// # Arguments
139 ///
140 /// * `mat` - The coefficient matrix A.
141 /// * `rhs` - The right-hand side vector b.
142 /// * `abstol` - The absolute tolerance for the residual.
143 /// * `reltol` - The relative tolerance for the residual.
144 ///
145 /// # Tolerance
146 ///
147 /// The tolerance is the stopping criterion for the CG method. It is the maximum of the absolute tolerance and the relative tolerance.
148 ///
149 /// The stopping criterion is `|r_k| ≤ max(reltol * |r_0|, abstol)`,
150 /// where `r_k ≈ A x_k - b` is the residual at the `k`th iteration, and `r_0 ≈ A x_0 - b` is the initial residual.
151 pub fn new(
152 mat: &'mat Mat,
153 rhs: &'mat Vector<f64>,
154 abstol: f64,
155 reltol: f64,
156 ) -> IterSolverResult<Self> {
157 if !mat.is_square() {
158 return Err(IterSolverError::DimensionError(format!(
159 "The matrix is not square, whose shape is ({}, {})",
160 mat.nrows(),
161 mat.ncols()
162 )));
163 }
164 #[cfg(feature = "faer")]
165 if !is_vector(rhs) {
166 return Err(IterSolverError::DimensionError(format!(
167 "The `rhs` should be a vector, but got a matrix with shape ({}, {}).",
168 rhs.nrows(),
169 rhs.ncols()
170 )));
171 }
172 if mat.nrows() != rhs.len() {
173 return Err(IterSolverError::DimensionError(format!(
174 "The matrix with order {}, and the rhs with length {}, do not match",
175 mat.nrows(),
176 rhs.len()
177 )));
178 }
179
180 let n = mat.nrows();
181 let x = zeros(n);
182
183 let r = rhs.clone();
184
185 let c = zeros(n);
186
187 let u = zeros(n);
188
189 let residual = norm_l2(&r);
190
191 let prev_residual = residual;
192 let iteration = 0;
193 let tol = abstol.max(reltol * residual);
194 Ok(Self {
195 mat,
196 solution: x,
197 residual,
198 iteration,
199 r,
200 c,
201 u,
202 tol,
203 prev_residual,
204 })
205 }
206
207 /// Create a new `CG` solver with the given matrix, right-hand side, tolerance, and initial guess.
208 ///
209 /// # Arguments
210 ///
211 /// * `mat` - The coefficient matrix A.
212 /// * `rhs` - The right-hand side vector b.
213 /// * `abstol` - The absolute tolerance for the residual.
214 /// * `reltol` - The relative tolerance for the residual.
215 /// * `initial_guess` - The initial guess for the solution.
216 ///
217 /// # Tolerance
218 ///
219 /// The tolerance is the stopping criterion for the CG method. It is the maximum of the absolute tolerance and the relative tolerance.
220 ///
221 /// The stopping criterion is `|r_k| ≤ max(reltol * |r_0|, abstol)`,
222 /// where `r_k ≈ A x_k - b` is the residual at the `k`th iteration, and `r_0 ≈ A x_0 - b` is the initial residual.
223 ///
224 /// # Initial Guess
225 ///
226 /// The initial guess is the starting point for the CG method. It is used to compute the initial residual.
227 pub fn new_with_initial_guess(
228 mat: &'mat Mat,
229 rhs: &'mat Vector<f64>,
230 initial_guess: Vector<f64>,
231 abstol: f64,
232 reltol: f64,
233 ) -> IterSolverResult<Self>
234 where
235 &'mat Mat: Mul<Vector<f64>, Output = Vector<f64>>,
236 {
237 if !mat.is_square() {
238 return Err(IterSolverError::DimensionError(format!(
239 "The matrix is not square, whose shape is ({}, {})",
240 mat.nrows(),
241 mat.ncols()
242 )));
243 }
244 #[cfg(not(feature = "ndarray"))]
245 if !is_vector(rhs) {
246 return Err(IterSolverError::DimensionError(format!(
247 "The `rhs` should be a vector, but got a matrix with shape ({}, {}).",
248 rhs.nrows(),
249 rhs.ncols()
250 )));
251 }
252
253 if mat.nrows() != rhs.len() {
254 return Err(IterSolverError::DimensionError(format!(
255 "The matrix with order {}, and the rhs with length {}, do not match",
256 mat.nrows(),
257 rhs.len()
258 )));
259 }
260 #[cfg(not(feature = "ndarray"))]
261 if !is_vector(&initial_guess) {
262 return Err(IterSolverError::DimensionError(format!(
263 "The `initial_guess` should be a vector, but got a matrix with shape ({}, {}).",
264 initial_guess.nrows(),
265 initial_guess.ncols()
266 )));
267 }
268
269 if initial_guess.len() != mat.nrows() {
270 return Err(IterSolverError::DimensionError(format!(
271 "The initial guess with length {}, and the matrix with order {}, do not match",
272 initial_guess.len(),
273 mat.nrows()
274 )));
275 }
276 let n = mat.nrows();
277 let r = rhs - mat * initial_guess.clone();
278
279 let c = zeros(n);
280
281 let u = zeros(n);
282
283 let residual = norm_l2(&r);
284
285 let prev_residual = residual;
286 let iteration = 0;
287 let tol = abstol.max(reltol * residual);
288 Ok(Self {
289 mat,
290 solution: initial_guess,
291 residual,
292 iteration,
293 r,
294 c,
295 u,
296 tol,
297 prev_residual,
298 })
299 }
300
301 /// Check if the solver has converged.
302 #[inline]
303 fn converged(&self) -> bool {
304 self.residual <= self.tol
305 }
306
307 /// Check if the solver has reached the maximum number of iterations.
308 #[inline]
309 fn done(&self) -> bool {
310 (self.iteration >= self.max_iter()) || self.converged()
311 }
312
313 /// Get the maximum number of iterations.
314 #[inline]
315 fn max_iter(&self) -> usize {
316 self.solution.len()
317 }
318
319 /// Consume the solver and return the solved result.
320 pub fn solve(mut self) -> Self {
321 self.by_ref().count();
322 self
323 }
324
325 /// Get the solution.
326 pub fn solution(&self) -> &Vector<f64> {
327 &self.solution
328 }
329
330 /// Get the residual.
331 pub fn residual(&self) -> f64 {
332 self.residual
333 }
334
335 /// Get the iteration.
336 pub fn iteration(&self) -> usize {
337 self.iteration
338 }
339
340 /// Get the matrix.
341 pub fn mat(&self) -> &Mat {
342 self.mat
343 }
344
345 /// Get the residual vector
346 pub fn residual_vector(&self) -> &Vector<f64> {
347 &self.r
348 }
349
350 /// Get the conjugate direction
351 pub fn conjugate_direction(&self) -> &Vector<f64> {
352 &self.u
353 }
354}
355
356impl<'mat, Mat: MatrixOp> Iterator for CG<'mat, Mat> {
357 type Item = f64;
358
359 fn next(&mut self) -> Option<Self::Item> {
360 if self.done() {
361 return None;
362 }
363
364 // u = r + beta * u
365 let beta = self.residual.powi(2) / self.prev_residual.powi(2);
366
367 axpy(&mut self.u, 1.0, &self.r, beta);
368
369 // c = A * u
370 self.mat.gemv(1.0, &self.u, 0.0, &mut self.c);
371
372 // update solution and residual
373 // x = x + alpha * u
374 // r = r - alpha * c
375 let alpha = self.residual.powi(2) / dot(&self.u, &self.c).unwrap();
376 axpy(&mut self.solution, alpha, &self.u, 1.0);
377 axpy(&mut self.r, -alpha, &self.c, 1.0);
378
379 self.prev_residual = self.residual;
380
381 self.residual = norm_l2(&self.r);
382
383 self.iteration += 1;
384
385 Some(self.residual)
386 }
387}
388
389/// Solve a linear system Ax = b using the Conjugate Gradient method.
390///
391/// # Arguments
392///
393/// * `mat` - The coefficient matrix A.
394/// * `rhs` - The right-hand side vector b.
395/// * `abstol` - The absolute tolerance for the residual.
396/// * `reltol` - The relative tolerance for the residual.
397///
398/// # Tolerance
399///
400/// The tolerance is the stopping criterion for the CG method. It is the maximum of the absolute tolerance and the relative tolerance.
401///
402/// The stopping criterion is `|r_k| ≤ max(reltol * |r_0|, abstol)`,
403/// where `r_k ≈ A x_k - b` is the residual at the `k`th iteration, and `r_0 ≈ A x_0 - b` is the initial residual.
404///
405/// # Examples
406///
407/// **With nalgebra feature:**
408///
409/// ```rust
410/// # #[cfg(feature = "nalgebra")]
411/// # {
412/// use nalgebra::DVector;
413/// use iterative_solvers::cg;
414///
415/// let n = 1024;
416/// let h = 1.0 / (n as f64);
417/// let a = vec![2.0 / (h * h); n - 1];
418/// let b = vec![-1.0 / (h * h); n - 2];
419/// let mat = symmetric_tridiagonal(&a, &b).unwrap();
420/// let rhs: Vec<_> = (1..n)
421/// .map(|i| PI * PI * (i as f64 * h * PI).sin())
422/// .collect();
423/// let rhs = DVector::from_vec(rhs);
424/// let abstol = 1e-10;
425/// let reltol = 1e-8;
426///
427/// let solution = cg(&mat, &rhs, abstol, reltol).unwrap();
428/// # }
429/// ```
430///
431/// **With faer feature:**
432///
433/// ```rust
434/// # #[cfg(feature = "faer")]
435/// # {
436/// use faer::Mat;
437/// use iterative_solvers::cg;
438/// use iterative_solvers::utils::dense::symmetric_tridiagonal;
439/// use std::f64::consts::PI;
440///
441/// let n = 1024;
442/// let h = 1.0 / (n as f64);
443/// let a = vec![2.0 / (h * h); n - 1];
444/// let b = vec![-1.0 / (h * h); n - 2];
445/// let mat = symmetric_tridiagonal(&a, &b).unwrap();
446/// let rhs: Vec<_> = (1..n)
447/// .map(|i| PI * PI * (i as f64 * h * PI).sin())
448/// .collect();
449/// let rhs = Mat::from_fn(n - 1, 1, |i, _| rhs[i]);
450/// let solution: Vec<_> = (1..n).map(|i| (i as f64 * h * PI).sin()).collect();
451/// let solution = Mat::from_fn(n - 1, 1, |i, _| solution[i]);
452/// let abstol = 1e-10;
453/// let reltol = 1e-8;
454///
455/// let solver = cg(&mat, &rhs, abstol, reltol).unwrap();
456/// let e = (solution - solver.solution()).norm_l2();
457/// println!("error: {}", e);
458/// # }
459/// ```
460///
461/// **With ndarray feature:**
462///
463/// ```rust
464/// # #[cfg(feature = "ndarray")]
465/// # {
466/// use ndarray::arr1;
467/// use iterative_solvers::cg;
468/// use iterative_solvers::utils::dense::symmetric_tridiagonal;
469/// use std::f64::consts::PI;
470///
471/// let n = 1024;
472/// let h = 1.0 / (n as f64);
473/// let a = vec![2.0 / (h * h); n - 1];
474/// let b = vec![-1.0 / (h * h); n - 2];
475/// let mat = symmetric_tridiagonal(&a, &b).unwrap();
476/// let rhs: Vec<_> = (1..n)
477/// .map(|i| PI * PI * (i as f64 * h * PI).sin())
478/// .collect();
479/// let rhs = arr1(&rhs);
480/// let solution: Vec<_> = (1..n).map(|i| (i as f64 * h * PI).sin()).collect();
481/// let solution = arr1(&solution);
482/// let abstol = 1e-10;
483/// let reltol = 1e-8;
484///
485/// let solver = cg(&mat, &rhs, abstol, reltol).unwrap();
486/// let e = (solution - solver.solution()).norm_l2();
487/// println!("error: {}", e);
488/// # }
489/// ```
490pub fn cg<'mat, Mat: MatrixOp>(
491 mat: &'mat Mat,
492 rhs: &'mat Vector<f64>,
493 abstol: f64,
494 reltol: f64,
495) -> IterSolverResult<CG<'mat, Mat>> {
496 let mut solver = CG::new(mat, rhs, abstol, reltol)?;
497 solver.by_ref().count();
498 Ok(solver)
499}
500
501/// Solve a linear system Ax = b using the Conjugate Gradient method.
502///
503/// # Arguments
504///
505/// * `mat` - The coefficient matrix A.
506/// * `rhs` - The right-hand side vector b.
507/// * `abstol` - The absolute tolerance for the residual.
508/// * `reltol` - The relative tolerance for the residual.
509/// * `initial_guess` - The initial guess for the solution.
510///
511/// # Tolerance
512///
513/// The tolerance is the stopping criterion for the CG method. It is the maximum of the absolute tolerance and the relative tolerance.
514///
515/// The stopping criterion is `|r_k| ≤ max(reltol * |r_0|, abstol)`,
516/// where `r_k ≈ A x_k - b` is the residual at the `k`th iteration, and `r_0 ≈ A x_0 - b` is the initial residual.
517///
518/// # Initial Guess
519///
520/// The initial guess is the starting point for the CG method. It is used to compute the initial residual.
521///
522/// # Examples
523///
524/// **With nalgebra feature:**
525///
526/// ```rust
527/// # #[cfg(feature = "nalgebra")]
528/// # {
529/// use nalgebra::DVector;
530/// use iterative_solvers::cg_with_initial_guess;
531///
532/// let n = 1024;
533/// let h = 1.0 / (n as f64);
534/// let a = vec![2.0 / (h * h); n - 1];
535/// let b = vec![-1.0 / (h * h); n - 2];
536/// let mat = symmetric_tridiagonal(&a, &b).unwrap();
537/// let rhs: Vec<_> = (1..n)
538/// .map(|i| PI * PI * (i as f64 * h * PI).sin())
539/// .collect();
540/// let rhs = DVector::from_vec(rhs);
541/// let abstol = 1e-10;
542/// let reltol = 1e-8;
543/// let initial_guess = DVector::from_vec(vec![1.0; n-1]);
544///
545/// let solution = cg_with_initial_guess(&mat, &rhs, initial_guess, abstol, reltol).unwrap();
546/// # }
547/// ```
548///
549/// **With faer feature:**
550///
551/// ```rust
552/// # #[cfg(feature = "faer")]
553/// # {
554/// use faer::Mat;
555/// use iterative_solvers::cg_with_initial_guess;
556/// use iterative_solvers::utils::dense::symmetric_tridiagonal;
557/// use std::f64::consts::PI;
558///
559/// let n = 1024;
560/// let h = 1.0 / (n as f64);
561/// let a = vec![2.0 / (h * h); n - 1];
562/// let b = vec![-1.0 / (h * h); n - 2];
563/// let mat = symmetric_tridiagonal(&a, &b).unwrap();
564/// let rhs: Vec<_> = (1..n)
565/// .map(|i| PI * PI * (i as f64 * h * PI).sin())
566/// .collect();
567/// let rhs = Mat::from_fn(n - 1, 1, |i, _| rhs[i]);
568/// let solution: Vec<_> = (1..n).map(|i| (i as f64 * h * PI).sin()).collect();
569/// let solution = Mat::from_fn(n - 1, 1, |i, _| solution[i]);
570/// let abstol = 1e-10;
571/// let reltol = 1e-8;
572/// let initial_guess = Mat::from_fn(n - 1, 1, |i, _| 1.0);
573///
574/// let solver = cg_with_initial_guess(&mat, &rhs, initial_guess, abstol, reltol).unwrap();
575/// let e = (solution - solver.solution()).norm_l2();
576/// println!("error: {}", e);
577/// # }
578/// ```
579///
580/// **With ndarray feature:**
581///
582/// ```rust
583/// # #[cfg(feature = "ndarray")]
584/// # {
585/// use ndarray::arr1;
586/// use iterative_solvers::cg_with_initial_guess;
587/// use iterative_solvers::utils::dense::symmetric_tridiagonal;
588/// use std::f64::consts::PI;
589///
590/// let n = 1024;
591/// let h = 1.0 / (n as f64);
592/// let a = vec![2.0 / (h * h); n - 1];
593/// let b = vec![-1.0 / (h * h); n - 2];
594/// let mat = symmetric_tridiagonal(&a, &b).unwrap();
595/// let rhs: Vec<_> = (1..n)
596/// .map(|i| PI * PI * (i as f64 * h * PI).sin())
597/// .collect();
598/// let rhs = arr1(&rhs);
599/// let solution: Vec<_> = (1..n).map(|i| (i as f64 * h * PI).sin()).collect();
600/// let solution = arr1(&solution);
601/// let abstol = 1e-10;
602/// let reltol = 1e-8;
603/// let initial_guess = arr1(&vec![1.0; n-1]);
604///
605/// let solver = cg_with_initial_guess(&mat, &rhs, initial_guess, abstol, reltol).unwrap();
606/// let e = (solution - solver.solution()).norm_l2();
607/// println!("error: {}", e);
608/// # }
609/// ```
610pub fn cg_with_initial_guess<'mat, Mat: MatrixOp>(
611 mat: &'mat Mat,
612 rhs: &'mat Vector<f64>,
613 initial_guess: Vector<f64>,
614 abstol: f64,
615 reltol: f64,
616) -> IterSolverResult<CG<'mat, Mat>>
617where
618 &'mat Mat: Mul<Vector<f64>, Output = Vector<f64>>,
619{
620 let mut solver = CG::new_with_initial_guess(mat, rhs, initial_guess, abstol, reltol)?;
621 solver.by_ref().count();
622 Ok(solver)
623}
624
625#[cfg(test)]
626mod tests {
627 use std::f64::consts::PI;
628
629 use super::*;
630 use crate::utils::{dense::symmetric_tridiagonal, sparse::symmetric_tridiagonal_csc};
631
632 #[test]
633 #[cfg(feature = "nalgebra")]
634 fn test_cg_dense() {
635 let n = 1024;
636 let h = 1.0 / (n as f64);
637 let a = vec![2.0 / (h * h); n - 1];
638 let b = vec![-1.0 / (h * h); n - 2];
639 let mat = symmetric_tridiagonal(&a, &b).unwrap();
640 let rhs: Vec<_> = (1..n)
641 .map(|i| PI * PI * (i as f64 * h * PI).sin())
642 .collect();
643 let solution: Vec<_> = (1..n).map(|i| (i as f64 * h * PI).sin()).collect();
644 let solution = Vector::from_vec(solution);
645 let rhs = Vector::from_vec(rhs);
646 let solver = cg(&mat, &rhs, 1e-10, 1e-8).unwrap();
647 let e = (solution - solver.solution()).norm();
648 assert!(e < 1e-4);
649 }
650
651 #[test]
652 #[cfg(feature = "faer")]
653 fn test_cg_dense() {
654 let n = 1024;
655 let h = 1.0 / (n as f64);
656 let a = vec![2.0 / (h * h); n - 1];
657 let b = vec![-1.0 / (h * h); n - 2];
658 let mat = symmetric_tridiagonal(&a, &b).unwrap();
659 let rhs: Vec<_> = (1..n)
660 .map(|i| PI * PI * (i as f64 * h * PI).sin())
661 .collect();
662 let solution: Vec<_> = (1..n).map(|i| (i as f64 * h * PI).sin()).collect();
663 let solution = faer::Mat::from_fn(n - 1, 1, |i, _| solution[i]);
664 let rhs = faer::Mat::from_fn(n - 1, 1, |i, _| rhs[i]);
665 let solver = cg(&mat, &rhs, 1e-10, 1e-8).unwrap();
666 let e = (solution - solver.solution()).norm_l2();
667 assert!(e < 1e-4);
668 }
669
670 #[test]
671 #[cfg(feature = "ndarray")]
672 fn test_cg_dense() {
673 use ndarray::arr1;
674 use ndarray_linalg::Norm;
675
676 let n = 1024;
677 let h = 1.0 / (n as f64);
678 let a = vec![2.0 / (h * h); n - 1];
679 let b = vec![-1.0 / (h * h); n - 2];
680 let mat = symmetric_tridiagonal(&a, &b).unwrap();
681 let rhs: Vec<_> = (1..n)
682 .map(|i| PI * PI * (i as f64 * h * PI).sin())
683 .collect();
684 let solution: Vec<_> = (1..n).map(|i| (i as f64 * h * PI).sin()).collect();
685 let solution = arr1(&solution);
686 let rhs = arr1(&rhs);
687 let solver = cg(&mat, &rhs, 1e-10, 1e-8).unwrap();
688 let e = (solution - solver.solution()).norm_l2();
689 assert!(e < 1e-4);
690 }
691
692 #[test]
693 #[cfg(feature = "nalgebra")]
694 fn test_cg_sparse() {
695 let n = 1024;
696 let h = 1.0 / (n as f64);
697 let a = vec![2.0 / (h * h); n - 1];
698 let b = vec![-1.0 / (h * h); n - 2];
699 let mat = symmetric_tridiagonal_csc(&a, &b).unwrap();
700 let rhs: Vec<_> = (1..n)
701 .map(|i| PI * PI * (i as f64 * h * PI).sin())
702 .collect();
703 let solution: Vec<_> = (1..n).map(|i| (i as f64 * h * PI).sin()).collect();
704 let solution = Vector::from_vec(solution);
705 let rhs = Vector::from_vec(rhs);
706 let solver = cg(&mat, &rhs, 1e-10, 1e-8).unwrap();
707 let e = (solution - solver.solution()).norm();
708 assert!(e < 1e-4);
709 }
710
711 #[test]
712 #[cfg(feature = "faer")]
713 fn test_cg_sparse() {
714 let n = 1024;
715 let h = 1.0 / (n as f64);
716 let a = vec![2.0 / (h * h); n - 1];
717 let b = vec![-1.0 / (h * h); n - 2];
718 let mat = symmetric_tridiagonal_csc(&a, &b).unwrap();
719 let rhs: Vec<_> = (1..n)
720 .map(|i| PI * PI * (i as f64 * h * PI).sin())
721 .collect();
722 let solution: Vec<_> = (1..n).map(|i| (i as f64 * h * PI).sin()).collect();
723 let solution = faer::Mat::from_fn(n - 1, 1, |i, _| solution[i]);
724 let rhs = faer::Mat::from_fn(n - 1, 1, |i, _| rhs[i]);
725 let solver = cg(&mat, &rhs, 1e-10, 1e-8).unwrap();
726 let e = (solution - solver.solution()).norm_l2();
727 assert!(e < 1e-4);
728 }
729
730 #[test]
731 #[cfg(feature = "ndarray")]
732 fn test_cg_sparse() {
733 use ndarray::arr1;
734 use ndarray_linalg::Norm;
735
736 let n = 1024;
737 let h = 1.0 / (n as f64);
738 let a = vec![2.0 / (h * h); n - 1];
739 let b = vec![-1.0 / (h * h); n - 2];
740 let mat = symmetric_tridiagonal_csc(&a, &b).unwrap();
741 let rhs: Vec<_> = (1..n)
742 .map(|i| PI * PI * (i as f64 * h * PI).sin())
743 .collect();
744 let solution: Vec<_> = (1..n).map(|i| (i as f64 * h * PI).sin()).collect();
745 let solution = arr1(&solution);
746 let rhs = arr1(&rhs);
747 let solver = cg(&mat, &rhs, 1e-10, 1e-8).unwrap();
748 let e = (solution - solver.solution()).norm_l2();
749 assert!(e < 1e-4);
750 }
751}