#![warn(missing_docs)]
extern crate ipopt_sys as ffi;
use crate::ffi::{Bool, Int};
use std::ffi::CString;
use std::slice;
pub type Number = f64; pub type Index = i32;
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) -> Vec<Number>;
fn objective(&self, x: &[Number], obj: &mut Number) -> bool;
fn objective_grad(&self, x: &[Number], grad_f: &mut [Number]) -> bool;
}
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_jac_non_zeros(&self) -> usize;
fn constraint(&self, x: &[Number], g: &mut [Number]) -> bool;
fn constraint_bounds(&self, g_l: &mut [Number], g_u: &mut [Number]) -> bool;
fn constraint_jac_indices(&self, rows: &mut [Index], cols: &mut [Index]) -> bool;
fn constraint_jac_values(&self, x: &[Number], 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],
obj_factor: Number,
lambda: &[Number],
vals: &mut [Number],
) -> bool;
}
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)
}
}
#[derive(Debug, PartialEq)]
pub struct SolverDataMut<'a, P: 'a> {
pub problem: &'a mut P,
pub primal_variables: &'a mut [Number],
pub lower_bound_multipliers: &'a mut [Number],
pub upper_bound_multipliers: &'a mut [Number],
pub constraint_multipliers: &'a mut [Number],
}
#[derive(Clone, Debug, PartialEq)]
pub struct SolverData<'a, P: 'a> {
pub problem: &'a P,
pub primal_variables: &'a [Number],
pub lower_bound_multipliers: &'a [Number],
pub upper_bound_multipliers: &'a [Number],
pub constraint_multipliers: &'a [Number],
}
#[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::IpoptProblem,
nlp_interface: P,
intermediate_callback: Option<IntermediateCallback<P>>,
num_primal_variables: usize,
num_dual_variables: usize,
}
impl<P: BasicProblem + std::fmt::Debug> std::fmt::Debug for Ipopt<P> {
fn fmt(&self, f: &mut std::fmt::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> {
pub fn new_unconstrained(nlp: P) -> Result<Self, CreateError> {
let num_vars = nlp.num_variables();
let x = nlp.initial_point();
if num_vars < 1 {
return Err(CreateError::NoOptimizationVariablesSpecified);
}
if x.len() != num_vars {
return Err(CreateError::InvalidInitialPoint);
}
let mut mult_x_l = Vec::with_capacity(num_vars);
mult_x_l.resize(num_vars, 0.0);
let mut mult_x_u = Vec::with_capacity(num_vars);
mult_x_u.resize(num_vars, 0.0);
let mut nlp_internal: ffi::IpoptProblem = ::std::ptr::null_mut();
let create_error = CreateProblemStatus::new(unsafe {
ffi::CreateIpoptProblem(
&mut nlp_internal as *mut ffi::IpoptProblem,
num_vars as Index,
x.as_ptr(),
mult_x_l.as_ptr(),
mult_x_u.as_ptr(),
0, ::std::ptr::null(),
0, 0, nlp.indexing_style() as Index,
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),
)
});
if CreateProblemStatus::Success != create_error {
return Err(create_error.into());
}
assert!(Self::set_ipopt_option(
nlp_internal,
"hessian_approximation",
"limited-memory"
));
Ok(Ipopt {
nlp_internal,
nlp_interface: nlp,
intermediate_callback: None,
num_primal_variables: num_vars,
num_dual_variables: 0,
})
}
fn set_ipopt_option<'a, O>(nlp: ffi::IpoptProblem, name: &str, option: O) -> bool
where
O: Into<IpoptOption<'a>>,
{
(unsafe {
let name_cstr = CString::new(name).unwrap();
let name_cstr = (&name_cstr).as_ptr() as *mut i8; match option.into() {
IpoptOption::Num(opt) => ffi::AddIpoptNumOption(nlp, name_cstr, opt as Number),
IpoptOption::Str(opt) => {
let opt_cstr = CString::new(opt).unwrap();
let opt_cstr = (&opt_cstr).as_ptr() as *mut i8;
ffi::AddIpoptStrOption(nlp, name_cstr, opt_cstr)
}
IpoptOption::Int(opt) => ffi::AddIpoptIntOption(nlp, name_cstr, opt as Int),
}
} != 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::SetIntermediateCallback(self.nlp_internal, Some(Self::intermediate_cb));
} else {
ffi::SetIntermediateCallback(self.nlp_internal, None);
}
}
}
pub fn solve(&mut self) -> SolveResult<P> {
let res = {
let udata_ptr = self as *mut Ipopt<P>;
unsafe { ffi::IpoptSolve(self.nlp_internal, udata_ptr as ffi::UserDataPtr) }
};
let Ipopt {
nlp_interface: ref mut problem,
num_primal_variables,
num_dual_variables,
..
} = *self;
SolveResult {
solver_data: SolverDataMut {
problem,
primal_variables: unsafe {
slice::from_raw_parts_mut(res.data.x, num_primal_variables)
},
lower_bound_multipliers: unsafe {
slice::from_raw_parts_mut(res.data.mult_x_L, num_primal_variables)
},
upper_bound_multipliers: unsafe {
slice::from_raw_parts_mut(res.data.mult_x_U, num_primal_variables)
},
constraint_multipliers: unsafe {
slice::from_raw_parts_mut(res.data.mult_g, num_dual_variables)
},
},
constraint_values: unsafe { slice::from_raw_parts(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 ffi::SolverData {
x,
mult_g,
mult_x_L,
mult_x_U,
} = unsafe { ffi::GetSolverData(nlp_internal) };
SolverDataMut {
problem,
primal_variables: unsafe { slice::from_raw_parts_mut(x, num_primal_variables) },
lower_bound_multipliers: unsafe {
slice::from_raw_parts_mut(mult_x_L, num_primal_variables)
},
upper_bound_multipliers: unsafe {
slice::from_raw_parts_mut(mult_x_U, num_primal_variables)
},
constraint_multipliers: unsafe {
slice::from_raw_parts_mut(mult_g, 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 ffi::SolverData {
x,
mult_g,
mult_x_L,
mult_x_U,
} = unsafe { ffi::GetSolverData(nlp_internal) };
SolverData {
problem,
primal_variables: unsafe { slice::from_raw_parts(x, num_primal_variables) },
lower_bound_multipliers: unsafe {
slice::from_raw_parts(mult_x_L, num_primal_variables)
},
upper_bound_multipliers: unsafe {
slice::from_raw_parts(mult_x_U, num_primal_variables)
},
constraint_multipliers: unsafe { slice::from_raw_parts(mult_g, num_dual_variables) },
}
}
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::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: *mut Number,
_new_x: Bool,
obj_value: *mut Number,
user_data: ffi::UserDataPtr,
) -> Bool {
let nlp = &mut (*(user_data as *mut Ipopt<P>)).nlp_interface;
nlp.objective(slice::from_raw_parts(x, n as usize), &mut *obj_value) as Bool
}
unsafe extern "C" fn eval_grad_f(
n: Index,
x: *mut Number,
_new_x: Bool,
grad_f: *mut Number,
user_data: ffi::UserDataPtr,
) -> Bool {
let nlp = &mut (*(user_data as *mut Ipopt<P>)).nlp_interface;
nlp.objective_grad(
slice::from_raw_parts(x, n as usize),
slice::from_raw_parts_mut(grad_f, n as usize),
) as Bool
}
unsafe extern "C" fn eval_g_none(
_n: Index,
_x: *mut Number,
_new_x: Bool,
_m: Index,
_g: *mut Number,
_user_data: ffi::UserDataPtr,
) -> Bool {
true as Bool
}
unsafe extern "C" fn eval_jac_g_none(
_n: Index,
_x: *mut Number,
_new_x: Bool,
_m: Index,
_nele_jac: Index,
_irow: *mut Index,
_jcol: *mut Index,
_values: *mut Number,
_user_data: ffi::UserDataPtr,
) -> Bool {
true as Bool
}
unsafe extern "C" fn eval_h_none(
_n: Index,
_x: *mut Number,
_new_x: Bool,
_obj_factor: Number,
_m: Index,
_lambda: *mut Number,
_new_lambda: Bool,
_nele_hess: Index,
_irow: *mut Index,
_jcol: *mut Index,
_values: *mut Number,
_user_data: ffi::UserDataPtr,
) -> Bool {
false as Bool
}
unsafe extern "C" fn intermediate_cb(
alg_mod: Index,
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::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();
let x = nlp.initial_point();
if num_vars < 1 {
return Err(CreateError::NoOptimizationVariablesSpecified);
}
if x.len() != num_vars {
return Err(CreateError::InvalidInitialPoint);
}
let mut mult_x_l = Vec::with_capacity(num_vars);
mult_x_l.resize(num_vars, 0.0);
let mut mult_x_u = Vec::with_capacity(num_vars);
mult_x_u.resize(num_vars, 0.0);
let mut nlp_internal: ffi::IpoptProblem = ::std::ptr::null_mut();
let create_error = CreateProblemStatus::new(unsafe {
ffi::CreateIpoptProblem(
&mut nlp_internal as *mut ffi::IpoptProblem,
num_vars as Index,
x.as_ptr(),
mult_x_l.as_ptr(),
mult_x_u.as_ptr(),
0, ::std::ptr::null(),
0, nlp.num_hessian_non_zeros() as Index,
nlp.indexing_style() as Index,
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),
)
});
if create_error != CreateProblemStatus::Success {
return Err(create_error.into());
}
Ok(Ipopt {
nlp_internal,
nlp_interface: nlp,
intermediate_callback: None,
num_primal_variables: num_vars,
num_dual_variables: 0,
})
}
unsafe extern "C" fn eval_h(
n: Index,
x: *mut Number,
_new_x: Bool,
obj_factor: Number,
_m: Index,
_lambda: *mut Number,
_new_lambda: Bool,
nele_hess: Index,
irow: *mut Index,
jcol: *mut Index,
values: *mut Number,
user_data: ffi::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_constraints = nlp.num_constraints();
let num_vars = nlp.num_variables();
let num_hess_nnz = nlp.num_hessian_non_zeros();
let num_constraint_jac_nnz = nlp.num_constraint_jac_non_zeros();
let x = nlp.initial_point();
if num_vars < 1 {
return Err(CreateError::NoOptimizationVariablesSpecified);
}
if x.len() != num_vars {
return Err(CreateError::InvalidInitialPoint);
}
if (num_constraints > 0 && num_constraint_jac_nnz == 0)
|| (num_constraints == 0 && num_constraint_jac_nnz > 0)
{
return Err(CreateError::InvalidConstraintJacobian);
}
let mut mult_g = Vec::with_capacity(num_constraints);
mult_g.resize(num_constraints, 0.0);
let mut mult_x_l = Vec::with_capacity(num_vars);
mult_x_l.resize(num_vars, 0.0);
let mut mult_x_u = Vec::with_capacity(num_vars);
mult_x_u.resize(num_vars, 0.0);
let mut nlp_internal: ffi::IpoptProblem = ::std::ptr::null_mut();
let create_error = CreateProblemStatus::new(unsafe {
ffi::CreateIpoptProblem(
&mut nlp_internal as *mut ffi::IpoptProblem,
num_vars as Index,
x.as_ptr(),
mult_x_l.as_ptr(),
mult_x_u.as_ptr(),
num_constraints as Index,
mult_g.as_ptr(),
num_constraint_jac_nnz as Index,
num_hess_nnz as Index,
nlp.indexing_style() as Index,
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),
)
});
if create_error != CreateProblemStatus::Success {
return Err(create_error.into());
}
Ok(Ipopt {
nlp_internal,
nlp_interface: nlp,
intermediate_callback: None,
num_primal_variables: num_vars,
num_dual_variables: num_constraints,
})
}
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::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: *mut Number,
_new_x: Bool,
m: Index,
g: *mut Number,
user_data: ffi::UserDataPtr,
) -> Bool {
let nlp = &mut (*(user_data as *mut Ipopt<P>)).nlp_interface;
nlp.constraint(
slice::from_raw_parts(x, n as usize),
slice::from_raw_parts_mut(g, m as usize),
) as Bool
}
unsafe extern "C" fn eval_jac_g(
n: Index,
x: *mut Number,
_new_x: Bool,
_m: Index,
nele_jac: Index,
irow: *mut Index,
jcol: *mut Index,
values: *mut Number,
user_data: ffi::UserDataPtr,
) -> Bool {
let nlp = &mut (*(user_data as *mut Ipopt<P>)).nlp_interface;
if values.is_null() {
nlp.constraint_jac_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_jac_values(
slice::from_raw_parts(x, n as usize),
slice::from_raw_parts_mut(values, nele_jac as usize),
) as Bool
}
}
unsafe extern "C" fn eval_full_h(
n: Index,
x: *mut Number,
_new_x: Bool,
obj_factor: Number,
m: Index,
lambda: *mut Number,
_new_lambda: Bool,
nele_hess: Index,
irow: *mut Index,
jcol: *mut Index,
values: *mut Number,
user_data: ffi::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),
obj_factor,
slice::from_raw_parts(lambda, m as usize),
slice::from_raw_parts_mut(values, nele_hess as usize),
) as Bool
}
}
}
impl<P: BasicProblem> Drop for Ipopt<P> {
fn drop(&mut self) {
unsafe {
ffi::FreeIpoptProblem(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,
}
#[allow(non_snake_case)]
impl SolveStatus {
fn new(status: ffi::ApplicationReturnStatus) -> Self {
use crate::SolveStatus as RS;
match status {
ffi::ApplicationReturnStatus_Solve_Succeeded => RS::SolveSucceeded,
ffi::ApplicationReturnStatus_Solved_To_Acceptable_Level => RS::SolvedToAcceptableLevel,
ffi::ApplicationReturnStatus_Infeasible_Problem_Detected => {
RS::InfeasibleProblemDetected
}
ffi::ApplicationReturnStatus_Search_Direction_Becomes_Too_Small => {
RS::SearchDirectionBecomesTooSmall
}
ffi::ApplicationReturnStatus_Diverging_Iterates => RS::DivergingIterates,
ffi::ApplicationReturnStatus_User_Requested_Stop => RS::UserRequestedStop,
ffi::ApplicationReturnStatus_Feasible_Point_Found => RS::FeasiblePointFound,
ffi::ApplicationReturnStatus_Maximum_Iterations_Exceeded => {
RS::MaximumIterationsExceeded
}
ffi::ApplicationReturnStatus_Restoration_Failed => RS::RestorationFailed,
ffi::ApplicationReturnStatus_Error_In_Step_Computation => RS::ErrorInStepComputation,
ffi::ApplicationReturnStatus_Maximum_CpuTime_Exceeded => RS::MaximumCpuTimeExceeded,
ffi::ApplicationReturnStatus_Not_Enough_Degrees_Of_Freedom => {
RS::NotEnoughDegreesOfFreedom
}
ffi::ApplicationReturnStatus_Invalid_Problem_Definition => RS::InvalidProblemDefinition,
ffi::ApplicationReturnStatus_Invalid_Option => RS::InvalidOption,
ffi::ApplicationReturnStatus_Invalid_Number_Detected => RS::InvalidNumberDetected,
ffi::ApplicationReturnStatus_Unrecoverable_Exception => RS::UnrecoverableException,
ffi::ApplicationReturnStatus_NonIpopt_Exception_Thrown => RS::NonIpoptExceptionThrown,
ffi::ApplicationReturnStatus_Insufficient_Memory => RS::InsufficientMemory,
ffi::ApplicationReturnStatus_Internal_Error => RS::InternalError,
_ => RS::UnknownError,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum CreateError {
NoOptimizationVariablesSpecified,
InvalidInitialPoint,
InvalidConstraintJacobian,
Unknown,
}
impl From<CreateProblemStatus> for CreateError {
fn from(s: CreateProblemStatus) -> CreateError {
match s {
CreateProblemStatus::MissingInitialGuess => CreateError::InvalidInitialPoint,
CreateProblemStatus::TooFewOptimizationVariables => {
CreateError::NoOptimizationVariablesSpecified
}
CreateProblemStatus::HaveJacobianElementsButNoConstraints => {
CreateError::InvalidConstraintJacobian
}
CreateProblemStatus::HaveConstraintsButNoJacobianElements => {
CreateError::InvalidConstraintJacobian
}
_ => CreateError::Unknown,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
enum CreateProblemStatus {
Success,
MissingInitialGuess,
TooFewOptimizationVariables,
ConstraintSizeIsNegative,
HaveJacobianElementsButNoConstraints,
HaveConstraintsButNoJacobianElements,
InvalidNumHessianElements,
MissingBounds,
MissingEvalF,
MissingEvalGradF,
HaveConstraintsButNoEvalGOrEvalJacG,
UnknownError,
}
#[allow(non_snake_case)]
impl CreateProblemStatus {
fn new(status: ffi::CreateProblemStatus) -> Self {
use crate::CreateProblemStatus as RS;
match status {
ffi::CreateProblemStatus_Success => RS::Success,
ffi::CreateProblemStatus_MissingInitialGuess => RS::MissingInitialGuess,
ffi::CreateProblemStatus_TooFewOptimizationVariables => RS::TooFewOptimizationVariables,
ffi::CreateProblemStatus_ConstraintSizeIsNegative => RS::ConstraintSizeIsNegative,
ffi::CreateProblemStatus_HaveJacobianElementsButNoConstraints => {
RS::HaveJacobianElementsButNoConstraints
}
ffi::CreateProblemStatus_HaveConstraintsButNoJacobianElements => {
RS::HaveConstraintsButNoJacobianElements
}
ffi::CreateProblemStatus_InvalidNumHessianElements => RS::InvalidNumHessianElements,
ffi::CreateProblemStatus_MissingBounds => RS::MissingBounds,
ffi::CreateProblemStatus_MissingEvalF => RS::MissingEvalF,
ffi::CreateProblemStatus_MissingEvalGradF => RS::MissingEvalGradF,
ffi::CreateProblemStatus_HaveConstraintsButNoEvalGOrEvalJacG => {
RS::HaveConstraintsButNoEvalGOrEvalJacG
}
_ => 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) -> Vec<Number> {
self.init_point.clone()
}
fn objective(&self, _: &[Number], _: &mut Number) -> bool {
true
}
fn objective_grad(&self, _: &[Number], _: &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 nlp1 = NlpUnconstrained {
init_point: vec![0.0],
..nlp.clone()
};
assert_eq!(
Ipopt::new_unconstrained(nlp1).unwrap_err(),
CreateError::InvalidInitialPoint
);
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) -> Vec<Number> {
self.init_point.clone()
}
fn objective(&self, _: &[Number], _: &mut Number) -> bool {
true
}
fn objective_grad(&self, _: &[Number], _: &mut [Number]) -> bool {
true
}
}
impl ConstrainedProblem for NlpConstrained {
fn num_constraints(&self) -> usize {
self.num_constraints
}
fn num_constraint_jac_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], _: &mut [Number]) -> bool {
true
}
fn constraint_jac_indices(&self, _: &mut [Index], _: &mut [Index]) -> bool {
true
}
fn constraint_jac_values(&self, _: &[Number], _: &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], _: 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 nlp1 = NlpConstrained {
init_point: vec![0.0],
..nlp.clone()
};
assert_eq!(
Ipopt::new(nlp1).unwrap_err(),
CreateError::InvalidInitialPoint
);
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
);
let nlp4 = NlpConstrained {
num_constraints: 0,
constraint_lower: vec![],
constraint_upper: vec![],
..nlp.clone()
};
assert_eq!(
Ipopt::new(nlp4).unwrap_err(),
CreateError::InvalidConstraintJacobian
);
}
}