use crate::DType;
use std::collections::VecDeque;
use numr::algorithm::linalg::LinearAlgebraAlgorithms;
use numr::error::Result;
use numr::ops::{ScalarOps, TensorOps};
use numr::runtime::{Runtime, RuntimeClient};
use numr::tensor::Tensor;
use crate::optimize::error::{OptimizeError, OptimizeResult};
use crate::optimize::impl_generic::utils::tensor_norm;
use crate::optimize::roots::traits::anderson::AndersonOptions;
use super::TensorRootResult;
pub fn anderson_impl<R, C, G>(
client: &C,
g: G,
x0: &Tensor<R>,
options: &AndersonOptions,
) -> OptimizeResult<TensorRootResult<R>>
where
R: Runtime<DType = DType>,
C: TensorOps<R> + ScalarOps<R> + LinearAlgebraAlgorithms<R> + RuntimeClient<R>,
G: Fn(&Tensor<R>) -> Result<Tensor<R>>,
{
let n = x0.shape()[0];
if n == 0 {
return Err(OptimizeError::InvalidInput {
context: "anderson: empty initial guess".to_string(),
});
}
let m = options.m;
let alpha = options.alpha;
let opts = &options.base;
let mut x = x0.clone();
let mut gx = g(&x).map_err(|e| OptimizeError::NumericalError {
message: format!("anderson: initial g eval - {}", e),
})?;
let mut fx = client
.sub(&gx, &x)
.map_err(|e| OptimizeError::NumericalError {
message: format!("anderson: initial residual - {}", e),
})?;
let mut x_history: VecDeque<Tensor<R>> = VecDeque::with_capacity(m + 2);
let mut f_history: VecDeque<Tensor<R>> = VecDeque::with_capacity(m + 2);
x_history.push_back(x.clone());
f_history.push_back(fx.clone());
for iter in 0..opts.max_iter {
let res_norm = tensor_norm(client, &fx).map_err(|e| OptimizeError::NumericalError {
message: format!("anderson: norm - {}", e),
})?;
if res_norm < opts.tol {
return Ok(TensorRootResult {
x,
fun: fx,
iterations: iter,
residual_norm: res_norm,
converged: true,
});
}
let mk = f_history.len();
if mk < 2 {
let step =
client
.mul_scalar(&fx, alpha)
.map_err(|e| OptimizeError::NumericalError {
message: format!("anderson: simple step - {}", e),
})?;
x = client
.add(&x, &step)
.map_err(|e| OptimizeError::NumericalError {
message: format!("anderson: x update simple - {}", e),
})?;
} else {
let num_cols = mk - 1;
let f_last = &f_history[mk - 1];
let mut diff_cols: Vec<Tensor<R>> = Vec::with_capacity(num_cols);
#[allow(clippy::needless_range_loop)]
for i in 0..num_cols {
let diff = client.sub(&f_history[i], f_last).map_err(|e| {
OptimizeError::NumericalError {
message: format!("anderson: f diff {} - {}", i, e),
}
})?;
let col = diff
.unsqueeze(1)
.map_err(|e| OptimizeError::NumericalError {
message: format!("anderson: unsqueeze diff - {}", e),
})?;
diff_cols.push(col);
}
let refs: Vec<&Tensor<R>> = diff_cols.iter().collect();
let diff_matrix = client
.cat(&refs, 1)
.map_err(|e| OptimizeError::NumericalError {
message: format!("anderson: cat diff matrix - {}", e),
})?;
let neg_f_last =
client
.mul_scalar(f_last, -1.0)
.map_err(|e| OptimizeError::NumericalError {
message: format!("anderson: neg f_last - {}", e),
})?;
let rhs = neg_f_last
.unsqueeze(1)
.map_err(|e| OptimizeError::NumericalError {
message: format!("anderson: unsqueeze rhs - {}", e),
})?;
let theta =
LinearAlgebraAlgorithms::lstsq(client, &diff_matrix, &rhs).map_err(|e| {
OptimizeError::NumericalError {
message: format!("anderson: lstsq - {}", e),
}
})?;
let theta_flat =
theta
.reshape(&[num_cols])
.map_err(|e| OptimizeError::NumericalError {
message: format!("anderson: reshape theta - {}", e),
})?;
let theta_sum_tensor = client.sum(&theta_flat, &[0], false).map_err(|e| {
OptimizeError::NumericalError {
message: format!("anderson: theta sum - {}", e),
}
})?;
let theta_sum: f64 =
theta_sum_tensor
.item::<f64>()
.map_err(|e| OptimizeError::NumericalError {
message: format!("anderson: theta sum scalar - {}", e),
})?;
let gamma_last = 1.0 - theta_sum;
let x_plus_af_last = {
let af = client.mul_scalar(f_last, alpha).map_err(|e| {
OptimizeError::NumericalError {
message: format!("anderson: alpha*f_last - {}", e),
}
})?;
client
.add(&x_history[mk - 1], &af)
.map_err(|e| OptimizeError::NumericalError {
message: format!("anderson: x+af last - {}", e),
})?
};
let mut x_new = client
.mul_scalar(&x_plus_af_last, gamma_last)
.map_err(|e| OptimizeError::NumericalError {
message: format!("anderson: gamma_last * term - {}", e),
})?;
for i in 0..num_cols {
let theta_i_tensor =
theta_flat
.narrow(0, i, 1)
.map_err(|e| OptimizeError::NumericalError {
message: format!("anderson: narrow theta {} - {}", i, e),
})?;
let theta_i: f64 =
theta_i_tensor
.item::<f64>()
.map_err(|e| OptimizeError::NumericalError {
message: format!("anderson: theta_{} scalar - {}", i, e),
})?;
let x_plus_af = {
let af = client.mul_scalar(&f_history[i], alpha).map_err(|e| {
OptimizeError::NumericalError {
message: format!("anderson: alpha*f_{} - {}", i, e),
}
})?;
client
.add(&x_history[i], &af)
.map_err(|e| OptimizeError::NumericalError {
message: format!("anderson: x+af {} - {}", i, e),
})?
};
let contrib = client.mul_scalar(&x_plus_af, theta_i).map_err(|e| {
OptimizeError::NumericalError {
message: format!("anderson: theta*term {} - {}", i, e),
}
})?;
x_new =
client
.add(&x_new, &contrib)
.map_err(|e| OptimizeError::NumericalError {
message: format!("anderson: accumulate {} - {}", i, e),
})?;
}
x = x_new;
}
gx = g(&x).map_err(|e| OptimizeError::NumericalError {
message: format!("anderson: g eval iter {} - {}", iter, e),
})?;
fx = client
.sub(&gx, &x)
.map_err(|e| OptimizeError::NumericalError {
message: format!("anderson: residual iter {} - {}", iter, e),
})?;
x_history.push_back(x.clone());
f_history.push_back(fx.clone());
if x_history.len() > m + 1 {
x_history.pop_front();
f_history.pop_front();
}
}
let final_norm = tensor_norm(client, &fx).map_err(|e| OptimizeError::NumericalError {
message: format!("anderson: final norm - {}", e),
})?;
Ok(TensorRootResult {
x,
fun: fx,
iterations: opts.max_iter,
residual_norm: final_norm,
converged: false,
})
}
#[cfg(test)]
mod tests {
use super::*;
use numr::runtime::cpu::{CpuClient, CpuDevice, CpuRuntime};
fn setup() -> (CpuDevice, CpuClient) {
let device = CpuDevice::new();
let client = CpuClient::new(device.clone());
(device, client)
}
#[test]
fn test_anderson_cosine_fixed_point() {
let (device, client) = setup();
let x0 = Tensor::<CpuRuntime>::from_slice(&[1.0f64], &[1], &device);
let result = anderson_impl(
&client,
|x| {
let data: Vec<f64> = x.to_vec();
let result: Vec<f64> = data.iter().map(|xi| xi.cos()).collect();
Ok(Tensor::from_slice(&result, x.shape(), x.device()))
},
&x0,
&AndersonOptions::default(),
)
.unwrap();
assert!(result.converged, "Anderson did not converge");
let sol: Vec<f64> = result.x.to_vec();
assert!((sol[0] - 0.7390851332).abs() < 1e-6, "sol = {}", sol[0]);
}
#[test]
fn test_anderson_linear_fixed_point() {
let (device, client) = setup();
let x0 = Tensor::<CpuRuntime>::from_slice(&[0.0f64], &[1], &device);
let result = anderson_impl(
&client,
|x| {
let data: Vec<f64> = x.to_vec();
let result: Vec<f64> = data.iter().map(|xi| 0.5 * xi + 1.0).collect();
Ok(Tensor::from_slice(&result, x.shape(), x.device()))
},
&x0,
&AndersonOptions::default(),
)
.unwrap();
assert!(result.converged);
let sol: Vec<f64> = result.x.to_vec();
assert!((sol[0] - 2.0).abs() < 1e-6);
}
}