pub(crate) use super::*;
#[test]
fn test_damped_newton_quadratic() {
let mut optimizer = DampedNewton::new(100, 1e-5);
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 result = optimizer.minimize(f, grad, x0);
assert_eq!(result.status, ConvergenceStatus::Converged);
assert!(result.solution[0].abs() < 1e-4);
assert!(result.solution[1].abs() < 1e-4);
}
#[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-10);
}
#[test]
fn test_damped_newton_new() {
let optimizer = DampedNewton::new(50, 1e-4);
assert_eq!(optimizer.max_iter, 50);
assert!((optimizer.tol - 1e-4).abs() < 1e-10);
assert!((optimizer.epsilon - 1e-5).abs() < 1e-10); }
#[test]
fn test_damped_newton_reset() {
let mut optimizer = DampedNewton::new(100, 1e-5);
optimizer.reset(); }
#[test]
#[should_panic(expected = "does not support stochastic updates")]
fn test_damped_newton_step_unimplemented() {
let mut optimizer = DampedNewton::new(100, 1e-5);
let mut params = Vector::from_slice(&[1.0, 2.0]);
let grad = Vector::from_slice(&[0.1, 0.2]);
optimizer.step(&mut params, &grad);
}
#[test]
fn test_damped_newton_max_iterations() {
let mut optimizer = DampedNewton::new(2, 1e-10);
let f = |x: &Vector<f32>| {
let a = 1.0 - x[0];
let b = x[1] - x[0] * x[0];
a * a + 100.0 * b * b
};
let grad = |x: &Vector<f32>| {
let dx0 = -2.0 * (1.0 - x[0]) - 400.0 * x[0] * (x[1] - x[0] * x[0]);
let dx1 = 200.0 * (x[1] - x[0] * x[0]);
Vector::from_slice(&[dx0, dx1])
};
let x0 = Vector::from_slice(&[10.0, 10.0]); let result = optimizer.minimize(f, grad, x0);
assert_eq!(result.status, ConvergenceStatus::MaxIterations);
assert_eq!(result.iterations, 2);
}
#[test]
fn test_damped_newton_numerical_error() {
let mut optimizer = DampedNewton::new(100, 1e-5);
let f = |x: &Vector<f32>| {
if x[0].abs() > 5.0 || x[1].abs() > 5.0 {
f32::NAN
} else {
x[0] * x[0] + x[1] * x[1]
}
};
let grad = |x: &Vector<f32>| {
if x[0].abs() > 4.0 || x[1].abs() > 4.0 {
Vector::from_slice(&[f32::NAN, f32::NAN])
} else {
Vector::from_slice(&[2.0 * x[0], 2.0 * x[1]])
}
};
let x0 = Vector::from_slice(&[3.0, 3.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_1d() {
let mut optimizer = DampedNewton::new(100, 1e-5);
let f = |x: &Vector<f32>| (x[0] - 3.0).powi(2);
let grad = |x: &Vector<f32>| Vector::from_slice(&[2.0 * (x[0] - 3.0)]);
let x0 = Vector::from_slice(&[10.0]);
let result = optimizer.minimize(f, grad, x0);
assert_eq!(result.status, ConvergenceStatus::Converged);
assert!((result.solution[0] - 3.0).abs() < 1e-4);
}
#[test]
fn test_damped_newton_3d() {
let mut optimizer = DampedNewton::new(100, 1e-4);
let f = |x: &Vector<f32>| x[0] * x[0] + x[1] * x[1] + x[2] * x[2];
let grad = |x: &Vector<f32>| Vector::from_slice(&[2.0 * x[0], 2.0 * x[1], 2.0 * x[2]]);
let x0 = Vector::from_slice(&[5.0, -3.0, 2.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);
assert!(result.solution[2].abs() < 1e-3);
}
#[test]
fn test_damped_newton_already_at_optimum() {
let mut optimizer = DampedNewton::new(100, 1e-5);
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 x0 = Vector::from_slice(&[1e-8, 1e-8]);
let result = optimizer.minimize(f, grad, x0);
assert_eq!(result.status, ConvergenceStatus::Converged);
assert_eq!(result.iterations, 0); }
#[test]
fn test_damped_newton_gradient_norm() {
let mut optimizer = DampedNewton::new(100, 1e-5);
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 x0 = Vector::from_slice(&[5.0, 5.0]);
let result = optimizer.minimize(f, grad, x0);
assert!(result.gradient_norm < 1e-5);
}
#[test]
fn test_damped_newton_objective_value() {
let mut optimizer = DampedNewton::new(100, 1e-5);
let f = |x: &Vector<f32>| (x[0] - 1.0).powi(2) + (x[1] - 2.0).powi(2);
let grad = |x: &Vector<f32>| Vector::from_slice(&[2.0 * (x[0] - 1.0), 2.0 * (x[1] - 2.0)]);
let x0 = Vector::from_slice(&[0.0, 0.0]);
let result = optimizer.minimize(f, grad, x0);
assert!(result.objective_value < 1e-6);
assert!((result.solution[0] - 1.0).abs() < 1e-4);
assert!((result.solution[1] - 2.0).abs() < 1e-4);
}
#[test]
fn test_damped_newton_elapsed_time() {
let mut optimizer = DampedNewton::new(100, 1e-5);
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 result = optimizer.minimize(f, grad, x0);
let _ = result.elapsed_time.as_nanos(); }
#[test]
fn test_damped_newton_constraint_violation_zero() {
let mut optimizer = DampedNewton::new(100, 1e-5);
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 result = optimizer.minimize(f, grad, x0);
assert!((result.constraint_violation - 0.0).abs() < 1e-10);
}
#[test]
fn test_damped_newton_debug_clone() {
let optimizer = DampedNewton::new(100, 1e-5);
let cloned = optimizer.clone();
assert_eq!(optimizer.max_iter, cloned.max_iter);
assert!((optimizer.tol - cloned.tol).abs() < 1e-10);
assert!((optimizer.epsilon - cloned.epsilon).abs() < 1e-10);
let debug_str = format!("{:?}", optimizer);
assert!(debug_str.contains("DampedNewton"));
}
#[test]
fn test_damped_newton_with_negative_hessian() {
let mut optimizer = DampedNewton::new(100, 1e-4);
let f = |x: &Vector<f32>| {
(x[0] - 5.0).powi(2) + (x[1] - 5.0).powi(2)
};
let grad = |x: &Vector<f32>| Vector::from_slice(&[2.0 * (x[0] - 5.0), 2.0 * (x[1] - 5.0)]);
let x0 = Vector::from_slice(&[0.0, 0.0]);
let result = optimizer.minimize(f, grad, x0);
assert_eq!(result.status, ConvergenceStatus::Converged);
assert!((result.solution[0] - 5.0).abs() < 1e-3);
assert!((result.solution[1] - 5.0).abs() < 1e-3);
}
#[test]
fn test_damped_newton_large_epsilon() {
let mut optimizer = DampedNewton::new(100, 1e-5).with_epsilon(0.1);
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 x0 = Vector::from_slice(&[5.0, 5.0]);
let result = optimizer.minimize(f, grad, x0);
assert_eq!(result.status, ConvergenceStatus::Converged);
}
#[test]
fn test_damped_newton_small_epsilon() {
let mut optimizer = DampedNewton::new(100, 1e-5).with_epsilon(1e-8);
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 x0 = Vector::from_slice(&[5.0, 5.0]);
let result = optimizer.minimize(f, grad, x0);
assert_eq!(result.status, ConvergenceStatus::Converged);
}
#[test]
fn test_norm_function() {
let v = Vector::from_slice(&[3.0, 4.0]);
let norm = DampedNewton::norm(&v);
assert!((norm - 5.0).abs() < 1e-6);
let zero = Vector::from_slice(&[0.0, 0.0]);
let norm_zero = DampedNewton::norm(&zero);
assert!(norm_zero.abs() < 1e-10);
}
#[test]
fn test_damped_newton_iterations_tracked() {
let mut optimizer = DampedNewton::new(100, 1e-5);
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(&[10.0]);
let result = optimizer.minimize(f, grad, x0);
assert!(result.iterations <= 100);
}
#[test]
fn test_approximate_hessian_symmetric() {
let optimizer = DampedNewton::new(100, 1e-5);
let grad = |x: &Vector<f32>| Vector::from_slice(&[2.0 * x[0], 4.0 * x[1]]);
let x = Vector::from_slice(&[1.0, 2.0]);
let hessian = optimizer.approximate_hessian(&grad, &x);
let (n, m) = hessian.shape();
assert_eq!(n, m);
assert_eq!(n, 2);
assert!((hessian.get(0, 0) - 2.0).abs() < 0.1);
assert!((hessian.get(1, 1) - 4.0).abs() < 0.1);
assert!((hessian.get(0, 1) - hessian.get(1, 0)).abs() < 1e-6);
}
#[test]
fn test_damped_newton_cholesky_fallback_not_descent() {
let mut optimizer = DampedNewton::new(100, 1e-4);
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 x0 = Vector::from_slice(&[5.0, 5.0]);
let result = optimizer.minimize(f, grad, x0);
assert_eq!(result.status, ConvergenceStatus::Converged);
}
#[test]
fn test_damped_newton_stalled() {
let mut optimizer = DampedNewton::new(100, 1e-20);
let f = |x: &Vector<f32>| 1.0 / (1.0 + (x[0] * x[0]).exp().min(1e30));
let grad = |x: &Vector<f32>| {
let exp_val = (x[0] * x[0]).exp().min(1e30);
let denom = (1.0 + exp_val).powi(2);
Vector::from_slice(&[-2.0 * x[0] * exp_val / denom])
};
let x0 = Vector::from_slice(&[100.0]);
let result = optimizer.minimize(f, grad, x0);
assert!(
result.status == ConvergenceStatus::Stalled
|| result.status == ConvergenceStatus::Converged
|| result.status == ConvergenceStatus::MaxIterations
|| result.status == ConvergenceStatus::NumericalError
);
}
#[test]
fn test_damped_newton_hessian_3d() {
let optimizer = DampedNewton::new(100, 1e-5);
let grad = |x: &Vector<f32>| Vector::from_slice(&[2.0 * x[0], 4.0 * x[1], 6.0 * x[2]]);
let x = Vector::from_slice(&[1.0, 1.0, 1.0]);
let hessian = optimizer.approximate_hessian(&grad, &x);
let (n, m) = hessian.shape();
assert_eq!(n, 3);
assert_eq!(m, 3);
assert!((hessian.get(0, 0) - 2.0).abs() < 0.1);
assert!((hessian.get(1, 1) - 4.0).abs() < 0.1);
assert!((hessian.get(2, 2) - 6.0).abs() < 0.1);
}