#[allow(clippy::cast_precision_loss)]
fn chebyshev_schedule(z: f64, tol: f64) -> (u32, usize) {
if z.is_nan() || z <= 0.0 {
return (1, 0);
}
let mut s = 1_u32;
loop {
let m = exp_chebyshev_degree(z / f64::from(s), tol / f64::from(s));
if m < MAX_CHEB_DEGREE || s >= (1 << 30) {
return (s, m);
}
s *= 2;
}
}
#[allow(clippy::too_many_arguments, clippy::many_single_char_names)]
fn expmv_chebyshev<F: SemiflowFloat, Op: SymmetricLinearOp<F> + ?Sized>(
op: &Op,
lambda_max: F,
tau: F,
v: &[F],
out: &mut [F],
tol: F,
scratch: &mut ScratchPool<F>,
) -> Result<(), SemiflowError> {
let n = op.n();
let z = (tau * lambda_max).to_f64().unwrap_or(f64::NAN) * 0.5;
let tol_f = tol.to_f64().unwrap_or(1e-10);
let (s, m) = chebyshev_schedule(z, tol_f);
if m == 0 {
out[..n].copy_from_slice(&v[..n]);
return Ok(());
}
let mut coeffs_f64 = Vec::new();
exp_chebyshev_coefficients(z / f64::from(s), tol_f / f64::from(s), &mut coeffs_f64);
let mut coeffs = scratch.take_vec(coeffs_f64.len());
for (c, &a) in coeffs.iter_mut().zip(&coeffs_f64) {
*c = F::from(a).unwrap_or_else(F::zero);
}
let scale = F::from(2.0_f64).unwrap() / lambda_max;
let mut bufs = [(); 4].map(|()| scratch.take_vec(n));
let mut current = scratch.take_vec(n);
current.copy_from_slice(&v[..n]);
for _ in 0..s {
let [t_prev, t_curr, spmv, result] = &mut bufs;
chebyshev_series(op, &coeffs, scale, ¤t, t_prev, t_curr, spmv, result);
core::mem::swap(&mut current, result);
}
let finite = current.iter().all(|x| x.is_finite());
out[..n].copy_from_slice(¤t);
scratch.return_vec(current);
scratch.return_vec(coeffs);
for b in bufs {
scratch.return_vec(b);
}
if finite {
Ok(())
} else {
Err(SemiflowError::DomainViolation {
what: "Chebyshev expmv: non-finite output (non-finite input or lambda_max bound)",
value: z,
})
}
}
#[allow(clippy::too_many_arguments)]
fn expmv_lanczos<F: SemiflowFloat, Op: SymmetricLinearOp<F> + ?Sized>(
op: &Op,
lambda_max: F,
tau: F,
v: &[F],
out: &mut [F],
m_max: usize,
tol: F,
scratch: &mut ScratchPool<F>,
) -> Result<(), SemiflowError> {
let n = op.n();
let (s, m) = lanczos_schedule(
lambda_max.to_f64().unwrap_or(f64::NAN),
tau.to_f64().unwrap_or(f64::NAN),
tol.to_f64().unwrap_or(1e-10),
m_max,
n,
);
if s == u64::MAX {
#[allow(clippy::cast_precision_loss)]
return Err(SemiflowError::DomainViolation {
what: "Lanczos expmv: tau*lambda_max needs more than 1e15 substeps at this m_max",
value: (tau * lambda_max).to_f64().unwrap_or(f64::NAN),
});
}
#[allow(clippy::cast_precision_loss)]
let step_tau = tau / F::from(s as f64).unwrap();
let mut current = scratch.take_vec(n);
let mut next = scratch.take_vec(n);
current.copy_from_slice(&v[..n]);
for _ in 0..s {
lanczos_step_inner(op, ¤t, &mut next, step_tau, m, scratch)?;
core::mem::swap(&mut current, &mut next);
}
out[..n].copy_from_slice(¤t);
scratch.return_vec(current);
scratch.return_vec(next);
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn graph_expmv_krylov<F, Op>(
op: &Op,
tau: F,
v: &[F],
out: &mut [F],
path: KrylovPath,
tol: F,
scratch: &mut ScratchPool<F>,
) -> Result<(), SemiflowError>
where
F: SemiflowFloat,
Op: SymmetricLinearOp<F>,
{
validate_tau(tau)?;
let lambda_max = op.lambda_max_bound();
check_schedule_arg(lambda_max, tau)?;
match path {
KrylovPath::Chebyshev => expmv_chebyshev(op, lambda_max, tau, v, out, tol, scratch),
KrylovPath::Lanczos { m_max } => {
expmv_lanczos(op, lambda_max, tau, v, out, m_max, tol, scratch)
}
KrylovPath::ImplicitEuler { n_steps, cg_max_iter } => implicit_euler_action(
op as &dyn SymmetricLinearOp<F>,
v,
out,
tau,
n_steps,
tol,
cg_max_iter,
scratch,
),
}
}