use std::marker::PhantomData;
use getset::{CopyGetters, Setters};
use nalgebra::{convert, storage::StorageMut, Dyn, IsContiguous, Vector};
use thiserror::Error;
use crate::core::{Domain, Problem, Solver, System};
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub enum SteffensenVariant {
Standard,
Liu,
}
#[derive(Debug, Clone, CopyGetters, Setters)]
#[getset(get_copy = "pub", set = "pub")]
pub struct SteffensenOptions<P: Problem> {
variant: SteffensenVariant,
#[getset(skip)]
_phantom: PhantomData<P::Field>,
}
impl<P: Problem> Default for SteffensenOptions<P> {
fn default() -> Self {
Self {
variant: SteffensenVariant::Liu,
_phantom: PhantomData,
}
}
}
pub struct Steffensen<P: Problem> {
options: SteffensenOptions<P>,
}
impl<P: Problem> Steffensen<P> {
pub fn new(p: &P, dom: &Domain<P::Field>) -> Self {
Self::with_options(p, dom, SteffensenOptions::default())
}
pub fn with_options(_p: &P, _dom: &Domain<P::Field>, options: SteffensenOptions<P>) -> Self {
Self { options }
}
pub fn reset(&mut self) {}
}
#[derive(Debug, Error, Clone)]
pub enum SteffensenError {
#[error("system is not one-dimensional")]
InvalidDimensionality,
}
impl<R: System> Solver<R> for Steffensen<R> {
const NAME: &'static str = "Steffensen";
type Error = SteffensenError;
fn solve_next<Sx, Srx>(
&mut self,
r: &R,
dom: &Domain<R::Field>,
x: &mut Vector<R::Field, Dyn, Sx>,
rx: &mut Vector<R::Field, Dyn, Srx>,
) -> Result<(), Self::Error>
where
Sx: StorageMut<R::Field, Dyn> + IsContiguous,
Srx: StorageMut<R::Field, Dyn>,
{
if dom.dim() != 1 {
return Err(SteffensenError::InvalidDimensionality);
}
let SteffensenOptions { variant, .. } = self.options;
let x0 = x[0];
r.eval(x, rx);
let r0x = rx[0];
if r0x == convert(0.0) {
return Ok(());
}
match variant {
SteffensenVariant::Standard => {
x[0] += r0x;
r.eval(x, rx);
let fz0 = rx[0];
x[0] = x0 - (r0x * r0x) / (fz0 - r0x);
r.eval(x, rx);
}
SteffensenVariant::Liu => {
x[0] += r0x;
let z0 = x[0];
r.eval(x, rx);
let r0z = rx[0];
let r0_xz = (r0z - r0x) / (z0 - x0);
x[0] = x0 - r0x / r0_xz;
let y0 = x[0];
r.eval(x, rx);
let r0y = rx[0];
let r0_xy = (r0y - r0x) / (y0 - x0);
let r0_yz = (r0z - r0y) / (z0 - y0);
x[0] = y0 - (r0_xy - r0_yz + r0_xz) / (r0_xy * r0_xy) * r0y;
r.eval(x, rx);
}
}
dom.project(x);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::testing::*;
use nalgebra::{convert, DimName, OVector, U1};
#[test]
fn sphere_standard() {
let f = Sphere::new(1);
let dom = f.domain();
let eps = convert(1e-12);
for x in f.initials() {
let mut options = SteffensenOptions::default();
options.set_variant(SteffensenVariant::Standard);
let solver = Steffensen::with_options(&f, &dom, options);
assert!(f.is_root(&solve(&f, &dom, solver, x, 40, eps).unwrap(), eps));
}
}
#[test]
fn sphere_liu() {
let f = Sphere::new(1);
let dom = f.domain();
let eps = convert(1e-12);
for x in f.initials() {
let mut options = SteffensenOptions::default();
options.set_variant(SteffensenVariant::Liu);
let solver = Steffensen::with_options(&f, &dom, options);
assert!(f.is_root(&solve(&f, &dom, solver, x, 15, eps).unwrap(), eps));
}
}
#[test]
fn infinite_solutions() {
let f = InfiniteSolutions::default();
let dom = f.domain();
let eps = convert(1e-12);
for x in f.initials() {
let solver = Steffensen::new(&f, &dom);
assert!(f.is_root(&solve(&f, &dom, solver, x, 25, eps).unwrap(), eps));
}
}
#[test]
fn stop_at_zero() {
let f = Sphere::new(1);
let dom = f.domain();
let eps = convert(1e-12);
let solver = Steffensen::new(&f, &dom);
let x = OVector::from_element_generic(Dyn(dom.dim()), U1::name(), 0.0);
assert!(f.is_root(&solve(&f, &dom, solver, x, 40, eps).unwrap(), eps));
}
}