use crate::core::{
ArgminFloat, Error, Gradient, Hessian, IterState, Problem, Solver, State, TerminationReason,
TerminationStatus, TrustRegionRadius, KV,
};
use argmin_math::{ArgminL2Norm, ArgminMul, ArgminWeightedDot};
#[cfg(feature = "serde1")]
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
#[derive(Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Default)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
pub struct CauchyPoint<F> {
radius: F,
}
impl<F> CauchyPoint<F>
where
F: ArgminFloat,
{
pub fn new() -> Self {
CauchyPoint { radius: F::nan() }
}
}
impl<O, F, P, G, H> Solver<O, IterState<P, G, (), H, (), F>> for CauchyPoint<F>
where
O: Gradient<Param = P, Gradient = G> + Hessian<Param = P, Hessian = H>,
P: Clone + ArgminMul<F, P> + ArgminWeightedDot<P, F, H>,
G: ArgminMul<F, P> + ArgminWeightedDot<G, F, H> + ArgminL2Norm<F>,
F: ArgminFloat,
{
fn name(&self) -> &str {
"Cauchy Point"
}
fn next_iter(
&mut self,
problem: &mut Problem<O>,
mut state: IterState<P, G, (), H, (), F>,
) -> Result<(IterState<P, G, (), H, (), F>, Option<KV>), Error> {
let param = state.take_param().ok_or_else(argmin_error_closure!(
NotInitialized,
concat!(
"`CauchyPoint` requires an initial parameter vector. ",
"Please provide an initial guess via `Executor`s `configure` method."
)
))?;
let grad = state
.take_gradient()
.map(Result::Ok)
.unwrap_or_else(|| problem.gradient(¶m))?;
let grad_norm = grad.l2_norm();
let hessian = state
.take_hessian()
.map(Result::Ok)
.unwrap_or_else(|| problem.hessian(¶m))?;
let wdp = grad.weighted_dot(&hessian, &grad);
let tau: F = if wdp <= float!(0.0) {
float!(1.0)
} else {
float!(1.0).min(grad_norm.powi(3) / (self.radius * wdp))
};
let new_param = grad.mul(&(-tau * self.radius / grad_norm));
Ok((state.param(new_param), None))
}
fn terminate(&mut self, state: &IterState<P, G, (), H, (), F>) -> TerminationStatus {
if state.get_iter() >= 1 {
TerminationStatus::Terminated(TerminationReason::MaxItersReached)
} else {
TerminationStatus::NotTerminated
}
}
}
impl<F> TrustRegionRadius<F> for CauchyPoint<F>
where
F: ArgminFloat,
{
fn set_radius(&mut self, radius: F) {
self.radius = radius;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::{test_utils::TestProblem, ArgminError};
use approx::assert_relative_eq;
test_trait_impl!(cauchypoint, CauchyPoint<f64>);
#[test]
fn test_new() {
let cp: CauchyPoint<f64> = CauchyPoint::new();
let CauchyPoint { radius } = cp;
assert_eq!(radius.to_ne_bytes(), f64::NAN.to_ne_bytes());
}
#[test]
fn test_next_iter() {
let param: Vec<f64> = vec![-1.0, 1.0];
let mut cp: CauchyPoint<f64> = CauchyPoint::new();
cp.set_radius(1.0);
let state: IterState<Vec<f64>, Vec<f64>, (), Vec<Vec<f64>>, (), f64> = IterState::new();
let problem = TestProblem::new();
let res = cp.next_iter(&mut Problem::new(problem), state);
assert_error!(
res,
ArgminError,
concat!(
"Not initialized: \"`CauchyPoint` requires an initial parameter vector. Please ",
"provide an initial guess via `Executor`s `configure` method.\""
)
);
let state: IterState<Vec<f64>, Vec<f64>, (), Vec<Vec<f64>>, (), f64> =
IterState::new().param(param);
let problem = TestProblem::new();
let (mut state_out, kv) = cp.next_iter(&mut Problem::new(problem), state).unwrap();
assert!(kv.is_none());
let s_param = state_out.take_param().unwrap();
assert_relative_eq!(s_param[0], 1.0f64 / 2.0f64.sqrt(), epsilon = f64::EPSILON);
assert_relative_eq!(s_param[1], -1.0f64 / 2.0f64.sqrt(), epsilon = f64::EPSILON);
}
}