use crate::optim::*;
use crate::primitives::Vector;
#[test]
fn test_projected_gd_nonnegative_constraint() {
let c = Vector::from_slice(&[1.0, -2.0, 3.0, -1.0]);
let objective = |x: &Vector<f32>| {
let mut obj = 0.0;
for i in 0..x.len() {
let diff = x[i] - c[i];
obj += 0.5 * diff * diff;
}
obj
};
let gradient = |x: &Vector<f32>| {
let mut grad = Vector::zeros(x.len());
for i in 0..x.len() {
grad[i] = x[i] - c[i];
}
grad
};
let project = |x: &Vector<f32>| prox::nonnegative(x);
let mut pgd = ProjectedGradientDescent::new(1000, 0.1, 1e-6);
let x0 = Vector::zeros(4);
let result = pgd.minimize(objective, gradient, project, x0);
assert_eq!(result.status, ConvergenceStatus::Converged);
assert!((result.solution[0] - 1.0).abs() < 1e-4); assert!(result.solution[1].abs() < 1e-4); assert!((result.solution[2] - 3.0).abs() < 1e-4); assert!(result.solution[3].abs() < 1e-4); }
#[test]
fn test_projected_gd_box_constraints() {
let c = Vector::from_slice(&[1.5, -1.0, 3.0, 0.5]);
let lower = Vector::zeros(4);
let upper = Vector::from_slice(&[2.0, 2.0, 2.0, 2.0]);
let objective = |x: &Vector<f32>| {
let mut obj = 0.0;
for i in 0..x.len() {
let diff = x[i] - c[i];
obj += 0.5 * diff * diff;
}
obj
};
let gradient = |x: &Vector<f32>| {
let mut grad = Vector::zeros(x.len());
for i in 0..x.len() {
grad[i] = x[i] - c[i];
}
grad
};
let lower_clone = lower.clone();
let upper_clone = upper.clone();
let project = move |x: &Vector<f32>| prox::project_box(x, &lower_clone, &upper_clone);
let mut pgd = ProjectedGradientDescent::new(1000, 0.1, 1e-6);
let x0 = Vector::ones(4);
let result = pgd.minimize(objective, gradient, project, x0);
assert_eq!(result.status, ConvergenceStatus::Converged);
assert!((result.solution[0] - 1.5).abs() < 1e-4); assert!(result.solution[1].abs() < 1e-4); assert!((result.solution[2] - 2.0).abs() < 1e-4); assert!((result.solution[3] - 0.5).abs() < 1e-4); }
#[test]
fn test_projected_gd_l2_ball() {
let c = Vector::from_slice(&[2.0, 2.0]);
let radius = 1.0;
let objective = |x: &Vector<f32>| {
let mut obj = 0.0;
for i in 0..x.len() {
let diff = x[i] - c[i];
obj += 0.5 * diff * diff;
}
obj
};
let gradient = |x: &Vector<f32>| {
let mut grad = Vector::zeros(x.len());
for i in 0..x.len() {
grad[i] = x[i] - c[i];
}
grad
};
let project = move |x: &Vector<f32>| prox::project_l2_ball(x, radius);
let mut pgd = ProjectedGradientDescent::new(1000, 0.1, 1e-6);
let x0 = Vector::zeros(2);
let result = pgd.minimize(objective, gradient, project, x0);
assert_eq!(result.status, ConvergenceStatus::Converged);
let norm =
(result.solution[0] * result.solution[0] + result.solution[1] * result.solution[1]).sqrt();
assert!((norm - radius).abs() < 1e-4); assert!((result.solution[0] - std::f32::consts::FRAC_1_SQRT_2).abs() < 1e-3); assert!((result.solution[1] - std::f32::consts::FRAC_1_SQRT_2).abs() < 1e-3);
}
#[test]
fn test_projected_gd_with_line_search() {
let c = Vector::from_slice(&[1.0, -2.0, 3.0]);
let objective = |x: &Vector<f32>| {
let mut obj = 0.0;
for i in 0..x.len() {
let diff = x[i] - c[i];
obj += 0.5 * diff * diff;
}
obj
};
let gradient = |x: &Vector<f32>| {
let mut grad = Vector::zeros(x.len());
for i in 0..x.len() {
grad[i] = x[i] - c[i];
}
grad
};
let project = |x: &Vector<f32>| prox::nonnegative(x);
let mut pgd = ProjectedGradientDescent::new(1000, 1.0, 1e-6).with_line_search(0.5);
let x0 = Vector::zeros(3);
let result = pgd.minimize(objective, gradient, project, x0);
assert_eq!(result.status, ConvergenceStatus::Converged);
assert!((result.solution[0] - 1.0).abs() < 1e-4);
assert!(result.solution[1].abs() < 1e-4);
assert!((result.solution[2] - 3.0).abs() < 1e-4);
}
#[test]
fn test_projected_gd_quadratic() {
let objective =
|x: &Vector<f32>| 0.5 * (2.0 * x[0] * x[0] + 2.0 * x[1] * x[1]) - (4.0 * x[0] - 2.0 * x[1]);
let gradient = |x: &Vector<f32>| Vector::from_slice(&[2.0 * x[0] - 4.0, 2.0 * x[1] + 2.0]);
let project = |x: &Vector<f32>| prox::nonnegative(x);
let mut pgd = ProjectedGradientDescent::new(1000, 0.1, 1e-6);
let x0 = Vector::zeros(2);
let result = pgd.minimize(objective, gradient, project, x0);
assert_eq!(result.status, ConvergenceStatus::Converged);
assert!((result.solution[0] - 2.0).abs() < 1e-3);
assert!(result.solution[1].abs() < 1e-3);
}
#[test]
fn test_projected_gd_convergence_tracking() {
let c = Vector::from_slice(&[1.0, 2.0]);
let objective = |x: &Vector<f32>| {
let mut obj = 0.0;
for i in 0..x.len() {
let diff = x[i] - c[i];
obj += 0.5 * diff * diff;
}
obj
};
let gradient = |x: &Vector<f32>| {
let mut grad = Vector::zeros(x.len());
for i in 0..x.len() {
grad[i] = x[i] - c[i];
}
grad
};
let project = |x: &Vector<f32>| prox::nonnegative(x);
let mut pgd = ProjectedGradientDescent::new(1000, 0.1, 1e-6);
let x0 = Vector::zeros(2);
let result = pgd.minimize(objective, gradient, project, x0);
assert_eq!(result.status, ConvergenceStatus::Converged);
assert!(result.iterations > 0);
assert!(result.elapsed_time.as_nanos() > 0);
assert!(result.gradient_norm < 1.0); }
#[test]
fn test_projected_gd_max_iterations() {
let c = Vector::from_slice(&[1.0, 2.0]);
let objective = |x: &Vector<f32>| {
let mut obj = 0.0;
for i in 0..x.len() {
let diff = x[i] - c[i];
obj += 0.5 * diff * diff;
}
obj
};
let gradient = |x: &Vector<f32>| {
let mut grad = Vector::zeros(x.len());
for i in 0..x.len() {
grad[i] = x[i] - c[i];
}
grad
};
let project = |x: &Vector<f32>| prox::nonnegative(x);
let mut pgd = ProjectedGradientDescent::new(3, 0.01, 1e-12); let x0 = Vector::zeros(2);
let result = pgd.minimize(objective, gradient, project, x0);
assert_eq!(result.status, ConvergenceStatus::MaxIterations);
assert_eq!(result.iterations, 3);
}
#[test]
fn test_projected_gd_unconstrained_equivalent() {
let c = Vector::from_slice(&[1.0, 2.0]);
let objective = |x: &Vector<f32>| {
let mut obj = 0.0;
for i in 0..x.len() {
let diff = x[i] - c[i];
obj += 0.5 * diff * diff;
}
obj
};
let gradient = |x: &Vector<f32>| {
let mut grad = Vector::zeros(x.len());
for i in 0..x.len() {
grad[i] = x[i] - c[i];
}
grad
};
let project = |x: &Vector<f32>| x.clone();
let mut pgd = ProjectedGradientDescent::new(1000, 0.1, 1e-6);
let x0 = Vector::zeros(2);
let result = pgd.minimize(objective, gradient, project, x0);
assert_eq!(result.status, ConvergenceStatus::Converged);
assert!((result.solution[0] - 1.0).abs() < 1e-4);
assert!((result.solution[1] - 2.0).abs() < 1e-4);
}
#[test]
fn test_augmented_lagrangian_linear_equality() {
let objective = |x: &Vector<f32>| 0.5 * (x[0] - 2.0).powi(2) + 0.5 * (x[1] - 3.0).powi(2);
let gradient = |x: &Vector<f32>| Vector::from_slice(&[x[0] - 2.0, x[1] - 3.0]);
let equality = |x: &Vector<f32>| Vector::from_slice(&[x[0] + x[1] - 1.0]);
let equality_jac = |_x: &Vector<f32>| vec![Vector::from_slice(&[1.0, 1.0])];
let mut al = AugmentedLagrangian::new(100, 1e-4, 1.0);
let x0 = Vector::zeros(2);
let result = al.minimize_equality(objective, gradient, equality, equality_jac, x0);
assert!(result.constraint_violation < 1e-3);
assert!((result.solution[0] + result.solution[1] - 1.0).abs() < 1e-3);
}
#[test]
fn test_augmented_lagrangian_multiple_constraints() {
let objective = |x: &Vector<f32>| 0.5 * (x[0] * x[0] + x[1] * x[1]);
let gradient = |x: &Vector<f32>| Vector::from_slice(&[x[0], x[1]]);
let equality = |x: &Vector<f32>| Vector::from_slice(&[x[0] + x[1] - 1.0, x[0] - x[1]]);
let equality_jac = |_x: &Vector<f32>| {
vec![
Vector::from_slice(&[1.0, 1.0]),
Vector::from_slice(&[1.0, -1.0]),
]
};
let mut al = AugmentedLagrangian::new(200, 1e-4, 1.0);
let x0 = Vector::zeros(2);
let result = al.minimize_equality(objective, gradient, equality, equality_jac, x0);
assert!(result.constraint_violation < 1e-3);
assert!((result.solution[0] - 0.5).abs() < 1e-2);
assert!((result.solution[1] - 0.5).abs() < 1e-2);
}
#[test]
fn test_augmented_lagrangian_3d() {
let c = Vector::from_slice(&[1.0, 2.0, 3.0]);
let objective = |x: &Vector<f32>| {
0.5 * ((x[0] - c[0]).powi(2) + (x[1] - c[1]).powi(2) + (x[2] - c[2]).powi(2))
};
let gradient = |x: &Vector<f32>| Vector::from_slice(&[x[0] - c[0], x[1] - c[1], x[2] - c[2]]);
let equality = |x: &Vector<f32>| Vector::from_slice(&[x[0] + x[1] + x[2] - 1.0]);
let equality_jac = |_x: &Vector<f32>| vec![Vector::from_slice(&[1.0, 1.0, 1.0])];
let mut al = AugmentedLagrangian::new(100, 1e-4, 1.0);
let x0 = Vector::zeros(3);
let result = al.minimize_equality(objective, gradient, equality, equality_jac, x0);
assert!(result.constraint_violation < 1e-3);
assert!((result.solution[0] + result.solution[1] + result.solution[2] - 1.0).abs() < 1e-3);
}
#[test]
fn test_augmented_lagrangian_quadratic_with_constraint() {
let objective = |x: &Vector<f32>| x[0] * x[0] + 2.0 * x[1] * x[1];
let gradient = |x: &Vector<f32>| Vector::from_slice(&[2.0 * x[0], 4.0 * x[1]]);
let equality = |x: &Vector<f32>| Vector::from_slice(&[2.0 * x[0] + x[1] - 1.0]);
let equality_jac = |_x: &Vector<f32>| vec![Vector::from_slice(&[2.0, 1.0])];
let mut al = AugmentedLagrangian::new(150, 1e-4, 1.0);
let x0 = Vector::zeros(2);
let result = al.minimize_equality(objective, gradient, equality, equality_jac, x0);
assert!(result.constraint_violation < 1e-3);
assert!((result.solution[0] - 4.0 / 9.0).abs() < 1e-2);
assert!((result.solution[1] - 1.0 / 9.0).abs() < 1e-2);
}
#[test]
fn test_augmented_lagrangian_convergence_tracking() {
let objective = |x: &Vector<f32>| 0.5 * (x[0] * x[0] + x[1] * x[1]);
let gradient = |x: &Vector<f32>| Vector::from_slice(&[x[0], x[1]]);
let equality = |x: &Vector<f32>| Vector::from_slice(&[x[0] + x[1] - 1.0]);
let equality_jac = |_x: &Vector<f32>| vec![Vector::from_slice(&[1.0, 1.0])];
let mut al = AugmentedLagrangian::new(100, 1e-4, 1.0);
let x0 = Vector::zeros(2);
let result = al.minimize_equality(objective, gradient, equality, equality_jac, x0);
assert_eq!(result.status, ConvergenceStatus::Converged);
assert!(result.iterations > 0);
assert!(result.elapsed_time.as_nanos() > 0);
assert!(result.constraint_violation < 1e-3);
}
#[test]
fn test_augmented_lagrangian_rho_adaptation() {
let objective = |x: &Vector<f32>| 0.5 * (x[0] * x[0] + x[1] * x[1]);
let gradient = |x: &Vector<f32>| Vector::from_slice(&[x[0], x[1]]);
let equality = |x: &Vector<f32>| Vector::from_slice(&[x[0] + x[1] - 1.0]);
let equality_jac = |_x: &Vector<f32>| vec![Vector::from_slice(&[1.0, 1.0])];
let mut al = AugmentedLagrangian::new(200, 1e-4, 1.0).with_rho_increase(3.0);
let x0 = Vector::zeros(2);
let result = al.minimize_equality(objective, gradient, equality, equality_jac, x0);
assert!(result.constraint_violation < 1e-2); }
#[test]
fn test_augmented_lagrangian_max_iterations() {
let objective = |x: &Vector<f32>| 0.5 * (x[0] * x[0] + x[1] * x[1]);
let gradient = |x: &Vector<f32>| Vector::from_slice(&[x[0], x[1]]);
let equality = |x: &Vector<f32>| Vector::from_slice(&[x[0] + x[1] - 1.0]);
let equality_jac = |_x: &Vector<f32>| vec![Vector::from_slice(&[1.0, 1.0])];
let mut al = AugmentedLagrangian::new(2, 1e-10, 1.0); let x0 = Vector::zeros(2);
let result = al.minimize_equality(objective, gradient, equality, equality_jac, x0);
assert_eq!(result.status, ConvergenceStatus::MaxIterations);
assert_eq!(result.iterations, 2);
}