use crate::optim::*;
use crate::primitives::Vector;
#[test]
fn test_cg_gradient_norm_tracking() {
let f = |x: &Vector<f32>| x[0] * x[0] + x[1] * x[1];
let grad = |x: &Vector<f32>| Vector::from_slice(&[2.0 * x[0], 2.0 * x[1]]);
let mut optimizer = ConjugateGradient::new(100, 1e-5, CGBetaFormula::PolakRibiere);
let x0 = Vector::from_slice(&[3.0, 4.0]);
let result = optimizer.minimize(f, grad, x0);
if result.status == ConvergenceStatus::Converged {
assert!(result.gradient_norm < 1e-5);
}
}
#[test]
fn test_cg_vs_lbfgs_quadratic() {
let f = |x: &Vector<f32>| x[0] * x[0] + 2.0 * x[1] * x[1] + 3.0 * x[2] * x[2];
let grad = |x: &Vector<f32>| Vector::from_slice(&[2.0 * x[0], 4.0 * x[1], 6.0 * x[2]]);
let x0 = Vector::from_slice(&[5.0, 3.0, 2.0]);
let mut cg = ConjugateGradient::new(100, 1e-5, CGBetaFormula::PolakRibiere);
let result_cg = cg.minimize(f, grad, x0.clone());
let mut lbfgs = LBFGS::new(100, 1e-5, 10);
let result_lbfgs = lbfgs.minimize(f, grad, x0);
assert_eq!(result_cg.status, ConvergenceStatus::Converged);
assert_eq!(result_lbfgs.status, ConvergenceStatus::Converged);
for i in 0..3 {
assert!((result_cg.solution[i] - result_lbfgs.solution[i]).abs() < 1e-3);
}
}
#[test]
fn test_damped_newton_new() {
let optimizer = DampedNewton::new(100, 1e-5);
assert_eq!(optimizer.max_iter, 100);
assert!((optimizer.tol - 1e-5).abs() < 1e-10);
assert!((optimizer.epsilon - 1e-5).abs() < 1e-10);
}
#[test]
fn test_damped_newton_with_epsilon() {
let optimizer = DampedNewton::new(100, 1e-5).with_epsilon(1e-6);
assert!((optimizer.epsilon - 1e-6).abs() < 1e-12);
}
#[test]
fn test_damped_newton_simple_quadratic() {
let f = |x: &Vector<f32>| x[0] * x[0];
let grad = |x: &Vector<f32>| Vector::from_slice(&[2.0 * x[0]]);
let mut optimizer = DampedNewton::new(100, 1e-5);
let x0 = Vector::from_slice(&[5.0]);
let result = optimizer.minimize(f, grad, x0);
assert_eq!(result.status, ConvergenceStatus::Converged);
assert!(result.solution[0].abs() < 1e-3);
}
#[test]
fn test_damped_newton_multidimensional_quadratic() {
let f = |x: &Vector<f32>| x[0] * x[0] + 2.0 * x[1] * x[1];
let grad = |x: &Vector<f32>| Vector::from_slice(&[2.0 * x[0], 4.0 * x[1]]);
let mut optimizer = DampedNewton::new(100, 1e-5);
let x0 = Vector::from_slice(&[5.0, 3.0]);
let result = optimizer.minimize(f, grad, x0);
assert_eq!(result.status, ConvergenceStatus::Converged);
assert!(result.solution[0].abs() < 1e-3);
assert!(result.solution[1].abs() < 1e-3);
}
#[test]
fn test_damped_newton_rosenbrock() {
let f = |v: &Vector<f32>| {
let x = v[0];
let y = v[1];
(1.0 - x).powi(2) + 100.0 * (y - x * x).powi(2)
};
let grad = |v: &Vector<f32>| {
let x = v[0];
let y = v[1];
let dx = -2.0 * (1.0 - x) - 400.0 * x * (y - x * x);
let dy = 200.0 * (y - x * x);
Vector::from_slice(&[dx, dy])
};
let mut optimizer = DampedNewton::new(200, 1e-4);
let x0 = Vector::from_slice(&[0.0, 0.0]);
let result = optimizer.minimize(f, grad, x0);
assert!(
result.status == ConvergenceStatus::Converged
|| result.status == ConvergenceStatus::MaxIterations
);
assert!((result.solution[0] - 1.0).abs() < 0.1);
assert!((result.solution[1] - 1.0).abs() < 0.1);
}
#[test]
fn test_damped_newton_sphere() {
let f = |x: &Vector<f32>| {
let mut sum = 0.0;
for i in 0..x.len() {
sum += x[i] * x[i];
}
sum
};
let grad = |x: &Vector<f32>| {
let mut g = Vector::zeros(x.len());
for i in 0..x.len() {
g[i] = 2.0 * x[i];
}
g
};
let mut optimizer = DampedNewton::new(100, 1e-5);
let x0 = Vector::from_slice(&[5.0, -3.0, 2.0]);
let result = optimizer.minimize(f, grad, x0);
assert_eq!(result.status, ConvergenceStatus::Converged);
for i in 0..3 {
assert!(result.solution[i].abs() < 1e-3);
}
}
#[test]
fn test_damped_newton_reset() {
let f = |x: &Vector<f32>| x[0] * x[0];
let grad = |x: &Vector<f32>| Vector::from_slice(&[2.0 * x[0]]);
let mut optimizer = DampedNewton::new(100, 1e-5);
let x0 = Vector::from_slice(&[5.0]);
optimizer.minimize(f, grad, x0.clone());
optimizer.reset();
let result = optimizer.minimize(f, grad, x0);
assert_eq!(result.status, ConvergenceStatus::Converged);
}
#[test]
fn test_damped_newton_max_iterations() {
let f = |v: &Vector<f32>| {
let x = v[0];
let y = v[1];
(1.0 - x).powi(2) + 100.0 * (y - x * x).powi(2)
};
let grad = |v: &Vector<f32>| {
let x = v[0];
let y = v[1];
let dx = -2.0 * (1.0 - x) - 400.0 * x * (y - x * x);
let dy = 200.0 * (y - x * x);
Vector::from_slice(&[dx, dy])
};
let mut optimizer = DampedNewton::new(3, 1e-10);
let x0 = Vector::from_slice(&[0.0, 0.0]);
let result = optimizer.minimize(f, grad, x0);
assert_eq!(result.status, ConvergenceStatus::MaxIterations);
assert_eq!(result.iterations, 3);
}
#[test]
#[should_panic(expected = "does not support stochastic")]
fn test_damped_newton_step_panics() {
let mut optimizer = DampedNewton::new(100, 1e-5);
let mut params = Vector::from_slice(&[1.0]);
let grad = Vector::from_slice(&[0.1]);
optimizer.step(&mut params, &grad);
}
#[test]
fn test_damped_newton_numerical_error_detection() {
let f = |x: &Vector<f32>| {
if x[0] < -100.0 {
f32::NAN
} else {
x[0] * x[0]
}
};
let grad = |x: &Vector<f32>| Vector::from_slice(&[2.0 * x[0]]);
let mut optimizer = DampedNewton::new(100, 1e-5);
let x0 = Vector::from_slice(&[0.0]);
let result = optimizer.minimize(f, grad, x0);
assert!(
result.status == ConvergenceStatus::Converged
|| result.status == ConvergenceStatus::NumericalError
|| result.status == ConvergenceStatus::MaxIterations
);
}
#[test]
fn test_damped_newton_gradient_norm_tracking() {
let f = |x: &Vector<f32>| x[0] * x[0] + x[1] * x[1];
let grad = |x: &Vector<f32>| Vector::from_slice(&[2.0 * x[0], 2.0 * x[1]]);
let mut optimizer = DampedNewton::new(100, 1e-5);
let x0 = Vector::from_slice(&[3.0, 4.0]);
let result = optimizer.minimize(f, grad, x0);
if result.status == ConvergenceStatus::Converged {
assert!(result.gradient_norm < 1e-5);
}
}
#[test]
fn test_damped_newton_quadratic_convergence() {
let f = |x: &Vector<f32>| x[0] * x[0] + x[1] * x[1];
let grad = |x: &Vector<f32>| Vector::from_slice(&[2.0 * x[0], 2.0 * x[1]]);
let mut optimizer = DampedNewton::new(100, 1e-10);
let x0 = Vector::from_slice(&[5.0, 5.0]);
let result = optimizer.minimize(f, grad, x0);
assert_eq!(result.status, ConvergenceStatus::Converged);
assert!(result.iterations < 20);
}
#[test]
fn test_damped_newton_vs_lbfgs_quadratic() {
let f = |x: &Vector<f32>| x[0] * x[0] + 2.0 * x[1] * x[1];
let grad = |x: &Vector<f32>| Vector::from_slice(&[2.0 * x[0], 4.0 * x[1]]);
let x0 = Vector::from_slice(&[5.0, 3.0]);
let mut dn = DampedNewton::new(100, 1e-5);
let result_dn = dn.minimize(f, grad, x0.clone());
let mut lbfgs = LBFGS::new(100, 1e-5, 10);
let result_lbfgs = lbfgs.minimize(f, grad, x0);
assert_eq!(result_dn.status, ConvergenceStatus::Converged);
assert_eq!(result_lbfgs.status, ConvergenceStatus::Converged);
assert!((result_dn.solution[0] - result_lbfgs.solution[0]).abs() < 1e-3);
assert!((result_dn.solution[1] - result_lbfgs.solution[1]).abs() < 1e-3);
}
#[test]
fn test_damped_newton_different_epsilon() {
let f = |x: &Vector<f32>| x[0] * x[0];
let grad = |x: &Vector<f32>| Vector::from_slice(&[2.0 * x[0]]);
let x0 = Vector::from_slice(&[5.0]);
let mut opt1 = DampedNewton::new(100, 1e-5).with_epsilon(1e-5);
let result1 = opt1.minimize(f, grad, x0.clone());
let mut opt2 = DampedNewton::new(100, 1e-5).with_epsilon(1e-7);
let result2 = opt2.minimize(f, grad, x0);
assert_eq!(result1.status, ConvergenceStatus::Converged);
assert_eq!(result2.status, ConvergenceStatus::Converged);
assert!((result1.solution[0] - result2.solution[0]).abs() < 1e-2);
}