#[cfg(feature = "complex")]
use crate::algebra::bridge::BridgeScratch;
use crate::algebra::prelude::*;
use crate::core::traits::MatVec;
use crate::error::KError;
use crate::matrix::convert::csr_from_linop;
use crate::matrix::op::LinOp;
use crate::matrix::sparse::CsrMatrix;
#[cfg(feature = "complex")]
use crate::ops::kpc::KPreconditioner;
use crate::preconditioner::Preconditioner as ObjPreconditioner;
#[cfg(feature = "complex")]
use crate::preconditioner::bridge::{
apply_pc_mut_s as bridge_apply_pc_mut_s, apply_pc_s as bridge_apply_pc_s,
};
use crate::preconditioner::legacy::Preconditioner;
use faer::Mat;
use std::sync::Arc;
use std::sync::Mutex;
pub struct ChebyshevPre {
matrix: Mat<f64>,
degree: usize,
lambda_min: f64,
lambda_max: f64,
}
impl ChebyshevPre {
pub fn new(matrix: Mat<f64>, degree: usize, lambda_min: f64, lambda_max: f64) -> Self {
Self {
matrix,
degree,
lambda_min,
lambda_max,
}
}
pub fn estimate_eigenvalue_bounds(matrix: &Mat<f64>, max_iters: usize, tol: f64) -> (f64, f64) {
let n = matrix.nrows();
if n == 0 {
return (1.0, 1.0);
}
let mut v = vec![1.0 / (n as f64).sqrt(); n];
let mut av = vec![0.0; n];
let mut lambda_max = 1.0;
for _ in 0..max_iters {
crate::matrix::op::LinOp::matvec(matrix, &v, &mut av);
let new_lambda_max: f64 = v.iter().zip(av.iter()).map(|(&vi, &avi)| vi * avi).sum();
if (new_lambda_max - lambda_max).abs() < tol * lambda_max.abs() {
lambda_max = new_lambda_max;
break;
}
lambda_max = new_lambda_max;
let norm: f64 = av.iter().map(|&x| x * x).sum::<f64>().sqrt();
if norm > 0.0 {
for i in 0..n {
v[i] = av[i] / norm;
}
}
}
let lambda_min = lambda_max * 0.01;
(lambda_min, lambda_max)
}
}
impl Preconditioner<Mat<f64>, Vec<f64>> for ChebyshevPre {
fn setup(&mut self, a: &Mat<f64>) -> Result<(), KError> {
self.matrix = a.clone();
Ok(())
}
fn apply(
&self,
_side: crate::preconditioner::PcSide,
r: &Vec<f64>,
z: &mut Vec<f64>,
) -> Result<(), KError> {
if r.len() != z.len() {
return Err(KError::SolveError("Vector length mismatch".to_string()));
}
apply_chebyshev(
&self.matrix,
r,
z,
self.lambda_min,
self.lambda_max,
self.degree,
);
Ok(())
}
}
pub struct Chebyshev {
pub degree: usize,
pub lambda_min: Option<f64>,
pub lambda_max: Option<f64>,
}
#[derive(Clone, Copy, Debug)]
pub struct ChebBounds {
pub lam_max: f64,
pub lam_min: f64,
}
fn apply_sym_scaled(
a: &CsrMatrix<f64>,
d_sqrt_inv: &[f64],
x: &[f64],
y: &mut [f64],
) -> Result<(), KError> {
let n = a.nrows();
if d_sqrt_inv.len() != n || x.len() != n || y.len() != n {
return Err(KError::InvalidInput(
"apply_sym_scaled: dimension mismatch".into(),
));
}
for i in 0..n {
y[i] = d_sqrt_inv[i] * x[i];
}
let mut tmp = vec![0.0; n];
a.spmv_scaled(1.0, y, 0.0, &mut tmp[..])?;
for i in 0..n {
y[i] = d_sqrt_inv[i] * tmp[i];
}
Ok(())
}
pub fn estimate_lmax_sym(
a: &CsrMatrix<f64>,
d_sqrt_inv: &[f64],
power_steps: usize,
) -> Result<f64, KError> {
let n = a.nrows();
if n == 0 {
return Ok(0.0);
}
if d_sqrt_inv.len() != n {
return Err(KError::InvalidInput(
"estimate_lmax_sym: dimension mismatch".into(),
));
}
let mut x = vec![0.0; n];
for i in 0..n {
let pattern = (i.wrapping_mul(2_654_435_761)) & 1;
x[i] = if pattern == 0 { 1.0 } else { -1.0 };
}
let mut nrm = x.iter().map(|v| v * v).sum::<f64>().sqrt();
if nrm == 0.0 {
x[0] = 1.0;
nrm = 1.0;
}
for xi in &mut x {
*xi /= nrm;
}
let mut y = vec![0.0; n];
let steps = power_steps.max(1);
for _ in 0..steps {
apply_sym_scaled(a, d_sqrt_inv, &x, &mut y)?;
let nrm = y.iter().map(|v| v * v).sum::<f64>().sqrt().max(1e-300);
for i in 0..n {
x[i] = y[i] / nrm;
}
}
apply_sym_scaled(a, d_sqrt_inv, &x, &mut y)?;
let lam = x.iter().zip(y.iter()).map(|(xi, yi)| xi * yi).sum::<f64>();
Ok(lam.max(0.0))
}
pub fn chebyshev_smooth_csr(
a: &CsrMatrix<f64>,
d_inv: &[f64],
rhs: &[f64],
z: &mut [f64],
deg: usize,
bounds: &ChebBounds,
work_r: &mut [f64],
work_q: &mut [f64],
work_aq: &mut [f64],
) -> Result<(), KError> {
if deg == 0 {
return Ok(());
}
let n = a.nrows();
if d_inv.len() != n || rhs.len() != n || z.len() != n {
return Err(KError::InvalidInput(
"chebyshev_smooth_csr: dimension mismatch".into(),
));
}
if work_r.len() != n || work_q.len() != n || work_aq.len() != n {
return Err(KError::InvalidInput(
"chebyshev_smooth_csr: workspace mismatch".into(),
));
}
if !bounds.lam_max.is_finite() || !bounds.lam_min.is_finite() || bounds.lam_max <= 0.0 {
return Err(KError::InvalidInput(
"chebyshev_smooth_csr: invalid eigenvalue bounds".into(),
));
}
a.spmv_scaled(1.0, z, 0.0, work_aq)?;
for i in 0..n {
work_r[i] = rhs[i] - work_aq[i];
}
let theta = (0.5 * (bounds.lam_max + bounds.lam_min)).max(1e-12);
let delta = 0.5 * (bounds.lam_max - bounds.lam_min);
let mut alpha = 1.0 / theta;
for i in 0..n {
work_q[i] = d_inv[i] * work_r[i];
}
for i in 0..n {
z[i] += alpha * work_q[i];
}
a.spmv_scaled(1.0, work_q, 0.0, work_aq)?;
for i in 0..n {
work_r[i] -= alpha * work_aq[i];
}
for _ in 1..deg {
let beta = 0.25 * delta * delta * alpha;
for i in 0..n {
work_q[i] = d_inv[i] * work_r[i] + beta * work_q[i];
}
alpha = 1.0 / (theta - beta);
for i in 0..n {
z[i] += alpha * work_q[i];
}
a.spmv_scaled(1.0, work_q, 0.0, work_aq)?;
for i in 0..n {
work_r[i] -= alpha * work_aq[i];
}
}
Ok(())
}
impl Chebyshev {
pub fn new(degree: usize, lambda_min: Option<f64>, lambda_max: Option<f64>) -> Self {
Self {
degree,
lambda_min,
lambda_max,
}
}
}
impl<M, V> Preconditioner<M, V> for Chebyshev
where
M: MatVec<Vec<f64>>,
V: AsRef<[f64]> + AsMut<[f64]> + Clone,
{
fn setup(&mut self, _a: &M) -> Result<(), KError> {
Ok(())
}
fn apply(
&self,
_side: crate::preconditioner::PcSide,
_r: &V,
_z: &mut V,
) -> Result<(), KError> {
Err(KError::SolveError(
"Chebyshev preconditioner requires matrix argument; use apply_chebyshev free function."
.to_string(),
))
}
}
#[allow(clippy::ptr_arg)]
pub fn apply_chebyshev<M>(a: &M, r: &Vec<f64>, z: &mut [f64], alpha: f64, beta: f64, m: usize)
where
M: MatVec<Vec<f64>>,
{
if (beta - alpha).abs() < f64::EPSILON {
z.copy_from_slice(r);
return;
}
let n = r.len();
let mut v0 = r.to_vec();
let mut v1 = vec![0.0; n];
let mut v2 = vec![0.0; n];
let c: f64 = (beta + alpha) / 2.0;
let d: f64 = (beta - alpha) / 2.0;
let tau = 1.0 / chebyshev_t(m, (0.0 - c) / d);
a.matvec(&v0, &mut v1);
for i in 0..n {
v1[i] = (v1[i] - c * v0[i]) / d;
}
if m == 0 {
z.copy_from_slice(&v0);
return;
} else if m == 1 {
z.copy_from_slice(&v1);
return;
}
for _k in 2..=m {
a.matvec(&v1, &mut v2);
for i in 0..n {
v2[i] = (2.0 * (v2[i] - c * v1[i]) / d) - v0[i];
}
std::mem::swap(&mut v0, &mut v1);
std::mem::swap(&mut v1, &mut v2);
}
#[cfg(feature = "rayon")]
{
use rayon::prelude::*;
z.par_iter_mut().enumerate().for_each(|(i, zi)| {
*zi = tau * v1[i];
});
}
#[cfg(not(feature = "rayon"))]
{
for i in 0..n {
z[i] = tau * v1[i];
}
}
}
fn chebyshev_t(m: usize, x: R) -> R {
if m == 0 {
1.0
} else if m == 1 {
x
} else {
let mut t0 = 1.0;
let mut t1 = x;
let mut t2;
for _ in 2..=m {
t2 = 2.0 * x * t1 - t0;
t0 = t1;
t1 = t2;
}
t1
}
}
#[derive(Default)]
struct ChebScratch {
v0: Vec<R>,
v1: Vec<R>,
v2: Vec<R>,
}
pub struct ChebyshevPc {
degree: usize,
lambda_min: f64,
lambda_max: f64,
a_csr: Option<Arc<CsrMatrix<f64>>>,
n: usize,
scratch: Mutex<ChebScratch>,
}
impl ChebyshevPc {
pub fn new(degree: usize, lambda_min: f64, lambda_max: f64) -> Self {
Self {
degree,
lambda_min,
lambda_max,
a_csr: None,
n: 0,
scratch: Mutex::new(ChebScratch::default()),
}
}
}
#[cfg(not(feature = "complex"))]
impl ObjPreconditioner for ChebyshevPc {
fn dims(&self) -> (usize, usize) {
(self.n, self.n)
}
fn setup(&mut self, op: &dyn LinOp<S = f64>) -> Result<(), crate::error::KError> {
let csr = csr_from_linop(op, 0.0)?;
let n = csr.nrows();
self.a_csr = Some(csr);
self.n = n;
let mut s = self.scratch.lock().unwrap();
if s.v0.len() != n {
s.v0.resize(n, R::default());
}
if s.v1.len() != n {
s.v1.resize(n, R::default());
}
if s.v2.len() != n {
s.v2.resize(n, R::default());
}
Ok(())
}
fn supports_numeric_update(&self) -> bool {
true
}
fn update_numeric(&mut self, op: &dyn LinOp<S = f64>) -> Result<(), crate::error::KError> {
let csr = csr_from_linop(op, 0.0)?;
self.a_csr = Some(csr);
Ok(())
}
fn apply(
&self,
_side: crate::preconditioner::PcSide,
r: &[f64],
z: &mut [f64],
) -> Result<(), crate::error::KError> {
use crate::error::KError;
let a = self
.a_csr
.as_ref()
.ok_or_else(|| KError::InvalidInput("ChebyshevPc not setup".into()))?;
if r.len() != self.n || z.len() != self.n {
return Err(KError::InvalidInput(
"dimension mismatch in ChebyshevPc::apply".into(),
));
}
let n = self.n;
let mut s = self.scratch.lock().unwrap();
let mut v0 = std::mem::take(&mut s.v0);
let mut v1 = std::mem::take(&mut s.v1);
let mut v2 = std::mem::take(&mut s.v2);
let c = (self.lambda_max + self.lambda_min) / 2.0;
let d = (self.lambda_max - self.lambda_min) / 2.0;
let res = (|| {
if d.abs() < f64::EPSILON {
z.copy_from_slice(r);
return Ok(());
}
let tau = 1.0 / chebyshev_t(self.degree, (0.0 - c) / d);
if self.degree == 0 {
z.copy_from_slice(r);
return Ok(());
}
a.spmv_scaled(1.0, r, 0.0, &mut v1[..n])?;
for i in 0..n {
v1[i] = (v1[i] - c * r[i]) / d;
}
if self.degree == 1 {
for i in 0..n {
z[i] = tau * v1[i];
}
return Ok(());
}
v0[..n].copy_from_slice(r);
for _k in 2..=self.degree {
a.spmv_scaled(1.0, &v1[..n], 0.0, &mut v2[..n])?;
for i in 0..n {
v2[i] = 2.0 * ((v2[i] - c * v1[i]) / d) - v0[i];
}
std::mem::swap(&mut v0, &mut v1);
std::mem::swap(&mut v1, &mut v2);
}
#[cfg(feature = "rayon")]
{
use rayon::prelude::*;
z.par_iter_mut()
.zip(v1[..n].par_iter())
.for_each(|(zi, &vi)| *zi = tau * vi);
}
#[cfg(not(feature = "rayon"))]
{
for i in 0..n {
z[i] = tau * v1[i];
}
}
Ok(())
})();
s.v0 = v0;
s.v1 = v1;
s.v2 = v2;
res
}
fn required_format(&self) -> crate::matrix::format::OpFormat {
crate::matrix::format::OpFormat::Csr
}
}
#[cfg(feature = "complex")]
impl ObjPreconditioner for ChebyshevPc {
fn setup(&mut self, _op: &dyn LinOp<S = S>) -> Result<(), crate::error::KError> {
Err(KError::Unsupported(
"Chebyshev does not support complex scalars yet".into(),
))
}
fn apply(
&self,
_side: crate::preconditioner::PcSide,
_x: &[S],
_y: &mut [S],
) -> Result<(), crate::error::KError> {
Err(KError::Unsupported(
"Chebyshev does not support complex scalars yet".into(),
))
}
}
#[cfg(feature = "complex")]
impl KPreconditioner for ChebyshevPc {
type Scalar = S;
#[inline]
fn dims(&self) -> (usize, usize) {
ObjPreconditioner::dims(self)
}
fn apply_s(
&self,
side: crate::preconditioner::PcSide,
x: &[S],
y: &mut [S],
scratch: &mut BridgeScratch,
) -> Result<(), crate::error::KError> {
bridge_apply_pc_s(self, side, x, y, scratch)
}
fn apply_mut_s(
&mut self,
side: crate::preconditioner::PcSide,
x: &[S],
y: &mut [S],
scratch: &mut BridgeScratch,
) -> Result<(), crate::error::KError> {
bridge_apply_pc_mut_s(self, side, x, y, scratch)
}
fn on_restart_s(
&mut self,
outer_iter: usize,
residual_norm: R,
) -> Result<(), crate::error::KError> {
ObjPreconditioner::on_restart(self, outer_iter, residual_norm)
}
}
#[cfg(all(test, not(feature = "complex")))]
mod tests {
use super::*;
use crate::core::traits::MatVec;
use crate::matrix::op::CsrOp;
use crate::preconditioner::PcSide;
use std::sync::Arc;
struct DenseMat<T> {
data: Vec<Vec<T>>,
}
impl<T: Copy> DenseMat<T> {
fn new(data: Vec<Vec<T>>) -> Self {
Self { data }
}
}
impl<T> MatVec<Vec<T>> for DenseMat<T>
where
T: Copy + std::ops::Mul<Output = T> + std::iter::Sum,
{
fn matvec(&self, x: &Vec<T>, y: &mut Vec<T>) {
for i in 0..self.data.len() {
y[i] = (0..self.data[0].len())
.map(|j| self.data[i][j] * x[j])
.sum();
}
}
}
#[test]
fn chebyshev_identity() {
let a = DenseMat::new(vec![vec![1.0f64, 0.0], vec![0.0, 1.0]]);
let r = vec![2.0f64, 3.0];
let mut z = vec![0.0; 2];
apply_chebyshev(&a, &r, &mut z, 1.0, 1.0, 1);
assert!(z.iter().all(|&zi| zi.is_finite()));
}
#[test]
fn chebyshev_diagonal() {
let a = DenseMat::new(vec![vec![2.0f64, 0.0], vec![0.0, 3.0]]);
let r = vec![1.0f64, 1.0];
let mut z = vec![0.0; 2];
apply_chebyshev(&a, &r, &mut z, 2.0, 3.0, 1);
assert!(z.iter().all(|&zi| zi.is_finite()));
}
#[test]
fn chebyshev_pc_degenerate_copy() {
let row_ptr = vec![0, 1, 2];
let col_idx = vec![0, 1];
let values = vec![1.0, 1.0];
let csr = Arc::new(CsrMatrix::from_csr(2, 2, row_ptr, col_idx, values));
let op = CsrOp::new(csr);
let mut pc = ChebyshevPc::new(3, 1.0, 1.0);
pc.setup(&op).expect("setup");
let rhs = vec![2.5, -3.0];
let mut out = vec![0.0; rhs.len()];
pc.apply(PcSide::Left, &rhs, &mut out).expect("apply");
for i in 0..rhs.len() {
assert!((out[i] - rhs[i]).abs() < 1e-14);
}
}
#[test]
fn chebyshev_pc_basic_spd() {
let row_ptr = vec![0, 2, 5, 7];
let col_idx = vec![0, 1, 0, 1, 2, 1, 2];
let values = vec![2.0, -1.0, -1.0, 2.0, -1.0, -1.0, 2.0];
let csr = Arc::new(CsrMatrix::from_csr(3, 3, row_ptr, col_idx, values));
let op = CsrOp::new(csr);
let mut pc = ChebyshevPc::new(2, 0.1, 4.0);
pc.setup(&op).expect("setup");
let rhs = vec![1.0, 0.0, -1.0];
let mut out = vec![0.0; rhs.len()];
pc.apply(PcSide::Left, &rhs, &mut out).expect("apply");
assert!(out.iter().all(|zi| zi.is_finite()));
assert!(out.iter().any(|&zi| zi.abs() > 1e-6));
}
}
#[cfg(all(test, feature = "complex"))]
mod complex_tests {
use super::*;
use crate::algebra::bridge::BridgeScratch;
use crate::error::KError;
use crate::ops::kpc::KPreconditioner;
use crate::preconditioner::PcSide;
#[test]
fn apply_s_reports_unsupported() {
let pc = ChebyshevPc::new(2, 0.5, 3.0);
let rhs = vec![S::one(); 2];
let mut out = vec![S::zero(); rhs.len()];
let mut scratch = BridgeScratch::default();
let err = pc
.apply_s(PcSide::Left, &rhs, &mut out, &mut scratch)
.unwrap_err();
assert!(matches!(err, KError::Unsupported(_)));
}
}