use std::ffi::CStr;
use std::os::raw::{c_char, c_double, c_int};
use crate::options::SolverOptions;
use crate::problem::NlpProblem;
use crate::result::{SolveResult, SolveStatus};
pub type EvalFCb = unsafe extern "C" fn(
n: c_int,
x: *const c_double,
new_x: c_int,
obj_value: *mut c_double,
user_data: *mut std::ffi::c_void,
) -> c_int;
pub type EvalGradFCb = unsafe extern "C" fn(
n: c_int,
x: *const c_double,
new_x: c_int,
grad_f: *mut c_double,
user_data: *mut std::ffi::c_void,
) -> c_int;
pub type EvalGCb = unsafe extern "C" fn(
n: c_int,
x: *const c_double,
new_x: c_int,
m: c_int,
g: *mut c_double,
user_data: *mut std::ffi::c_void,
) -> c_int;
pub type EvalJacGCb = unsafe extern "C" fn(
n: c_int,
x: *const c_double,
new_x: c_int,
m: c_int,
nele_jac: c_int,
i_row: *mut c_int,
j_col: *mut c_int,
values: *mut c_double,
user_data: *mut std::ffi::c_void,
) -> c_int;
pub type EvalHCb = unsafe extern "C" fn(
n: c_int,
x: *const c_double,
new_x: c_int,
obj_factor: c_double,
m: c_int,
lambda: *const c_double,
new_lambda: c_int,
nele_hess: c_int,
i_row: *mut c_int,
j_col: *mut c_int,
values: *mut c_double,
user_data: *mut std::ffi::c_void,
) -> c_int;
pub struct CApiProblem {
n: usize,
m: usize,
x_l: Vec<f64>,
x_u: Vec<f64>,
g_l: Vec<f64>,
g_u: Vec<f64>,
nele_jac: usize,
nele_hess: usize,
eval_f: EvalFCb,
eval_grad_f: EvalGradFCb,
eval_g: EvalGCb,
eval_jac_g: EvalJacGCb,
eval_h: EvalHCb,
options: SolverOptions,
initial_x: Vec<f64>,
user_data: *mut std::ffi::c_void,
log_cb: Option<crate::logging::LogCb>,
log_cb_user_data: *mut std::ffi::c_void,
last_iterations: usize,
last_obj: f64,
last_primal_inf: f64,
last_dual_inf: f64,
last_compl: f64,
last_wall_time: f64,
}
unsafe impl Send for CApiProblem {}
unsafe impl Sync for CApiProblem {}
impl NlpProblem for CApiProblem {
fn num_variables(&self) -> usize {
self.n
}
fn num_constraints(&self) -> usize {
self.m
}
fn bounds(&self, x_l: &mut [f64], x_u: &mut [f64]) {
x_l.copy_from_slice(&self.x_l);
x_u.copy_from_slice(&self.x_u);
}
fn constraint_bounds(&self, g_l: &mut [f64], g_u: &mut [f64]) {
g_l.copy_from_slice(&self.g_l);
g_u.copy_from_slice(&self.g_u);
}
fn initial_point(&self, x0: &mut [f64]) {
x0.copy_from_slice(&self.initial_x);
}
fn objective(&self, x: &[f64]) -> f64 {
let mut obj = 0.0_f64;
let ok = unsafe {
(self.eval_f)(
self.n as c_int,
x.as_ptr(),
1, &mut obj,
self.user_data,
)
};
if ok == 0 {
f64::NAN
} else {
obj
}
}
fn gradient(&self, x: &[f64], grad: &mut [f64]) {
unsafe {
(self.eval_grad_f)(
self.n as c_int,
x.as_ptr(),
1,
grad.as_mut_ptr(),
self.user_data,
);
}
}
fn constraints(&self, x: &[f64], g: &mut [f64]) {
unsafe {
(self.eval_g)(
self.n as c_int,
x.as_ptr(),
1,
self.m as c_int,
g.as_mut_ptr(),
self.user_data,
);
}
}
fn jacobian_structure(&self) -> (Vec<usize>, Vec<usize>) {
let nnz = self.nele_jac;
let mut i_row = vec![0_i32; nnz];
let mut j_col = vec![0_i32; nnz];
unsafe {
(self.eval_jac_g)(
self.n as c_int,
std::ptr::null(),
1,
self.m as c_int,
nnz as c_int,
i_row.as_mut_ptr(),
j_col.as_mut_ptr(),
std::ptr::null_mut(),
self.user_data,
);
}
(
i_row.into_iter().map(|v| v as usize).collect(),
j_col.into_iter().map(|v| v as usize).collect(),
)
}
fn jacobian_values(&self, x: &[f64], vals: &mut [f64]) {
let nnz = self.nele_jac;
unsafe {
(self.eval_jac_g)(
self.n as c_int,
x.as_ptr(),
1,
self.m as c_int,
nnz as c_int,
std::ptr::null_mut(),
std::ptr::null_mut(),
vals.as_mut_ptr(),
self.user_data,
);
}
}
fn hessian_structure(&self) -> (Vec<usize>, Vec<usize>) {
let nnz = self.nele_hess;
let mut i_row = vec![0_i32; nnz];
let mut j_col = vec![0_i32; nnz];
unsafe {
(self.eval_h)(
self.n as c_int,
std::ptr::null(),
1,
1.0,
self.m as c_int,
std::ptr::null(),
1,
nnz as c_int,
i_row.as_mut_ptr(),
j_col.as_mut_ptr(),
std::ptr::null_mut(),
self.user_data,
);
}
(
i_row.into_iter().map(|v| v as usize).collect(),
j_col.into_iter().map(|v| v as usize).collect(),
)
}
fn hessian_values(&self, x: &[f64], obj_factor: f64, lambda: &[f64], vals: &mut [f64]) {
let nnz = self.nele_hess;
unsafe {
(self.eval_h)(
self.n as c_int,
x.as_ptr(),
1,
obj_factor,
self.m as c_int,
lambda.as_ptr(),
1,
nnz as c_int,
std::ptr::null_mut(),
std::ptr::null_mut(),
vals.as_mut_ptr(),
self.user_data,
);
}
}
}
#[repr(C)]
pub enum RipoptReturnStatus {
SolveSucceeded = 0,
InfeasibleProblem = 2,
MaxIterExceeded = 5,
RestorationFailed = 6,
ErrorInStepComputation = 7,
NotEnoughDegreesOfFreedom = 10,
InvalidProblemDefinition = 11,
InternalError = -1,
}
fn map_status(s: SolveStatus) -> RipoptReturnStatus {
match s {
SolveStatus::Optimal => RipoptReturnStatus::SolveSucceeded,
SolveStatus::Infeasible | SolveStatus::LocalInfeasibility => {
RipoptReturnStatus::InfeasibleProblem
}
SolveStatus::MaxIterations => RipoptReturnStatus::MaxIterExceeded,
SolveStatus::RestorationFailed => RipoptReturnStatus::RestorationFailed,
SolveStatus::NumericalError => RipoptReturnStatus::ErrorInStepComputation,
SolveStatus::Unbounded => RipoptReturnStatus::InvalidProblemDefinition,
SolveStatus::InternalError => RipoptReturnStatus::InternalError,
}
}
#[no_mangle]
pub unsafe extern "C" fn ripopt_create(
n: c_int,
x_l: *const c_double,
x_u: *const c_double,
m: c_int,
g_l: *const c_double,
g_u: *const c_double,
nele_jac: c_int,
nele_hess: c_int,
eval_f: EvalFCb,
eval_grad_f: EvalGradFCb,
eval_g: EvalGCb,
eval_jac_g: EvalJacGCb,
eval_h: EvalHCb,
) -> *mut CApiProblem {
let n = n as usize;
let m = m as usize;
let (g_l_vec, g_u_vec) = if m > 0 {
(
std::slice::from_raw_parts(g_l, m).to_vec(),
std::slice::from_raw_parts(g_u, m).to_vec(),
)
} else {
(vec![], vec![])
};
let problem = Box::new(CApiProblem {
n,
m,
x_l: std::slice::from_raw_parts(x_l, n).to_vec(),
x_u: std::slice::from_raw_parts(x_u, n).to_vec(),
g_l: g_l_vec,
g_u: g_u_vec,
nele_jac: nele_jac as usize,
nele_hess: nele_hess as usize,
eval_f,
eval_grad_f,
eval_g,
eval_jac_g,
eval_h,
options: SolverOptions::default(),
initial_x: vec![0.0; n],
user_data: std::ptr::null_mut(),
log_cb: None,
log_cb_user_data: std::ptr::null_mut(),
last_iterations: 0,
last_obj: 0.0,
last_primal_inf: 0.0,
last_dual_inf: 0.0,
last_compl: 0.0,
last_wall_time: 0.0,
});
Box::into_raw(problem)
}
#[no_mangle]
pub unsafe extern "C" fn ripopt_free(problem: *mut CApiProblem) {
if !problem.is_null() {
drop(Box::from_raw(problem));
}
}
#[no_mangle]
pub unsafe extern "C" fn ripopt_add_num_option(
problem: *mut CApiProblem,
keyword: *const c_char,
val: c_double,
) -> c_int {
let p = &mut *problem;
let key = match CStr::from_ptr(keyword).to_str() {
Ok(s) => s,
Err(_) => return 0,
};
match key {
"tol" => p.options.tol = val,
"mu_init" => p.options.mu_init = val,
"mu_min" => p.options.mu_min = val,
"tau_min" => p.options.tau_min = val,
"mu_linear_decrease_factor" => p.options.mu_linear_decrease_factor = val,
"mu_superlinear_decrease_power" => p.options.mu_superlinear_decrease_power = val,
"bound_push" => p.options.bound_push = val,
"bound_frac" => p.options.bound_frac = val,
"slack_bound_push" => p.options.slack_bound_push = val,
"slack_bound_frac" => p.options.slack_bound_frac = val,
"constr_viol_tol" => p.options.constr_viol_tol = val,
"dual_inf_tol" => p.options.dual_inf_tol = val,
"compl_inf_tol" => p.options.compl_inf_tol = val,
"warm_start_bound_push" => p.options.warm_start_bound_push = val,
"warm_start_bound_frac" => p.options.warm_start_bound_frac = val,
"warm_start_mult_bound_push" => p.options.warm_start_mult_bound_push = val,
"nlp_lower_bound_inf" => p.options.nlp_lower_bound_inf = val,
"nlp_upper_bound_inf" => p.options.nlp_upper_bound_inf = val,
"kappa" => p.options.kappa = val,
"constr_mult_init_max" => p.options.constr_mult_init_max = val,
"max_wall_time" => p.options.max_wall_time = val,
"barrier_tol_factor" => p.options.barrier_tol_factor = val,
"adaptive_mu_monotone_init_factor" => p.options.adaptive_mu_monotone_init_factor = val,
_ => return 0,
}
1
}
#[no_mangle]
pub unsafe extern "C" fn ripopt_add_int_option(
problem: *mut CApiProblem,
keyword: *const c_char,
val: c_int,
) -> c_int {
let p = &mut *problem;
let key = match CStr::from_ptr(keyword).to_str() {
Ok(s) => s,
Err(_) => return 0,
};
match key {
"max_iter" => p.options.max_iter = val as usize,
"print_level" => p.options.print_level = val.clamp(0, 12) as u8,
"max_soc" => p.options.max_soc = val as usize,
"watchdog_shortened_iter_trigger" => {
p.options.watchdog_shortened_iter_trigger = val as usize
}
"watchdog_trial_iter_max" => p.options.watchdog_trial_iter_max = val as usize,
"sparse_threshold" => p.options.sparse_threshold = val as usize,
"restoration_max_iter" => p.options.restoration_max_iter = val as usize,
"gondzio_mcc_max" => p.options.gondzio_mcc_max = val as usize,
_ => return 0,
}
1
}
#[no_mangle]
pub unsafe extern "C" fn ripopt_add_str_option(
problem: *mut CApiProblem,
keyword: *const c_char,
val: *const c_char,
) -> c_int {
let p = &mut *problem;
let key = match CStr::from_ptr(keyword).to_str() {
Ok(s) => s,
Err(_) => return 0,
};
let value = match CStr::from_ptr(val).to_str() {
Ok(s) => s,
Err(_) => return 0,
};
match key {
"mu_strategy" => {
p.options.mu_strategy_adaptive = value == "adaptive";
}
"warm_start_init_point" => {
p.options.warm_start = value == "yes";
}
"mu_allow_increase" => {
p.options.mu_allow_increase = value == "yes";
}
"least_squares_mult_init" => {
p.options.least_squares_mult_init = value == "yes";
}
"constraint_slack_barrier" => {
p.options.constraint_slack_barrier = value == "yes";
}
"disable_nlp_restoration" => {
p.options.disable_nlp_restoration = value == "yes";
}
"enable_slack_fallback" => {
p.options.enable_slack_fallback = value == "yes";
}
"enable_lbfgs_fallback" => {
p.options.enable_lbfgs_fallback = value == "yes";
}
"enable_al_fallback" => {
p.options.enable_al_fallback = value == "yes";
}
"enable_preprocessing" => {
p.options.enable_preprocessing = value == "yes";
}
"detect_linear_constraints" => {
p.options.detect_linear_constraints = value == "yes";
}
"enable_sqp_fallback" => {
p.options.enable_sqp_fallback = value == "yes";
}
"hessian_approximation" => {
match value {
"limited-memory" => p.options.hessian_approximation_lbfgs = true,
"exact" => p.options.hessian_approximation_lbfgs = false,
_ => return 0,
}
}
"enable_lbfgs_hessian_fallback" => {
p.options.enable_lbfgs_hessian_fallback = value == "yes";
}
"mehrotra_pc" => {
p.options.mehrotra_pc = value == "yes";
}
"proactive_infeasibility_detection" => {
p.options.proactive_infeasibility_detection = value == "yes";
}
_ => return 0,
}
1
}
#[no_mangle]
pub unsafe extern "C" fn ripopt_solve(
problem: *mut CApiProblem,
x: *mut c_double,
g: *mut c_double,
obj_val: *mut c_double,
mult_g: *mut c_double,
mult_x_l: *mut c_double,
mult_x_u: *mut c_double,
user_data: *mut std::ffi::c_void,
) -> c_int {
let p = &mut *problem;
p.user_data = user_data;
p.initial_x
.copy_from_slice(std::slice::from_raw_parts(x, p.n));
if let Some(cb) = p.log_cb {
crate::logging::set_log_callback(Some((cb, p.log_cb_user_data)));
}
let result: SolveResult = crate::solve(p, &p.options.clone());
crate::logging::set_log_callback(None);
p.last_iterations = result.iterations;
p.last_obj = result.objective;
p.last_primal_inf = result.diagnostics.final_primal_inf;
p.last_dual_inf = result.diagnostics.final_dual_inf;
p.last_compl = result.diagnostics.final_compl;
p.last_wall_time = result.diagnostics.wall_time_secs;
std::slice::from_raw_parts_mut(x, p.n).copy_from_slice(&result.x);
if !obj_val.is_null() {
*obj_val = result.objective;
}
if !g.is_null() {
std::slice::from_raw_parts_mut(g, p.m).copy_from_slice(&result.constraint_values);
}
if !mult_g.is_null() {
std::slice::from_raw_parts_mut(mult_g, p.m)
.copy_from_slice(&result.constraint_multipliers);
}
if !mult_x_l.is_null() {
std::slice::from_raw_parts_mut(mult_x_l, p.n)
.copy_from_slice(&result.bound_multipliers_lower);
}
if !mult_x_u.is_null() {
std::slice::from_raw_parts_mut(mult_x_u, p.n)
.copy_from_slice(&result.bound_multipliers_upper);
}
map_status(result.status) as c_int
}
#[no_mangle]
pub unsafe extern "C" fn ripopt_set_log_callback(
problem: *mut CApiProblem,
callback: Option<crate::logging::LogCb>,
user_data: *mut std::ffi::c_void,
) {
let p = &mut *problem;
p.log_cb = callback;
p.log_cb_user_data = user_data;
}
#[no_mangle]
pub unsafe extern "C" fn ripopt_get_iter_count(problem: *const CApiProblem) -> c_int {
(*problem).last_iterations as c_int
}
#[no_mangle]
pub unsafe extern "C" fn ripopt_get_solve_time(problem: *const CApiProblem) -> c_double {
(*problem).last_wall_time
}
#[no_mangle]
pub unsafe extern "C" fn ripopt_get_primal_inf(problem: *const CApiProblem) -> c_double {
(*problem).last_primal_inf
}
#[no_mangle]
pub unsafe extern "C" fn ripopt_get_dual_inf(problem: *const CApiProblem) -> c_double {
(*problem).last_dual_inf
}
#[no_mangle]
pub unsafe extern "C" fn ripopt_get_compl_inf(problem: *const CApiProblem) -> c_double {
(*problem).last_compl
}