use crate::LinalgError;
use crate::faer_ndarray::FaerEigh;
use crate::roundoff::accumulation_growth;
use faer::Side;
use ndarray::{Array1, Array2};
use std::collections::VecDeque;
#[derive(Debug, Clone)]
pub struct AndersonAccelerator {
depth: usize,
dimension: Option<usize>,
previous_residual: Option<Vec<f64>>,
image_differences: VecDeque<Vec<f64>>,
residual_differences: VecDeque<Vec<f64>>,
}
impl AndersonAccelerator {
pub fn new(depth: usize) -> Result<Self, LinalgError> {
if depth == 0 {
return Err(LinalgError::InvalidInput(
"Anderson acceleration requires a positive history depth".to_string(),
));
}
Ok(Self {
depth,
dimension: None,
previous_residual: None,
image_differences: VecDeque::with_capacity(depth),
residual_differences: VecDeque::with_capacity(depth),
})
}
pub fn history_len(&self) -> usize {
self.residual_differences.len()
}
pub fn reset(&mut self) {
self.previous_residual = None;
self.image_differences.clear();
self.residual_differences.clear();
}
pub fn propose(
&mut self,
residual: &[f64],
taken_step: &[f64],
) -> Result<Option<Vec<f64>>, LinalgError> {
if residual.len() != taken_step.len() {
return Err(LinalgError::InvalidInput(format!(
"Anderson residual width {} != step width {}",
residual.len(),
taken_step.len()
)));
}
if residual.is_empty() {
return Err(LinalgError::InvalidInput(
"Anderson acceleration requires a non-empty state".to_string(),
));
}
match self.dimension {
None => self.dimension = Some(residual.len()),
Some(dimension) if dimension != residual.len() => {
return Err(LinalgError::InvalidInput(format!(
"Anderson state width changed from {dimension} to {}",
residual.len()
)));
}
Some(_) => {}
}
if residual
.iter()
.chain(taken_step)
.any(|value| !value.is_finite())
{
return Err(LinalgError::InvalidInput(
"Anderson acceleration requires a finite residual and step".to_string(),
));
}
if let Some(previous_residual) = self.previous_residual.as_ref() {
let residual_difference: Vec<f64> = residual
.iter()
.zip(previous_residual)
.map(|(new, old)| new - old)
.collect();
let image_difference: Vec<f64> = taken_step
.iter()
.zip(&residual_difference)
.map(|(step, difference)| step + difference)
.collect();
if self.residual_differences.len() == self.depth {
self.image_differences.pop_front();
self.residual_differences.pop_front();
}
self.image_differences.push_back(image_difference);
self.residual_differences.push_back(residual_difference);
}
self.previous_residual = Some(residual.to_vec());
let order = self.residual_differences.len();
if order == 0 {
return Ok(None);
}
let Some(coefficients) = self.solve_multisecant(residual, order)? else {
return Ok(None);
};
let mut step = residual.to_vec();
for (column, weight) in self.image_differences.iter().zip(coefficients.iter()) {
for (value, difference) in step.iter_mut().zip(column) {
*value -= weight * difference;
}
}
if step.iter().any(|value| !value.is_finite()) {
return Ok(None);
}
Ok(Some(step))
}
fn solve_multisecant(
&self,
residual: &[f64],
order: usize,
) -> Result<Option<Array1<f64>>, LinalgError> {
let mut normal = Array2::<f64>::zeros((order, order));
for (left, left_column) in self.residual_differences.iter().enumerate() {
for (right, right_column) in self.residual_differences.iter().enumerate().skip(left) {
let entry: f64 = left_column
.iter()
.zip(right_column)
.map(|(a, b)| a * b)
.sum();
normal[[left, right]] = entry;
normal[[right, left]] = entry;
}
}
let trace: f64 = (0..order).map(|index| normal[[index, index]]).sum();
if !(trace.is_finite() && trace > 0.0) {
return Ok(None);
}
let floor = accumulation_growth(self.dimension.unwrap_or(0).max(1)) * trace;
if !floor.is_finite() {
return Ok(None);
}
let rhs = Array1::from_iter(self.residual_differences.iter().map(|column| {
column
.iter()
.zip(residual)
.map(|(difference, value)| difference * value)
.sum::<f64>()
}));
let (eigenvalues, eigenvectors) = normal.eigh(Side::Lower).map_err(|error| {
LinalgError::InvalidInput(format!(
"Anderson multisecant eigendecomposition failed: {error}"
))
})?;
let projected = eigenvectors.t().dot(&rhs);
let mut spectral = Array1::<f64>::zeros(order);
for mode in 0..order {
if eigenvalues[mode] > floor {
spectral[mode] = projected[mode] / eigenvalues[mode];
}
}
let coefficients = eigenvectors.dot(&spectral);
if coefficients.iter().any(|value| !value.is_finite()) {
return Ok(None);
}
Ok(Some(coefficients))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_affine_contraction_is_solved_in_dimension_steps() {
let a = [[0.975_f64, 0.02, 0.0], [0.0, 0.90, 0.03], [0.01, 0.0, 0.80]];
let b = [1.0_f64, -2.0, 0.5];
let map = |x: &[f64]| -> Vec<f64> {
(0..3)
.map(|row| (0..3).map(|col| a[row][col] * x[col]).sum::<f64>() + b[row])
.collect()
};
let mut reference = vec![0.0_f64; 3];
for _ in 0..20_000 {
reference = map(&reference);
}
assert!(
reference.iter().all(|value| value.is_finite()),
"the fixture must contract: {reference:?}"
);
let mut accelerator = AndersonAccelerator::new(3).expect("accelerator");
let mut x = vec![0.0_f64; 3];
let mut taken_step = vec![0.0_f64; 3];
let mut accelerated_error = f64::INFINITY;
for _ in 0..6 {
let g = map(&x);
let residual: Vec<f64> = g.iter().zip(&x).map(|(g, x)| g - x).collect();
let step = accelerator
.propose(&residual, &taken_step)
.expect("propose")
.unwrap_or_else(|| residual.clone());
for (value, delta) in x.iter_mut().zip(&step) {
*value += delta;
}
taken_step = step;
accelerated_error = x
.iter()
.zip(&reference)
.map(|(a, b)| (a - b).abs())
.fold(0.0_f64, f64::max);
}
assert!(
accelerated_error < 1.0e-10,
"six accelerated steps must solve a 3-dimensional affine map; error {accelerated_error:.3e}"
);
let mut plain = vec![0.0_f64; 3];
for _ in 0..6 {
plain = map(&plain);
}
let plain_error = plain
.iter()
.zip(&reference)
.map(|(a, b)| (a - b).abs())
.fold(0.0_f64, f64::max);
assert!(
plain_error > 1.0e3 * accelerated_error,
"the comparison is only meaningful if the plain rate is genuinely slow: \
plain {plain_error:.3e} vs accelerated {accelerated_error:.3e}"
);
}
#[test]
fn a_flat_gauge_direction_does_not_blow_up_the_extrapolation() {
let map = |x: &[f64]| -> Vec<f64> { vec![0.98 * x[0] + 1.0, 0.95 * x[1] - 0.5, x[2]] };
let mut accelerator = AndersonAccelerator::new(4).expect("accelerator");
let mut x = vec![0.0_f64, 0.0, 7.0];
let mut taken_step = vec![0.0_f64; 3];
for _ in 0..8 {
let g = map(&x);
let residual: Vec<f64> = g.iter().zip(&x).map(|(g, x)| g - x).collect();
let step = accelerator
.propose(&residual, &taken_step)
.expect("propose")
.unwrap_or_else(|| residual.clone());
for (value, delta) in x.iter_mut().zip(&step) {
*value += delta;
}
taken_step = step;
assert!(x.iter().all(|value| value.is_finite()), "{x:?}");
assert_eq!(x[2], 7.0, "the flat coordinate must not move");
}
assert!((x[0] - 50.0).abs() < 1.0e-8, "x0 -> 1/(1-0.98) = 50");
assert!((x[1] + 10.0).abs() < 1.0e-8, "x1 -> -0.5/(1-0.95) = -10");
}
#[test]
fn the_configuration_and_the_state_width_are_validated() {
assert!(AndersonAccelerator::new(0).is_err());
let mut accelerator = AndersonAccelerator::new(2).expect("accelerator");
assert!(accelerator.propose(&[], &[]).is_err());
assert!(accelerator.propose(&[1.0], &[1.0, 2.0]).is_err());
assert!(accelerator.propose(&[0.1, 0.2], &[0.0, 0.0]).is_ok());
assert!(
accelerator.propose(&[0.1], &[0.0]).is_err(),
"a state that changes width is a caller error, not a silent restart"
);
assert!(accelerator.propose(&[0.1, f64::NAN], &[0.0, 0.0]).is_err());
}
#[test]
fn the_history_starts_and_resets_empty() {
let mut accelerator = AndersonAccelerator::new(2).expect("accelerator");
assert_eq!(accelerator.history_len(), 0);
assert!(
accelerator
.propose(&[0.1, 0.2], &[0.0, 0.0])
.expect("propose")
.is_none()
);
assert!(
accelerator
.propose(&[0.05, 0.1], &[0.1, 0.2])
.expect("propose")
.is_some()
);
assert_eq!(accelerator.history_len(), 1);
accelerator.reset();
assert_eq!(accelerator.history_len(), 0);
assert!(
accelerator
.propose(&[0.02, 0.05], &[0.05, 0.1])
.expect("propose")
.is_none()
);
}
}