#![doc = include_str!("../README.md")]
use nlopt_cobyla::nlopt_constraint;
mod nlopt_cobyla;
pub use crate::nlopt_cobyla::Func;
use crate::nlopt_cobyla::{
NLoptConstraintCfg,
NLoptFunctionCfg,
cobyla_minimize,
nlopt_constraint_raw_callback, nlopt_function_raw_callback,
nlopt_stopping,
};
use std::os::raw::c_void;
#[derive(Debug, Clone, Copy)]
pub enum FailStatus {
Failure,
InvalidArgs,
OutOfMemory,
RoundoffLimited,
ForcedStop,
UnexpectedError,
}
#[derive(Debug, Clone, Copy)]
pub enum SuccessStatus {
Success,
StopValReached,
FtolReached,
XtolReached,
MaxEvalReached,
MaxTimeReached,
}
type FailOutcome = (FailStatus, Vec<f64>, f64);
type SuccessOutcome = (SuccessStatus, Vec<f64>, f64);
#[derive(Debug, Clone, Default)]
pub struct StopTols {
pub ftol_rel: f64,
pub ftol_abs: f64,
pub xtol_rel: f64,
pub xtol_abs: Vec<f64>,
}
pub enum RhoBeg {
All(f64),
Set(Vec<f64>),
}
#[allow(clippy::useless_conversion)]
#[allow(clippy::too_many_arguments)]
pub fn minimize<F: Func<U>, G: Func<U>, U: Clone>(
func: F,
xinit: &[f64],
bounds: &[(f64, f64)],
cons: &[G],
args: U,
maxeval: usize,
rhobeg: RhoBeg,
stop_tol: Option<StopTols>,
) -> Result<SuccessOutcome, FailOutcome> {
let fn_cfg = Box::new(NLoptFunctionCfg {
objective_fn: func,
user_data: args.clone(),
});
let fn_cfg_ptr = Box::into_raw(fn_cfg) as *mut c_void;
let mut cstr_tol = 0.0;
let mut cstr_cfg = cons
.iter()
.map(|c| {
let c_cfg = Box::new(NLoptConstraintCfg {
constraint_fn: c as &dyn Func<U>,
user_data: args.clone(),
});
let c_cfg_ptr = Box::into_raw(c_cfg) as *mut c_void;
nlopt_constraint {
m: 1,
f: Some(nlopt_constraint_raw_callback::<F, U>),
pre: None,
mf: None,
f_data: c_cfg_ptr,
tol: &mut cstr_tol,
}
})
.collect::<Vec<_>>();
let mut x = vec![0.; xinit.len()];
x.copy_from_slice(xinit);
let n = x.len() as u32;
let m = cons.len() as u32;
if bounds.len() != x.len() {
panic!(
"{}",
format!(
"Minimize Error: Bounds and x should have same size! Got {} for bounds and {} for x.",
bounds.len(),
x.len()
)
)
}
let lbs: Vec<f64> = bounds.iter().map(|b| b.0).collect();
let ubs: Vec<f64> = bounds.iter().map(|b| b.1).collect();
let x_weights = vec![0.; n as usize];
let mut dx = match rhobeg {
RhoBeg::All(val) => vec![val; n as usize],
RhoBeg::Set(val) => {
if val.len() != n as usize {
panic!(
"{}",
format!(
"Minimize Error: xtol_abs should have x dim size ({}), got {}",
n,
val.len()
)
)
} else {
val
}
}
};
let mut minf = f64::INFINITY;
let mut nevals_p = 0;
let mut force_stop = 0;
let stop_tol = stop_tol.unwrap_or_default();
let xtol_abs = if stop_tol.xtol_abs.is_empty() {
std::ptr::null()
} else if stop_tol.xtol_abs.len() != n as usize {
panic!(
"{}",
format!(
"Minimize Error: xtol_abs should have x dim size ({}), got {}",
n,
stop_tol.xtol_abs.len()
)
);
} else {
stop_tol.xtol_abs.as_ptr()
};
let mut stop = nlopt_stopping {
n,
minf_max: -f64::INFINITY,
ftol_rel: stop_tol.ftol_rel,
ftol_abs: stop_tol.ftol_abs,
xtol_rel: stop_tol.xtol_rel,
xtol_abs,
x_weights: x_weights.as_ptr(),
nevals_p: &mut nevals_p,
maxeval: maxeval as i32,
maxtime: 0.0,
start: 0.0,
force_stop: &mut force_stop,
stop_msg: "".to_string(),
};
let status = unsafe {
cobyla_minimize::<U>(
n.into(),
Some(nlopt_function_raw_callback::<F, U>),
fn_cfg_ptr,
m.into(),
cstr_cfg.as_mut_ptr(),
0,
std::ptr::null_mut(),
lbs.as_ptr(),
ubs.as_ptr(),
x.as_mut_ptr(),
&mut minf,
&mut stop,
dx.as_mut_ptr(),
)
};
unsafe {
let _ = Box::from_raw(fn_cfg_ptr as *mut NLoptFunctionCfg<F, U>);
};
match status {
-1 => Err((FailStatus::Failure, x, minf)),
-2 => Err((FailStatus::InvalidArgs, x, minf)),
-3 => Err((FailStatus::OutOfMemory, x, minf)),
-4 => Err((FailStatus::RoundoffLimited, x, minf)),
-5 => Err((FailStatus::ForcedStop, x, minf)),
1 => Ok((SuccessStatus::Success, x, minf)),
2 => Ok((SuccessStatus::StopValReached, x, minf)),
3 => Ok((SuccessStatus::FtolReached, x, minf)),
4 => Ok((SuccessStatus::XtolReached, x, minf)),
5 => Ok((SuccessStatus::MaxEvalReached, x, minf)),
6 => Ok((SuccessStatus::MaxTimeReached, x, minf)),
_ => Err((FailStatus::UnexpectedError, x, minf)),
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
use crate::nlopt_cobyla::cobyla_minimize;
use crate::nlopt_cobyla::nlopt_stopping;
fn raw_paraboloid(
_n: libc::c_uint,
x: *const libc::c_double,
_gradient: *mut libc::c_double,
_func_data: *mut libc::c_void,
) -> libc::c_double {
unsafe {
let r1 = *x.offset(0) + 1.0;
let r2 = *x.offset(1);
10.0 * (r1 * r1) + (r2 * r2) as libc::c_double
}
}
#[test]
fn test_cobyla_minimize() {
let mut x = vec![1., 1.];
let mut lb = vec![-10.0, -10.0];
let mut ub = vec![10.0, 10.0];
let mut dx = vec![0.5, 0.5];
let mut minf = f64::INFINITY;
let mut nevals_p = 0;
let mut force_stop = 0;
let mut stop = nlopt_stopping {
n: 2,
minf_max: -f64::INFINITY,
ftol_rel: 0.0,
ftol_abs: 0.0,
xtol_rel: 0.0,
xtol_abs: std::ptr::null(),
x_weights: std::ptr::null(),
nevals_p: &mut nevals_p,
maxeval: 1000,
maxtime: 0.0,
start: 0.0,
force_stop: &mut force_stop,
stop_msg: "".to_string(),
};
let res = unsafe {
cobyla_minimize::<()>(
2,
Some(raw_paraboloid),
std::ptr::null_mut(),
0,
std::ptr::null_mut(),
0,
std::ptr::null_mut(),
lb.as_mut_ptr(),
ub.as_mut_ptr(),
x.as_mut_ptr(),
&mut minf,
&mut stop,
dx.as_mut_ptr(),
)
};
println!("status = {:?}", res);
println!("x = {:?}", x);
assert_abs_diff_eq!(x.as_slice(), [-1., 0.].as_slice(), epsilon = 1e-4);
}
fn paraboloid(x: &[f64], _data: &mut ()) -> f64 {
10. * (x[0] + 1.).powf(2.) + x[1].powf(2.)
}
#[test]
fn test_paraboloid() {
let xinit = vec![1., 1.];
let mut cons: Vec<&dyn Func<()>> = vec![];
let cstr1 = |x: &[f64], _user_data: &mut ()| x[0];
cons.push(&cstr1 as &dyn Func<()>);
match minimize(
paraboloid,
&xinit,
&[(-10., 10.), (-10., 10.)],
&cons,
(),
200,
RhoBeg::All(0.5),
None,
) {
Ok((_, x, _)) => {
let exp = [0., 0.];
for (act, exp) in x.iter().zip(exp.iter()) {
assert_abs_diff_eq!(act, exp, epsilon = 1e-3);
}
}
Err((status, _, _)) => {
panic!("{}", format!("Error status : {:?}", status));
}
}
}
fn fletcher9115(x: &[f64], _user_data: &mut ()) -> f64 {
-x[0] - x[1]
}
fn cstr1(x: &[f64], _user_data: &mut ()) -> f64 {
x[1] - x[0] * x[0]
}
fn cstr2(x: &[f64], _user_data: &mut ()) -> f64 {
1. - x[0] * x[0] - x[1] * x[1]
}
#[test]
fn test_fletcher9115() {
let xinit = vec![1., 1.];
let cons = vec![&cstr1 as &dyn Func<()>, &cstr2 as &dyn Func<()>];
let stop_tol = StopTols {
ftol_rel: 1e-4,
xtol_rel: 1e-4,
..StopTols::default()
};
match minimize(
fletcher9115,
&xinit,
&[(-10., 10.), (-10., 10.)],
&cons,
(),
200,
RhoBeg::All(0.5),
Some(stop_tol),
) {
Ok((_, x, _)) => {
let sqrt_0_5: f64 = 0.5_f64.sqrt();
let exp = [sqrt_0_5, sqrt_0_5];
for (act, exp) in x.iter().zip(exp.iter()) {
assert_abs_diff_eq!(act, exp, epsilon = 1e-3);
}
}
Err((status, _, _)) => {
println!("Error status : {:?}", status);
panic!("Test fail");
}
}
}
fn xsinx(x: &[f64], _user_data: &mut ()) -> f64 {
(x[0] - 3.5) * f64::sin((x[0] - 3.5) / std::f64::consts::PI)
}
#[test]
fn test_xsinx() {
let xinit = vec![10.];
let mut cons: Vec<&dyn Func<()>> = vec![];
let cstr1 = |x: &[f64], _user_data: &mut ()| 17. - x[0];
cons.push(&cstr1 as &dyn Func<()>);
match minimize(
xsinx,
&xinit,
&[(0., 25.)],
&cons,
(),
200,
RhoBeg::All(0.5),
None,
) {
Ok((_, x, _)) => {
let exp = [17.];
for (act, exp) in x.iter().zip(exp.iter()) {
assert_abs_diff_eq!(act, exp, epsilon = 1e-2);
}
}
Err((status, _, _)) => {
panic!("{}", format!("Error status : {:?}", status));
}
}
}
}