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 {
pub fun: Box<dyn Fn(&[f64]) -> f64 + Send + Sync>,
pub jac: Option<Box<dyn Fn(&[f64]) -> Array1<f64> + Send + Sync>>,
pub kind: ConstraintKind,
pub lb: Option<f64>,
pub ub: Option<f64>,
}
impl fmt::Debug for Constraint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Constraint")
.field("kind", &self.kind)
.field("lb", &self.lb)
.field("ub", &self.ub)
.field("has_jac", &self.jac.is_some())
.finish_non_exhaustive()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConstraintKind {
Equality,
Inequality,
}
impl Constraint {
pub const EQUALITY: ConstraintKind = ConstraintKind::Equality;
pub const INEQUALITY: ConstraintKind = ConstraintKind::Inequality;
pub fn new<F>(fun: F, kind: ConstraintKind) -> Self
where
F: Fn(&[f64]) -> f64 + Send + Sync + 'static,
{
Constraint {
fun: Box::new(fun),
jac: None,
kind,
lb: None,
ub: None,
}
}
pub fn new_bounds(lb: Option<f64>, ub: Option<f64>) -> Self {
Constraint {
fun: Box::new(|_| 0.0), jac: None,
kind: ConstraintKind::Inequality,
lb,
ub,
}
}
pub fn with_jacobian<J>(mut self, jac: J) -> Self
where
J: Fn(&[f64]) -> Array1<f64> + Send + Sync + 'static,
{
self.jac = Some(Box::new(jac));
self
}
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],
method: Method,
options: Option<Options>,
) -> OptimizeResult<OptimizeResults<f64>>
where
F: Fn(&[f64]) -> f64 + Clone,
S: Data<Elem = f64>,
{
minimize_constrained_with_jac(
func,
None::<fn(&[f64]) -> Array1<f64>>,
x0,
constraints,
method,
options,
)
}
#[allow(dead_code)]
pub fn minimize_constrained_with_jac<F, G, S>(
func: F,
jac: Option<G>,
x0: &ArrayBase<S, Ix1>,
constraints: &[Constraint],
method: Method,
options: Option<Options>,
) -> OptimizeResult<OptimizeResults<f64>>
where
F: Fn(&[f64]) -> f64 + Clone,
G: Fn(&[f64]) -> Array1<f64> + Clone,
S: Data<Elem = f64>,
{
let options = options.unwrap_or_default();
let obj_jac: Option<Box<dyn Fn(&[f64]) -> Array1<f64>>> =
jac.map(|g| Box::new(g) as Box<dyn Fn(&[f64]) -> Array1<f64>>);
let obj_jac_ref: Option<&dyn Fn(&[f64]) -> Array1<f64>> = obj_jac.as_ref().map(|b| b.as_ref());
match method {
Method::SLSQP => minimize_slsqp(func, x0, constraints, obj_jac_ref, &options),
Method::TrustConstr => minimize_trust_constr(func, x0, constraints, obj_jac_ref, &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_idx: Arc<Vec<usize>> = Arc::new(
constraints
.iter()
.enumerate()
.filter(|(_, c)| c.kind == ConstraintKind::Equality)
.map(|(i, _)| i)
.collect(),
);
let ineq_idx: Arc<Vec<usize>> = Arc::new(
constraints
.iter()
.enumerate()
.filter(|(_, c)| c.kind == ConstraintKind::Inequality)
.map(|(i, _)| i)
.collect(),
);
let has_eq = !eq_idx.is_empty();
let has_ineq = !ineq_idx.is_empty();
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 has_eq && has_ineq {
let eq_arc = Arc::clone(&eq_idx);
let eq_closure = move |x: &ArrayView1<f64>| {
let xs = view_to_slice(x);
Array1::from_vec(eq_arc.iter().map(|&i| (constraints[i].fun)(&xs)).collect())
};
let ineq_arc = Arc::clone(&ineq_idx);
let ineq_closure = move |x: &ArrayView1<f64>| {
let xs = view_to_slice(x);
Array1::from_vec(
ineq_arc
.iter()
.map(|&i| (constraints[i].fun)(&xs))
.collect(),
)
};
minimize_augmented_lagrangian(
al_fun,
x0_arr,
Some(eq_closure),
Some(ineq_closure),
Some(al_options),
)?
} else if has_eq {
let eq_arc = Arc::clone(&eq_idx);
let eq_closure = move |x: &ArrayView1<f64>| {
let xs = view_to_slice(x);
Array1::from_vec(eq_arc.iter().map(|&i| (constraints[i].fun)(&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_idx);
let ineq_closure = move |x: &ArrayView1<f64>| {
let xs = view_to_slice(x);
Array1::from_vec(
ineq_arc
.iter()
.map(|&i| (constraints[i].fun)(&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),
}
}