use alloc::{vec, vec::Vec};
use crate::{
error::SemiflowError,
expmv::select_s_m,
float::{from_f64, SemiflowFloat},
generator_action::GeneratorAction,
phi_action_helpers::{aug_matvec_inplace, Coupling},
phi_krylov::{axpy, dot, norm2, KrylovPhiOptions, KrylovPhiReport},
pow2::pow2_scale,
scratch::ScratchPool,
};
const GAMMA: f64 = 0.9;
const DELTA: f64 = 1.2;
const BREAKDOWN: f64 = 1e-12;
const MAX_REJECT: usize = 10;
pub(crate) struct Augmented<'a, F: SemiflowFloat, Op> {
pub(crate) op: &'a Op,
pub(crate) coupling: &'a Coupling<'a, F>,
pub(crate) tau: F,
pub(crate) n: usize,
}
impl<F: SemiflowFloat, Op: GeneratorAction<F>> Augmented<'_, F, Op> {
fn apply(&self, x: &[F], out: &mut [F], av: &mut [F]) {
out.copy_from_slice(x);
let (w_u, w_c) = out.split_at_mut(self.n);
aug_matvec_inplace(self.op, self.coupling, w_u, w_c, self.tau, F::one(), av);
}
}
fn round2(x: f64) -> f64 {
let s = libm::pow(10.0, libm::floor(libm::log10(x)) - 1.0);
libm::ceil(x / s) * s
}
struct Basis<F> {
v: Vec<Vec<F>>,
h: Vec<f64>,
mb: usize,
k1: usize,
avnorm: f64,
}
#[allow(clippy::many_single_char_names)] pub(crate) fn hess_exp_e1(
h: &[f64],
dim: usize,
mx: usize,
t: f64,
) -> Result<Vec<f64>, SemiflowError> {
let norm = (0..mx)
.map(|c| (0..mx).map(|r| libm::fabs(h[r * dim + c])).sum::<f64>())
.fold(0.0, f64::max);
let (s, deg) = select_s_m(norm, t)?;
let ts = t / f64::from(s);
let (mut y, mut w, mut hw) = (vec![0.0; mx], vec![0.0; mx], vec![0.0; mx]);
y[0] = 1.0;
for _ in 0..s {
w.copy_from_slice(&y);
for k in 1..=deg {
for (r, out) in hw.iter_mut().enumerate() {
let row = &h[r * dim..r * dim + mx];
let c0 = r.saturating_sub(1);
*out = row[c0..].iter().zip(&w[c0..]).map(|(a, b)| a * b).sum();
}
let f = ts / f64::from(k);
for (wi, &x) in w.iter_mut().zip(&hw) {
*wi = f * x;
}
for (yi, &wi) in y.iter_mut().zip(&w) {
*yi += wi;
}
}
}
Ok(y)
}
#[allow(clippy::many_single_char_names, clippy::too_many_arguments)] fn arnoldi<F: SemiflowFloat, Op: GeneratorAction<F>>(
aug: &Augmented<'_, F, Op>,
y: &[F],
beta: f64,
m: usize,
(anorm, tol): (f64, f64),
av: &mut [F],
rep: &mut KrylovPhiReport,
) -> Basis<F> {
let dim = m + 2;
let mut h = vec![0.0; dim * dim];
let mut v: Vec<Vec<F>> = Vec::with_capacity(m + 1);
v.push(y.iter().map(|&x| x * from_f64::<F>(1.0 / beta)).collect());
for j in 0..m {
let mut z = vec![F::zero(); y.len()];
aug.apply(&v[j], &mut z, av);
rep.matvecs += 1;
for (i, vi) in v.iter().enumerate() {
let c = dot(&z, vi);
h[i * dim + j] = c;
axpy(&mut z, -c, vi);
}
let hn = norm2(&z);
if hn <= BREAKDOWN * anorm.max(1.0) && (j + 1 == y.len() || hn * beta <= tol) {
return Basis {
v,
h,
mb: j + 1,
k1: 0,
avnorm: hn,
};
}
h[(j + 1) * dim + j] = hn;
v.push(z.iter().map(|&x| x * from_f64::<F>(1.0 / hn)).collect());
}
h[(m + 1) * dim + m] = 1.0;
let mut z = vec![F::zero(); y.len()];
aug.apply(&v[m], &mut z, av);
rep.matvecs += 1;
Basis {
v,
h,
mb: m,
k1: 2,
avnorm: norm2(&z),
}
}
fn local_error(f: &[f64], m: usize, beta: f64, avnorm: f64) -> (f64, f64) {
let phi1 = libm::fabs(beta * f[m]);
let phi2 = libm::fabs(beta * f[m + 1] * avnorm);
#[allow(clippy::cast_precision_loss)] let (one_m, one_m1) = (1.0 / m as f64, 1.0 / (m as f64 - 1.0).max(1.0));
if phi1 > 10.0 * phi2 {
(phi2, one_m)
} else if phi1 > phi2 {
((phi1 * phi2) / (phi1 - phi2), one_m)
} else {
(phi1, one_m1)
}
}
pub(crate) fn expv<F: SemiflowFloat, Op: GeneratorAction<F>>(
aug: &Augmented<'_, F, Op>,
anorm: f64,
y: &mut [F],
opts: KrylovPhiOptions,
scratch: &mut ScratchPool<F>,
) -> Result<KrylovPhiReport, SemiflowError> {
if !anorm.is_finite() {
return Err(SemiflowError::DomainViolation {
what: "phi_combination_krylov: non-finite norm bound",
value: anorm,
});
}
let mut rep = KrylovPhiReport::default();
let Some((down, up)) = pow2_scale(y) else {
return Ok(rep);
};
for x in y.iter_mut() {
*x *= down;
}
let beta0 = norm2(y);
let tol = opts.tol * beta0;
let m = opts.m.clamp(2, y.len().max(2));
let mut av = scratch.take_vec(aug.n);
let result = substeps(aug, anorm, y, tol, m, opts.max_substeps, &mut av, &mut rep);
scratch.return_vec(av);
for x in y.iter_mut() {
*x *= up;
}
rep.est_error /= beta0;
result.map(|()| rep)
}
#[allow(clippy::too_many_arguments)]
fn substeps<F: SemiflowFloat, Op: GeneratorAction<F>>(
aug: &Augmented<'_, F, Op>,
anorm: f64,
y: &mut [F],
tol: f64,
m: usize,
max_substeps: usize,
av: &mut [F],
rep: &mut KrylovPhiReport,
) -> Result<(), SemiflowError> {
#[allow(clippy::cast_precision_loss)]
let mf = m as f64;
let fact = libm::pow((mf + 1.0) / core::f64::consts::E, mf + 1.0)
* libm::sqrt(2.0 * core::f64::consts::PI * (mf + 1.0));
let mut beta = norm2(y);
let mut elapsed = 0.0_f64;
let mut next_step = round2(
(1.0 / anorm.max(f64::MIN_POSITIVE))
* libm::pow(fact * tol / (4.0 * beta * anorm.max(1.0)), 1.0 / mf),
);
while elapsed < 1.0 {
if rep.substeps >= max_substeps {
return Err(SemiflowError::ConvergenceFailed {
last_residual: rep.est_error,
max_iter: max_substeps,
});
}
let basis = arnoldi(aug, y, beta, m, (anorm, tol), av, rep);
let remaining = 1.0 - elapsed;
let (t_step, coeff, err) = trial_steps(&basis, m, beta, tol, (next_step, remaining), rep)?;
y.fill(F::zero());
for (i, &c) in coeff.iter().enumerate() {
axpy(y, beta * c, &basis.v[i]);
}
elapsed += t_step;
rep.substeps += 1;
rep.est_error += err.max(anorm * f64::EPSILON * beta);
let xm = 1.0 / mf;
next_step = if err > 0.0 {
round2(GAMMA * t_step * libm::pow(t_step * tol / err, xm))
} else {
1.0 - elapsed
};
beta = norm2(y);
if beta == 0.0 {
return Ok(());
}
}
Ok(())
}
fn trial_steps<F: SemiflowFloat>(
b: &Basis<F>,
m: usize,
beta: f64,
tol: f64,
(t_new, remaining): (f64, f64),
rep: &mut KrylovPhiReport,
) -> Result<(f64, Vec<f64>, f64), SemiflowError> {
let dim = m + 2;
if b.k1 == 0 {
let f = hess_exp_e1(&b.h, dim, b.mb, remaining)?;
return Ok((remaining, f, beta * b.avnorm * remaining));
}
let mut t_step = t_new.min(remaining);
for _ in 0..=MAX_REJECT {
let mut f = hess_exp_e1(&b.h, dim, m + 2, t_step)?;
let (err, xm) = local_error(&f, m, beta, b.avnorm);
if err <= DELTA * t_step * tol {
f.truncate(m + 1);
return Ok((t_step, f, err));
}
rep.rejected += 1;
t_step = round2(GAMMA * t_step * libm::pow(t_step * tol / err, xm));
}
Err(SemiflowError::ConvergenceFailed {
last_residual: tol,
max_iter: MAX_REJECT,
})
}