#![allow(non_camel_case_types, non_snake_case)]
#![allow(unsafe_op_in_unsafe_fn, dead_code)]
#![cfg_attr(test, allow(clippy::unwrap_used, clippy::expect_used))]
pub mod fortran;
pub mod solver;
use pounce_algorithm::alg_builder::AlgorithmBuilder;
use pounce_algorithm::application::{
default_backend_factory, feral_config_from_options, IpoptApplication,
};
use pounce_algorithm::intermediate as ip_intermediate;
use pounce_nlp::return_codes::ApplicationReturnStatus;
use pounce_nlp::solve_statistics::SolveStatistics;
use pounce_nlp::tnlp::{
BoundsInfo, IndexStyle, IpoptCq, IpoptData, NlpInfo, ScalingRequest, Solution, SparsityRequest,
StartingPoint, TNLP,
};
use pounce_restoration::resto_alg_builder::RestoAlgorithmBuilder;
use pounce_restoration::resto_inner_solver::{
make_default_restoration_factory, InnerBackendFactoryFactory,
};
use std::cell::RefCell;
use std::ffi::{c_char, c_int, c_void, CStr};
use std::rc::Rc;
pub type Number = f64;
pub type Index = c_int;
pub type Bool = c_int;
const TRUE: Bool = 1;
const FALSE: Bool = 0;
pub struct IpoptProblemInfo {
pub(crate) app: IpoptApplication,
pub(crate) n: Index,
pub(crate) m: Index,
pub(crate) nele_jac: Index,
pub(crate) nele_hess: Index,
pub(crate) index_style: Index,
pub(crate) x_l: Vec<Number>,
pub(crate) x_u: Vec<Number>,
pub(crate) g_l: Vec<Number>,
pub(crate) g_u: Vec<Number>,
pub(crate) eval_f: Option<Eval_F_CB>,
pub(crate) eval_g: Option<Eval_G_CB>,
pub(crate) eval_grad_f: Option<Eval_Grad_F_CB>,
pub(crate) eval_jac_g: Option<Eval_Jac_G_CB>,
pub(crate) eval_h: Option<Eval_H_CB>,
pub(crate) intermediate_cb: Option<Intermediate_CB>,
pub(crate) user_scaling: Option<UserScaling>,
pub(crate) last_solve: Option<LastSolve>,
}
#[derive(Clone)]
pub(crate) struct UserScaling {
obj_scaling: Number,
x_scaling: Option<Vec<Number>>,
g_scaling: Option<Vec<Number>>,
}
#[derive(Clone, Default)]
pub(crate) struct LastSolve {
pub(crate) stats: SolveStatistics,
}
pub type IpoptProblem = *mut IpoptProblemInfo;
pub type Eval_F_CB = unsafe extern "C" fn(
n: Index,
x: *const Number,
new_x: Bool,
obj_value: *mut Number,
user_data: *mut c_void,
) -> Bool;
pub type Eval_Grad_F_CB = unsafe extern "C" fn(
n: Index,
x: *const Number,
new_x: Bool,
grad_f: *mut Number,
user_data: *mut c_void,
) -> Bool;
pub type Eval_G_CB = unsafe extern "C" fn(
n: Index,
x: *const Number,
new_x: Bool,
m: Index,
g: *mut Number,
user_data: *mut c_void,
) -> Bool;
pub type Eval_Jac_G_CB = unsafe extern "C" fn(
n: Index,
x: *const Number,
new_x: Bool,
m: Index,
nele_jac: Index,
iRow: *mut Index,
jCol: *mut Index,
values: *mut Number,
user_data: *mut c_void,
) -> Bool;
pub type Eval_H_CB = unsafe extern "C" fn(
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: *mut c_void,
) -> Bool;
pub type Intermediate_CB = unsafe extern "C" fn(
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: *mut c_void,
) -> Bool;
#[no_mangle]
pub unsafe extern "C" fn CreateIpoptProblem(
n: Index,
x_L: *const Number,
x_U: *const Number,
m: Index,
g_L: *const Number,
g_U: *const Number,
nele_jac: Index,
nele_hess: Index,
index_style: Index,
eval_f: Option<Eval_F_CB>,
eval_g: Option<Eval_G_CB>,
eval_grad_f: Option<Eval_Grad_F_CB>,
eval_jac_g: Option<Eval_Jac_G_CB>,
eval_h: Option<Eval_H_CB>,
) -> IpoptProblem {
if n < 0 || m < 0 || nele_jac < 0 || nele_hess < 0 {
return std::ptr::null_mut();
}
if !(0..=1).contains(&index_style) {
return std::ptr::null_mut();
}
if eval_f.is_none() || eval_grad_f.is_none() {
return std::ptr::null_mut();
}
if m > 0 && (eval_g.is_none() || eval_jac_g.is_none()) {
return std::ptr::null_mut();
}
if n > 0 && (x_L.is_null() || x_U.is_null()) {
return std::ptr::null_mut();
}
if m > 0 && (g_L.is_null() || g_U.is_null()) {
return std::ptr::null_mut();
}
let x_l = if n > 0 {
std::slice::from_raw_parts(x_L, n as usize).to_vec()
} else {
Vec::new()
};
let x_u = if n > 0 {
std::slice::from_raw_parts(x_U, n as usize).to_vec()
} else {
Vec::new()
};
let g_l_vec = if m > 0 {
std::slice::from_raw_parts(g_L, m as usize).to_vec()
} else {
Vec::new()
};
let g_u_vec = if m > 0 {
std::slice::from_raw_parts(g_U, m as usize).to_vec()
} else {
Vec::new()
};
let info = Box::new(IpoptProblemInfo {
app: IpoptApplication::new(),
n,
m,
nele_jac,
nele_hess,
index_style,
x_l,
x_u,
g_l: g_l_vec,
g_u: g_u_vec,
eval_f,
eval_g,
eval_grad_f,
eval_jac_g,
eval_h,
intermediate_cb: None,
user_scaling: None,
last_solve: None,
});
Box::into_raw(info)
}
#[no_mangle]
pub unsafe extern "C" fn FreeIpoptProblem(ipopt_problem: IpoptProblem) {
if ipopt_problem.is_null() {
return;
}
drop(Box::from_raw(ipopt_problem));
}
unsafe fn keyword_str<'a>(keyword: *const c_char) -> Option<&'a str> {
if keyword.is_null() {
return None;
}
CStr::from_ptr(keyword).to_str().ok()
}
#[no_mangle]
pub unsafe extern "C" fn AddIpoptStrOption(
ipopt_problem: IpoptProblem,
keyword: *const c_char,
val: *const c_char,
) -> Bool {
if ipopt_problem.is_null() {
return FALSE;
}
let info = &mut *ipopt_problem;
let Some(k) = keyword_str(keyword) else {
return FALSE;
};
if val.is_null() {
return FALSE;
}
let Ok(v) = CStr::from_ptr(val).to_str() else {
return FALSE;
};
match info.app.options_mut().set_string_value(k, v, true, false) {
Ok(_) => TRUE,
Err(_) => FALSE,
}
}
#[no_mangle]
pub unsafe extern "C" fn AddIpoptNumOption(
ipopt_problem: IpoptProblem,
keyword: *const c_char,
val: Number,
) -> Bool {
if ipopt_problem.is_null() {
return FALSE;
}
let info = &mut *ipopt_problem;
let Some(k) = keyword_str(keyword) else {
return FALSE;
};
match info
.app
.options_mut()
.set_numeric_value(k, val, true, false)
{
Ok(_) => TRUE,
Err(_) => FALSE,
}
}
#[no_mangle]
pub unsafe extern "C" fn AddIpoptIntOption(
ipopt_problem: IpoptProblem,
keyword: *const c_char,
val: Index,
) -> Bool {
if ipopt_problem.is_null() {
return FALSE;
}
let info = &mut *ipopt_problem;
let Some(k) = keyword_str(keyword) else {
return FALSE;
};
match info.app.options_mut().set_integer_value(
k,
val as pounce_common::types::Index,
true,
false,
) {
Ok(_) => TRUE,
Err(_) => FALSE,
}
}
#[no_mangle]
pub unsafe extern "C" fn OpenIpoptOutputFile(
ipopt_problem: IpoptProblem,
file_name: *const c_char,
print_level: c_int,
) -> Bool {
if ipopt_problem.is_null() || file_name.is_null() {
return FALSE;
}
let info = &mut *ipopt_problem;
let Ok(fname) = CStr::from_ptr(file_name).to_str() else {
return FALSE;
};
if info.app.open_output_file(fname, print_level) {
TRUE
} else {
FALSE
}
}
#[no_mangle]
pub unsafe extern "C" fn SetIpoptProblemScaling(
ipopt_problem: IpoptProblem,
obj_scaling: Number,
x_scaling: *const Number,
g_scaling: *const Number,
) -> Bool {
if ipopt_problem.is_null() {
return FALSE;
}
let info = &mut *ipopt_problem;
let n = info.n as usize;
let m = info.m as usize;
let x_vec = if !x_scaling.is_null() && n > 0 {
Some(std::slice::from_raw_parts(x_scaling, n).to_vec())
} else {
None
};
let g_vec = if !g_scaling.is_null() && m > 0 {
Some(std::slice::from_raw_parts(g_scaling, m).to_vec())
} else {
None
};
info.user_scaling = Some(UserScaling {
obj_scaling,
x_scaling: x_vec,
g_scaling: g_vec,
});
TRUE
}
#[allow(clippy::too_many_arguments)]
#[no_mangle]
pub unsafe extern "C" fn IpoptSolve(
ipopt_problem: IpoptProblem,
x: *mut Number,
g: *mut Number,
obj_val: *mut Number,
mult_g: *mut Number,
mult_x_L: *mut Number,
mult_x_U: *mut Number,
user_data: *mut c_void,
) -> Index {
if ipopt_problem.is_null() {
return ApplicationReturnStatus::InternalError as Index;
}
let info = &mut *ipopt_problem;
if info.n < 0 || info.m < 0 {
return ApplicationReturnStatus::InvalidProblemDefinition as Index;
}
if info.n > 0 && x.is_null() {
return ApplicationReturnStatus::InvalidProblemDefinition as Index;
}
let n_us = info.n as usize;
let m_us = info.m as usize;
let initial_x = if n_us > 0 {
std::slice::from_raw_parts(x, n_us).to_vec()
} else {
Vec::new()
};
let bridge = Rc::new(RefCell::new(CCallbackTnlp {
n: info.n,
m: info.m,
nele_jac: info.nele_jac,
nele_hess: info.nele_hess,
index_style: info.index_style,
x_l: info.x_l.clone(),
x_u: info.x_u.clone(),
g_l: info.g_l.clone(),
g_u: info.g_u.clone(),
initial_x,
eval_f: info.eval_f,
eval_grad_f: info.eval_grad_f,
eval_g: info.eval_g,
eval_jac_g: info.eval_jac_g,
eval_h: info.eval_h,
user_data,
intermediate_cb: info.intermediate_cb,
user_scaling: info.user_scaling.clone(),
final_status: None,
final_x: vec![0.0; n_us],
final_z_l: vec![0.0; n_us],
final_z_u: vec![0.0; n_us],
final_g: vec![0.0; m_us],
final_lambda: vec![0.0; m_us],
final_obj: 0.0,
}));
let feral_cfg = feral_config_from_options(info.app.options());
let bff: InnerBackendFactoryFactory = Box::new(move || default_backend_factory(feral_cfg));
let resto_factory = make_default_restoration_factory(
RestoAlgorithmBuilder::new(),
AlgorithmBuilder::new(),
bff,
);
info.app.set_restoration_factory(resto_factory);
let bridge_for_solve: Rc<RefCell<dyn TNLP>> = bridge.clone();
let status = info.app.optimize_tnlp(bridge_for_solve);
info.last_solve = Some(LastSolve {
stats: info.app.statistics(),
});
let bridge_ref = bridge.borrow();
if !x.is_null() && n_us > 0 {
std::ptr::copy_nonoverlapping(bridge_ref.final_x.as_ptr(), x, n_us);
}
if !g.is_null() && m_us > 0 {
std::ptr::copy_nonoverlapping(bridge_ref.final_g.as_ptr(), g, m_us);
}
if !obj_val.is_null() {
*obj_val = bridge_ref.final_obj;
}
if !mult_g.is_null() && m_us > 0 {
std::ptr::copy_nonoverlapping(bridge_ref.final_lambda.as_ptr(), mult_g, m_us);
}
if !mult_x_L.is_null() && n_us > 0 {
std::ptr::copy_nonoverlapping(bridge_ref.final_z_l.as_ptr(), mult_x_L, n_us);
}
if !mult_x_U.is_null() && n_us > 0 {
std::ptr::copy_nonoverlapping(bridge_ref.final_z_u.as_ptr(), mult_x_U, n_us);
}
status as Index
}
#[no_mangle]
pub unsafe extern "C" fn SetIntermediateCallback(
ipopt_problem: IpoptProblem,
intermediate_cb: Option<Intermediate_CB>,
) -> Bool {
if ipopt_problem.is_null() {
return FALSE;
}
let info = &mut *ipopt_problem;
info.intermediate_cb = intermediate_cb;
TRUE
}
#[allow(clippy::too_many_arguments)]
#[no_mangle]
pub unsafe extern "C" fn GetIpoptCurrentIterate(
ipopt_problem: IpoptProblem,
_scaled: Bool,
n: Index,
x: *mut Number,
z_l: *mut Number,
z_u: *mut Number,
m: Index,
g: *mut Number,
lambda: *mut Number,
) -> Bool {
if ipopt_problem.is_null() {
return FALSE;
}
let info = &*ipopt_problem;
if n != info.n || m != info.m {
return FALSE;
}
let result = ip_intermediate::with_current(|ctx| {
let data = ctx.data.borrow();
let Some(curr) = data.curr.as_ref() else {
return false;
};
let nlp = ctx.nlp.borrow();
let n_us = n as usize;
let m_us = m as usize;
if !x.is_null() && n_us > 0 {
let full_x = nlp.lift_x_to_full(&*curr.x);
if full_x.len() != n_us {
return false;
}
std::ptr::copy_nonoverlapping(full_x.as_ptr(), x, n_us);
}
if !z_l.is_null() && n_us > 0 {
let full = nlp.pack_z_l_for_user(&*curr.z_l);
if full.len() != n_us {
return false;
}
std::ptr::copy_nonoverlapping(full.as_ptr(), z_l, n_us);
}
if !z_u.is_null() && n_us > 0 {
let full = nlp.pack_z_u_for_user(&*curr.z_u);
if full.len() != n_us {
return false;
}
std::ptr::copy_nonoverlapping(full.as_ptr(), z_u, n_us);
}
if !g.is_null() && m_us > 0 {
let cq = ctx.cq.borrow();
let full = nlp.pack_g_for_user(&*cq.curr_c(), &*cq.curr_d());
if full.len() != m_us {
return false;
}
std::ptr::copy_nonoverlapping(full.as_ptr(), g, m_us);
}
if !lambda.is_null() && m_us > 0 {
let full = nlp.pack_lambda_for_user(&*curr.y_c, &*curr.y_d);
if full.len() != m_us {
return false;
}
std::ptr::copy_nonoverlapping(full.as_ptr(), lambda, m_us);
}
true
});
if result.unwrap_or(false) {
TRUE
} else {
FALSE
}
}
#[allow(clippy::too_many_arguments)]
#[no_mangle]
pub unsafe extern "C" fn GetIpoptCurrentViolations(
ipopt_problem: IpoptProblem,
_scaled: Bool,
n: Index,
x_l_violation: *mut Number,
x_u_violation: *mut Number,
compl_x_l: *mut Number,
compl_x_u: *mut Number,
grad_lag_x: *mut Number,
m: Index,
nlp_constraint_violation: *mut Number,
compl_g: *mut Number,
) -> Bool {
if ipopt_problem.is_null() {
return FALSE;
}
let info = &*ipopt_problem;
if n != info.n || m != info.m {
return FALSE;
}
let result = ip_intermediate::with_current(|ctx| {
let data = ctx.data.borrow();
let Some(_curr) = data.curr.as_ref() else {
return false;
};
drop(data);
let nlp = ctx.nlp.borrow();
let cq = ctx.cq.borrow();
let n_us = n as usize;
let m_us = m as usize;
if !x_l_violation.is_null() && n_us > 0 {
let mut v = vec![0.0; n_us];
let slack = cq.curr_slack_x_l();
let z_l_full = nlp.pack_z_l_for_user(&*slack);
for (i, s) in z_l_full.iter().enumerate() {
v[i] = (-s).max(0.0);
}
std::ptr::copy_nonoverlapping(v.as_ptr(), x_l_violation, n_us);
}
if !x_u_violation.is_null() && n_us > 0 {
let mut v = vec![0.0; n_us];
let slack = cq.curr_slack_x_u();
let s_full = nlp.pack_z_u_for_user(&*slack);
for (i, s) in s_full.iter().enumerate() {
v[i] = (-s).max(0.0);
}
std::ptr::copy_nonoverlapping(v.as_ptr(), x_u_violation, n_us);
}
if !compl_x_l.is_null() && n_us > 0 {
let v = nlp.pack_z_l_for_user(&*cq.curr_compl_x_l());
if v.len() != n_us {
return false;
}
std::ptr::copy_nonoverlapping(v.as_ptr(), compl_x_l, n_us);
}
if !compl_x_u.is_null() && n_us > 0 {
let v = nlp.pack_z_u_for_user(&*cq.curr_compl_x_u());
if v.len() != n_us {
return false;
}
std::ptr::copy_nonoverlapping(v.as_ptr(), compl_x_u, n_us);
}
if !grad_lag_x.is_null() && n_us > 0 {
let glx = cq.curr_grad_lag_x();
let full = nlp.lift_x_to_full(&*glx);
if full.len() != n_us {
return false;
}
std::ptr::copy_nonoverlapping(full.as_ptr(), grad_lag_x, n_us);
}
if !nlp_constraint_violation.is_null() && m_us > 0 {
let zero = vec![0.0; m_us];
std::ptr::copy_nonoverlapping(zero.as_ptr(), nlp_constraint_violation, m_us);
}
if !compl_g.is_null() && m_us > 0 {
let zero = vec![0.0; m_us];
std::ptr::copy_nonoverlapping(zero.as_ptr(), compl_g, m_us);
}
true
});
if result.unwrap_or(false) {
TRUE
} else {
FALSE
}
}
#[no_mangle]
pub unsafe extern "C" fn GetIpoptVersion(
major: *mut c_int,
minor: *mut c_int,
release: *mut c_int,
) {
let (mj, mn, pt) = parse_pkg_version(env!("CARGO_PKG_VERSION"));
if !major.is_null() {
*major = mj;
}
if !minor.is_null() {
*minor = mn;
}
if !release.is_null() {
*release = pt;
}
}
fn parse_pkg_version(v: &str) -> (c_int, c_int, c_int) {
let mut it = v.split('.').map(|s| s.parse::<c_int>().unwrap_or(0));
(
it.next().unwrap_or(0),
it.next().unwrap_or(0),
it.next().unwrap_or(0),
)
}
#[no_mangle]
pub unsafe extern "C" fn GetIpoptIterCount(ipopt_problem: IpoptProblem) -> Index {
last_stat(ipopt_problem, |s| s.iteration_count).unwrap_or(0)
}
#[no_mangle]
pub unsafe extern "C" fn GetIpoptSolveTime(ipopt_problem: IpoptProblem) -> Number {
last_stat(ipopt_problem, |s| s.total_wallclock_time_secs).unwrap_or(0.0)
}
#[no_mangle]
pub unsafe extern "C" fn GetIpoptPrimalInf(ipopt_problem: IpoptProblem) -> Number {
last_stat(ipopt_problem, |s| s.final_constr_viol).unwrap_or(0.0)
}
#[no_mangle]
pub unsafe extern "C" fn GetIpoptDualInf(ipopt_problem: IpoptProblem) -> Number {
last_stat(ipopt_problem, |s| s.final_dual_inf).unwrap_or(0.0)
}
#[no_mangle]
pub unsafe extern "C" fn GetIpoptComplInf(ipopt_problem: IpoptProblem) -> Number {
last_stat(ipopt_problem, |s| s.final_compl).unwrap_or(0.0)
}
unsafe fn last_stat<T, F>(ipopt_problem: IpoptProblem, f: F) -> Option<T>
where
F: FnOnce(&SolveStatistics) -> T,
{
if ipopt_problem.is_null() {
return None;
}
(*ipopt_problem).last_solve.as_ref().map(|ls| f(&ls.stats))
}
pub(crate) struct CCallbackTnlp {
pub(crate) n: Index,
pub(crate) m: Index,
pub(crate) nele_jac: Index,
pub(crate) nele_hess: Index,
pub(crate) index_style: Index,
pub(crate) x_l: Vec<Number>,
pub(crate) x_u: Vec<Number>,
pub(crate) g_l: Vec<Number>,
pub(crate) g_u: Vec<Number>,
pub(crate) initial_x: Vec<Number>,
pub(crate) eval_f: Option<Eval_F_CB>,
pub(crate) eval_grad_f: Option<Eval_Grad_F_CB>,
pub(crate) eval_g: Option<Eval_G_CB>,
pub(crate) eval_jac_g: Option<Eval_Jac_G_CB>,
pub(crate) eval_h: Option<Eval_H_CB>,
pub(crate) user_data: *mut c_void,
pub(crate) intermediate_cb: Option<Intermediate_CB>,
pub(crate) user_scaling: Option<UserScaling>,
pub(crate) final_status: Option<pounce_nlp::alg_types::SolverReturn>,
pub(crate) final_x: Vec<Number>,
pub(crate) final_z_l: Vec<Number>,
pub(crate) final_z_u: Vec<Number>,
pub(crate) final_g: Vec<Number>,
pub(crate) final_lambda: Vec<Number>,
pub(crate) final_obj: Number,
}
impl TNLP for CCallbackTnlp {
fn get_nlp_info(&mut self) -> Option<NlpInfo> {
Some(NlpInfo {
n: self.n as pounce_common::types::Index,
m: self.m as pounce_common::types::Index,
nnz_jac_g: self.nele_jac as pounce_common::types::Index,
nnz_h_lag: self.nele_hess as pounce_common::types::Index,
index_style: if self.index_style == 1 {
IndexStyle::Fortran
} else {
IndexStyle::C
},
})
}
fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
if !self.x_l.is_empty() {
b.x_l.copy_from_slice(&self.x_l);
}
if !self.x_u.is_empty() {
b.x_u.copy_from_slice(&self.x_u);
}
if !self.g_l.is_empty() {
b.g_l.copy_from_slice(&self.g_l);
}
if !self.g_u.is_empty() {
b.g_u.copy_from_slice(&self.g_u);
}
true
}
fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
if !self.initial_x.is_empty() {
sp.x.copy_from_slice(&self.initial_x);
}
true
}
fn get_scaling_parameters(&mut self, req: ScalingRequest<'_>) -> bool {
let Some(s) = self.user_scaling.as_ref() else {
return false;
};
*req.obj_scaling = s.obj_scaling;
if let Some(x) = s.x_scaling.as_ref() {
if x.len() == req.x_scaling.len() {
req.x_scaling.copy_from_slice(x);
*req.use_x_scaling = true;
}
} else {
*req.use_x_scaling = false;
}
if let Some(g) = s.g_scaling.as_ref() {
if g.len() == req.g_scaling.len() {
req.g_scaling.copy_from_slice(g);
*req.use_g_scaling = true;
}
} else {
*req.use_g_scaling = false;
}
true
}
fn eval_f(&mut self, x: &[Number], new_x: bool) -> Option<Number> {
let cb = self.eval_f?;
let mut obj = 0.0;
let ok = unsafe {
cb(
self.n,
x.as_ptr() as *mut Number,
if new_x { TRUE } else { FALSE },
&mut obj,
self.user_data,
)
};
if ok != FALSE {
Some(obj)
} else {
None
}
}
fn eval_grad_f(&mut self, x: &[Number], new_x: bool, grad_f: &mut [Number]) -> bool {
let Some(cb) = self.eval_grad_f else {
return false;
};
let ok = unsafe {
cb(
self.n,
x.as_ptr() as *mut Number,
if new_x { TRUE } else { FALSE },
grad_f.as_mut_ptr(),
self.user_data,
)
};
ok != FALSE
}
fn eval_g(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
if self.m == 0 {
return true;
}
let Some(cb) = self.eval_g else {
return false;
};
let ok = unsafe {
cb(
self.n,
x.as_ptr() as *mut Number,
if new_x { TRUE } else { FALSE },
self.m,
g.as_mut_ptr(),
self.user_data,
)
};
ok != FALSE
}
fn eval_jac_g(&mut self, x: Option<&[Number]>, new_x: bool, mode: SparsityRequest<'_>) -> bool {
if self.m == 0 || self.nele_jac == 0 {
return true;
}
let Some(cb) = self.eval_jac_g else {
return false;
};
let x_ptr = x
.map(|s| s.as_ptr() as *mut Number)
.unwrap_or(std::ptr::null_mut());
let ok = match mode {
SparsityRequest::Structure { irow, jcol } => unsafe {
cb(
self.n,
x_ptr,
if new_x { TRUE } else { FALSE },
self.m,
self.nele_jac,
irow.as_mut_ptr(),
jcol.as_mut_ptr(),
std::ptr::null_mut(),
self.user_data,
)
},
SparsityRequest::Values { values } => unsafe {
cb(
self.n,
x_ptr,
if new_x { TRUE } else { FALSE },
self.m,
self.nele_jac,
std::ptr::null_mut(),
std::ptr::null_mut(),
values.as_mut_ptr(),
self.user_data,
)
},
};
ok != FALSE
}
fn eval_h(
&mut self,
x: Option<&[Number]>,
new_x: bool,
obj_factor: Number,
lambda: Option<&[Number]>,
new_lambda: bool,
mode: SparsityRequest<'_>,
) -> bool {
let Some(cb) = self.eval_h else {
return false;
};
if self.nele_hess == 0 {
return true;
}
let x_ptr = x
.map(|s| s.as_ptr() as *mut Number)
.unwrap_or(std::ptr::null_mut());
let lambda_ptr = lambda
.map(|s| s.as_ptr() as *mut Number)
.unwrap_or(std::ptr::null_mut());
let ok = match mode {
SparsityRequest::Structure { irow, jcol } => unsafe {
cb(
self.n,
x_ptr,
if new_x { TRUE } else { FALSE },
obj_factor,
self.m,
lambda_ptr,
if new_lambda { TRUE } else { FALSE },
self.nele_hess,
irow.as_mut_ptr(),
jcol.as_mut_ptr(),
std::ptr::null_mut(),
self.user_data,
)
},
SparsityRequest::Values { values } => unsafe {
cb(
self.n,
x_ptr,
if new_x { TRUE } else { FALSE },
obj_factor,
self.m,
lambda_ptr,
if new_lambda { TRUE } else { FALSE },
self.nele_hess,
std::ptr::null_mut(),
std::ptr::null_mut(),
values.as_mut_ptr(),
self.user_data,
)
},
};
ok != FALSE
}
fn intermediate_callback(
&mut self,
stats: pounce_nlp::tnlp::IterStats,
_ip_data: &IpoptData,
_ip_cq: &IpoptCq,
) -> bool {
let Some(cb) = self.intermediate_cb else {
return true;
};
let ok = unsafe {
cb(
stats.mode as Index,
stats.iter as Index,
stats.obj_value,
stats.inf_pr,
stats.inf_du,
stats.mu,
stats.d_norm,
stats.regularization_size,
stats.alpha_du,
stats.alpha_pr,
stats.ls_trials as Index,
self.user_data,
)
};
ok != FALSE
}
fn finalize_solution(&mut self, sol: Solution<'_>, _d: &IpoptData, _q: &IpoptCq) {
self.final_status = Some(sol.status);
if !sol.x.is_empty() {
self.final_x.copy_from_slice(sol.x);
}
if !sol.z_l.is_empty() {
self.final_z_l.copy_from_slice(sol.z_l);
}
if !sol.z_u.is_empty() {
self.final_z_u.copy_from_slice(sol.z_u);
}
if !sol.g.is_empty() {
self.final_g.copy_from_slice(sol.g);
}
if !sol.lambda.is_empty() {
self.final_lambda.copy_from_slice(sol.lambda);
}
self.final_obj = sol.obj_value;
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::CString;
unsafe extern "C" fn dummy_eval_f(
_n: Index,
_x: *const Number,
_new_x: Bool,
_obj_value: *mut Number,
_user_data: *mut c_void,
) -> Bool {
TRUE
}
unsafe extern "C" fn dummy_eval_grad_f(
_n: Index,
_x: *const Number,
_new_x: Bool,
_grad_f: *mut Number,
_user_data: *mut c_void,
) -> Bool {
TRUE
}
fn create_unconstrained() -> IpoptProblem {
let xl = [-1.0; 4];
let xu = [1.0; 4];
unsafe {
CreateIpoptProblem(
4,
xl.as_ptr(),
xu.as_ptr(),
0,
std::ptr::null(),
std::ptr::null(),
0,
10,
0,
Some(dummy_eval_f),
None,
Some(dummy_eval_grad_f),
None,
None,
)
}
}
#[test]
fn create_succeeds_for_unconstrained_problem() {
let p = create_unconstrained();
assert!(!p.is_null());
unsafe { FreeIpoptProblem(p) };
}
#[test]
fn create_returns_null_on_missing_required_callbacks() {
let xl = [-1.0; 4];
let xu = [1.0; 4];
let p = unsafe {
CreateIpoptProblem(
4,
xl.as_ptr(),
xu.as_ptr(),
0,
std::ptr::null(),
std::ptr::null(),
0,
10,
0,
None, None,
Some(dummy_eval_grad_f),
None,
None,
)
};
assert!(p.is_null());
}
#[test]
fn create_returns_null_on_negative_n() {
let p = unsafe {
CreateIpoptProblem(
-1,
std::ptr::null(),
std::ptr::null(),
0,
std::ptr::null(),
std::ptr::null(),
0,
10,
0,
Some(dummy_eval_f),
None,
Some(dummy_eval_grad_f),
None,
None,
)
};
assert!(p.is_null());
}
#[test]
fn create_returns_null_on_invalid_index_style() {
let xl = [0.0; 1];
let xu = [1.0; 1];
let p = unsafe {
CreateIpoptProblem(
1,
xl.as_ptr(),
xu.as_ptr(),
0,
std::ptr::null(),
std::ptr::null(),
0,
1,
2, Some(dummy_eval_f),
None,
Some(dummy_eval_grad_f),
None,
None,
)
};
assert!(p.is_null());
}
#[test]
fn add_int_option_forwards_to_application() {
let p = create_unconstrained();
let key = CString::new("print_level").unwrap();
let ok = unsafe { AddIpoptIntOption(p, key.as_ptr(), 5) };
assert_eq!(ok, TRUE);
let info = unsafe { &*p };
let (level, found) = info
.app
.options()
.get_integer_value("print_level", "")
.unwrap();
assert!(found);
assert_eq!(level, 5);
unsafe { FreeIpoptProblem(p) };
}
#[test]
fn add_str_option_with_invalid_key_returns_false() {
let p = create_unconstrained();
let key = CString::new("totally_unknown_option").unwrap();
let val = CString::new("yes").unwrap();
let ok = unsafe { AddIpoptStrOption(p, key.as_ptr(), val.as_ptr()) };
assert_eq!(ok, FALSE);
unsafe { FreeIpoptProblem(p) };
}
#[test]
fn add_options_on_null_problem_returns_false() {
let key = CString::new("print_level").unwrap();
let v = CString::new("yes").unwrap();
unsafe {
assert_eq!(
AddIpoptIntOption(std::ptr::null_mut(), key.as_ptr(), 5),
FALSE
);
assert_eq!(
AddIpoptNumOption(std::ptr::null_mut(), key.as_ptr(), 1.0),
FALSE
);
assert_eq!(
AddIpoptStrOption(std::ptr::null_mut(), key.as_ptr(), v.as_ptr()),
FALSE
);
}
}
unsafe extern "C" fn dummy_intermediate(
_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: *mut c_void,
) -> Bool {
TRUE
}
#[test]
fn set_intermediate_callback_stores_pointer() {
let p = create_unconstrained();
let ok = unsafe { SetIntermediateCallback(p, Some(dummy_intermediate)) };
assert_eq!(ok, TRUE);
let info = unsafe { &*p };
assert!(info.intermediate_cb.is_some());
unsafe { FreeIpoptProblem(p) };
}
#[test]
fn solve_returns_internal_error_on_null_problem() {
let rc = unsafe {
IpoptSolve(
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
)
};
assert_eq!(rc, -199);
}
#[test]
fn free_null_is_safe() {
unsafe { FreeIpoptProblem(std::ptr::null_mut()) };
}
unsafe extern "C" fn quad_eval_f(
_n: Index,
x: *const Number,
_new_x: Bool,
obj_value: *mut Number,
_user_data: *mut c_void,
) -> Bool {
let v = *x.offset(0);
*obj_value = (v - 2.0) * (v - 2.0);
TRUE
}
unsafe extern "C" fn quad_eval_grad_f(
_n: Index,
x: *const Number,
_new_x: Bool,
grad: *mut Number,
_user_data: *mut c_void,
) -> Bool {
let v = *x.offset(0);
*grad.offset(0) = 2.0 * (v - 2.0);
TRUE
}
unsafe extern "C" fn quad_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: *mut c_void,
) -> Bool {
if !irow.is_null() && !jcol.is_null() && values.is_null() {
*irow.offset(0) = 0;
*jcol.offset(0) = 0;
} else if irow.is_null() && jcol.is_null() && !values.is_null() {
*values.offset(0) = 2.0 * obj_factor;
} else {
return FALSE;
}
TRUE
}
#[test]
fn solve_drives_unconstrained_quadratic_through_bridge() {
let xl = [-1.0e20];
let xu = [1.0e20];
let p = unsafe {
CreateIpoptProblem(
1,
xl.as_ptr(),
xu.as_ptr(),
0,
std::ptr::null(),
std::ptr::null(),
0,
1,
0,
Some(quad_eval_f),
None,
Some(quad_eval_grad_f),
None,
Some(quad_eval_h),
)
};
assert!(!p.is_null());
let mut x = [0.0_f64];
let mut obj = 0.0_f64;
let rc = unsafe {
IpoptSolve(
p,
x.as_mut_ptr(),
std::ptr::null_mut(),
&mut obj,
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
)
};
assert_eq!(rc, ApplicationReturnStatus::SolveSucceeded as Index);
assert!((x[0] - 2.0).abs() < 1e-6, "x[0] = {}", x[0]);
assert!(obj.abs() < 1e-10, "obj = {}", obj);
unsafe { FreeIpoptProblem(p) };
}
#[test]
fn solve_invalid_problem_definition_when_x_null() {
let p = create_unconstrained();
let rc = unsafe {
IpoptSolve(
p,
std::ptr::null_mut(), std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
)
};
assert_eq!(
rc,
ApplicationReturnStatus::InvalidProblemDefinition as Index
);
unsafe { FreeIpoptProblem(p) };
}
#[test]
fn get_version_writes_pkg_version() {
let (mut mj, mut mn, mut pt) = (-1, -1, -1);
unsafe { GetIpoptVersion(&mut mj, &mut mn, &mut pt) };
let expected = parse_pkg_version(env!("CARGO_PKG_VERSION"));
assert_eq!((mj, mn, pt), expected);
}
#[test]
fn get_version_tolerates_null_buffers() {
unsafe {
GetIpoptVersion(
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
)
};
}
#[test]
fn set_scaling_stores_user_supplied_arrays() {
let p = create_unconstrained();
let xs = [2.0, 3.0, 4.0, 5.0];
let ok = unsafe { SetIpoptProblemScaling(p, 7.0, xs.as_ptr(), std::ptr::null()) };
assert_eq!(ok, TRUE);
let info = unsafe { &*p };
let s = info.user_scaling.as_ref().unwrap();
assert_eq!(s.obj_scaling, 7.0);
assert_eq!(s.x_scaling.as_deref(), Some(&xs[..]));
assert!(s.g_scaling.is_none());
unsafe { FreeIpoptProblem(p) };
}
#[test]
fn set_scaling_on_null_problem_returns_false() {
let ok = unsafe {
SetIpoptProblemScaling(
std::ptr::null_mut(),
1.0,
std::ptr::null(),
std::ptr::null(),
)
};
assert_eq!(ok, FALSE);
}
#[test]
fn open_output_file_writes_and_attaches_journal() {
let p = create_unconstrained();
let dir = std::env::temp_dir().join("pounce-cinterface-test");
let _ = std::fs::create_dir_all(&dir);
let path = dir.join("output.log");
let cstr = CString::new(path.to_string_lossy().as_bytes()).unwrap();
let ok = unsafe { OpenIpoptOutputFile(p, cstr.as_ptr(), 5) };
assert_eq!(ok, TRUE);
let info = unsafe { &*p };
let (level, found) = info
.app
.options()
.get_integer_value("file_print_level", "")
.unwrap();
assert!(found);
assert_eq!(level, 5);
unsafe { FreeIpoptProblem(p) };
let _ = std::fs::remove_file(&path);
}
#[test]
fn open_output_file_with_null_inputs_returns_false() {
let key = CString::new("nope").unwrap();
unsafe {
assert_eq!(
OpenIpoptOutputFile(std::ptr::null_mut(), key.as_ptr(), 0),
FALSE
);
}
let p = create_unconstrained();
unsafe {
assert_eq!(OpenIpoptOutputFile(p, std::ptr::null(), 0), FALSE);
FreeIpoptProblem(p);
}
}
#[test]
fn get_current_iterate_returns_false_outside_callback() {
let p = create_unconstrained();
let rc = unsafe {
GetIpoptCurrentIterate(
p,
FALSE,
0,
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
0,
std::ptr::null_mut(),
std::ptr::null_mut(),
)
};
assert_eq!(rc, FALSE);
unsafe { FreeIpoptProblem(p) };
}
#[test]
fn get_current_violations_returns_false_outside_callback() {
let p = create_unconstrained();
let rc = unsafe {
GetIpoptCurrentViolations(
p,
FALSE,
0,
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
0,
std::ptr::null_mut(),
std::ptr::null_mut(),
)
};
assert_eq!(rc, FALSE);
unsafe { FreeIpoptProblem(p) };
}
#[test]
fn post_solve_stats_zero_before_solve() {
let p = create_unconstrained();
unsafe {
assert_eq!(GetIpoptIterCount(p), 0);
assert_eq!(GetIpoptSolveTime(p), 0.0);
assert_eq!(GetIpoptPrimalInf(p), 0.0);
assert_eq!(GetIpoptDualInf(p), 0.0);
assert_eq!(GetIpoptComplInf(p), 0.0);
FreeIpoptProblem(p);
}
}
#[test]
fn post_solve_stats_populated_after_solve() {
let xl = [-1.0e20];
let xu = [1.0e20];
let p = unsafe {
CreateIpoptProblem(
1,
xl.as_ptr(),
xu.as_ptr(),
0,
std::ptr::null(),
std::ptr::null(),
0,
1,
0,
Some(quad_eval_f),
None,
Some(quad_eval_grad_f),
None,
Some(quad_eval_h),
)
};
let mut x = [0.0_f64];
let mut obj = 0.0_f64;
let rc = unsafe {
IpoptSolve(
p,
x.as_mut_ptr(),
std::ptr::null_mut(),
&mut obj,
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
)
};
assert_eq!(rc, ApplicationReturnStatus::SolveSucceeded as Index);
unsafe {
assert!(GetIpoptIterCount(p) >= 0);
assert!(GetIpoptSolveTime(p) >= 0.0);
assert!(GetIpoptPrimalInf(p).is_finite());
assert!(GetIpoptDualInf(p).is_finite());
assert!(GetIpoptComplInf(p).is_finite());
FreeIpoptProblem(p);
}
}
unsafe extern "C" fn cb_quad_eval_g(
_n: Index,
x: *const Number,
_new_x: Bool,
_m: Index,
g: *mut Number,
_user_data: *mut c_void,
) -> Bool {
*g.offset(0) = *x.offset(0);
TRUE
}
unsafe extern "C" fn cb_quad_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: *mut c_void,
) -> Bool {
assert_eq!(nele_jac, 1);
if !irow.is_null() {
*irow.offset(0) = 0;
*jcol.offset(0) = 0;
}
if !values.is_null() {
*values.offset(0) = 1.0;
}
TRUE
}
unsafe extern "C" fn cb_quad_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: *mut c_void,
) -> Bool {
if !irow.is_null() {
*irow.offset(0) = 0;
*jcol.offset(0) = 0;
}
if !values.is_null() {
*values.offset(0) = 2.0 * obj_factor;
}
TRUE
}
fn create_callback_test_problem() -> IpoptProblem {
let xl = [-1.0e20];
let xu = [1.0e20];
let gl = [-10.0];
let gu = [10.0];
unsafe {
CreateIpoptProblem(
1,
xl.as_ptr(),
xu.as_ptr(),
1,
gl.as_ptr(),
gu.as_ptr(),
1,
1,
0,
Some(quad_eval_f),
Some(cb_quad_eval_g),
Some(quad_eval_grad_f),
Some(cb_quad_eval_jac_g),
Some(cb_quad_eval_h),
)
}
}
static CB_ITER_COUNTER: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
static CB_LAST_ITER: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(-1);
static CB_INSPECTOR_OK: std::sync::atomic::AtomicBool =
std::sync::atomic::AtomicBool::new(false);
unsafe extern "C" fn counting_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: *mut c_void,
) -> Bool {
CB_ITER_COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
CB_LAST_ITER.store(iter_count, std::sync::atomic::Ordering::SeqCst);
let problem = user_data as IpoptProblem;
let mut x = [0.0_f64];
let rc = GetIpoptCurrentIterate(
problem,
FALSE,
1,
x.as_mut_ptr(),
std::ptr::null_mut(),
std::ptr::null_mut(),
1,
std::ptr::null_mut(),
std::ptr::null_mut(),
);
if rc == TRUE && x[0].is_finite() {
CB_INSPECTOR_OK.store(true, std::sync::atomic::Ordering::SeqCst);
}
TRUE
}
#[test]
fn intermediate_callback_fires_per_iteration_and_inspector_reads_x() {
CB_ITER_COUNTER.store(0, std::sync::atomic::Ordering::SeqCst);
CB_LAST_ITER.store(-1, std::sync::atomic::Ordering::SeqCst);
CB_INSPECTOR_OK.store(false, std::sync::atomic::Ordering::SeqCst);
let p = create_callback_test_problem();
assert!(!p.is_null());
let ok = unsafe { SetIntermediateCallback(p, Some(counting_cb)) };
assert_eq!(ok, TRUE);
let mut x = [0.0_f64];
let mut obj = 0.0_f64;
let rc = unsafe {
IpoptSolve(
p,
x.as_mut_ptr(),
std::ptr::null_mut(),
&mut obj,
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
p as *mut c_void,
)
};
assert_eq!(rc, ApplicationReturnStatus::SolveSucceeded as Index);
let n_fires = CB_ITER_COUNTER.load(std::sync::atomic::Ordering::SeqCst);
assert!(n_fires >= 2, "callback fired {n_fires} times, want >=2");
assert!(
CB_LAST_ITER.load(std::sync::atomic::Ordering::SeqCst) >= 1,
"last iter should be >= 1 after at least one accepted step"
);
assert!(
CB_INSPECTOR_OK.load(std::sync::atomic::Ordering::SeqCst),
"GetIpoptCurrentIterate did not return a usable x"
);
unsafe { FreeIpoptProblem(p) };
}
unsafe extern "C" fn user_stop_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: *mut c_void,
) -> Bool {
FALSE
}
#[test]
fn intermediate_callback_false_surfaces_user_requested_stop() {
let p = create_callback_test_problem();
assert!(!p.is_null());
let ok = unsafe { SetIntermediateCallback(p, Some(user_stop_cb)) };
assert_eq!(ok, TRUE);
let mut x = [0.0_f64];
let rc = unsafe {
IpoptSolve(
p,
x.as_mut_ptr(),
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
)
};
assert_eq!(rc, ApplicationReturnStatus::UserRequestedStop as Index);
unsafe { FreeIpoptProblem(p) };
}
#[test]
fn parse_pkg_version_handles_missing_components() {
assert_eq!(parse_pkg_version("1.2.3"), (1, 2, 3));
assert_eq!(parse_pkg_version("4.5"), (4, 5, 0));
assert_eq!(parse_pkg_version(""), (0, 0, 0));
assert_eq!(parse_pkg_version("1.x.3"), (1, 0, 3));
}
use crate::solver::{
IpoptCreateSolver, IpoptFreeSolver, IpoptSolverGetKktDim, IpoptSolverKktSolve,
IpoptSolverSolve,
};
#[test]
fn solver_create_consumes_problem_handle() {
let mut p = create_unconstrained();
assert!(!p.is_null());
let s = unsafe { IpoptCreateSolver(&mut p) };
assert!(!s.is_null());
assert!(
p.is_null(),
"IpoptCreateSolver should NULL out the caller's handle"
);
unsafe { IpoptFreeSolver(s) };
}
#[test]
fn solver_create_null_inputs_return_null() {
let s = unsafe { IpoptCreateSolver(std::ptr::null_mut()) };
assert!(s.is_null());
let mut p: IpoptProblem = std::ptr::null_mut();
let s = unsafe { IpoptCreateSolver(&mut p) };
assert!(s.is_null());
}
#[test]
fn solver_free_null_is_safe() {
unsafe { IpoptFreeSolver(std::ptr::null_mut()) };
}
#[test]
fn solver_solve_drives_quadratic_and_retains_factor() {
let xl = [-1.0e20];
let xu = [1.0e20];
let mut p = unsafe {
CreateIpoptProblem(
1,
xl.as_ptr(),
xu.as_ptr(),
0,
std::ptr::null(),
std::ptr::null(),
0,
1,
0,
Some(quad_eval_f),
None,
Some(quad_eval_grad_f),
None,
Some(quad_eval_h),
)
};
assert!(!p.is_null());
let s = unsafe { IpoptCreateSolver(&mut p) };
assert!(!s.is_null());
let mut x = [0.0_f64];
let mut obj = 0.0_f64;
let rc = unsafe {
IpoptSolverSolve(
s,
x.as_mut_ptr(),
std::ptr::null_mut(),
&mut obj,
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
)
};
assert_eq!(rc, ApplicationReturnStatus::SolveSucceeded as Index);
assert!((x[0] - 2.0).abs() < 1e-6);
assert!(obj.abs() < 1e-10);
let dim = unsafe { IpoptSolverGetKktDim(s) };
assert!(dim > 0, "expected positive KKT dim, got {dim}");
let rhs = vec![0.0_f64; dim as usize];
let mut lhs = vec![1.0_f64; dim as usize];
let ok = unsafe { IpoptSolverKktSolve(s, rhs.as_ptr(), lhs.as_mut_ptr()) };
assert_eq!(ok, TRUE);
for (i, v) in lhs.iter().enumerate() {
assert!(v.abs() < 1e-10, "lhs[{i}] = {v} not ~0");
}
unsafe { IpoptFreeSolver(s) };
}
#[test]
fn solver_kkt_dim_minus_one_before_solve() {
let mut p = create_unconstrained();
let s = unsafe { IpoptCreateSolver(&mut p) };
assert_eq!(unsafe { IpoptSolverGetKktDim(s) }, -1);
unsafe { IpoptFreeSolver(s) };
}
}