use num_traits::Float;
#[derive(Debug, Clone)]
pub struct ConvergenceParams<F> {
pub max_iter: usize,
pub grad_tol: F,
pub step_tol: F,
pub func_tol: F,
}
impl Default for ConvergenceParams<f64> {
fn default() -> Self {
ConvergenceParams {
max_iter: 100,
grad_tol: 1e-8,
step_tol: 1e-12,
func_tol: 0.0,
}
}
}
impl Default for ConvergenceParams<f32> {
fn default() -> Self {
ConvergenceParams {
max_iter: 100,
grad_tol: 1e-5,
step_tol: 1e-7,
func_tol: 0.0,
}
}
}
pub fn norm<F: Float>(v: &[F]) -> F {
kahan_sum(v.iter().map(|&x| x * x)).sqrt()
}
pub fn dot<F: Float>(a: &[F], b: &[F]) -> F {
debug_assert_eq!(a.len(), b.len());
kahan_sum(a.iter().zip(b.iter()).map(|(&x, &y)| x * y))
}
pub(crate) fn check_convergence<F: Float>(
grad_norm: F,
step_norm: F,
f_prev: F,
f_val: F,
p: &ConvergenceParams<F>,
) -> Option<crate::result::TerminationReason> {
use crate::result::TerminationReason;
if grad_norm < p.grad_tol {
return Some(TerminationReason::GradientNorm);
}
if step_norm < p.step_tol {
return Some(TerminationReason::StepSize);
}
if p.func_tol > F::zero() && (f_prev - f_val).abs() < p.func_tol * (F::one() + f_val.abs()) {
return Some(TerminationReason::FunctionChange);
}
None
}
const KAHAN_THRESHOLD: usize = 64;
#[inline]
fn kahan_sum<F: Float, I: Iterator<Item = F>>(iter: I) -> F {
let mut it = iter;
let mut s = F::zero();
let mut c = F::zero();
let mut n = 0usize;
let mut prefix: [F; KAHAN_THRESHOLD] = [F::zero(); KAHAN_THRESHOLD];
for slot in prefix.iter_mut() {
if let Some(x) = it.next() {
*slot = x;
n += 1;
} else {
break;
}
}
if n < KAHAN_THRESHOLD {
for &x in prefix.iter().take(n) {
s = s + x;
}
return s;
}
for x in prefix.iter().copied().chain(it) {
let t = s + x;
if s.abs() >= x.abs() {
c = c + ((s - t) + x);
} else {
c = c + ((x - t) + s);
}
s = t;
}
s + c
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn m36_norm_kahan_tight_for_long_vector() {
let v: Vec<f64> = (0..10_000).map(|_| 1.0).collect();
let n = norm(&v);
assert!(
(n - 100.0).abs() < 1e-12,
"norm of 10k ones not near 100: got {}",
n
);
}
#[test]
fn m36_norm_short_vector_still_works() {
let v = vec![3.0_f64, 4.0];
let n = norm(&v);
assert!((n - 5.0).abs() < 1e-15);
}
}