#![warn(missing_docs)]
use ipopt_sys as ffi;
use crate::ffi::{CNLP_Bool as Bool, CNLP_Int as Int};
pub use crate::ffi::{
CNLP_Index as Index, CNLP_Number as Number, };
use std::ffi::CString;
use std::fmt::{Debug, Display, Formatter};
use std::slice;
pub trait BasicProblem {
fn indexing_style(&self) -> IndexingStyle {
IndexingStyle::CStyle
}
fn num_variables(&self) -> usize;
fn bounds(&self, x_l: &mut [Number], x_u: &mut [Number]) -> bool;
fn initial_point(&self, x: &mut [Number]) -> bool;
fn initial_bounds_multipliers(&self, z_l: &mut [Number], z_u: &mut [Number]) -> bool {
for (l, u) in z_l.iter_mut().zip(z_u.iter_mut()) {
*l = 0.0;
*u = 0.0;
}
true
}
fn objective(&self, x: &[Number], new_x: bool, obj: &mut Number) -> bool;
fn objective_grad(&self, x: &[Number], new_x: bool, grad_f: &mut [Number]) -> bool;
fn variable_scaling(&self, _x_scaling: &mut [Number]) -> bool {
false
}
fn objective_scaling(&self) -> f64 {
1.0
}
}
pub trait NewtonProblem: BasicProblem {
fn num_hessian_non_zeros(&self) -> usize;
fn hessian_indices(&self, rows: &mut [Index], cols: &mut [Index]) -> bool;
fn hessian_values(&self, x: &[Number], vals: &mut [Number]) -> bool;
}
pub trait ConstrainedProblem: BasicProblem {
fn num_constraints(&self) -> usize;
fn num_constraint_jacobian_non_zeros(&self) -> usize;
fn constraint(&self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool;
fn constraint_bounds(&self, g_l: &mut [Number], g_u: &mut [Number]) -> bool;
fn initial_constraint_multipliers(&self, lambda: &mut [Number]) -> bool {
for l in lambda.iter_mut() {
*l = 0.0;
}
true
}
fn constraint_jacobian_indices(&self, rows: &mut [Index], cols: &mut [Index]) -> bool;
fn constraint_jacobian_values(&self, x: &[Number], new_x: bool, vals: &mut [Number]) -> bool;
fn num_hessian_non_zeros(&self) -> usize;
fn hessian_indices(&self, rows: &mut [Index], cols: &mut [Index]) -> bool;
fn hessian_values(
&self,
x: &[Number],
new_x: bool,
obj_factor: Number,
lambda: &[Number],
vals: &mut [Number],
) -> bool;
fn constraint_scaling(&self, _g_scaling: &mut [Number]) -> bool {
false
}
}
#[derive(Clone, Copy)]
pub enum IpoptOption<'a> {
Num(f64),
Str(&'a str),
Int(i32),
}
impl<'a> From<f64> for IpoptOption<'a> {
fn from(opt: f64) -> Self {
IpoptOption::Num(opt)
}
}
impl<'a> From<&'a str> for IpoptOption<'a> {
fn from(opt: &'a str) -> Self {
IpoptOption::Str(opt)
}
}
impl<'a> From<i32> for IpoptOption<'a> {
fn from(opt: i32) -> Self {
IpoptOption::Int(opt)
}
}
fn reconstruct_dual_slice<'a>(g: *const Number, num_dual_vars: usize) -> &'a [Number] {
if num_dual_vars == 0 {
&[]
} else {
unsafe { slice::from_raw_parts(g, num_dual_vars) }
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Solution<'a> {
pub primal_variables: &'a [Number],
pub lower_bound_multipliers: &'a [Number],
pub upper_bound_multipliers: &'a [Number],
pub constraint_multipliers: &'a [Number],
}
impl<'a> Solution<'a> {
fn from_raw(
data: ffi::CNLP_SolverData,
num_primal_vars: usize,
num_dual_vars: usize,
) -> Solution<'a> {
dbg!(num_dual_vars);
dbg!(data.mult_g);
Solution {
primal_variables: unsafe { slice::from_raw_parts(data.x, num_primal_vars) },
lower_bound_multipliers: unsafe {
slice::from_raw_parts(data.mult_x_L, num_primal_vars)
},
upper_bound_multipliers: unsafe {
slice::from_raw_parts(data.mult_x_U, num_primal_vars)
},
constraint_multipliers: reconstruct_dual_slice(data.mult_g, num_dual_vars),
}
}
}
#[derive(Debug, PartialEq)]
pub struct SolverDataMut<'a, P: 'a> {
pub problem: &'a mut P,
pub solution: Solution<'a>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct SolverData<'a, P: 'a> {
pub problem: &'a P,
pub solution: Solution<'a>,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum AlgorithmMode {
Regular,
RestorationPhase,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct IntermediateCallbackData {
pub alg_mod: AlgorithmMode,
pub iter_count: Index,
pub obj_value: Number,
pub inf_pr: Number,
pub inf_du: Number,
pub mu: Number,
pub d_norm: Number,
pub regularization_size: Number,
pub alpha_du: Number,
pub alpha_pr: Number,
pub ls_trials: Index,
}
#[derive(Debug, PartialEq)]
pub struct SolveResult<'a, P: 'a> {
pub solver_data: SolverDataMut<'a, P>,
pub constraint_values: &'a [Number],
pub objective_value: Number,
pub status: SolveStatus,
}
pub type IntermediateCallback<P> = fn(&mut P, IntermediateCallbackData) -> bool;
pub struct Ipopt<P: BasicProblem> {
nlp_internal: ffi::CNLP_ProblemPtr,
nlp_interface: P,
intermediate_callback: Option<IntermediateCallback<P>>,
num_primal_variables: usize,
num_dual_variables: usize,
}
impl<P: BasicProblem + Debug> Debug for Ipopt<P> {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
write!(f,
"Ipopt {{ nlp_internal: {:?}, nlp_interface: {:?}, intermediate_callback: {:?}, num_primal_variables: {:?}, num_dual_variables: {:?} }}",
self.nlp_internal,
self.nlp_interface,
if self.intermediate_callback.is_some() { "Some" } else { "None" },
self.num_primal_variables,
self.num_dual_variables)
}
}
unsafe impl<P: BasicProblem> Send for Ipopt<P> {}
impl<P: BasicProblem> Ipopt<P> {
fn new_impl(
nlp_internal: ffi::CNLP_ProblemPtr,
nlp: P,
num_vars: usize,
num_constraints: usize,
) -> Ipopt<P> {
let mut ipopt = Ipopt {
nlp_internal,
nlp_interface: nlp,
intermediate_callback: None,
num_primal_variables: num_vars,
num_dual_variables: num_constraints,
};
unsafe {
ffi::cnlp_init_solution(
ipopt.nlp_internal,
&mut ipopt as *mut Ipopt<P> as *mut std::ffi::c_void,
);
}
ipopt
}
pub fn new_unconstrained(nlp: P) -> Result<Self, CreateError> {
let num_vars = nlp.num_variables();
if num_vars < 1 {
return Err(CreateError::NoOptimizationVariablesSpecified);
}
let mut nlp_internal: ffi::CNLP_ProblemPtr = ::std::ptr::null_mut();
let create_error = CreateProblemStatus::new(unsafe {
ffi::cnlp_create_problem(
&mut nlp_internal as *mut ffi::CNLP_ProblemPtr,
nlp.indexing_style() as Index,
Some(Self::basic_sizes),
Some(Self::basic_init),
Some(Self::variable_only_bounds),
Some(Self::eval_f),
Some(Self::eval_g_none),
Some(Self::eval_grad_f),
Some(Self::eval_jac_g_none),
Some(Self::eval_h_none),
Some(Self::basic_scaling),
)
});
if CreateProblemStatus::Success != create_error {
return Err(create_error.into());
}
assert!(Self::set_ipopt_option(
nlp_internal,
"hessian_approximation",
"limited-memory"
));
Ok(Self::new_impl(nlp_internal, nlp, num_vars, 0))
}
fn set_ipopt_option<'a, O>(nlp: ffi::CNLP_ProblemPtr, name: &str, option: O) -> bool
where
O: Into<IpoptOption<'a>>,
{
let result = unsafe {
let name_cstr = CString::new(name).unwrap();
match option.into() {
IpoptOption::Num(opt) => {
ffi::cnlp_add_num_option(nlp, name_cstr.as_ptr(), opt as Number)
}
IpoptOption::Str(opt) => {
let opt_cstr = CString::new(opt).unwrap();
ffi::cnlp_add_str_option(nlp, name_cstr.as_ptr(), opt_cstr.as_ptr())
}
IpoptOption::Int(opt) => {
ffi::cnlp_add_int_option(nlp, name_cstr.as_ptr(), opt as Int)
}
}
};
result != 0 }
pub fn set_option<'a, O>(&mut self, name: &str, option: O) -> Option<&mut Self>
where
O: Into<IpoptOption<'a>>,
{
let success = Self::set_ipopt_option(self.nlp_internal, name, option);
if success {
Some(self)
} else {
None
}
}
pub fn set_intermediate_callback(&mut self, mb_cb: Option<IntermediateCallback<P>>)
where
P: BasicProblem,
{
self.intermediate_callback = mb_cb;
unsafe {
if mb_cb.is_some() {
ffi::cnlp_set_intermediate_callback(self.nlp_internal, Some(Self::intermediate_cb));
} else {
ffi::cnlp_set_intermediate_callback(self.nlp_internal, None);
}
}
}
pub fn solve(&mut self) -> SolveResult<P> {
let res = {
let udata_ptr = self as *mut Ipopt<P>;
unsafe { ffi::cnlp_solve(self.nlp_internal, udata_ptr as ffi::CNLP_UserDataPtr) }
};
let Ipopt {
nlp_interface: ref mut problem,
num_primal_variables,
num_dual_variables,
..
} = *self;
SolveResult {
solver_data: SolverDataMut {
problem,
solution: Solution::from_raw(res.data, num_primal_variables, num_dual_variables),
},
constraint_values: reconstruct_dual_slice(res.g, num_dual_variables),
objective_value: res.obj_val,
status: SolveStatus::new(res.status),
}
}
#[allow(non_snake_case)]
pub fn solver_data_mut(&mut self) -> SolverDataMut<P> {
let Ipopt {
nlp_interface: ref mut problem,
nlp_internal,
num_primal_variables,
num_dual_variables,
..
} = *self;
let data = unsafe { ffi::cnlp_get_solver_data(nlp_internal) };
SolverDataMut {
problem,
solution: Solution::from_raw(data, num_primal_variables, num_dual_variables),
}
}
#[allow(non_snake_case)]
pub fn solver_data(&self) -> SolverData<P> {
let Ipopt {
nlp_interface: ref problem,
nlp_internal,
num_primal_variables,
num_dual_variables,
..
} = *self;
let data = unsafe { ffi::cnlp_get_solver_data(nlp_internal) };
SolverData {
problem,
solution: Solution::from_raw(data, num_primal_variables, num_dual_variables),
}
}
unsafe extern "C" fn basic_init(
n: Index,
init_x: Bool,
x: *mut Number,
init_z: Bool,
z_l: *mut Number,
z_u: *mut Number,
m: Index,
_init_lambda: Bool,
_lambda: *mut Number,
user_data: ffi::CNLP_UserDataPtr,
) -> Bool {
assert_eq!(m, 0);
let nlp = &mut (*(user_data as *mut Ipopt<P>)).nlp_interface;
if init_x != 0 {
let x = slice::from_raw_parts_mut(x, n as usize);
if !nlp.initial_point(x) {
for i in 0..n as usize {
x[i] = 0.0;
} }
}
if init_z != 0 {
let z_l = slice::from_raw_parts_mut(z_l, n as usize);
let z_u = slice::from_raw_parts_mut(z_u, n as usize);
if !nlp.initial_bounds_multipliers(z_l, z_u) {
for i in 0..n as usize {
z_l[i] = 0.0;
z_u[i] = 0.0;
} }
}
true as Bool
}
unsafe extern "C" fn basic_sizes(
n: *mut Index,
m: *mut Index,
nnz_jac_g: *mut Index,
nnz_h_lag: *mut Index,
user_data: ffi::CNLP_UserDataPtr,
) -> Bool {
let ipopt = &mut (*(user_data as *mut Ipopt<P>));
ipopt.num_primal_variables = ipopt.nlp_interface.num_variables();
ipopt.num_dual_variables = 0;
*n = ipopt.num_primal_variables as Index;
*m = ipopt.num_dual_variables as Index;
*nnz_jac_g = 0; *nnz_h_lag = 0; true as Bool
}
unsafe extern "C" fn variable_only_bounds(
n: Index,
x_l: *mut Number,
x_u: *mut Number,
m: Index,
_g_l: *mut Number,
_g_u: *mut Number,
user_data: ffi::CNLP_UserDataPtr,
) -> Bool {
assert_eq!(m, 0);
let nlp = &mut (*(user_data as *mut Ipopt<P>)).nlp_interface;
nlp.bounds(
slice::from_raw_parts_mut(x_l, n as usize),
slice::from_raw_parts_mut(x_u, n as usize),
) as Bool
}
unsafe extern "C" fn eval_f(
n: Index,
x: *const Number,
new_x: Bool,
obj_value: *mut Number,
user_data: ffi::CNLP_UserDataPtr,
) -> Bool {
let nlp = &mut (*(user_data as *mut Ipopt<P>)).nlp_interface;
nlp.objective(
slice::from_raw_parts(x, n as usize),
new_x != 0,
&mut *obj_value,
) as Bool
}
unsafe extern "C" fn eval_grad_f(
n: Index,
x: *const Number,
new_x: Bool,
grad_f: *mut Number,
user_data: ffi::CNLP_UserDataPtr,
) -> Bool {
let nlp = &mut (*(user_data as *mut Ipopt<P>)).nlp_interface;
nlp.objective_grad(
slice::from_raw_parts(x, n as usize),
new_x != 0,
slice::from_raw_parts_mut(grad_f, n as usize),
) as Bool
}
unsafe extern "C" fn eval_g_none(
_n: Index,
_x: *const Number,
_new_x: Bool,
_m: Index,
_g: *mut Number,
_user_data: ffi::CNLP_UserDataPtr,
) -> Bool {
true as Bool
}
unsafe extern "C" fn eval_jac_g_none(
_n: Index,
_x: *const Number,
_new_x: Bool,
_m: Index,
_nele_jac: Index,
_irow: *mut Index,
_jcol: *mut Index,
_values: *mut Number,
_user_data: ffi::CNLP_UserDataPtr,
) -> Bool {
true as Bool
}
unsafe extern "C" fn eval_h_none(
_n: Index,
_x: *const Number,
_new_x: Bool,
_obj_factor: Number,
_m: Index,
_lambda: *const Number,
_new_lambda: Bool,
_nele_hess: Index,
_irow: *mut Index,
_jcol: *mut Index,
_values: *mut Number,
_user_data: ffi::CNLP_UserDataPtr,
) -> Bool {
false as Bool
}
unsafe extern "C" fn basic_scaling(
obj_scaling: *mut Number,
use_x_scaling: *mut Bool,
n: Index,
x_scaling: *mut Number,
use_g_scaling: *mut Bool,
m: Index,
_g_scaling: *mut Number,
user_data: ffi::CNLP_UserDataPtr,
) -> Bool {
assert_eq!(m, 0);
let nlp = &mut (*(user_data as *mut Ipopt<P>)).nlp_interface;
*obj_scaling = nlp.objective_scaling();
*use_x_scaling =
nlp.variable_scaling(slice::from_raw_parts_mut(x_scaling, n as usize)) as Bool;
*use_g_scaling = false as Bool;
true as Bool
}
unsafe extern "C" fn intermediate_cb(
alg_mod: ffi::CNLP_AlgorithmMode,
iter_count: Index,
obj_value: Number,
inf_pr: Number,
inf_du: Number,
mu: Number,
d_norm: Number,
regularization_size: Number,
alpha_du: Number,
alpha_pr: Number,
ls_trials: Index,
user_data: ffi::CNLP_UserDataPtr,
) -> Bool {
let ip = &mut (*(user_data as *mut Ipopt<P>));
if let Some(callback) = ip.intermediate_callback {
(callback)(
&mut ip.nlp_interface,
IntermediateCallbackData {
alg_mod: match alg_mod {
0 => AlgorithmMode::Regular,
_ => AlgorithmMode::RestorationPhase,
},
iter_count,
obj_value,
inf_pr,
inf_du,
mu,
d_norm,
regularization_size,
alpha_du,
alpha_pr,
ls_trials,
},
) as Bool
} else {
true as Bool
}
}
}
impl<P: NewtonProblem> Ipopt<P> {
pub fn new_newton(nlp: P) -> Result<Self, CreateError> {
let num_vars = nlp.num_variables();
if num_vars < 1 {
return Err(CreateError::NoOptimizationVariablesSpecified);
}
let mut nlp_internal: ffi::CNLP_ProblemPtr = ::std::ptr::null_mut();
let create_error = CreateProblemStatus::new(unsafe {
ffi::cnlp_create_problem(
&mut nlp_internal as *mut ffi::CNLP_ProblemPtr,
nlp.indexing_style() as Index,
Some(Self::newton_sizes),
Some(Self::basic_init),
Some(Self::variable_only_bounds),
Some(Self::eval_f),
Some(Self::eval_g_none),
Some(Self::eval_grad_f),
Some(Self::eval_jac_g_none),
Some(Self::eval_h),
Some(Self::basic_scaling),
)
});
if create_error != CreateProblemStatus::Success {
return Err(create_error.into());
}
Ok(Self::new_impl(nlp_internal, nlp, num_vars, 0))
}
unsafe extern "C" fn newton_sizes(
n: *mut Index,
m: *mut Index,
nnz_jac_g: *mut Index,
nnz_h_lag: *mut Index,
user_data: ffi::CNLP_UserDataPtr,
) -> Bool {
Self::basic_sizes(n, m, nnz_jac_g, nnz_h_lag, user_data);
let nlp = &mut (*(user_data as *mut Ipopt<P>)).nlp_interface;
*nnz_h_lag = nlp.num_hessian_non_zeros() as Index;
true as Bool
}
unsafe extern "C" fn eval_h(
n: Index,
x: *const Number,
_new_x: Bool,
obj_factor: Number,
_m: Index,
_lambda: *const Number,
_new_lambda: Bool,
nele_hess: Index,
irow: *mut Index,
jcol: *mut Index,
values: *mut Number,
user_data: ffi::CNLP_UserDataPtr,
) -> Bool {
let nlp = &mut (*(user_data as *mut Ipopt<P>)).nlp_interface;
if values.is_null() {
nlp.hessian_indices(
slice::from_raw_parts_mut(irow, nele_hess as usize),
slice::from_raw_parts_mut(jcol, nele_hess as usize),
) as Bool
} else {
let result = nlp.hessian_values(
slice::from_raw_parts(x, n as usize),
slice::from_raw_parts_mut(values, nele_hess as usize),
) as Bool;
let start_idx = nlp.indexing_style() as isize;
for i in start_idx..nele_hess as isize {
*values.offset(i) *= obj_factor;
}
result
}
}
}
impl<P: ConstrainedProblem> Ipopt<P> {
pub fn new(nlp: P) -> Result<Self, CreateError> {
let num_vars = nlp.num_variables();
if num_vars < 1 {
return Err(CreateError::NoOptimizationVariablesSpecified);
}
let num_constraints = nlp.num_constraints();
let num_constraint_jac_nnz = nlp.num_constraint_jacobian_non_zeros();
if (num_constraints > 0 && num_constraint_jac_nnz == 0)
|| (num_constraints == 0 && num_constraint_jac_nnz > 0)
{
return Err(CreateError::InvalidConstraintJacobian {
num_constraints,
num_constraint_jac_nnz,
});
}
let mut nlp_internal: ffi::CNLP_ProblemPtr = ::std::ptr::null_mut();
let create_error = CreateProblemStatus::new(unsafe {
ffi::cnlp_create_problem(
&mut nlp_internal as *mut ffi::CNLP_ProblemPtr,
nlp.indexing_style() as Index,
Some(Self::sizes),
Some(Self::init),
Some(Self::bounds),
Some(Self::eval_f),
Some(Self::eval_g),
Some(Self::eval_grad_f),
Some(Self::eval_jac_g),
Some(Self::eval_full_h),
Some(Self::scaling),
)
});
if create_error != CreateProblemStatus::Success {
return Err(create_error.into());
}
Ok(Self::new_impl(nlp_internal, nlp, num_vars, num_constraints))
}
unsafe extern "C" fn init(
n: Index,
init_x: Bool,
x: *mut Number,
init_z: Bool,
z_l: *mut Number,
z_u: *mut Number,
m: Index,
init_lambda: Bool,
lambda: *mut Number,
user_data: ffi::CNLP_UserDataPtr,
) -> Bool {
let nlp = &mut (*(user_data as *mut Ipopt<P>)).nlp_interface;
if init_x != 0 {
let x = slice::from_raw_parts_mut(x, n as usize);
if !nlp.initial_point(x) {
for i in 0..n as usize {
x[i] = 0.0;
} }
}
if init_z != 0 {
let z_l = slice::from_raw_parts_mut(z_l, n as usize);
let z_u = slice::from_raw_parts_mut(z_u, n as usize);
if !nlp.initial_bounds_multipliers(z_l, z_u) {
for i in 0..n as usize {
z_l[i] = 0.0;
z_u[i] = 0.0;
} }
}
if init_lambda != 0 {
let lambda = slice::from_raw_parts_mut(lambda, m as usize);
if !nlp.initial_constraint_multipliers(lambda) {
for i in 0..m as usize {
lambda[i] = 0.0;
} }
}
true as Bool
}
unsafe extern "C" fn sizes(
n: *mut Index,
m: *mut Index,
nnz_jac_g: *mut Index,
nnz_h_lag: *mut Index,
user_data: ffi::CNLP_UserDataPtr,
) -> Bool {
let ipopt = &mut (*(user_data as *mut Ipopt<P>));
ipopt.num_primal_variables = ipopt.nlp_interface.num_variables();
ipopt.num_dual_variables = ipopt.nlp_interface.num_constraints();
*n = ipopt.num_primal_variables as Index;
*m = ipopt.num_dual_variables as Index;
*nnz_jac_g = ipopt.nlp_interface.num_constraint_jacobian_non_zeros() as Index;
*nnz_h_lag = ipopt.nlp_interface.num_hessian_non_zeros() as Index;
true as Bool
}
unsafe extern "C" fn bounds(
n: Index,
x_l: *mut Number,
x_u: *mut Number,
m: Index,
g_l: *mut Number,
g_u: *mut Number,
user_data: ffi::CNLP_UserDataPtr,
) -> Bool {
let nlp = &mut (*(user_data as *mut Ipopt<P>)).nlp_interface;
(nlp.bounds(
slice::from_raw_parts_mut(x_l, n as usize),
slice::from_raw_parts_mut(x_u, n as usize),
) && nlp.constraint_bounds(
slice::from_raw_parts_mut(g_l, m as usize),
slice::from_raw_parts_mut(g_u, m as usize),
)) as Bool
}
unsafe extern "C" fn eval_g(
n: Index,
x: *const Number,
new_x: Bool,
m: Index,
g: *mut Number,
user_data: ffi::CNLP_UserDataPtr,
) -> Bool {
let nlp = &mut (*(user_data as *mut Ipopt<P>)).nlp_interface;
nlp.constraint(
slice::from_raw_parts(x, n as usize),
new_x != 0,
slice::from_raw_parts_mut(g, m as usize),
) as Bool
}
unsafe extern "C" fn eval_jac_g(
n: Index,
x: *const Number,
new_x: Bool,
_m: Index,
nele_jac: Index,
irow: *mut Index,
jcol: *mut Index,
values: *mut Number,
user_data: ffi::CNLP_UserDataPtr,
) -> Bool {
let nlp = &mut (*(user_data as *mut Ipopt<P>)).nlp_interface;
if values.is_null() {
nlp.constraint_jacobian_indices(
slice::from_raw_parts_mut(irow, nele_jac as usize),
slice::from_raw_parts_mut(jcol, nele_jac as usize),
) as Bool
} else {
nlp.constraint_jacobian_values(
slice::from_raw_parts(x, n as usize),
new_x != 0,
slice::from_raw_parts_mut(values, nele_jac as usize),
) as Bool
}
}
unsafe extern "C" fn eval_full_h(
n: Index,
x: *const Number,
new_x: Bool,
obj_factor: Number,
m: Index,
lambda: *const Number,
_new_lambda: Bool,
nele_hess: Index,
irow: *mut Index,
jcol: *mut Index,
values: *mut Number,
user_data: ffi::CNLP_UserDataPtr,
) -> Bool {
let nlp = &mut (*(user_data as *mut Ipopt<P>)).nlp_interface;
if values.is_null() {
nlp.hessian_indices(
slice::from_raw_parts_mut(irow, nele_hess as usize),
slice::from_raw_parts_mut(jcol, nele_hess as usize),
) as Bool
} else {
nlp.hessian_values(
slice::from_raw_parts(x, n as usize),
new_x != 0,
obj_factor,
slice::from_raw_parts(lambda, m as usize),
slice::from_raw_parts_mut(values, nele_hess as usize),
) as Bool
}
}
unsafe extern "C" fn scaling(
obj_scaling: *mut Number,
use_x_scaling: *mut Bool,
n: Index,
x_scaling: *mut Number,
use_g_scaling: *mut Bool,
m: Index,
g_scaling: *mut Number,
user_data: ffi::CNLP_UserDataPtr,
) -> Bool {
let nlp = &mut (*(user_data as *mut Ipopt<P>)).nlp_interface;
*obj_scaling = nlp.objective_scaling();
*use_x_scaling =
nlp.variable_scaling(slice::from_raw_parts_mut(x_scaling, n as usize)) as Bool;
*use_g_scaling =
nlp.constraint_scaling(slice::from_raw_parts_mut(g_scaling, m as usize)) as Bool;
true as Bool
}
}
impl<P: BasicProblem> Drop for Ipopt<P> {
fn drop(&mut self) {
unsafe {
ffi::cnlp_free_problem(self.nlp_internal);
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum IndexingStyle {
CStyle = 0,
FortranStyle = 1,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum SolveStatus {
SolveSucceeded,
SolvedToAcceptableLevel,
FeasiblePointFound,
InfeasibleProblemDetected,
SearchDirectionBecomesTooSmall,
DivergingIterates,
UserRequestedStop,
MaximumIterationsExceeded,
MaximumCpuTimeExceeded,
RestorationFailed,
ErrorInStepComputation,
InvalidOption,
NotEnoughDegreesOfFreedom,
InvalidProblemDefinition,
InvalidNumberDetected,
UnrecoverableException,
NonIpoptExceptionThrown,
InsufficientMemory,
InternalError,
UnknownError,
}
impl Display for SolveStatus {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
match *self {
SolveStatus::SolveSucceeded => write!(f, "
Console Message: `EXIT: Optimal Solution Found.`\n\n\
This message indicates that IPOPT found a (locally) optimal point within the desired \
tolerances."),
SolveStatus::SolvedToAcceptableLevel => write!(f, "
Console Message: `EXIT: Solved To Acceptable Level.`\n\n\
This indicates that the algorithm did not converge to the \"desired\" tolerances, but \
that it was able to obtain a point satisfying the \"acceptable\" tolerance level as \
specified by the `acceptable_*` options. This may happen if the desired tolerances \
are too small for the current problem."),
SolveStatus::FeasiblePointFound => write!(f, "
Console Message: `EXIT: Feasible point for square problem found.`\n\n\
This message is printed if the problem is \"square\" (i.e., it has as many equality \
constraints as free variables) and IPOPT found a feasible point."),
SolveStatus::InfeasibleProblemDetected => write!(f, "
Console Message: `EXIT: Converged to a point of local infeasibility. Problem may be \
infeasible.`\n\n\
The restoration phase converged to a point that is a minimizer for the constraint \
violation (in the l1-norm), but is not feasible for the original problem. This \
indicates that the problem may be infeasible (or at least that the algorithm is stuck \
at a locally infeasible point). The returned point (the minimizer of the constraint \
violation) might help you to find which constraint is causing the problem. If you \
believe that the NLP is feasible, it might help to start the optimization from a \
different point."),
SolveStatus::SearchDirectionBecomesTooSmall => write!(f, "
Console Message: `EXIT: Search Direction is becoming Too Small.`\n\n\
This indicates that IPOPT is calculating very small step sizes and is making very \
little progress. This could happen if the problem has been solved to the best numerical \
accuracy possible given the current scaling."),
SolveStatus::DivergingIterates => write!(f, "
Console Message: `EXIT: Iterates diverging; problem might be unbounded.`\n\n\
This message is printed if the max-norm of the iterates becomes larger than the value \
of the option `diverging_iterates_tol`. This can happen if the problem is unbounded \
below and the iterates are diverging."),
SolveStatus::UserRequestedStop => write!(f, "
Console Message: `EXIT: Stopping optimization at current point as requested by \
user.`\n\n\
This message is printed if the user call-back method intermediate_callback returned \
false."),
SolveStatus::MaximumIterationsExceeded => write!(f, "
Console Message: `EXIT: Maximum Number of Iterations Exceeded.`\n\n\
This indicates that IPOPT has exceeded the maximum number of iterations as specified by \
the option `max_iter`."),
SolveStatus::MaximumCpuTimeExceeded => write!(f, "
Console Message: `EXIT: Maximum CPU time exceeded.`\n\n\
This indicates that IPOPT has exceeded the maximum number of CPU seconds as specified \
by the option `max_cpu_time`."),
SolveStatus::RestorationFailed => write!(f, "
Console Message: `EXIT: Restoration Failed!`\n\n\
This indicates that the restoration phase failed to find a feasible point that was \
acceptable to the filter line search for the original problem. This could happen if the \
problem is highly degenerate, does not satisfy the constraint qualification, or if \
your NLP code provides incorrect derivative information."),
SolveStatus::ErrorInStepComputation => write!(f, "
Console Output: `EXIT: Error in step computation (regularization becomes too large?)!`\n\n\
This messages is printed if IPOPT is unable to compute a search direction, despite \
several attempts to modify the iteration matrix. Usually, the value of the \
regularization parameter then becomes too large. One situation where this can happen is \
when values in the Hessian are invalid (NaN or Inf). You can check whether this is \
true by using the `check_derivatives_for_naninf` option."),
SolveStatus::InvalidOption => write!(f, "
Console Message: (details about the particular error will be output to the console)\n\n\
This indicates that there was some problem specifying the options. See the specific \
message for details."),
SolveStatus::NotEnoughDegreesOfFreedom => write!(f, "
Console Message: `EXIT: Problem has too few degrees of freedom.`\n\n\
This indicates that your problem, as specified, has too few degrees of freedom. This \
can happen if you have too many equality constraints, or if you fix too many variables \
(IPOPT removes fixed variables by default, see also the `fixed_variable_treatment` \
option)."),
SolveStatus::InvalidProblemDefinition => write!(f, "
Console Message: (no console message, this is a return code for the C and Fortran \
interfaces only.)\n\n\
This indicates that there was an exception of some sort when building the \
IpoptProblem structure in the C or Fortran interface. Likely there is an error in \
your model or the main routine."),
SolveStatus::InvalidNumberDetected => write!(f, "An invalid number like `NaN` was detected."),
SolveStatus::UnrecoverableException => write!(f, "
Console Message: (details about the particular error will be output to the \
console)\n\n\
This indicates that IPOPT has thrown an exception that does not have an internal \
return code. See the specific message for details."),
SolveStatus::NonIpoptExceptionThrown => write!(f, "
Console Message: `Unknown Exception caught in Ipopt`\n\n\
An unknown exception was caught in IPOPT. This exception could have originated from \
your model or any linked in third party code."),
SolveStatus::InsufficientMemory => write!(f, "
Console Message: `EXIT: Not enough memory.`\n\n\
An error occurred while trying to allocate memory. The problem may be too large for \
your current memory and swap configuration."),
SolveStatus::InternalError => write!(f, "
Console: `EXIT: INTERNAL ERROR: Unknown SolverReturn value - Notify IPOPT Authors.`\n\n\
An unknown internal error has occurred. Please notify the authors of IPOPT via the \
mailing list."),
SolveStatus::UnknownError => write!(f, "Unclassified error."),
}
}
}
#[allow(non_snake_case)]
impl SolveStatus {
fn new(status: ffi::CNLP_ApplicationReturnStatus) -> Self {
use crate::SolveStatus as RS;
match status {
ffi::CNLP_ApplicationReturnStatus_CNLP_SOLVE_SUCCEEDED => RS::SolveSucceeded,
ffi::CNLP_ApplicationReturnStatus_CNLP_SOLVED_TO_ACCEPTABLE_LEVEL => {
RS::SolvedToAcceptableLevel
}
ffi::CNLP_ApplicationReturnStatus_CNLP_INFEASIBLE_PROBLEM_DETECTED => {
RS::InfeasibleProblemDetected
}
ffi::CNLP_ApplicationReturnStatus_CNLP_SEARCH_DIRECTION_BECOMES_TOO_SMALL => {
RS::SearchDirectionBecomesTooSmall
}
ffi::CNLP_ApplicationReturnStatus_CNLP_DIVERGING_ITERATES => RS::DivergingIterates,
ffi::CNLP_ApplicationReturnStatus_CNLP_USER_REQUESTED_STOP => RS::UserRequestedStop,
ffi::CNLP_ApplicationReturnStatus_CNLP_FEASIBLE_POINT_FOUND => RS::FeasiblePointFound,
ffi::CNLP_ApplicationReturnStatus_CNLP_MAXIMUM_ITERATIONS_EXCEEDED => {
RS::MaximumIterationsExceeded
}
ffi::CNLP_ApplicationReturnStatus_CNLP_RESTORATION_FAILED => RS::RestorationFailed,
ffi::CNLP_ApplicationReturnStatus_CNLP_ERROR_IN_STEP_COMPUTATION => {
RS::ErrorInStepComputation
}
ffi::CNLP_ApplicationReturnStatus_CNLP_MAXIMUM_CPUTIME_EXCEEDED => {
RS::MaximumCpuTimeExceeded
}
ffi::CNLP_ApplicationReturnStatus_CNLP_NOT_ENOUGH_DEGREES_OF_FREEDOM => {
RS::NotEnoughDegreesOfFreedom
}
ffi::CNLP_ApplicationReturnStatus_CNLP_INVALID_PROBLEM_DEFINITION => {
RS::InvalidProblemDefinition
}
ffi::CNLP_ApplicationReturnStatus_CNLP_INVALID_OPTION => RS::InvalidOption,
ffi::CNLP_ApplicationReturnStatus_CNLP_INVALID_NUMBER_DETECTED => {
RS::InvalidNumberDetected
}
ffi::CNLP_ApplicationReturnStatus_CNLP_UNRECOVERABLE_EXCEPTION => {
RS::UnrecoverableException
}
ffi::CNLP_ApplicationReturnStatus_CNLP_NONIPOPT_EXCEPTION_THROWN => {
RS::NonIpoptExceptionThrown
}
ffi::CNLP_ApplicationReturnStatus_CNLP_INSUFFICIENT_MEMORY => RS::InsufficientMemory,
ffi::CNLP_ApplicationReturnStatus_CNLP_INTERNAL_ERROR => RS::InternalError,
_ => RS::UnknownError,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum CreateError {
NoOptimizationVariablesSpecified,
InvalidConstraintJacobian {
num_constraints: usize,
num_constraint_jac_nnz: usize,
},
Unknown,
}
impl Display for CreateError {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
match *self {
CreateError::NoOptimizationVariablesSpecified => {
write!(f, "No optimization variables were provided.")
}
CreateError::InvalidConstraintJacobian {
num_constraints,
num_constraint_jac_nnz,
} => write!(
f,
"The number of constraint Jacobian elements ({}) is inconsistent with the \
number of constraints ({}).",
num_constraint_jac_nnz, num_constraints
),
CreateError::Unknown => write!(
f,
"Unexpected error occurred. This is likely an internal bug."
),
}
}
}
impl std::error::Error for CreateError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}
impl From<CreateProblemStatus> for CreateError {
fn from(_s: CreateProblemStatus) -> CreateError {
CreateError::Unknown
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
enum CreateProblemStatus {
Success,
MissingInitialGuess,
MissingBounds,
MissingEvalF,
MissingEvalGradF,
InvalidProblemDefinition,
UnknownError,
}
#[allow(non_snake_case)]
impl CreateProblemStatus {
fn new(status: ffi::CNLP_CreateProblemStatus) -> Self {
use crate::CreateProblemStatus as RS;
match status {
ffi::CNLP_CreateProblemStatus_CNLP_SUCCESS => RS::Success,
ffi::CNLP_CreateProblemStatus_CNLP_MISSING_INITIAL_GUESS => RS::MissingInitialGuess,
ffi::CNLP_CreateProblemStatus_CNLP_MISSING_BOUNDS => RS::MissingBounds,
ffi::CNLP_CreateProblemStatus_CNLP_MISSING_EVAL_F => RS::MissingEvalF,
ffi::CNLP_CreateProblemStatus_CNLP_MISSING_EVAL_GRAD_F => RS::MissingEvalGradF,
ffi::CNLP_CreateProblemStatus_CNLP_INVALID_PROBLEM_DEFINITION_ON_CREATE => {
RS::InvalidProblemDefinition
}
ffi::CNLP_CreateProblemStatus_CNLP_UNRECOVERABLE_EXCEPTION_ON_CREATE => {
RS::UnknownError
}
_ => RS::UnknownError,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Clone, Debug)]
struct NlpUnconstrained {
num_vars: usize,
init_point: Vec<Number>,
lower: Vec<Number>,
upper: Vec<Number>,
}
impl BasicProblem for NlpUnconstrained {
fn num_variables(&self) -> usize {
self.num_vars
}
fn bounds(&self, x_l: &mut [Number], x_u: &mut [Number]) -> bool {
x_l.copy_from_slice(&self.lower);
x_u.copy_from_slice(&self.upper);
true
}
fn initial_point(&self, x: &mut [Number]) -> bool {
x.copy_from_slice(&self.init_point);
true
}
fn objective(&self, _: &[Number], _: bool, _: &mut Number) -> bool {
true
}
fn objective_grad(&self, _: &[Number], _: bool, _: &mut [Number]) -> bool {
true
}
}
#[test]
fn invalid_construction_unconstrained_test() {
let nlp = NlpUnconstrained {
num_vars: 2,
init_point: vec![0.0, 0.0],
lower: vec![-1e20; 2],
upper: vec![1e20; 2],
};
assert!(Ipopt::new_unconstrained(nlp.clone()).is_ok());
let nlp4 = NlpUnconstrained {
num_vars: 0,
..nlp.clone()
};
assert_eq!(
Ipopt::new_unconstrained(nlp4).unwrap_err(),
CreateError::NoOptimizationVariablesSpecified
);
}
#[derive(Debug, Clone)]
struct NlpConstrained {
num_vars: usize,
num_constraints: usize,
num_constraint_jac_nnz: usize,
num_hess_nnz: usize,
constraint_lower: Vec<Number>,
constraint_upper: Vec<Number>,
init_point: Vec<Number>,
lower: Vec<Number>,
upper: Vec<Number>,
}
impl BasicProblem for NlpConstrained {
fn num_variables(&self) -> usize {
self.num_vars
}
fn bounds(&self, x_l: &mut [Number], x_u: &mut [Number]) -> bool {
x_l.copy_from_slice(&self.lower);
x_u.copy_from_slice(&self.upper);
true
}
fn initial_point(&self, x: &mut [Number]) -> bool {
x.copy_from_slice(&self.init_point.clone());
true
}
fn objective(&self, _: &[Number], _: bool, _: &mut Number) -> bool {
true
}
fn objective_grad(&self, _: &[Number], _: bool, _: &mut [Number]) -> bool {
true
}
}
impl ConstrainedProblem for NlpConstrained {
fn num_constraints(&self) -> usize {
self.num_constraints
}
fn num_constraint_jacobian_non_zeros(&self) -> usize {
self.num_constraint_jac_nnz
}
fn constraint_bounds(&self, g_l: &mut [Number], g_u: &mut [Number]) -> bool {
g_l.copy_from_slice(&self.constraint_lower);
g_u.copy_from_slice(&self.constraint_upper);
true
}
fn constraint(&self, _: &[Number], _: bool, _: &mut [Number]) -> bool {
true
}
fn constraint_jacobian_indices(&self, _: &mut [Index], _: &mut [Index]) -> bool {
true
}
fn constraint_jacobian_values(&self, _: &[Number], _: bool, _: &mut [Number]) -> bool {
true
}
fn num_hessian_non_zeros(&self) -> usize {
self.num_hess_nnz
}
fn hessian_indices(&self, _: &mut [Index], _: &mut [Index]) -> bool {
true
}
fn hessian_values(
&self,
_: &[Number],
_: bool,
_: Number,
_: &[Number],
_: &mut [Number],
) -> bool {
true
}
}
#[test]
fn invalid_construction_constrained_test() {
let nlp = NlpConstrained {
num_vars: 4,
num_constraints: 2,
num_constraint_jac_nnz: 8,
num_hess_nnz: 10,
constraint_lower: vec![25.0, 40.0],
constraint_upper: vec![2.0e19, 40.0],
init_point: vec![1.0, 5.0, 5.0, 1.0],
lower: vec![1.0; 4],
upper: vec![5.0; 4],
};
assert!(Ipopt::new(nlp.clone()).is_ok());
let nlp2 = NlpConstrained {
num_vars: 0,
..nlp.clone()
};
assert_eq!(
Ipopt::new(nlp2).unwrap_err(),
CreateError::NoOptimizationVariablesSpecified
);
let nlp3 = NlpConstrained {
num_constraint_jac_nnz: 0,
..nlp.clone()
};
assert_eq!(
Ipopt::new(nlp3).unwrap_err(),
CreateError::InvalidConstraintJacobian {
num_constraints: 2,
num_constraint_jac_nnz: 0
}
);
let nlp4 = NlpConstrained {
num_constraints: 0,
constraint_lower: vec![],
constraint_upper: vec![],
..nlp.clone()
};
assert_eq!(
Ipopt::new(nlp4).unwrap_err(),
CreateError::InvalidConstraintJacobian {
num_constraints: 0,
num_constraint_jac_nnz: 8
}
);
}
#[test]
fn no_solve_validity_test() {
let nlp = NlpConstrained {
num_vars: 4,
num_constraints: 2,
num_constraint_jac_nnz: 8,
num_hess_nnz: 10,
constraint_lower: vec![25.0, 40.0],
constraint_upper: vec![2.0e19, 40.0],
init_point: vec![1.0, 5.0, 5.0, 1.0],
lower: vec![1.0; 4],
upper: vec![5.0; 4],
};
let solver = Ipopt::new(nlp.clone()).expect("Failed to create Ipopt solver");
let SolverData {
solution:
Solution {
primal_variables,
constraint_multipliers,
lower_bound_multipliers,
upper_bound_multipliers,
},
..
} = solver.solver_data();
assert_eq!(primal_variables, nlp.init_point.as_slice());
assert_eq!(constraint_multipliers, vec![0.0; 2].as_slice());
assert_eq!(lower_bound_multipliers, vec![0.0; 4].as_slice());
assert_eq!(upper_bound_multipliers, vec![0.0; 4].as_slice());
}
}