//! This is a generic solver for Ax = b type of equation //! //! Example: //! ``` //! use smartcore::linalg::basic::arrays::Array1; //! use smartcore::linalg::basic::arrays::Array2; //! use smartcore::linalg::basic::matrix::DenseMatrix; //! use smartcore::linear::bg_solver::*; //! use smartcore::numbers::floatnum::FloatNumber; //! use smartcore::linear::bg_solver::BiconjugateGradientSolver; //! //! pub struct BGSolver {} //! impl<'a, T: FloatNumber, X: Array2> BiconjugateGradientSolver<'a, T, X> for BGSolver {} //! //! let a = DenseMatrix::from_2d_array(&[&[25., 15., -5.], &[15., 18., 0.], &[-5., 0., //! 11.]]).unwrap(); //! let b = vec![40., 51., 28.]; //! let expected = vec![1.0, 2.0, 3.0]; //! let mut x = Vec::zeros(3); //! let solver = BGSolver {}; //! let err: f64 = solver.solve_mut(&a, &b, &mut x, 1e-6, 6).unwrap(); //! ``` //! //! for more information take a look at [this Wikipedia article](https://en.wikipedia.org/wiki/Biconjugate_gradient_method) //! and [this paper](https://www.cs.cmu.edu/~quake-papers/painless-conjugate-gradient.pdf) use crate::error::Failed; use crate::linalg::basic::arrays::{Array, Array1, Array2, ArrayView1, MutArrayView1}; use crate::numbers::floatnum::FloatNumber; /// Trait for Biconjugate Gradient Solver pub trait BiconjugateGradientSolver<'a, T: FloatNumber, X: Array2> { /// Solve Ax = b fn solve_mut( &self, a: &'a X, b: &Vec, x: &mut Vec, tol: T, max_iter: usize, ) -> Result { if tol <= T::zero() { return Err(Failed::fit("tolerance shoud be > 0")); } if max_iter == 0 { return Err(Failed::fit("maximum number of iterations should be > 0")); } let n = b.shape(); let mut r = Vec::zeros(n); let mut rr = Vec::zeros(n); let mut z = Vec::zeros(n); let mut zz = Vec::zeros(n); self.mat_vec_mul(a, x, &mut r); for j in 0..n { r[j] = b[j] - r[j]; rr[j] = r[j]; } let bnrm = b.norm(2f64); self.solve_preconditioner(a, &r[..], &mut z[..]); let mut p = Vec::zeros(n); let mut pp = Vec::zeros(n); let mut bkden = T::zero(); let mut err = T::zero(); for iter in 1..max_iter { let mut bknum = T::zero(); self.solve_preconditioner(a, &rr, &mut zz); for j in 0..n { bknum += z[j] * rr[j]; } if iter == 1 { p[..n].copy_from_slice(&z[..n]); pp[..n].copy_from_slice(&zz[..n]); } else { let bk = bknum / bkden; for j in 0..n { p[j] = bk * pp[j] + z[j]; pp[j] = bk * pp[j] + zz[j]; } } bkden = bknum; self.mat_vec_mul(a, &p, &mut z); let mut akden = T::zero(); for j in 0..n { akden += z[j] * pp[j]; } let ak = bknum / akden; self.mat_t_vec_mul(a, &pp, &mut zz); for j in 0..n { x[j] += ak * p[j]; r[j] -= ak * z[j]; rr[j] -= ak * zz[j]; } self.solve_preconditioner(a, &r, &mut z); err = T::from_f64(r.norm(2f64) / bnrm).unwrap(); if err <= tol { break; } } Ok(err) } /// solve preconditioner fn solve_preconditioner(&self, a: &'a X, b: &[T], x: &mut [T]) { let diag = Self::diag(a); let n = diag.len(); for (i, diag_i) in diag.iter().enumerate().take(n) { if *diag_i != T::zero() { x[i] = b[i] / *diag_i; } else { x[i] = b[i]; } } } /// y = Ax fn mat_vec_mul(&self, a: &X, x: &Vec, y: &mut Vec) { y.copy_from(&x.xa(false, a)); } /// y = Atx fn mat_t_vec_mul(&self, a: &X, x: &Vec, y: &mut Vec) { y.copy_from(&x.xa(true, a)); } /// Extract the diagonal from a matrix fn diag(a: &X) -> Vec { let (nrows, ncols) = a.shape(); let n = nrows.min(ncols); let mut d = Vec::with_capacity(n); for i in 0..n { d.push(*a.get((i, i))); } d } } #[cfg(test)] mod tests { use super::*; use crate::linalg::basic::arrays::Array2; use crate::linalg::basic::matrix::DenseMatrix; pub struct BGSolver {} impl> BiconjugateGradientSolver<'_, T, X> for BGSolver {} #[test] fn bg_solver() { let a = DenseMatrix::from_2d_array(&[&[25., 15., -5.], &[15., 18., 0.], &[-5., 0., 11.]]) .unwrap(); let b = vec![40., 51., 28.]; let expected = [1.0, 2.0, 3.0]; let mut x = Vec::zeros(3); let solver = BGSolver {}; let err: f64 = solver.solve_mut(&a, &b, &mut x, 1e-6, 6).unwrap(); assert!(x .iter() .zip(expected.iter()) .all(|(&a, &b)| (a - b).abs() < 1e-4)); assert!((err - 0.0).abs() < 1e-4); } }