use crate::error::OptimizeResult;
use crate::result::OptimizeResults;
use scirs2_core::ndarray::{Array1, ArrayBase, Data, Ix1};
use std::fmt;
pub mod augmented_lagrangian;
pub mod cobyla;
pub mod enhanced_sqp;
pub mod epsilon_constraint;
pub mod feasibility_rules;
pub mod interior_point;
pub mod lp_qp_interior;
pub mod penalty;
pub mod slsqp;
pub mod sqp;
pub mod sqp_advanced;
pub mod trust_constr;
pub mod trust_constr_advanced;
pub use augmented_lagrangian::{
minimize_augmented_lagrangian, minimize_equality_constrained, minimize_inequality_constrained,
AugmentedLagrangianOptions, AugmentedLagrangianResult,
};
pub use cobyla::minimize_cobyla;
pub use interior_point::{
minimize_interior_point, minimize_interior_point_constrained, InteriorPointOptions,
InteriorPointResult,
};
pub use slsqp::minimize_slsqp;
pub use sqp::{minimize_sqp, SqpOptions, SqpResult};
pub use trust_constr::{
minimize_trust_constr, minimize_trust_constr_with_derivatives, GradientFn, HessianFn,
HessianUpdate,
};
#[cfg(test)]
mod tests;
pub type ConstraintFn = fn(&[f64]) -> f64;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Method {
SLSQP,
TrustConstr,
COBYLA,
InteriorPoint,
AugmentedLagrangian,
SQP,
}
impl fmt::Display for Method {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Method::SLSQP => write!(f, "SLSQP"),
Method::TrustConstr => write!(f, "trust-constr"),
Method::COBYLA => write!(f, "COBYLA"),
Method::InteriorPoint => write!(f, "interior-point"),
Method::AugmentedLagrangian => write!(f, "augmented-lagrangian"),
Method::SQP => write!(f, "SQP"),
}
}
}
#[derive(Debug, Clone)]
pub struct Options {
pub maxiter: Option<usize>,
pub ftol: Option<f64>,
pub gtol: Option<f64>,
pub ctol: Option<f64>,
pub eps: Option<f64>,
pub disp: bool,
pub return_all: bool,
}
impl Default for Options {
fn default() -> Self {
Options {
maxiter: None,
ftol: Some(1e-8),
gtol: Some(1e-8),
ctol: Some(1e-8),
eps: Some(1e-8),
disp: false,
return_all: false,
}
}
}
pub struct Constraint<F> {
pub fun: F,
pub kind: ConstraintKind,
pub lb: Option<f64>,
pub ub: Option<f64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConstraintKind {
Equality,
Inequality,
}
impl Constraint<fn(&[f64]) -> f64> {
pub const EQUALITY: ConstraintKind = ConstraintKind::Equality;
pub const INEQUALITY: ConstraintKind = ConstraintKind::Inequality;
pub fn new(fun: fn(&[f64]) -> f64, kind: ConstraintKind) -> Self {
Constraint {
fun,
kind,
lb: None,
ub: None,
}
}
pub fn new_bounds(lb: Option<f64>, ub: Option<f64>) -> Self {
Constraint {
fun: |_| 0.0, kind: ConstraintKind::Inequality,
lb,
ub,
}
}
}
impl<F> Constraint<F> {
pub fn is_bounds(&self) -> bool {
self.lb.is_some() || self.ub.is_some()
}
}
#[allow(dead_code)]
pub fn minimize_constrained<F, S>(
func: F,
x0: &ArrayBase<S, Ix1>,
constraints: &[Constraint<ConstraintFn>],
method: Method,
options: Option<Options>,
) -> OptimizeResult<OptimizeResults<f64>>
where
F: Fn(&[f64]) -> f64 + Clone,
S: Data<Elem = f64>,
{
let options = options.unwrap_or_default();
match method {
Method::SLSQP => minimize_slsqp(func, x0, constraints, &options),
Method::TrustConstr => minimize_trust_constr(func, x0, constraints, &options),
Method::COBYLA => minimize_cobyla(func, x0, constraints, &options),
Method::InteriorPoint => {
let x0_arr = Array1::from_vec(x0.to_vec());
let ip_options = InteriorPointOptions {
max_iter: options.maxiter.unwrap_or(100),
tol: options.gtol.unwrap_or(1e-8),
feas_tol: options.ctol.unwrap_or(1e-8),
..Default::default()
};
match minimize_interior_point_constrained(func, x0_arr, constraints, Some(ip_options)) {
Ok(result) => {
let opt_result = OptimizeResults::<f64> {
x: result.x,
fun: result.fun,
nit: result.nit,
nfev: result.nfev,
success: result.success,
message: result.message,
jac: None,
hess: None,
constr: None,
njev: 0, nhev: 0, maxcv: 0, status: if result.success { 0 } else { 1 },
};
Ok(opt_result)
}
Err(e) => Err(e),
}
}
Method::AugmentedLagrangian => {
use scirs2_core::ndarray::{Array1, ArrayView1};
use std::sync::Arc;
let x0_arr = Array1::from_vec(x0.to_vec());
let eq_fns: Arc<Vec<ConstraintFn>> = Arc::new(
constraints
.iter()
.filter(|c| c.kind == ConstraintKind::Equality)
.map(|c| c.fun)
.collect(),
);
let ineq_fns: Arc<Vec<ConstraintFn>> = Arc::new(
constraints
.iter()
.filter(|c| c.kind == ConstraintKind::Inequality)
.map(|c| c.fun)
.collect(),
);
let func_clone = func.clone();
let al_fun = move |x: &ArrayView1<f64>| func_clone(x.as_slice().unwrap_or(&[]));
let al_options = AugmentedLagrangianOptions {
max_iter: options.maxiter.unwrap_or(100),
constraint_tol: options.ctol.unwrap_or(1e-8),
optimality_tol: options.gtol.unwrap_or(1e-8),
..Default::default()
};
#[inline]
fn view_to_slice(x: &ArrayView1<f64>) -> Vec<f64> {
x.iter().copied().collect()
}
let result = if !eq_fns.is_empty() && !ineq_fns.is_empty() {
let eq_arc = Arc::clone(&eq_fns);
let eq_closure = move |x: &ArrayView1<f64>| {
let xs = view_to_slice(x);
Array1::from_vec(eq_arc.iter().map(|f| f(&xs)).collect())
};
let ineq_arc = Arc::clone(&ineq_fns);
let ineq_closure = move |x: &ArrayView1<f64>| {
let xs = view_to_slice(x);
Array1::from_vec(ineq_arc.iter().map(|f| f(&xs)).collect())
};
minimize_augmented_lagrangian(
al_fun,
x0_arr,
Some(eq_closure),
Some(ineq_closure),
Some(al_options),
)?
} else if !eq_fns.is_empty() {
let eq_arc = Arc::clone(&eq_fns);
let eq_closure = move |x: &ArrayView1<f64>| {
let xs = view_to_slice(x);
Array1::from_vec(eq_arc.iter().map(|f| f(&xs)).collect())
};
minimize_augmented_lagrangian(
al_fun,
x0_arr,
Some(eq_closure),
None::<fn(&ArrayView1<f64>) -> Array1<f64>>,
Some(al_options),
)?
} else {
let ineq_arc = Arc::clone(&ineq_fns);
let ineq_closure = move |x: &ArrayView1<f64>| {
let xs = view_to_slice(x);
Array1::from_vec(ineq_arc.iter().map(|f| f(&xs)).collect())
};
minimize_augmented_lagrangian(
al_fun,
x0_arr,
None::<fn(&ArrayView1<f64>) -> Array1<f64>>,
Some(ineq_closure),
Some(al_options),
)?
};
Ok(OptimizeResults::<f64> {
x: result.x,
fun: result.fun,
nit: result.nit,
nfev: result.nfev,
success: result.success,
message: result.message,
jac: None,
hess: None,
constr: None,
njev: 0,
nhev: 0,
maxcv: 0,
status: if result.success { 0 } else { 1 },
})
}
Method::SQP => sqp::minimize_sqp_compat(func, x0, constraints, &options),
}
}