use crate::error::{SparseError, SparseResult};
use crate::linalg::interface::LinearOperator;
use scirs2_core::numeric::{Float, NumAssign, SparseElement};
use std::iter::Sum;
#[derive(Debug, Clone)]
pub struct MINRESResult<F> {
pub x: Vec<F>,
pub iterations: usize,
pub residual_norm: F,
pub converged: bool,
pub message: String,
}
pub struct MINRESOptions<F> {
pub max_iter: usize,
pub rtol: F,
pub atol: F,
pub x0: Option<Vec<F>>,
pub preconditioner: Option<Box<dyn LinearOperator<F>>>,
}
impl<F: Float> Default for MINRESOptions<F> {
fn default() -> Self {
Self {
max_iter: 1000,
rtol: F::from(1e-8).expect("Failed to convert constant to float"),
atol: F::from(1e-12).expect("Failed to convert constant to float"),
x0: None,
preconditioner: None,
}
}
}
#[allow(dead_code)]
pub fn minres<F>(
a: &dyn LinearOperator<F>,
b: &[F],
options: MINRESOptions<F>,
) -> SparseResult<MINRESResult<F>>
where
F: Float + NumAssign + Sum + SparseElement + 'static,
{
let (m, n) = a.shape();
if m != n {
return Err(SparseError::DimensionMismatch {
expected: m,
found: n,
});
}
if b.len() != n {
return Err(SparseError::DimensionMismatch {
expected: n,
found: b.len(),
});
}
let mut x = options
.x0
.clone()
.unwrap_or_else(|| vec![F::sparse_zero(); n]);
let bnorm = norm2(b);
if bnorm < options.atol {
return Ok(MINRESResult {
x,
iterations: 0,
residual_norm: F::sparse_zero(),
converged: true,
message: "System has zero right-hand side".to_string(),
});
}
let r1 = compute_residual(a, b, &x)?;
let y = if let Some(ref prec) = options.preconditioner {
prec.matvec(&r1)?
} else {
r1.clone()
};
let beta1_sq = inner(&r1, &y);
if beta1_sq < F::sparse_zero() {
return Err(SparseError::ComputationError(
"Indefinite preconditioner".to_string(),
));
}
if beta1_sq == F::sparse_zero() {
return Ok(MINRESResult {
x,
iterations: 0,
residual_norm: F::sparse_zero(),
converged: true,
message: "Initial residual is zero".to_string(),
});
}
let beta1 = beta1_sq.sqrt();
let mut oldb = F::sparse_zero();
let mut beta = beta1;
let mut dbar = F::sparse_zero();
let mut epsln = F::sparse_zero();
let mut phibar = beta1;
let mut cs = F::from(-1.0).expect("Failed to convert constant to float");
let mut sn = F::sparse_zero();
let mut w = vec![F::sparse_zero(); n];
let mut w2 = vec![F::sparse_zero(); n];
let mut r2 = r1.clone();
let mut v = vec![F::sparse_zero(); n];
let mut r1 = vec![F::sparse_zero(); n];
let mut y_vec = y;
let mut gmax = F::sparse_zero();
let mut gmin = F::max_value();
let mut tnorm2 = F::sparse_zero();
let mut qrnorm = beta1;
let eps = F::epsilon();
for itn in 0..options.max_iter {
let s = F::sparse_one() / beta;
for i in 0..n {
v[i] = s * y_vec[i];
}
let mut y_new = a.matvec(&v)?;
if itn >= 1 {
for i in 0..n {
y_new[i] -= (beta / oldb) * r1[i];
}
}
let alfa = inner(&v, &y_new);
for i in 0..n {
y_new[i] -= (alfa / beta) * r2[i];
}
r1 = r2;
r2 = y_new;
y_vec = if let Some(ref prec) = options.preconditioner {
prec.matvec(&r2)?
} else {
r2.clone()
};
oldb = beta;
let beta_sq = inner(&r2, &y_vec);
if beta_sq < F::sparse_zero() {
return Err(SparseError::ComputationError(
"Non-symmetric matrix".to_string(),
));
}
beta = beta_sq.sqrt();
tnorm2 = tnorm2 + alfa * alfa + oldb * oldb + beta * beta;
let oldeps = epsln;
let delta = cs * dbar + sn * alfa;
let gbar = sn * dbar - cs * alfa;
epsln = sn * beta;
dbar = -cs * beta;
let gamma = (gbar * gbar + beta * beta).sqrt();
let gamma_clamped = if gamma > eps { gamma } else { eps };
cs = gbar / gamma_clamped;
sn = beta / gamma_clamped;
let phi = cs * phibar;
phibar = sn * phibar;
let denom = F::sparse_one() / gamma_clamped;
let w1 = w2;
w2 = w;
w = vec![F::sparse_zero(); n];
for i in 0..n {
w[i] = (v[i] - oldeps * w1[i] - delta * w2[i]) * denom;
x[i] += phi * w[i];
}
gmax = gmax.max(gamma);
gmin = gmin.min(gamma);
qrnorm = phibar;
let rnorm = qrnorm;
let anorm = tnorm2.sqrt();
let ynorm = norm2(&x);
let test1 = if ynorm == F::sparse_zero() || anorm == F::sparse_zero() {
F::infinity()
} else {
rnorm / (anorm * ynorm)
};
if test1 <= options.rtol || rnorm <= options.atol {
return Ok(MINRESResult {
x,
iterations: itn + 1,
residual_norm: rnorm,
converged: true,
message: "Converged".to_string(),
});
}
}
Ok(MINRESResult {
x,
iterations: options.max_iter,
residual_norm: qrnorm,
converged: false,
message: "Maximum iterations reached".to_string(),
})
}
#[allow(dead_code)]
fn compute_residual<F>(a: &dyn LinearOperator<F>, b: &[F], x: &[F]) -> SparseResult<Vec<F>>
where
F: Float + NumAssign,
{
let ax = a.matvec(x)?;
Ok(b.iter()
.zip(ax.iter())
.map(|(&bi, &axi)| bi - axi)
.collect())
}
#[allow(dead_code)]
fn norm2<F: Float + Sum>(v: &[F]) -> F {
v.iter().map(|&x| x * x).sum::<F>().sqrt()
}
#[allow(dead_code)]
fn inner<F: Float + Sum>(a: &[F], b: &[F]) -> F {
a.iter().zip(b.iter()).map(|(&ai, &bi)| ai * bi).sum()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::csr::CsrMatrix;
use crate::linalg::interface::{AsLinearOperator, DiagonalOperator, IdentityOperator};
#[test]
fn test_minres_identity() {
let identity = IdentityOperator::<f64>::new(3);
let b = vec![1.0, 2.0, 3.0];
let options = MINRESOptions::default();
let result = minres(&identity, &b, options).expect("Operation failed");
println!("Identity test result: {:?}", result);
println!("Expected x: {:?}", b);
println!("Actual x: {:?}", result.x);
let ax = identity.matvec(&result.x).expect("Operation failed");
println!("Ax = {:?}", ax);
println!("b = {:?}", b);
assert!(result.converged);
for (i, (xi, bi)) in result.x.iter().zip(&b).enumerate() {
let diff = (xi - bi).abs();
println!("x[{}]: expected {}, got {}, diff {}", i, bi, xi, diff);
assert!(
diff < 1e-10,
"x[{}]: expected {}, got {}, diff {}",
i,
bi,
xi,
diff
);
}
}
#[test]
fn test_minres_diagonal() {
let diag = DiagonalOperator::new(vec![2.0, -3.0, 4.0]); let b = vec![2.0, -6.0, 12.0];
let options = MINRESOptions::default();
let result = minres(&diag, &b, options).expect("Operation failed");
println!("Result: {:?}", result);
println!("Solution x: {:?}", result.x);
println!("Iterations: {}", result.iterations);
println!("Converged: {}", result.converged);
println!("Residual norm: {}", result.residual_norm);
let ax = diag.matvec(&result.x).expect("Operation failed");
println!("Ax = {:?}", ax);
println!("b = {:?}", b);
assert!(result.converged);
let expected = vec![1.0, 2.0, 3.0];
for (xi, ei) in result.x.iter().zip(&expected) {
assert!((xi - ei).abs() < 1e-9, "Expected {}, got {}", ei, xi);
}
}
#[test]
fn test_minres_symmetric_indefinite() {
let rows = vec![0, 0, 1, 1, 1, 2, 2];
let cols = vec![0, 1, 0, 1, 2, 1, 2];
let data = vec![4.0, -1.0, -1.0, 2.0, -1.0, -1.0, 4.0];
let shape = (3, 3);
let matrix = CsrMatrix::new(data, rows, cols, shape).expect("Operation failed");
let op = matrix.as_linear_operator();
let b = vec![1.0, 0.0, 1.0];
let options = MINRESOptions::default();
let result = minres(op.as_ref(), &b, options).expect("Operation failed");
assert!(result.converged);
let ax = op.matvec(&result.x).expect("Operation failed");
for (axi, bi) in ax.iter().zip(&b) {
assert!((axi - bi).abs() < 1e-9);
}
}
#[test]
fn test_minres_preconditioned() {
let rows = vec![0, 0, 1, 1, 1, 2, 2];
let cols = vec![0, 1, 0, 1, 2, 1, 2];
let data = vec![4.0, -1.0, -1.0, 4.0, -1.0, -1.0, 4.0];
let shape = (3, 3);
let matrix = CsrMatrix::new(data, rows, cols, shape).expect("Operation failed");
let op = matrix.as_linear_operator();
let b = vec![1.0, 2.0, 3.0];
let preconditioner = Box::new(DiagonalOperator::new(vec![1.0 / 4.0, 1.0 / 4.0, 1.0 / 4.0]));
let options = MINRESOptions {
preconditioner: Some(preconditioner),
..Default::default()
};
let result = minres(op.as_ref(), &b, options).expect("Operation failed");
assert!(result.converged);
let ax = op.matvec(&result.x).expect("Operation failed");
for (axi, bi) in ax.iter().zip(&b) {
assert!((axi - bi).abs() < 1e-9);
}
}
}