use crate::constrained::{Constraint, ConstraintFn, ConstraintKind, Options};
use crate::error::OptimizeResult;
use crate::result::OptimizeResults;
use scirs2_core::ndarray::{Array1, Array2, ArrayBase, Axis, Data, Ix1};
#[allow(clippy::many_single_char_names)]
#[allow(dead_code)]
pub fn minimize_slsqp<F, S>(
func: F,
x0: &ArrayBase<S, Ix1>,
constraints: &[Constraint<ConstraintFn>],
options: &Options,
) -> OptimizeResult<OptimizeResults<f64>>
where
F: Fn(&[f64]) -> f64,
S: Data<Elem = f64>,
{
let ftol = options.ftol.unwrap_or(1e-8);
let gtol = options.gtol.unwrap_or(1e-8);
let ctol = options.ctol.unwrap_or(1e-8);
let maxiter = options.maxiter.unwrap_or(100 * x0.len());
let eps = options.eps.unwrap_or(1e-8);
let n = x0.len();
let mut x = x0.to_owned();
let mut f = func(x.as_slice().expect("Operation failed"));
let mut nfev = 1;
let mut ineq_constraints = Vec::new();
let mut eq_constraints = Vec::new();
for (i, constraint) in constraints.iter().enumerate() {
if !constraint.is_bounds() {
match constraint.kind {
ConstraintKind::Inequality => ineq_constraints.push((i, constraint)),
ConstraintKind::Equality => eq_constraints.push((i, constraint)),
}
}
}
let n_ineq = ineq_constraints.len();
let n_eq = eq_constraints.len();
let mut lambda_ineq = Array1::zeros(n_ineq);
let mut lambda_eq = Array1::zeros(n_eq);
let mut g = Array1::zeros(n);
for i in 0..n {
let mut x_h = x.clone();
x_h[i] += eps;
let f_h = func(x_h.as_slice().expect("Operation failed"));
g[i] = (f_h - f) / eps;
nfev += 1;
}
let mut c_ineq = Array1::zeros(n_ineq);
let mut c_eq = Array1::zeros(n_eq);
for (idx, (_, constraint)) in ineq_constraints.iter().enumerate() {
let val = (constraint.fun)(x.as_slice().expect("Operation failed"));
c_ineq[idx] = val; nfev += 1;
}
for (idx, (_, constraint)) in eq_constraints.iter().enumerate() {
let val = (constraint.fun)(x.as_slice().expect("Operation failed"));
c_eq[idx] = val; nfev += 1;
}
let mut a_ineq = Array2::zeros((n_ineq, n));
let mut a_eq = Array2::zeros((n_eq, n));
for (idx, (_, constraint)) in ineq_constraints.iter().enumerate() {
for j in 0..n {
let mut x_h = x.clone();
x_h[j] += eps;
let c_h = (constraint.fun)(x_h.as_slice().expect("Operation failed"));
a_ineq[[idx, j]] = (c_h - c_ineq[idx]) / eps;
nfev += 1;
}
}
for (idx, (_, constraint)) in eq_constraints.iter().enumerate() {
for j in 0..n {
let mut x_h = x.clone();
x_h[j] += eps;
let c_h = (constraint.fun)(x_h.as_slice().expect("Operation failed"));
a_eq[[idx, j]] = (c_h - c_eq[idx]) / eps;
nfev += 1;
}
}
let mut h_inv = Array2::eye(n);
let mut iter = 0;
while iter < maxiter {
let mut max_ineq_violation = 0.0;
let mut max_eq_violation = 0.0;
for &ci in c_ineq.iter() {
if ci < -ctol {
max_ineq_violation = f64::max(max_ineq_violation, -ci);
}
}
for &hi in c_eq.iter() {
max_eq_violation = f64::max(max_eq_violation, hi.abs());
}
let max_constraint_violation = f64::max(max_ineq_violation, max_eq_violation);
if g.iter().all(|&gi| gi.abs() < gtol) && max_constraint_violation < ctol {
break;
}
let mut p = Array1::zeros(n);
if max_constraint_violation > ctol {
for (idx, &ci) in c_ineq.iter().enumerate() {
if ci < -ctol {
for j in 0..n {
p[j] -= a_ineq[[idx, j]] * ci; }
}
}
for (idx, &hi) in c_eq.iter().enumerate() {
if hi.abs() > ctol {
for j in 0..n {
p[j] -= a_eq[[idx, j]] * hi; }
}
}
} else {
p = -&h_inv.dot(&g);
for (idx, &ci) in c_ineq.iter().enumerate() {
if ci.abs() < ctol {
let mut normal = Array1::zeros(n);
for j in 0..n {
normal[j] = a_ineq[[idx, j]];
}
let norm = normal.dot(&normal).sqrt();
if norm > 1e-10 {
normal = &normal / norm;
let p_dot_normal = p.dot(&normal);
if p_dot_normal < 0.0 {
p = &p - &(&normal * p_dot_normal);
}
}
}
}
for (idx, _) in c_eq.iter().enumerate() {
let mut normal = Array1::zeros(n);
for j in 0..n {
normal[j] = a_eq[[idx, j]];
}
let norm = normal.dot(&normal).sqrt();
if norm > 1e-10 {
normal = &normal / norm;
let p_dot_normal = p.dot(&normal);
p = &p - &(&normal * p_dot_normal);
}
}
}
let mut alpha = 1.0;
let c1 = 1e-4; let rho = 0.5;
let mut x_new = &x + &(&p * alpha);
let mut f_new = func(x_new.as_slice().expect("Operation failed"));
nfev += 1;
let mut c_ineq_new = Array1::zeros(n_ineq);
let mut c_eq_new = Array1::zeros(n_eq);
for (idx, (_, constraint)) in ineq_constraints.iter().enumerate() {
c_ineq_new[idx] = (constraint.fun)(x_new.as_slice().expect("Operation failed"));
nfev += 1;
}
for (idx, (_, constraint)) in eq_constraints.iter().enumerate() {
c_eq_new[idx] = (constraint.fun)(x_new.as_slice().expect("Operation failed"));
nfev += 1;
}
let max_viol = f64::max(max_ineq_violation, max_eq_violation);
let mut max_viol_new = 0.0;
for &ci in c_ineq_new.iter() {
max_viol_new = f64::max(max_viol_new, f64::max(0.0, -ci));
}
for &hi in c_eq_new.iter() {
max_viol_new = f64::max(max_viol_new, hi.abs());
}
let g_dot_p = g.dot(&p);
while (f_new > f + c1 * alpha * g_dot_p && max_viol <= ctol)
|| (max_viol_new > max_viol && max_viol > ctol)
{
alpha *= rho;
if alpha < 1e-10 {
break;
}
x_new = &x + &(&p * alpha);
f_new = func(x_new.as_slice().expect("Operation failed"));
nfev += 1;
for (idx, (_, constraint)) in ineq_constraints.iter().enumerate() {
c_ineq_new[idx] = (constraint.fun)(x_new.as_slice().expect("Operation failed"));
nfev += 1;
}
for (idx, (_, constraint)) in eq_constraints.iter().enumerate() {
c_eq_new[idx] = (constraint.fun)(x_new.as_slice().expect("Operation failed"));
nfev += 1;
}
max_viol_new = 0.0;
for &ci in c_ineq_new.iter() {
max_viol_new = f64::max(max_viol_new, f64::max(0.0, -ci));
}
for &hi in c_eq_new.iter() {
max_viol_new = f64::max(max_viol_new, hi.abs());
}
}
if ((f - f_new).abs() < ftol * (1.0 + f.abs())) && alpha * p.dot(&p).sqrt() < ftol {
break;
}
let mut g_new = Array1::zeros(n);
for i in 0..n {
let mut x_h = x_new.clone();
x_h[i] += eps;
let f_h = func(x_h.as_slice().expect("Operation failed"));
g_new[i] = (f_h - f_new) / eps;
nfev += 1;
}
let mut a_ineq_new = Array2::zeros((n_ineq, n));
let mut a_eq_new = Array2::zeros((n_eq, n));
for (idx, (_, constraint)) in ineq_constraints.iter().enumerate() {
for j in 0..n {
let mut x_h = x_new.clone();
x_h[j] += eps;
let c_h = (constraint.fun)(x_h.as_slice().expect("Operation failed"));
a_ineq_new[[idx, j]] = (c_h - c_ineq_new[idx]) / eps;
nfev += 1;
}
}
for (idx, (_, constraint)) in eq_constraints.iter().enumerate() {
for j in 0..n {
let mut x_h = x_new.clone();
x_h[j] += eps;
let c_h = (constraint.fun)(x_h.as_slice().expect("Operation failed"));
a_eq_new[[idx, j]] = (c_h - c_eq_new[idx]) / eps;
nfev += 1;
}
}
for (idx, &ci) in c_ineq_new.iter().enumerate() {
if ci.abs() < ctol {
let mut normal = Array1::zeros(n);
for j in 0..n {
normal[j] = a_ineq_new[[idx, j]];
}
let norm = normal.dot(&normal).sqrt();
if norm > 1e-10 {
normal = &normal / norm;
lambda_ineq[idx] = -g_new.dot(&normal);
lambda_ineq[idx] = lambda_ineq[idx].max(0.0);
}
} else {
lambda_ineq[idx] = 0.0;
}
}
for (idx, _) in c_eq_new.iter().enumerate() {
let mut normal = Array1::zeros(n);
for j in 0..n {
normal[j] = a_eq_new[[idx, j]];
}
let norm = normal.dot(&normal).sqrt();
if norm > 1e-10 {
normal = &normal / norm;
lambda_eq[idx] = -g_new.dot(&normal);
}
}
let s = &x_new - &x;
let y = &g_new - &g;
let mut y_lag = y.clone();
for (idx, &li) in lambda_ineq.iter().enumerate() {
if li > 0.0 {
for j in 0..n {
y_lag[j] += li * (a_ineq_new[[idx, j]] - a_ineq[[idx, j]]);
}
}
}
for (idx, &li) in lambda_eq.iter().enumerate() {
for j in 0..n {
y_lag[j] += li * (a_eq_new[[idx, j]] - a_eq[[idx, j]]);
}
}
let rho_bfgs = 1.0 / y_lag.dot(&s);
if rho_bfgs.is_finite() && rho_bfgs > 0.0 {
let i_mat = Array2::eye(n);
let y_row = y_lag.clone().insert_axis(Axis(0));
let s_col = s.clone().insert_axis(Axis(1));
let y_s_t = y_row.dot(&s_col);
let term1 = &i_mat - &(&y_s_t * rho_bfgs);
let s_row = s.clone().insert_axis(Axis(0));
let y_col = y_lag.clone().insert_axis(Axis(1));
let s_y_t = s_row.dot(&y_col);
let term2 = &i_mat - &(&s_y_t * rho_bfgs);
let term3 = &term1.dot(&h_inv);
h_inv = term3.dot(&term2) + rho_bfgs * s_col.dot(&s_row);
}
x = x_new;
f = f_new;
g = g_new;
c_ineq = c_ineq_new;
c_eq = c_eq_new;
a_ineq = a_ineq_new;
a_eq = a_eq_new;
iter += 1;
}
let mut c_result = Array1::zeros(constraints.len());
let mut ineq_idx = 0;
let mut eq_idx = 0;
for (i, constraint) in constraints.iter().enumerate() {
if !constraint.is_bounds() {
match constraint.kind {
ConstraintKind::Inequality => {
c_result[i] = c_ineq[ineq_idx];
ineq_idx += 1;
}
ConstraintKind::Equality => {
c_result[i] = c_eq[eq_idx];
eq_idx += 1;
}
}
}
}
let mut result = OptimizeResults::default();
result.x = x;
result.fun = f;
result.jac = Some(g.into_raw_vec_and_offset().0);
result.constr = Some(c_result);
result.nfev = nfev;
result.nit = iter;
result.success = iter < maxiter;
if result.success {
result.message = "Optimization terminated successfully.".to_string();
} else {
result.message = "Maximum iterations reached.".to_string();
}
Ok(result)
}