use crate::csr::CsrMatrix;
use crate::error::{SparseError, SparseResult};
use crate::linalg::interface::LinearOperator;
use scirs2_core::numeric::{Float, NumAssign, SparseElement};
use std::fmt::Debug;
use std::iter::Sum;
pub struct JacobiPreconditioner<F> {
inv_diagonal: Vec<F>,
size: usize,
}
impl<F: Float + SparseElement> JacobiPreconditioner<F> {
pub fn new(matrix: &CsrMatrix<F>) -> SparseResult<Self>
where
F: Debug,
{
let n = matrix.rows();
let mut inv_diagonal = vec![F::sparse_zero(); n];
for (i, diag_inv) in inv_diagonal.iter_mut().enumerate().take(n) {
let diag_val = matrix.get(i, i);
if diag_val.abs() < F::epsilon() {
return Err(SparseError::ValueError(format!(
"Zero diagonal element at position {i}"
)));
}
*diag_inv = F::sparse_one() / diag_val;
}
Ok(Self {
inv_diagonal,
size: n,
})
}
pub fn from_diagonal(diagonal: Vec<F>) -> SparseResult<Self> {
let size = diagonal.len();
let mut inv_diagonal = vec![F::sparse_zero(); size];
for (i, &d) in diagonal.iter().enumerate() {
if d.abs() < F::epsilon() {
return Err(SparseError::ValueError(format!(
"Zero _diagonal element at position {i}"
)));
}
inv_diagonal[i] = F::sparse_one() / d;
}
Ok(Self { inv_diagonal, size })
}
}
impl<F: Float + NumAssign + SparseElement> LinearOperator<F> for JacobiPreconditioner<F> {
fn shape(&self) -> (usize, usize) {
(self.size, self.size)
}
fn matvec(&self, x: &[F]) -> SparseResult<Vec<F>> {
if x.len() != self.size {
return Err(SparseError::DimensionMismatch {
expected: self.size,
found: x.len(),
});
}
Ok(x.iter()
.zip(&self.inv_diagonal)
.map(|(&xi, &di)| xi * di)
.collect())
}
fn has_adjoint(&self) -> bool {
true
}
fn rmatvec(&self, x: &[F]) -> SparseResult<Vec<F>> {
self.matvec(x)
}
}
pub struct SSORPreconditioner<F> {
matrix: CsrMatrix<F>,
omega: F,
diagonal: Vec<F>,
}
impl<F: Float + NumAssign + Sum + Debug + SparseElement + 'static> SSORPreconditioner<F> {
pub fn new(matrix: CsrMatrix<F>, omega: F) -> SparseResult<Self> {
if omega <= F::sparse_zero()
|| omega >= F::from(2.0).expect("Failed to convert constant to float")
{
return Err(SparseError::ValueError(
"Relaxation parameter omega must be in (0, 2)".to_string(),
));
}
let n = matrix.rows();
let mut diagonal = vec![F::sparse_zero(); n];
for (i, diag) in diagonal.iter_mut().enumerate().take(n) {
*diag = matrix.get(i, i);
if diag.abs() < F::epsilon() {
return Err(SparseError::ValueError(format!(
"Zero diagonal element at position {i}"
)));
}
}
Ok(Self {
matrix,
omega,
diagonal,
})
}
}
impl<F: Float + NumAssign + Sum + Debug + SparseElement + 'static> LinearOperator<F>
for SSORPreconditioner<F>
{
fn shape(&self) -> (usize, usize) {
self.matrix.shape()
}
fn matvec(&self, x: &[F]) -> SparseResult<Vec<F>> {
let n = self.matrix.rows();
if x.len() != n {
return Err(SparseError::DimensionMismatch {
expected: n,
found: x.len(),
});
}
let mut y = vec![F::sparse_zero(); n];
for i in 0..n {
let mut sum = x[i];
let row_range = self.matrix.row_range(i);
let row_indices = &self.matrix.indices[row_range.clone()];
let row_data = &self.matrix.data[row_range];
for (idx, &j) in row_indices.iter().enumerate() {
if j < i {
sum -= self.omega * row_data[idx] * y[j];
}
}
y[i] = sum / self.diagonal[i];
}
let mut z = vec![F::sparse_zero(); n];
for i in 0..n {
z[i] = y[i] * self.diagonal[i];
}
let mut w = vec![F::sparse_zero(); n];
for i in (0..n).rev() {
let mut sum = z[i];
let row_range = self.matrix.row_range(i);
let row_indices = &self.matrix.indices[row_range.clone()];
let row_data = &self.matrix.data[row_range];
for (idx, &j) in row_indices.iter().enumerate() {
if j > i {
sum -= self.omega * row_data[idx] * w[j];
}
}
w[i] = sum / self.diagonal[i];
}
Ok(w)
}
fn has_adjoint(&self) -> bool {
false }
}
pub struct ILU0Preconditioner<F> {
l_data: Vec<F>,
u_data: Vec<F>,
l_indices: Vec<usize>,
u_indices: Vec<usize>,
l_indptr: Vec<usize>,
u_indptr: Vec<usize>,
n: usize,
}
impl<F: Float + NumAssign + Sum + Debug + SparseElement + 'static> ILU0Preconditioner<F> {
pub fn new(matrix: &CsrMatrix<F>) -> SparseResult<Self> {
let n = matrix.rows();
let mut data = matrix.data.clone();
let indices = matrix.indices.clone();
let indptr = matrix.indptr.clone();
for k in 0..n {
let k_diag_idx = find_diagonal_index(&indices, &indptr, k)?;
let k_diag = data[k_diag_idx];
if k_diag.abs() < F::epsilon() {
return Err(SparseError::ValueError(format!(
"Zero pivot at position {k}"
)));
}
for i in (k + 1)..n {
let row_start = indptr[i];
let row_end = indptr[i + 1];
let mut k_pos = None;
for (&col, j) in indices[row_start..row_end].iter().zip(row_start..row_end) {
if col == k {
k_pos = Some(j);
break;
}
if col > k {
break;
}
}
if let Some(ki_idx) = k_pos {
let mult = data[ki_idx] / k_diag;
data[ki_idx] = mult;
let k_row_start = indptr[k];
let k_row_end = indptr[k + 1];
for kj_idx in k_row_start..k_row_end {
let j = indices[kj_idx];
if j <= k {
continue;
}
for ij_idx in row_start..row_end {
if indices[ij_idx] == j {
data[ij_idx] = data[ij_idx] - mult * data[kj_idx];
break;
}
}
}
}
}
}
let mut l_data = Vec::new();
let mut u_data = Vec::new();
let mut l_indices = Vec::new();
let mut u_indices = Vec::new();
let mut l_indptr = vec![0];
let mut u_indptr = vec![0];
for i in 0..n {
let row_start = indptr[i];
let row_end = indptr[i + 1];
for j in row_start..row_end {
let col = indices[j];
let val = data[j];
match col.cmp(&i) {
std::cmp::Ordering::Less => {
l_indices.push(col);
l_data.push(val);
}
std::cmp::Ordering::Equal => {
u_indices.push(col);
u_data.push(val);
}
std::cmp::Ordering::Greater => {
u_indices.push(col);
u_data.push(val);
}
}
}
l_indptr.push(l_indices.len());
u_indptr.push(u_indices.len());
}
Ok(Self {
l_data,
u_data,
l_indices,
u_indices,
l_indptr,
u_indptr,
n,
})
}
}
impl<F: Float + NumAssign + Sum + Debug + SparseElement + 'static> LinearOperator<F>
for ILU0Preconditioner<F>
{
fn shape(&self) -> (usize, usize) {
(self.n, self.n)
}
fn matvec(&self, x: &[F]) -> SparseResult<Vec<F>> {
if x.len() != self.n {
return Err(SparseError::DimensionMismatch {
expected: self.n,
found: x.len(),
});
}
let mut y = vec![F::sparse_zero(); self.n];
for i in 0..self.n {
y[i] = x[i];
let row_start = self.l_indptr[i];
let row_end = self.l_indptr[i + 1];
for j in row_start..row_end {
let col = self.l_indices[j];
y[i] = y[i] - self.l_data[j] * y[col];
}
}
let mut z = vec![F::sparse_zero(); self.n];
for i in (0..self.n).rev() {
z[i] = y[i];
let row_start = self.u_indptr[i];
let row_end = self.u_indptr[i + 1];
let mut diag_val = F::sparse_one();
for j in row_start..row_end {
let col = self.u_indices[j];
match col.cmp(&i) {
std::cmp::Ordering::Equal => diag_val = self.u_data[j],
std::cmp::Ordering::Greater => z[i] = z[i] - self.u_data[j] * z[col],
std::cmp::Ordering::Less => {}
}
}
z[i] /= diag_val;
}
Ok(z)
}
fn has_adjoint(&self) -> bool {
false }
}
#[allow(dead_code)]
fn find_diagonal_index(indices: &[usize], indptr: &[usize], row: usize) -> SparseResult<usize> {
let row_start = indptr[row];
let row_end = indptr[row + 1];
for (idx, &col) in indices[row_start..row_end]
.iter()
.enumerate()
.map(|(i, col)| (i + row_start, col))
{
if col == row {
return Ok(idx);
}
}
Err(SparseError::ValueError(format!(
"Missing diagonal element at row {row}"
)))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_jacobi_preconditioner() {
let diagonal = vec![2.0, 3.0, 4.0];
let precond = JacobiPreconditioner::from_diagonal(diagonal).expect("Operation failed");
let x = vec![2.0, 6.0, 12.0];
let result = precond.matvec(&x).expect("Operation failed");
assert!((result[0] - 1.0).abs() < 1e-10);
assert!((result[1] - 2.0).abs() < 1e-10);
assert!((result[2] - 3.0).abs() < 1e-10);
}
}