use std::os::raw::{c_uint, c_ulong, c_void};
use std::slice;
use self::nlopt_sys as sys;
#[allow(non_camel_case_types)]
#[allow(non_upper_case_globals)]
#[allow(dead_code)]
mod nlopt_sys;
#[derive(Debug, Clone, Copy)]
pub enum Target {
Maximize,
Minimize,
}
#[repr(u32)]
#[derive(Clone, Copy, Debug)]
pub enum Algorithm {
Direct = sys::nlopt_algorithm_NLOPT_GN_DIRECT,
DirectL = sys::nlopt_algorithm_NLOPT_GN_DIRECT_L,
DirectLRand = sys::nlopt_algorithm_NLOPT_GN_DIRECT_L_RAND,
DirectNoscal = sys::nlopt_algorithm_NLOPT_GN_DIRECT_NOSCAL,
DirectLNoscal = sys::nlopt_algorithm_NLOPT_GN_DIRECT_L_NOSCAL,
DirectLRandNoscal = sys::nlopt_algorithm_NLOPT_GN_DIRECT_L_RAND_NOSCAL,
OrigDirect = sys::nlopt_algorithm_NLOPT_GN_ORIG_DIRECT,
OrigDirectL = sys::nlopt_algorithm_NLOPT_GN_ORIG_DIRECT_L,
Crs2Lm = sys::nlopt_algorithm_NLOPT_GN_CRS2_LM,
GMlsl = sys::nlopt_algorithm_NLOPT_G_MLSL,
GMlslLds = sys::nlopt_algorithm_NLOPT_G_MLSL_LDS,
GnMlsl = sys::nlopt_algorithm_NLOPT_GN_MLSL,
GdMlsl = sys::nlopt_algorithm_NLOPT_GD_MLSL,
GnMlslLds = sys::nlopt_algorithm_NLOPT_GN_MLSL_LDS,
GdMlslLds = sys::nlopt_algorithm_NLOPT_GD_MLSL_LDS,
StoGo = sys::nlopt_algorithm_NLOPT_GD_STOGO,
StoGoRand = sys::nlopt_algorithm_NLOPT_GD_STOGO_RAND,
Isres = sys::nlopt_algorithm_NLOPT_GN_ISRES,
Esch = sys::nlopt_algorithm_NLOPT_GN_ESCH,
Cobyla = sys::nlopt_algorithm_NLOPT_LN_COBYLA,
Bobyqa = sys::nlopt_algorithm_NLOPT_LN_BOBYQA,
Newuoa = sys::nlopt_algorithm_NLOPT_LN_NEWUOA,
NewuoaBound = sys::nlopt_algorithm_NLOPT_LN_NEWUOA_BOUND,
Praxis = sys::nlopt_algorithm_NLOPT_LN_PRAXIS,
Neldermead = sys::nlopt_algorithm_NLOPT_LN_NELDERMEAD,
Sbplx = sys::nlopt_algorithm_NLOPT_LN_SBPLX,
Mma = sys::nlopt_algorithm_NLOPT_LD_MMA,
Slsqp = sys::nlopt_algorithm_NLOPT_LD_SLSQP,
Lbfgs = sys::nlopt_algorithm_NLOPT_LD_LBFGS,
LdVar1 = sys::nlopt_algorithm_NLOPT_LD_VAR1,
LdVar2 = sys::nlopt_algorithm_NLOPT_LD_VAR2,
TNewton = sys::nlopt_algorithm_NLOPT_LD_TNEWTON,
TNewtonRestart = sys::nlopt_algorithm_NLOPT_LD_TNEWTON_RESTART,
TNewtonPrecond = sys::nlopt_algorithm_NLOPT_LD_TNEWTON_PRECOND,
TNewtonPrecondRestart = sys::nlopt_algorithm_NLOPT_LD_TNEWTON_PRECOND_RESTART,
Auglag = sys::nlopt_algorithm_NLOPT_AUGLAG,
AuglagEq = sys::nlopt_algorithm_NLOPT_AUGLAG_EQ,
LnAuglag = sys::nlopt_algorithm_NLOPT_LN_AUGLAG,
LdAuglagEq = sys::nlopt_algorithm_NLOPT_LD_AUGLAG_EQ,
LdAuglag = sys::nlopt_algorithm_NLOPT_LD_AUGLAG,
LnAuglagEq = sys::nlopt_algorithm_NLOPT_LN_AUGLAG_EQ,
Ccsaq = sys::nlopt_algorithm_NLOPT_LD_CCSAQ,
Ags = sys::nlopt_algorithm_NLOPT_GN_AGS,
}
#[repr(i32)]
#[derive(Debug, Clone, Copy)]
pub enum FailState {
Failure = sys::nlopt_result_NLOPT_FAILURE,
InvalidArgs = sys::nlopt_result_NLOPT_INVALID_ARGS,
OutOfMemory = sys::nlopt_result_NLOPT_OUT_OF_MEMORY,
RoundoffLimited = sys::nlopt_result_NLOPT_ROUNDOFF_LIMITED,
ForcedStop = sys::nlopt_result_NLOPT_FORCED_STOP,
}
#[repr(i32)]
#[derive(Debug, Clone, Copy)]
pub enum SuccessState {
Success = sys::nlopt_result_NLOPT_SUCCESS,
StopValReached = sys::nlopt_result_NLOPT_STOPVAL_REACHED,
FtolReached = sys::nlopt_result_NLOPT_FTOL_REACHED,
XtolReached = sys::nlopt_result_NLOPT_XTOL_REACHED,
MaxEvalReached = sys::nlopt_result_NLOPT_MAXEVAL_REACHED,
MaxTimeReached = sys::nlopt_result_NLOPT_MAXTIME_REACHED,
}
pub type OptResult = std::result::Result<SuccessState, FailState>;
fn result_from_outcome(outcome: sys::nlopt_result) -> OptResult {
use self::FailState::*;
use self::SuccessState::*;
if outcome < 0 {
let err = match outcome {
sys::nlopt_result_NLOPT_FAILURE => Failure,
sys::nlopt_result_NLOPT_INVALID_ARGS => InvalidArgs,
sys::nlopt_result_NLOPT_OUT_OF_MEMORY => OutOfMemory,
sys::nlopt_result_NLOPT_ROUNDOFF_LIMITED => RoundoffLimited,
sys::nlopt_result_NLOPT_FORCED_STOP => ForcedStop,
v => panic!("Unknown fail state {}", v),
};
Err(err)
} else {
let ok = match outcome {
sys::nlopt_result_NLOPT_SUCCESS => Success,
sys::nlopt_result_NLOPT_STOPVAL_REACHED => StopValReached,
sys::nlopt_result_NLOPT_FTOL_REACHED => FtolReached,
sys::nlopt_result_NLOPT_XTOL_REACHED => XtolReached,
sys::nlopt_result_NLOPT_MAXEVAL_REACHED => MaxEvalReached,
sys::nlopt_result_NLOPT_MAXTIME_REACHED => MaxTimeReached,
v => panic!("Unknown success state {}", v),
};
Ok(ok)
}
}
extern "C" fn function_raw_callback<F: ObjFn<T>, T>(
n: c_uint,
x: *const f64,
g: *mut f64,
params: *mut c_void,
) -> f64 {
let argument = unsafe { slice::from_raw_parts(x, n as usize) };
let gradient = if g.is_null() {
None
} else {
Some(unsafe { slice::from_raw_parts_mut(g, n as usize) })
};
let f = unsafe { &mut *(params as *mut FunctionCfg<F, T>) };
(f.objective_fn)(argument, gradient, &mut f.user_data)
}
extern "C" fn constraint_raw_callback<F: ObjFn<T>, T>(
n: c_uint,
x: *const f64,
g: *mut f64,
params: *mut c_void,
) -> f64 {
let f = unsafe { &mut *(params as *mut ConstraintCfg<F, T>) };
let argument = unsafe { slice::from_raw_parts(x, n as usize) };
let gradient = if g.is_null() {
None
} else {
Some(unsafe { slice::from_raw_parts_mut(g, n as usize) })
};
(f.objective_fn)(argument, gradient, &mut f.user_data)
}
extern "C" fn mfunction_raw_callback<F: MObjFn<T>, T>(
m: u32,
re: *mut f64,
n: u32,
x: *const f64,
g: *mut f64,
d: *mut c_void,
) {
let f = unsafe { &mut *(d as *mut MConstraintCfg<F, T>) };
let re = unsafe { slice::from_raw_parts_mut(re, m as usize) };
let argument = unsafe { slice::from_raw_parts(x, n as usize) };
let gradient: Option<&mut [f64]> = if g.is_null() {
None
} else {
Some(unsafe { slice::from_raw_parts_mut(g, (n as usize) * (m as usize)) })
};
(f.constraint)(re, argument, gradient, &mut f.user_data);
}
pub trait ObjFn<U>: Fn(&[f64], Option<&mut [f64]>, &mut U) -> f64 {}
impl<T, U> ObjFn<U> for T where T: Fn(&[f64], Option<&mut [f64]>, &mut U) -> f64 {}
struct FunctionCfg<F: ObjFn<T>, T> {
pub objective_fn: F,
pub user_data: T,
}
type ConstraintCfg<F, T> = FunctionCfg<F, T>;
pub trait MObjFn<U>: Fn(&mut [f64], &[f64], Option<&mut [f64]>, &mut U) {}
impl<T, U> MObjFn<U> for T where T: Fn(&mut [f64], &[f64], Option<&mut [f64]>, &mut U) {}
struct MConstraintCfg<F: MObjFn<T>, T> {
constraint: F,
user_data: T,
}
struct WrapSysNlopt(sys::nlopt_opt);
impl Drop for WrapSysNlopt {
fn drop(&mut self) {
unsafe {
sys::nlopt_destroy(self.0);
};
}
}
pub struct Nlopt<F: ObjFn<T>, T> {
algorithm: Algorithm,
n_dims: usize,
target: Target,
nloptc_obj: WrapSysNlopt,
func_cfg: Box<FunctionCfg<F, T>>,
}
impl<F: ObjFn<T>, T> Nlopt<F, T> {
pub fn new(
algorithm: Algorithm,
n_dims: usize,
objective_fn: F,
target: Target,
user_data: T,
) -> Nlopt<F, T> {
let nloptc_obj = unsafe { sys::nlopt_create(algorithm as u32, n_dims as u32) };
let func_cfg = Box::new(FunctionCfg {
objective_fn,
user_data, });
let fn_cfg_ptr = &*func_cfg as *const _ as *mut c_void;
let nlopt = Nlopt {
algorithm,
n_dims,
target,
nloptc_obj: WrapSysNlopt(nloptc_obj),
func_cfg,
};
match target {
Target::Minimize => unsafe {
sys::nlopt_set_min_objective(
nlopt.nloptc_obj.0,
Some(function_raw_callback::<F, T>),
fn_cfg_ptr,
)
},
Target::Maximize => unsafe {
sys::nlopt_set_max_objective(
nlopt.nloptc_obj.0,
Some(function_raw_callback::<F, T>),
fn_cfg_ptr,
)
},
};
nlopt
}
pub fn get_algorithm(&self) -> Algorithm {
self.algorithm
}
pub fn recover_user_data(self) -> T {
self.func_cfg.user_data
}
pub fn set_lower_bounds(&mut self, bound: &[f64]) -> OptResult {
result_from_outcome(unsafe {
sys::nlopt_set_lower_bounds(self.nloptc_obj.0, bound.as_ptr())
})
}
pub fn set_upper_bounds(&mut self, bound: &[f64]) -> OptResult {
result_from_outcome(unsafe {
sys::nlopt_set_upper_bounds(self.nloptc_obj.0, bound.as_ptr())
})
}
pub fn set_lower_bound(&mut self, bound: f64) -> OptResult {
let v = vec![bound; self.n_dims];
self.set_lower_bounds(&v)
}
pub fn set_upper_bound(&mut self, bound: f64) -> OptResult {
let v = vec![bound; self.n_dims];
self.set_upper_bounds(&v)
}
pub fn get_upper_bounds(&self) -> Option<Vec<f64>> {
let mut bound: Vec<f64> = vec![0.0_f64; self.n_dims];
let b = bound.as_mut_ptr();
let res = unsafe { sys::nlopt_get_upper_bounds(self.nloptc_obj.0, b as *mut f64) };
result_from_outcome(res).ok().map(|_| bound)
}
pub fn get_lower_bounds(&self) -> Option<Vec<f64>> {
let mut bound: Vec<f64> = vec![0.0_f64; self.n_dims];
let b = bound.as_mut_ptr();
let res = unsafe { sys::nlopt_get_lower_bounds(self.nloptc_obj.0, b as *mut f64) };
result_from_outcome(res).ok().map(|_| bound)
}
pub fn add_equality_constraint<G: ObjFn<U>, U>(
&mut self,
constraint: G,
user_data: U,
tolerance: f64,
) -> OptResult {
self.add_constraint(constraint, user_data, tolerance, true)
}
pub fn add_inequality_constraint<G: ObjFn<U>, U>(
&mut self,
constraint: G,
user_data: U,
tolerance: f64,
) -> OptResult {
self.add_constraint(constraint, user_data, tolerance, false)
}
fn add_constraint<G: ObjFn<U>, U>(
&mut self,
constraint: G,
user_data: U,
tolerance: f64,
is_equality: bool,
) -> OptResult {
let constraint = ConstraintCfg {
objective_fn: constraint,
user_data,
};
let ptr = Box::into_raw(Box::new(constraint)) as *mut c_void;
let outcome = unsafe {
if is_equality {
sys::nlopt_add_equality_constraint(
self.nloptc_obj.0,
Some(constraint_raw_callback::<G, U>),
ptr,
tolerance,
)
} else {
sys::nlopt_add_inequality_constraint(
self.nloptc_obj.0,
Some(constraint_raw_callback::<G, U>),
ptr,
tolerance,
)
}
};
result_from_outcome(outcome)
}
pub fn add_equality_mconstraint<G: MObjFn<U>, U>(
&mut self,
m: usize,
constraint: G,
user_data: U,
tolerance: &[f64],
) -> OptResult {
self.add_mconstraint(m, constraint, user_data, tolerance, true)
}
pub fn add_inequality_mconstraint<G: MObjFn<U>, U>(
&mut self,
m: usize,
constraint: G,
user_data: U,
tolerance: &[f64],
) -> OptResult {
self.add_mconstraint(m, constraint, user_data, tolerance, false)
}
fn add_mconstraint<G: MObjFn<U>, U>(
&mut self,
m: usize,
constraint: G,
user_data: U,
tolerance: &[f64],
is_equality: bool,
) -> OptResult {
assert_eq!(m, tolerance.len());
let mconstraint = MConstraintCfg {
constraint,
user_data,
};
let ptr = Box::into_raw(Box::new(mconstraint)) as *mut c_void;
let outcome = unsafe {
if is_equality {
sys::nlopt_add_equality_mconstraint(
self.nloptc_obj.0,
m as c_uint,
Some(mfunction_raw_callback::<G, U>),
ptr,
tolerance.as_ptr(),
)
} else {
sys::nlopt_add_inequality_mconstraint(
self.nloptc_obj.0,
m as c_uint,
Some(mfunction_raw_callback::<G, U>),
ptr,
tolerance.as_ptr(),
)
}
};
result_from_outcome(outcome)
}
pub fn remove_constraints(&mut self) -> OptResult {
result_from_outcome(unsafe {
std::cmp::min(
sys::nlopt_remove_inequality_constraints(self.nloptc_obj.0),
sys::nlopt_remove_equality_constraints(self.nloptc_obj.0),
)
})
}
pub fn set_stopval(&mut self, stopval: f64) -> OptResult {
result_from_outcome(unsafe { sys::nlopt_set_stopval(self.nloptc_obj.0, stopval) })
}
pub fn get_stopval(&self) -> f64 {
unsafe { sys::nlopt_get_stopval(self.nloptc_obj.0) }
}
pub fn set_ftol_rel(&mut self, tolerance: f64) -> OptResult {
result_from_outcome(unsafe { sys::nlopt_set_ftol_rel(self.nloptc_obj.0, tolerance) })
}
pub fn get_ftol_rel(&self) -> Option<f64> {
unsafe {
match sys::nlopt_get_ftol_rel(self.nloptc_obj.0) {
x if x < 0.0 => None,
x => Some(x),
}
}
}
pub fn set_ftol_abs(&mut self, tolerance: f64) -> OptResult {
result_from_outcome(unsafe { sys::nlopt_set_ftol_abs(self.nloptc_obj.0, tolerance) })
}
pub fn get_ftol_abs(&self) -> Option<f64> {
match unsafe { sys::nlopt_get_ftol_abs(self.nloptc_obj.0) } {
x if x < 0.0 => None,
x => Some(x),
}
}
pub fn set_xtol_rel(&mut self, tolerance: f64) -> OptResult {
result_from_outcome(unsafe { sys::nlopt_set_xtol_rel(self.nloptc_obj.0, tolerance) })
}
pub fn get_xtol_rel(&self) -> Option<f64> {
match unsafe { sys::nlopt_get_xtol_rel(self.nloptc_obj.0) } {
x if x < 0.0 => None,
x => Some(x),
}
}
pub fn set_xtol_abs(&mut self, tolerance: &[f64]) -> OptResult {
result_from_outcome(unsafe {
sys::nlopt_set_xtol_abs(self.nloptc_obj.0, tolerance.as_ptr())
})
}
pub fn set_xtol_abs1(&mut self, tolerance: f64) -> OptResult {
let tol: &[f64] = &vec![tolerance; self.n_dims];
self.set_xtol_abs(tol)
}
pub fn get_xtol_abs(&mut self) -> Option<Vec<f64>> {
let mut tol: Vec<f64> = vec![0.0_f64; self.n_dims];
let b = tol.as_mut_ptr();
let res = unsafe { sys::nlopt_get_xtol_abs(self.nloptc_obj.0, b as *mut f64) };
result_from_outcome(res).ok().map(|_| tol)
}
pub fn set_maxeval(&mut self, maxeval: u32) -> OptResult {
result_from_outcome(unsafe { sys::nlopt_set_maxeval(self.nloptc_obj.0, maxeval as i32) })
}
pub fn get_maxeval(&mut self) -> Option<u32> {
match unsafe { sys::nlopt_get_maxeval(self.nloptc_obj.0) } {
x if x < 0 => None,
x => Some(x as u32),
}
}
pub fn set_maxtime(&mut self, timeout: f64) -> OptResult {
result_from_outcome(unsafe { sys::nlopt_set_maxtime(self.nloptc_obj.0, timeout) })
}
pub fn get_maxtime(&self) -> Option<f64> {
match unsafe { sys::nlopt_get_maxtime(self.nloptc_obj.0) } {
x if x < 0.0 => None,
x => Some(x),
}
}
pub fn force_stop(&mut self, stopval: Option<i32>) -> OptResult {
result_from_outcome(unsafe {
match stopval {
Some(x) => sys::nlopt_set_force_stop(self.nloptc_obj.0, x),
None => sys::nlopt_force_stop(self.nloptc_obj.0),
}
})
}
pub fn get_force_stop(&mut self) -> Option<i32> {
match unsafe { sys::nlopt_get_force_stop(self.nloptc_obj.0) } {
0 => None,
x => Some(x),
}
}
pub fn set_local_optimizer(&mut self, local_opt: Nlopt<impl ObjFn<()>, ()>) -> OptResult {
result_from_outcome(unsafe {
sys::nlopt_set_local_optimizer(self.nloptc_obj.0, local_opt.nloptc_obj.0)
})
}
pub fn get_local_optimizer(&mut self, algorithm: Algorithm) -> Nlopt<impl ObjFn<()>, ()> {
fn stub_opt(_: &[f64], _: Option<&mut [f64]>, _: &mut ()) -> f64 {
unreachable!()
}
Nlopt::new(algorithm, self.n_dims, stub_opt, self.target, ())
}
pub fn set_initial_step(&mut self, dx: &[f64]) -> OptResult {
result_from_outcome(unsafe { sys::nlopt_set_initial_step(self.nloptc_obj.0, dx.as_ptr()) })
}
pub fn set_initial_step1(&mut self, dx: f64) -> OptResult {
let d: &[f64] = &vec![dx; self.n_dims];
self.set_initial_step(d)
}
pub fn get_initial_step(&mut self, x: &[f64]) -> Option<Vec<f64>> {
let mut dx: Vec<f64> = vec![0.0_f64; self.n_dims];
let b = dx.as_mut_ptr();
let res =
unsafe { sys::nlopt_get_initial_step(self.nloptc_obj.0, x.as_ptr(), b as *mut f64) };
result_from_outcome(res).ok().map(|_| dx)
}
pub fn set_population(&mut self, population: usize) -> OptResult {
result_from_outcome(unsafe {
sys::nlopt_set_population(self.nloptc_obj.0, population as u32)
})
}
pub fn get_population(&mut self) -> usize {
unsafe { sys::nlopt_get_population(self.nloptc_obj.0) as usize }
}
pub fn srand_seed(seed: Option<u64>) {
unsafe {
match seed {
None => sys::nlopt_srand_time(),
Some(x) => sys::nlopt_srand(x as c_ulong),
}
}
}
pub fn set_vector_storage(&mut self, m: Option<usize>) -> OptResult {
let outcome = match m {
None => unsafe { sys::nlopt_set_vector_storage(self.nloptc_obj.0, 0_u32) },
Some(x) => unsafe { sys::nlopt_set_vector_storage(self.nloptc_obj.0, x as u32) },
};
result_from_outcome(outcome)
}
pub fn get_vector_storage(&mut self) -> usize {
unsafe { sys::nlopt_get_vector_storage(self.nloptc_obj.0) as usize }
}
pub fn version() -> (i32, i32, i32) {
unsafe {
let mut i: i32 = 0;
let mut j: i32 = 0;
let mut k: i32 = 0;
sys::nlopt_version(&mut i, &mut j, &mut k);
(i, j, k)
}
}
pub fn optimize(&self, x_init: &mut [f64]) -> Result<(SuccessState, f64), (FailState, f64)> {
let mut min_value: f64 = 0.0;
let res =
unsafe { sys::nlopt_optimize(self.nloptc_obj.0, x_init.as_mut_ptr(), &mut min_value) };
result_from_outcome(res)
.map(|s| (s, min_value))
.map_err(|e| (e, min_value))
}
}
pub fn approximate_gradient<F>(x0: &[f64], f: F, grad: &mut [f64])
where
F: Fn(&[f64]) -> f64,
{
let n = x0.len();
let mut x0 = x0.to_vec();
let eps = f64::EPSILON.powf(1.0 / 3.0);
for i in 0..n {
let x0i = x0[i];
x0[i] = x0i - eps;
let fl = f(&x0);
x0[i] = x0i + eps;
let fh = f(&x0);
grad[i] = (fh - fl) / (2.0 * eps);
x0[i] = x0i;
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
#[test]
fn test_approx_gradient() {
fn square(x: &[f64]) -> f64 {
x.iter().map(|v| v * v).sum()
}
let x0 = vec![0., 1., 6.];
let mut grad = vec![0.; 3];
approximate_gradient(&x0, square, &mut grad);
let expect = vec![0., 2., 12.];
for (r, e) in grad.iter().zip(expect.iter()) {
assert!((r - e).abs() < 0.0000001)
}
}
#[test]
fn test_user_data() {
fn objfn(x: &[f64], grad: Option<&mut [f64]>, coefs: &mut (f64, Vec<f64>)) -> f64 {
assert!(grad.is_none());
let x = x[0];
coefs.0 * x * x + coefs.1[0] * x + coefs.1[1]
}
let mut params = vec![1.];
let userdata = (1.0, vec![-2., 3.]);
let mut opt = Nlopt::<_, (f64, Vec<f64>)>::new(
Algorithm::Bobyqa,
2,
objfn,
Target::Minimize,
userdata,
);
opt.set_xtol_rel(1e-8).unwrap();
let res = opt.optimize(&mut params).unwrap();
assert_eq!(res.1, 2.0);
assert_eq!(params, vec![1.0]);
}
#[test]
fn test_lbfgs() {
fn flb(x: &[f64]) -> f64 {
let p = x.len();
Some(1.0)
.into_iter()
.chain(std::iter::repeat(4.0))
.take(p)
.zip(
x.iter()
.zip(Some(1.0).into_iter().chain(x.iter().cloned()).take(p))
.map(|(x, xd)| (x - xd.powi(2)).powi(2)),
)
.map(|(fst, snd)| fst * snd)
.sum()
}
fn objfn(x: &[f64], grad: Option<&mut [f64]>, _user_data: &mut ()) -> f64 {
let grad = grad.unwrap();
approximate_gradient(x, flb, grad);
flb(x)
}
let mut opt = Nlopt::<_, ()>::new(Algorithm::Lbfgs, 25, objfn, Target::Minimize, ());
let mut x0 = vec![3.0; 25];
let xl = vec![2.0; 25];
let xu = vec![4.0; 25];
assert!(opt
.get_upper_bounds()
.unwrap()
.into_iter()
.all(|v| v == f64::INFINITY));
assert!(opt
.get_lower_bounds()
.unwrap()
.into_iter()
.all(|v| v == f64::NEG_INFINITY));
opt.set_upper_bounds(&xu).unwrap();
opt.set_lower_bounds(&xl).unwrap();
opt.set_lower_bounds(&xl).unwrap();
assert_eq!(opt.get_upper_bounds().unwrap(), xu);
assert_eq!(opt.get_lower_bounds().unwrap(), xl);
opt.set_xtol_rel(1e-8).unwrap();
let mut expect = vec![2.0; 25];
expect[23] = 2.1090933511928247;
expect[24] = 4.;
match opt.optimize(&mut x0) {
Ok((_, v)) => assert_eq!(v, 368.10591287433397),
Err((e, _)) => panic!("{:?}", e),
}
assert_eq!(&x0, &expect);
}
#[test]
fn test_praxis_closure() {
fn objfn(x: &[f64]) -> f64 {
((x[0] - 1.) * (x[0] - 1.) + x[1] * x[1]) * -1. + 4.
}
let opt = Nlopt::new(
Algorithm::Praxis,
2,
|x: &[f64], _: Option<&mut [f64]>, _: &mut ()| objfn(x),
Target::Maximize,
(),
);
assert_eq!(
opt.get_upper_bounds().unwrap(),
&[f64::INFINITY, f64::INFINITY]
);
let mut input = vec![3.0, -2.0];
let (_s, val) = opt.optimize(&mut input).unwrap();
assert_eq!(val, 4.);
assert_eq!(input, &[1., 0.]);
}
#[test]
fn test_auglag() {
fn objfn(x: &[f64], _grad: Option<&mut [f64]>, _user_data: &mut ()) -> f64 {
(x[0] - 2.0).powi(2) + (x[1] - 1.0).powi(2)
}
fn hin(x: &[f64], _grad: Option<&mut [f64]>, _user_data: &mut ()) -> f64 {
0.25 * x[0].powi(2) + x[1].powi(2) - 1.
}
fn heq(x: &[f64], _grad: Option<&mut [f64]>, _user_data: &mut ()) -> f64 {
x[0] - 2.0 * x[1] + 1.
}
let mut opt = Nlopt::new(Algorithm::Auglag, 2, objfn, Target::Minimize, ());
opt.add_inequality_constraint(hin, (), 1e-6).unwrap();
opt.add_equality_constraint(heq, (), 1e-6).unwrap();
opt.set_xtol_rel(1e-6).unwrap();
assert_eq!(
opt.get_upper_bounds().unwrap(),
&[f64::INFINITY, f64::INFINITY]
);
assert_eq!(
opt.get_lower_bounds().unwrap(),
&[f64::NEG_INFINITY, f64::NEG_INFINITY]
);
let mut local_opt = opt.get_local_optimizer(Algorithm::Cobyla);
local_opt.set_xtol_rel(1e-6).unwrap();
opt.set_local_optimizer(local_opt).unwrap();
let mut input = vec![1., 1.];
let (_s, v) = opt.optimize(&mut input).unwrap();
assert_abs_diff_eq!(v, 1.3934640682303436, epsilon = 1e-6);
let expected = vec![0.8228760595426139, 0.9114376093794901];
assert_abs_diff_eq!(expected.as_slice(), input.as_slice(), epsilon = 1e-6);
}
#[test]
fn test_auglag_mconstraint() {
fn objfn(x: &[f64], _grad: Option<&mut [f64]>, _user_data: &mut ()) -> f64 {
(x[0] - 2.0).powi(2) + (x[1] - 1.0).powi(2)
}
fn hin(x: &[f64]) -> f64 {
0.25 * x[0].powi(2) + x[1].powi(2) - 1.
}
fn heq(x: &[f64]) -> f64 {
x[0] - 2.0 * x[1] + 1.
}
fn m_ineq_constraint(result: &mut [f64], x: &[f64]) {
result[0] = hin(x);
result[1] = hin(x) - 1.0;
result[2] = hin(x) - 2.0;
}
fn m_eq_constraint(result: &mut [f64], x: &[f64]) {
result[0] = heq(x);
}
let mut opt = Nlopt::new(Algorithm::Auglag, 2, objfn, Target::Minimize, ());
opt.add_inequality_mconstraint(
3,
|r: &mut [f64], x: &[f64], _: Option<&mut [f64]>, _: &mut ()| m_ineq_constraint(r, x),
(),
&[1e-6; 3],
)
.unwrap();
opt.add_equality_mconstraint(
1,
|r: &mut [f64], x: &[f64], _: Option<&mut [f64]>, _: &mut ()| m_eq_constraint(r, x),
(),
&[1e-6; 1],
)
.unwrap();
opt.set_xtol_rel(1e-6).unwrap();
let mut local_opt = opt.get_local_optimizer(Algorithm::Bobyqa);
assert_eq!(local_opt.get_xtol_rel().unwrap(), 0.0);
local_opt.set_xtol_rel(1e-6).unwrap();
assert_eq!(local_opt.get_xtol_rel().unwrap(), 1e-6);
opt.set_local_optimizer(local_opt).unwrap();
let mut input = vec![1., 1.];
let (_s, v) = opt.optimize(&mut input).unwrap();
assert_eq!(v, 1.3934648637383988); }
#[test]
fn test_cobyla_with_no_memory_leaks() {
use std::cell::Cell;
use std::rc::Rc;
fn objfn(x: &[f64], _grad: Option<&mut [f64]>, call_ct: &mut Rc<Cell<u32>>) -> f64 {
let v: u32 = call_ct.get();
call_ct.set(v + 1);
let x = x[0];
(x - 3.0) * (x - 3.0) + 7.0 }
let user_data = Rc::new(Cell::new(0));
let mut nlopt = Nlopt::new(
Algorithm::Cobyla,
1,
objfn,
Target::Minimize,
user_data.clone(),
);
assert_eq!(nlopt.get_xtol_abs().unwrap(), &[0.0]);
nlopt.set_xtol_abs1(1e-15).unwrap();
assert_eq!(nlopt.get_xtol_abs().unwrap(), &[1e-15]);
let mut input = [0.0];
let (_s, v) = nlopt.optimize(&mut input).unwrap();
assert_eq!(input[0], 3.0);
assert_eq!(v, 7.0);
assert_eq!(Rc::strong_count(&user_data), 2);
drop(nlopt);
assert_eq!(Rc::strong_count(&user_data), 1);
}
#[test]
fn test_neldermead_recover_user_data() {
struct WrapU32(u32);
impl Drop for WrapU32 {
fn drop(&mut self) {
panic!("undroppable")
}
}
fn objfn(x: &[f64], _grad: Option<&mut [f64]>, call_ct: &mut WrapU32) -> f64 {
call_ct.0 += 1;
let x = x[0];
(x - 5.0) * (x - 5.0) + 9.0 }
let mut nlopt = Nlopt::new(
Algorithm::Neldermead,
1,
objfn,
Target::Minimize,
WrapU32(0),
);
nlopt.set_xtol_abs1(1e-15).unwrap();
let mut input = [0.0];
let (_s, v) = nlopt.optimize(&mut input).unwrap();
assert_eq!(input[0], 4.999999970199497);
assert_eq!(v, 9.0);
let call_ct = nlopt.recover_user_data();
assert_eq!(call_ct.0, 101);
std::mem::forget(call_ct);
}
}