use std::ffi::CStr;
use std::io::Write;
use std::os::raw::{c_char, c_double, c_int, c_void};
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 type IntermediateCb = unsafe extern "C" fn(
alg_mod: c_int, iter: c_int,
obj_value: c_double,
inf_pr: c_double,
inf_du: c_double,
mu: c_double,
d_norm: c_double, regularization_size: c_double, alpha_du: c_double,
alpha_pr: c_double,
ls_trials: c_int,
user_data: *mut 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,
index_style: 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,
log_file: Option<Box<std::fs::File>>,
intermediate_cb: Option<IntermediateCb>,
intermediate_cb_user_data: *mut c_void,
initial_y: Vec<f64>,
initial_z_l: Vec<f64>,
initial_z_u: Vec<f64>,
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], new_x: bool, obj: &mut f64) -> bool {
let ok = unsafe {
(self.eval_f)(
self.n as c_int,
x.as_ptr(),
new_x as c_int,
obj,
self.user_data,
)
};
ok != 0
}
fn gradient(&self, x: &[f64], new_x: bool, grad: &mut [f64]) -> bool {
let ok = unsafe {
(self.eval_grad_f)(
self.n as c_int,
x.as_ptr(),
new_x as c_int,
grad.as_mut_ptr(),
self.user_data,
)
};
ok != 0
}
fn constraints(&self, x: &[f64], new_x: bool, g: &mut [f64]) -> bool {
let ok = unsafe {
(self.eval_g)(
self.n as c_int,
x.as_ptr(),
new_x as c_int,
self.m as c_int,
g.as_mut_ptr(),
self.user_data,
)
};
ok != 0
}
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,
);
}
let offset = self.index_style; (
i_row.into_iter().map(|v| v as usize - offset).collect(),
j_col.into_iter().map(|v| v as usize - offset).collect(),
)
}
fn jacobian_values(&self, x: &[f64], new_x: bool, vals: &mut [f64]) -> bool {
let nnz = self.nele_jac;
let ok = unsafe {
(self.eval_jac_g)(
self.n as c_int,
x.as_ptr(),
new_x as c_int,
self.m as c_int,
nnz as c_int,
std::ptr::null_mut(),
std::ptr::null_mut(),
vals.as_mut_ptr(),
self.user_data,
)
};
ok != 0
}
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,
);
}
let offset = self.index_style; (
i_row.into_iter().map(|v| v as usize - offset).collect(),
j_col.into_iter().map(|v| v as usize - offset).collect(),
)
}
fn hessian_values(&self, x: &[f64], new_x: bool, obj_factor: f64, lambda: &[f64], vals: &mut [f64]) -> bool {
let nnz = self.nele_hess;
let ok = unsafe {
(self.eval_h)(
self.n as c_int,
x.as_ptr(),
new_x as c_int,
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,
)
};
ok != 0
}
}
#[repr(C)]
pub enum RipoptReturnStatus {
SolveSucceeded = 0,
SolvedToAcceptableLevel = 1,
InfeasibleProblem = 2,
SearchDirectionTooSmall = 3,
DivergingIterates = 4,
UserRequestedStop = 5,
MaxIterExceeded = -1,
RestorationFailed = -2,
ErrorInStepComputation = -3,
MaxWallTimeExceeded = -5,
NotEnoughDegreesOfFreedom = -10,
InvalidProblemDefinition = -11,
InvalidNumberDetected = -13,
InternalError = -199,
}
fn map_status(s: SolveStatus) -> RipoptReturnStatus {
match s {
SolveStatus::Optimal => RipoptReturnStatus::SolveSucceeded,
SolveStatus::Acceptable => RipoptReturnStatus::SolvedToAcceptableLevel,
SolveStatus::Infeasible | SolveStatus::LocalInfeasibility => {
RipoptReturnStatus::InfeasibleProblem
}
SolveStatus::MaxIterations => RipoptReturnStatus::MaxIterExceeded,
SolveStatus::RestorationFailed => RipoptReturnStatus::RestorationFailed,
SolveStatus::NumericalError => RipoptReturnStatus::ErrorInStepComputation,
SolveStatus::EvaluationError => RipoptReturnStatus::InvalidNumberDetected,
SolveStatus::UserRequestedStop => RipoptReturnStatus::UserRequestedStop,
SolveStatus::Unbounded => RipoptReturnStatus::DivergingIterates,
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,
index_style: 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,
index_style: index_style 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(),
log_file: None,
intermediate_cb: None,
intermediate_cb_user_data: std::ptr::null_mut(),
initial_y: vec![0.0; m],
initial_z_l: vec![0.0; n],
initial_z_u: vec![0.0; n],
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 p.options.warm_start {
if !mult_g.is_null() && p.m > 0 {
p.initial_y.copy_from_slice(std::slice::from_raw_parts(mult_g, p.m));
}
if !mult_x_l.is_null() {
p.initial_z_l.copy_from_slice(std::slice::from_raw_parts(mult_x_l, p.n));
}
if !mult_x_u.is_null() {
p.initial_z_u.copy_from_slice(std::slice::from_raw_parts(mult_x_u, p.n));
}
}
if let Some(cb) = p.log_cb {
crate::logging::set_log_callback(Some((cb, p.log_cb_user_data)));
}
if let Some(cb) = p.intermediate_cb {
crate::intermediate::set_intermediate_callback(Some((cb, p.intermediate_cb_user_data)));
}
let mut opts = p.options.clone();
if opts.warm_start {
if p.initial_y.iter().any(|&v| v != 0.0) {
opts.warm_start_y = Some(p.initial_y.clone());
}
if p.initial_z_l.iter().any(|&v| v != 0.0) {
opts.warm_start_z_l = Some(p.initial_z_l.clone());
}
if p.initial_z_u.iter().any(|&v| v != 0.0) {
opts.warm_start_z_u = Some(p.initial_z_u.clone());
}
}
let result: SolveResult = crate::solve(p, &opts);
crate::logging::set_log_callback(None);
crate::intermediate::set_intermediate_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
}
unsafe extern "C" fn file_log_callback(msg: *const c_char, user_data: *mut c_void) {
let file = &mut *(user_data as *mut std::fs::File);
if let Ok(s) = CStr::from_ptr(msg).to_str() {
let _ = writeln!(file, "{}", s);
}
}
#[no_mangle]
pub unsafe extern "C" fn ripopt_open_output_file(
problem: *mut CApiProblem,
filename: *const c_char,
print_level: c_int,
) -> c_int {
let p = &mut *problem;
let path = match CStr::from_ptr(filename).to_str() {
Ok(s) => s,
Err(_) => return 0,
};
match std::fs::File::create(path) {
Ok(file) => {
let boxed = Box::new(file);
let ptr = &*boxed as *const std::fs::File as *mut c_void;
p.log_file = Some(boxed);
p.log_cb = Some(file_log_callback);
p.log_cb_user_data = ptr;
p.options.print_level = print_level.clamp(0, 12) as u8;
1
}
Err(_) => 0,
}
}
#[no_mangle]
pub unsafe extern "C" fn ripopt_set_intermediate_callback(
problem: *mut CApiProblem,
callback: Option<IntermediateCb>,
user_data: *mut c_void,
) {
let p = &mut *problem;
p.intermediate_cb = callback;
p.intermediate_cb_user_data = user_data;
}
#[no_mangle]
pub unsafe extern "C" fn ripopt_set_scaling(
problem: *mut CApiProblem,
obj_scaling: c_double,
x_scaling: *const c_double,
g_scaling: *const c_double,
) {
let p = &mut *problem;
p.options.user_obj_scaling = Some(obj_scaling);
if !x_scaling.is_null() && p.n > 0 {
p.options.user_x_scaling = Some(
std::slice::from_raw_parts(x_scaling, p.n).to_vec(),
);
}
if !g_scaling.is_null() && p.m > 0 {
p.options.user_g_scaling = Some(
std::slice::from_raw_parts(g_scaling, p.m).to_vec(),
);
}
}
#[no_mangle]
pub unsafe extern "C" fn ripopt_get_current_iterate(
_problem: *const CApiProblem,
n: c_int,
x: *mut c_double,
z_l: *mut c_double,
z_u: *mut c_double,
m: c_int,
g: *mut c_double,
lambda: *mut c_double,
) -> c_int {
let n = n as usize;
let m = m as usize;
crate::intermediate::with_current_iterate(|snap| {
if !x.is_null() && snap.x.len() >= n {
std::slice::from_raw_parts_mut(x, n).copy_from_slice(&snap.x[..n]);
}
if !z_l.is_null() && snap.z_l.len() >= n {
std::slice::from_raw_parts_mut(z_l, n).copy_from_slice(&snap.z_l[..n]);
}
if !z_u.is_null() && snap.z_u.len() >= n {
std::slice::from_raw_parts_mut(z_u, n).copy_from_slice(&snap.z_u[..n]);
}
if !g.is_null() && snap.g.len() >= m {
std::slice::from_raw_parts_mut(g, m).copy_from_slice(&snap.g[..m]);
}
if !lambda.is_null() && snap.lambda.len() >= m {
std::slice::from_raw_parts_mut(lambda, m).copy_from_slice(&snap.lambda[..m]);
}
})
.map(|_| 1)
.unwrap_or(0)
}
#[no_mangle]
pub unsafe extern "C" fn ripopt_get_current_violations(
_problem: *const CApiProblem,
n: c_int,
x_l_violation: *mut c_double,
x_u_violation: *mut c_double,
compl_x_l: *mut c_double,
compl_x_u: *mut c_double,
grad_lag_x: *mut c_double,
m: c_int,
constraint_violation: *mut c_double,
compl_g: *mut c_double,
) -> c_int {
let n = n as usize;
let m = m as usize;
crate::intermediate::with_current_iterate(|snap| {
if !x_l_violation.is_null() && snap.x_l_violation.len() >= n {
std::slice::from_raw_parts_mut(x_l_violation, n).copy_from_slice(&snap.x_l_violation[..n]);
}
if !x_u_violation.is_null() && snap.x_u_violation.len() >= n {
std::slice::from_raw_parts_mut(x_u_violation, n).copy_from_slice(&snap.x_u_violation[..n]);
}
if !compl_x_l.is_null() && snap.compl_x_l.len() >= n {
std::slice::from_raw_parts_mut(compl_x_l, n).copy_from_slice(&snap.compl_x_l[..n]);
}
if !compl_x_u.is_null() && snap.compl_x_u.len() >= n {
std::slice::from_raw_parts_mut(compl_x_u, n).copy_from_slice(&snap.compl_x_u[..n]);
}
if !grad_lag_x.is_null() && snap.grad_lag_x.len() >= n {
std::slice::from_raw_parts_mut(grad_lag_x, n).copy_from_slice(&snap.grad_lag_x[..n]);
}
if !constraint_violation.is_null() && snap.constraint_violation.len() >= m {
std::slice::from_raw_parts_mut(constraint_violation, m).copy_from_slice(&snap.constraint_violation[..m]);
}
if !compl_g.is_null() && snap.compl_g.len() >= m {
std::slice::from_raw_parts_mut(compl_g, m).copy_from_slice(&snap.compl_g[..m]);
}
})
.map(|_| 1)
.unwrap_or(0)
}
#[no_mangle]
pub unsafe extern "C" fn ripopt_get_version(
major: *mut c_int,
minor: *mut c_int,
patch: *mut c_int,
) {
if !major.is_null() {
*major = env!("CARGO_PKG_VERSION_MAJOR").parse().unwrap_or(0);
}
if !minor.is_null() {
*minor = env!("CARGO_PKG_VERSION_MINOR").parse().unwrap_or(0);
}
if !patch.is_null() {
*patch = env!("CARGO_PKG_VERSION_PATCH").parse().unwrap_or(0);
}
}