use echidna_optim::objective::Objective;
use echidna_optim::result::TerminationReason;
use echidna_optim::{lbfgs, newton, trust_region, LbfgsConfig, NewtonConfig, TrustRegionConfig};
struct PredictedNegative;
impl Objective<f64> for PredictedNegative {
fn dim(&self) -> usize {
1
}
fn eval_grad(&mut self, x: &[f64]) -> (f64, Vec<f64>) {
(x[0] * x[0] + 1.0, vec![2.0 * x[0]])
}
fn hvp(&mut self, x: &[f64], _v: &[f64]) -> (Vec<f64>, Vec<f64>) {
(vec![2.0 * x[0]], vec![-1e20])
}
}
#[test]
fn l28_trust_region_collapse_via_predicted_negative() {
let mut obj = PredictedNegative;
let mut cfg = TrustRegionConfig::<f64>::default();
cfg.convergence.max_iter = 1000;
cfg.min_radius = 1.0;
cfg.initial_radius = 2.0;
let result = trust_region(&mut obj, &[1.0], &cfg);
assert_eq!(
result.termination,
TerminationReason::NumericalError,
"`predicted<=0` shrink must return NumericalError, got {:?}",
result.termination
);
}
struct FlatValleyLargeOffset;
impl Objective<f64> for FlatValleyLargeOffset {
fn dim(&self) -> usize {
1
}
fn eval_grad(&mut self, x: &[f64]) -> (f64, Vec<f64>) {
let d = x[0] - 1.0;
let f = 1e10 + d.powi(10);
let g = 10.0 * d.powi(9);
(f, vec![g])
}
fn eval_hessian(&mut self, x: &[f64]) -> (f64, Vec<f64>, Vec<Vec<f64>>) {
let (f, g) = self.eval_grad(x);
let d = x[0] - 1.0;
let h = 90.0 * d.powi(8);
(f, g, vec![vec![h]])
}
fn hvp(&mut self, x: &[f64], v: &[f64]) -> (Vec<f64>, Vec<f64>) {
let (_, g) = self.eval_grad(x);
let d = x[0] - 1.0;
let h = 90.0 * d.powi(8);
(g, vec![h * v[0]])
}
}
#[test]
fn l33_lbfgs_func_tol_is_relative_for_large_magnitude() {
let mut obj = FlatValleyLargeOffset;
let mut cfg = LbfgsConfig::<f64>::default();
cfg.convergence.max_iter = 500;
cfg.convergence.grad_tol = 1e-40;
cfg.convergence.step_tol = 0.0;
cfg.convergence.func_tol = 1e-6;
let result = lbfgs(&mut obj, &[2.0], &cfg);
assert_eq!(
result.termination,
TerminationReason::FunctionChange,
"relative func_tol must fire on large-magnitude objective, got {:?}",
result.termination
);
}
#[test]
fn l33_newton_func_tol_is_relative_for_large_magnitude() {
let mut obj = FlatValleyLargeOffset;
let mut cfg = NewtonConfig::<f64>::default();
cfg.convergence.max_iter = 500;
cfg.convergence.grad_tol = 1e-40;
cfg.convergence.step_tol = 0.0;
cfg.convergence.func_tol = 1e-6;
let result = newton(&mut obj, &[2.0], &cfg);
assert_eq!(
result.termination,
TerminationReason::FunctionChange,
"relative func_tol must fire on large-magnitude objective, got {:?}",
result.termination
);
}
#[test]
fn l33_trust_region_func_tol_is_relative_for_large_magnitude() {
let mut obj = FlatValleyLargeOffset;
let mut cfg = TrustRegionConfig::<f64>::default();
cfg.convergence.max_iter = 500;
cfg.convergence.grad_tol = 1e-40;
cfg.convergence.step_tol = 0.0;
cfg.convergence.func_tol = 1e-6;
let result = trust_region(&mut obj, &[2.0], &cfg);
assert_eq!(
result.termination,
TerminationReason::FunctionChange,
"relative func_tol must fire on large-magnitude objective, got {:?}",
result.termination
);
}