use crate::constrained::*;
use crate::error::OptimizeError;
use scirs2_core::ndarray::array;
#[allow(dead_code)]
fn objective(x: &[f64]) -> f64 {
(x[0] - 1.0).powi(2) + (x[1] - 2.5).powi(2)
}
#[allow(dead_code)]
fn constraint(x: &[f64]) -> f64 {
3.0 - x[0] - x[1] }
#[test]
#[allow(dead_code)]
fn test_minimize_constrained_placeholder() {
let x0 = array![0.0, 0.0];
let constraints = vec![Constraint::new(constraint, Constraint::INEQUALITY)];
let options = Options {
maxiter: Some(1), ..Options::default()
};
let result = minimize_constrained(
objective,
&x0.view(),
&constraints,
Method::SLSQP,
Some(options),
)
.expect("Operation failed");
assert!(!result.success);
assert!(result.constr.is_some());
let constr = result.constr.expect("Operation failed");
assert_eq!(constr.len(), 1);
}
#[test]
#[allow(dead_code)]
fn test_minimize_slsqp() {
let x0 = array![0.0, 0.0];
let constraints = vec![Constraint::new(constraint, Constraint::INEQUALITY)];
let options = Options {
maxiter: Some(100),
gtol: Some(1e-6),
ftol: Some(1e-6),
ctol: Some(1e-6),
..Options::default()
};
let result = minimize_constrained(
objective,
&x0.view(),
&constraints,
Method::SLSQP,
Some(options),
)
.expect("Operation failed");
assert!(result.x[0] >= 0.0);
assert!(result.x[1] >= 0.0);
let initial_value = objective(&[0.0, 0.0]);
assert!(result.fun <= initial_value);
assert!(result.constr.is_some());
println!(
"SLSQP result: x = {:?}, f = {}, iterations = {}",
result.x, result.fun, result.nit
);
}
#[test]
#[allow(dead_code)]
fn test_minimize_trust_constr() {
let x0 = array![0.0, 0.0];
let constraints = vec![Constraint::new(constraint, Constraint::INEQUALITY)];
let options = Options {
maxiter: Some(500), gtol: Some(1e-6),
ftol: Some(1e-6),
ctol: Some(1e-6),
..Options::default()
};
let result = minimize_constrained(
objective,
&x0.view(),
&constraints,
Method::TrustConstr,
Some(options.clone()),
)
.expect("Operation failed");
assert!(result.x[0] >= 0.0);
assert!(result.x[1] >= 0.0);
let initial_value = objective(&[0.0, 0.0]);
assert!(result.fun <= initial_value);
assert!(result.constr.is_some());
println!(
"TrustConstr result: x = {:?}, f = {}, iterations = {}",
result.x, result.fun, result.nit
);
}
#[test]
#[allow(dead_code)]
fn test_constrained_rosenbrock() {
fn rosenbrock(x: &[f64]) -> f64 {
100.0 * (x[1] - x[0].powi(2)).powi(2) + (1.0 - x[0]).powi(2)
}
fn circle_constraint(x: &[f64]) -> f64 {
1.5 - (x[0].powi(2) + x[1].powi(2)) }
let x0 = array![0.0, 0.0];
let constraints = vec![Constraint::new(circle_constraint, Constraint::INEQUALITY)];
let options = Options {
maxiter: Some(1000), gtol: Some(1e-4), ftol: Some(1e-4),
ctol: Some(1e-4),
..Options::default()
};
let options_copy1 = options.clone();
let options_copy2 = options.clone();
let result_slsqp = minimize_constrained(
rosenbrock,
&x0.view(),
&constraints,
Method::SLSQP,
Some(options_copy1),
)
.expect("Operation failed");
let result_trust = minimize_constrained(
rosenbrock,
&x0.view(),
&constraints,
Method::TrustConstr,
Some(options_copy2),
)
.expect("Operation failed");
println!(
"SLSQP Rosenbrock result: x = {:?}, f = {}, iterations = {}",
result_slsqp.x, result_slsqp.fun, result_slsqp.nit
);
println!(
"TrustConstr Rosenbrock result: x = {:?}, f = {}, iterations = {}",
result_trust.x, result_trust.fun, result_trust.nit
);
let initial_value = rosenbrock(&[0.0, 0.0]);
assert!(result_slsqp.fun < initial_value);
assert!(result_trust.fun < initial_value);
let constr_slsqp = result_slsqp.constr.expect("Operation failed");
let constr_trust = result_trust.constr.expect("Operation failed");
assert!(constr_slsqp[0] >= -0.01); assert!(constr_trust[0] >= -0.01); }
#[test]
#[allow(dead_code)]
fn test_cobyla_not_implemented() {
let x0 = array![0.0, 0.0];
let constraints = vec![Constraint::new(constraint, Constraint::INEQUALITY)];
let result = minimize_constrained(objective, &x0.view(), &constraints, Method::COBYLA, None);
assert!(result.is_ok());
let opt_result = result.expect("Operation failed");
assert!(opt_result.success || opt_result.nit > 0); }
#[test]
fn test_augmented_lagrangian_wired_up() {
let x0 = array![0.5, 0.5];
let constraints = vec![Constraint::new(constraint, Constraint::INEQUALITY)];
let options = Options {
maxiter: Some(50),
..Options::default()
};
let result = minimize_constrained(
objective,
&x0.view(),
&constraints,
Method::AugmentedLagrangian,
Some(options),
);
assert!(
result.is_ok(),
"AugmentedLagrangian returned an error: {:?}",
result.err()
);
let opt_result = result.expect("AugmentedLagrangian failed");
assert!(opt_result.nit > 0 || opt_result.success);
assert!(opt_result.fun.is_finite());
}
#[test]
fn test_augmented_lagrangian_equality_wired_up() {
fn obj_sum_sq(x: &[f64]) -> f64 {
x[0].powi(2) + x[1].powi(2)
}
fn eq_con(x: &[f64]) -> f64 {
x[0] + x[1] - 2.0
}
let x0 = array![0.5, 1.5];
let constraints = vec![Constraint::new(eq_con, Constraint::EQUALITY)];
let options = Options {
maxiter: Some(100),
..Options::default()
};
let result = minimize_constrained(
obj_sum_sq,
&x0.view(),
&constraints,
Method::AugmentedLagrangian,
Some(options),
);
assert!(
result.is_ok(),
"AugmentedLagrangian equality failed: {:?}",
result.err()
);
let opt_result = result.expect("AugmentedLagrangian equality failed");
assert!(opt_result.fun.is_finite());
}
#[test]
fn test_issue_126_constraint_captures_variable() {
let threshold = 3.0_f64;
let obj = |x: &[f64]| (x[0] - 1.0).powi(2) + (x[1] - 2.5).powi(2);
let cons = vec![Constraint::new(
move |x: &[f64]| threshold - x[0] - x[1],
Constraint::INEQUALITY,
)];
let x0 = array![1.5, 1.5];
let result = minimize_constrained(obj, &x0, &cons, Method::SLSQP, None)
.expect("constrained minimisation should not error");
assert!(result.success, "SLSQP did not converge: {}", result.message);
let sum = result.x[0] + result.x[1];
assert!(
(sum - threshold).abs() < 1e-2,
"x0 + x1 = {} (expected ~{})",
sum,
threshold
);
assert!(result.fun.is_finite());
assert!(
(result.fun - 0.125).abs() < 1e-2,
"objective = {} (expected ~0.125)",
result.fun
);
}
#[test]
fn test_issue_126_heterogeneous_closures_in_vec() {
let upper = 3.0_f64; let lower = 0.1_f64;
let obj = |x: &[f64]| (x[0] - 1.0).powi(2) + (x[1] - 2.5).powi(2);
let closure_a = move |x: &[f64]| upper - x[0] - x[1];
let closure_b = move |x: &[f64]| x[0] - lower;
let cons = vec![
Constraint::new(closure_a, Constraint::INEQUALITY),
Constraint::new(closure_b, Constraint::INEQUALITY),
];
let x0 = array![1.5, 1.5];
let result = minimize_constrained(obj, &x0, &cons, Method::SLSQP, None)
.expect("constrained minimisation should not error");
assert!(result.fun.is_finite());
assert_eq!(result.constr.expect("constraint values present").len(), 2);
}
#[test]
fn test_issue_127_analytical_objective_gradient() {
let threshold = 3.0_f64;
let obj = |x: &[f64]| (x[0] - 1.0).powi(2) + (x[1] - 2.5).powi(2);
let x0 = array![1.5, 1.5];
let cons_fd = vec![Constraint::new(
move |x: &[f64]| threshold - x[0] - x[1],
Constraint::INEQUALITY,
)];
let fd = minimize_constrained(obj, &x0, &cons_fd, Method::SLSQP, None)
.expect("FD minimisation should not error");
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
let grad_calls = Arc::new(AtomicUsize::new(0));
let gc = Arc::clone(&grad_calls);
let obj_grad = move |x: &[f64]| {
gc.fetch_add(1, Ordering::Relaxed);
array![2.0 * (x[0] - 1.0), 2.0 * (x[1] - 2.5)]
};
let cons_jac = vec![Constraint::new(
move |x: &[f64]| threshold - x[0] - x[1],
Constraint::INEQUALITY,
)];
let jac =
minimize_constrained_with_jac(obj, Some(obj_grad), &x0, &cons_jac, Method::SLSQP, None)
.expect("analytical-gradient minimisation should not error");
assert!(
grad_calls.load(Ordering::Relaxed) > 0,
"analytical objective gradient was never invoked — the analytical path is broken \
(a silent finite-difference fallback would still match the FD baseline)"
);
assert!(jac.success, "SLSQP (analytical grad) did not converge");
assert!(
(jac.x[0] - fd.x[0]).abs() < 1e-4,
"x0: analytical {} vs FD {}",
jac.x[0],
fd.x[0]
);
assert!(
(jac.x[1] - fd.x[1]).abs() < 1e-4,
"x1: analytical {} vs FD {}",
jac.x[1],
fd.x[1]
);
assert!(
(jac.fun - fd.fun).abs() < 1e-4,
"objective: analytical {} vs FD {}",
jac.fun,
fd.fun
);
}
#[test]
fn test_issue_127_constraint_with_jacobian() {
let threshold = 3.0_f64;
let obj = |x: &[f64]| (x[0] - 1.0).powi(2) + (x[1] - 2.5).powi(2);
let x0 = array![1.5, 1.5];
let cons_fd = vec![Constraint::new(
move |x: &[f64]| threshold - x[0] - x[1],
Constraint::INEQUALITY,
)];
let fd = minimize_constrained(obj, &x0, &cons_fd, Method::SLSQP, None)
.expect("FD minimisation should not error");
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
let jac_calls = Arc::new(AtomicUsize::new(0));
let jcc = Arc::clone(&jac_calls);
let cons_jac = vec![Constraint::new(
move |x: &[f64]| threshold - x[0] - x[1],
Constraint::INEQUALITY,
)
.with_jacobian(move |_x: &[f64]| {
jcc.fetch_add(1, Ordering::Relaxed);
array![-1.0, -1.0]
})];
let jac = minimize_constrained(obj, &x0, &cons_jac, Method::SLSQP, None)
.expect("constraint-Jacobian minimisation should not error");
assert!(
jac_calls.load(Ordering::Relaxed) > 0,
"analytical constraint Jacobian was never invoked — `with_jacobian` wiring is broken \
(a silent finite-difference fallback would still match the FD baseline)"
);
assert!(jac.success, "SLSQP (constraint Jacobian) did not converge");
assert!(
(jac.x[0] - fd.x[0]).abs() < 1e-4,
"x0: analytical-jac {} vs FD {}",
jac.x[0],
fd.x[0]
);
assert!(
(jac.x[1] - fd.x[1]).abs() < 1e-4,
"x1: analytical-jac {} vs FD {}",
jac.x[1],
fd.x[1]
);
assert!(
(jac.fun - fd.fun).abs() < 1e-4,
"objective: analytical-jac {} vs FD {}",
jac.fun,
fd.fun
);
}