1#![allow(non_camel_case_types, non_snake_case)]
31#![allow(unsafe_op_in_unsafe_fn, dead_code)]
32#![cfg_attr(test, allow(clippy::unwrap_used, clippy::expect_used))]
33
34pub mod fortran;
35pub mod solver;
36
37use pounce_algorithm::alg_builder::AlgorithmBuilder;
38use pounce_algorithm::application::{
39 default_backend_factory, feral_config_from_options, IpoptApplication,
40};
41use pounce_algorithm::intermediate as ip_intermediate;
42use pounce_nlp::return_codes::ApplicationReturnStatus;
43use pounce_nlp::solve_statistics::SolveStatistics;
44use pounce_nlp::tnlp::{
45 BoundsInfo, IndexStyle, IpoptCq, IpoptData, NlpInfo, ScalingRequest, Solution, SparsityRequest,
46 StartingPoint, TNLP,
47};
48use pounce_restoration::resto_alg_builder::RestoAlgorithmBuilder;
49use pounce_restoration::resto_inner_solver::{
50 make_default_restoration_factory, InnerBackendFactoryFactory,
51};
52use std::cell::RefCell;
53use std::ffi::{c_char, c_int, c_void, CStr};
54use std::rc::Rc;
55
56pub type Number = f64;
58pub type Index = c_int;
60pub type Bool = c_int;
62
63const TRUE: Bool = 1;
64const FALSE: Bool = 0;
65
66pub struct IpoptProblemInfo {
69 pub(crate) app: IpoptApplication,
70 pub(crate) n: Index,
71 pub(crate) m: Index,
72 pub(crate) nele_jac: Index,
73 pub(crate) nele_hess: Index,
74 pub(crate) index_style: Index,
75 pub(crate) x_l: Vec<Number>,
76 pub(crate) x_u: Vec<Number>,
77 pub(crate) g_l: Vec<Number>,
78 pub(crate) g_u: Vec<Number>,
79 pub(crate) eval_f: Option<Eval_F_CB>,
80 pub(crate) eval_g: Option<Eval_G_CB>,
81 pub(crate) eval_grad_f: Option<Eval_Grad_F_CB>,
82 pub(crate) eval_jac_g: Option<Eval_Jac_G_CB>,
83 pub(crate) eval_h: Option<Eval_H_CB>,
84 pub(crate) intermediate_cb: Option<Intermediate_CB>,
85 pub(crate) user_scaling: Option<UserScaling>,
89 pub(crate) last_solve: Option<LastSolve>,
93}
94
95#[derive(Clone)]
98pub(crate) struct UserScaling {
99 obj_scaling: Number,
100 x_scaling: Option<Vec<Number>>,
101 g_scaling: Option<Vec<Number>>,
102}
103
104#[derive(Clone, Default)]
107pub(crate) struct LastSolve {
108 pub(crate) stats: SolveStatistics,
109}
110
111pub type IpoptProblem = *mut IpoptProblemInfo;
112
113pub type Eval_F_CB = unsafe extern "C" fn(
117 n: Index,
118 x: *const Number,
119 new_x: Bool,
120 obj_value: *mut Number,
121 user_data: *mut c_void,
122) -> Bool;
123
124pub type Eval_Grad_F_CB = unsafe extern "C" fn(
125 n: Index,
126 x: *const Number,
127 new_x: Bool,
128 grad_f: *mut Number,
129 user_data: *mut c_void,
130) -> Bool;
131
132pub type Eval_G_CB = unsafe extern "C" fn(
133 n: Index,
134 x: *const Number,
135 new_x: Bool,
136 m: Index,
137 g: *mut Number,
138 user_data: *mut c_void,
139) -> Bool;
140
141pub type Eval_Jac_G_CB = unsafe extern "C" fn(
142 n: Index,
143 x: *const Number,
144 new_x: Bool,
145 m: Index,
146 nele_jac: Index,
147 iRow: *mut Index,
148 jCol: *mut Index,
149 values: *mut Number,
150 user_data: *mut c_void,
151) -> Bool;
152
153pub type Eval_H_CB = unsafe extern "C" fn(
154 n: Index,
155 x: *const Number,
156 new_x: Bool,
157 obj_factor: Number,
158 m: Index,
159 lambda: *const Number,
160 new_lambda: Bool,
161 nele_hess: Index,
162 iRow: *mut Index,
163 jCol: *mut Index,
164 values: *mut Number,
165 user_data: *mut c_void,
166) -> Bool;
167
168pub type Intermediate_CB = unsafe extern "C" fn(
169 alg_mod: Index,
170 iter_count: Index,
171 obj_value: Number,
172 inf_pr: Number,
173 inf_du: Number,
174 mu: Number,
175 d_norm: Number,
176 regularization_size: Number,
177 alpha_du: Number,
178 alpha_pr: Number,
179 ls_trials: Index,
180 user_data: *mut c_void,
181) -> Bool;
182
183#[no_mangle]
194pub unsafe extern "C" fn CreateIpoptProblem(
195 n: Index,
196 x_L: *const Number,
197 x_U: *const Number,
198 m: Index,
199 g_L: *const Number,
200 g_U: *const Number,
201 nele_jac: Index,
202 nele_hess: Index,
203 index_style: Index,
204 eval_f: Option<Eval_F_CB>,
205 eval_g: Option<Eval_G_CB>,
206 eval_grad_f: Option<Eval_Grad_F_CB>,
207 eval_jac_g: Option<Eval_Jac_G_CB>,
208 eval_h: Option<Eval_H_CB>,
209) -> IpoptProblem {
210 if n < 0 || m < 0 || nele_jac < 0 || nele_hess < 0 {
211 return std::ptr::null_mut();
212 }
213 if !(0..=1).contains(&index_style) {
214 return std::ptr::null_mut();
215 }
216 if eval_f.is_none() || eval_grad_f.is_none() {
217 return std::ptr::null_mut();
218 }
219 if m > 0 && (eval_g.is_none() || eval_jac_g.is_none()) {
220 return std::ptr::null_mut();
221 }
222 if n > 0 && (x_L.is_null() || x_U.is_null()) {
223 return std::ptr::null_mut();
224 }
225 if m > 0 && (g_L.is_null() || g_U.is_null()) {
226 return std::ptr::null_mut();
227 }
228
229 let x_l = if n > 0 {
230 std::slice::from_raw_parts(x_L, n as usize).to_vec()
231 } else {
232 Vec::new()
233 };
234 let x_u = if n > 0 {
235 std::slice::from_raw_parts(x_U, n as usize).to_vec()
236 } else {
237 Vec::new()
238 };
239 let g_l_vec = if m > 0 {
240 std::slice::from_raw_parts(g_L, m as usize).to_vec()
241 } else {
242 Vec::new()
243 };
244 let g_u_vec = if m > 0 {
245 std::slice::from_raw_parts(g_U, m as usize).to_vec()
246 } else {
247 Vec::new()
248 };
249
250 let info = Box::new(IpoptProblemInfo {
251 app: IpoptApplication::new(),
252 n,
253 m,
254 nele_jac,
255 nele_hess,
256 index_style,
257 x_l,
258 x_u,
259 g_l: g_l_vec,
260 g_u: g_u_vec,
261 eval_f,
262 eval_g,
263 eval_grad_f,
264 eval_jac_g,
265 eval_h,
266 intermediate_cb: None,
267 user_scaling: None,
268 last_solve: None,
269 });
270 Box::into_raw(info)
271}
272
273#[no_mangle]
280pub unsafe extern "C" fn FreeIpoptProblem(ipopt_problem: IpoptProblem) {
281 if ipopt_problem.is_null() {
282 return;
283 }
284 drop(Box::from_raw(ipopt_problem));
285}
286
287unsafe fn keyword_str<'a>(keyword: *const c_char) -> Option<&'a str> {
288 if keyword.is_null() {
289 return None;
290 }
291 CStr::from_ptr(keyword).to_str().ok()
292}
293
294#[no_mangle]
301pub unsafe extern "C" fn AddIpoptStrOption(
302 ipopt_problem: IpoptProblem,
303 keyword: *const c_char,
304 val: *const c_char,
305) -> Bool {
306 if ipopt_problem.is_null() {
307 return FALSE;
308 }
309 let info = &mut *ipopt_problem;
310 let Some(k) = keyword_str(keyword) else {
311 return FALSE;
312 };
313 if val.is_null() {
314 return FALSE;
315 }
316 let Ok(v) = CStr::from_ptr(val).to_str() else {
317 return FALSE;
318 };
319 match info.app.options_mut().set_string_value(k, v, true, false) {
320 Ok(_) => TRUE,
321 Err(_) => FALSE,
322 }
323}
324
325#[no_mangle]
332pub unsafe extern "C" fn AddIpoptNumOption(
333 ipopt_problem: IpoptProblem,
334 keyword: *const c_char,
335 val: Number,
336) -> Bool {
337 if ipopt_problem.is_null() {
338 return FALSE;
339 }
340 let info = &mut *ipopt_problem;
341 let Some(k) = keyword_str(keyword) else {
342 return FALSE;
343 };
344 match info
345 .app
346 .options_mut()
347 .set_numeric_value(k, val, true, false)
348 {
349 Ok(_) => TRUE,
350 Err(_) => FALSE,
351 }
352}
353
354#[no_mangle]
361pub unsafe extern "C" fn AddIpoptIntOption(
362 ipopt_problem: IpoptProblem,
363 keyword: *const c_char,
364 val: Index,
365) -> Bool {
366 if ipopt_problem.is_null() {
367 return FALSE;
368 }
369 let info = &mut *ipopt_problem;
370 let Some(k) = keyword_str(keyword) else {
371 return FALSE;
372 };
373 match info.app.options_mut().set_integer_value(
374 k,
375 val as pounce_common::types::Index,
376 true,
377 false,
378 ) {
379 Ok(_) => TRUE,
380 Err(_) => FALSE,
381 }
382}
383
384#[no_mangle]
398pub unsafe extern "C" fn OpenIpoptOutputFile(
399 ipopt_problem: IpoptProblem,
400 file_name: *const c_char,
401 print_level: c_int,
402) -> Bool {
403 if ipopt_problem.is_null() || file_name.is_null() {
404 return FALSE;
405 }
406 let info = &mut *ipopt_problem;
407 let Ok(fname) = CStr::from_ptr(file_name).to_str() else {
408 return FALSE;
409 };
410 if info.app.open_output_file(fname, print_level) {
411 TRUE
412 } else {
413 FALSE
414 }
415}
416
417#[no_mangle]
431pub unsafe extern "C" fn SetIpoptProblemScaling(
432 ipopt_problem: IpoptProblem,
433 obj_scaling: Number,
434 x_scaling: *const Number,
435 g_scaling: *const Number,
436) -> Bool {
437 if ipopt_problem.is_null() {
438 return FALSE;
439 }
440 let info = &mut *ipopt_problem;
441 let n = info.n as usize;
442 let m = info.m as usize;
443 let x_vec = if !x_scaling.is_null() && n > 0 {
444 Some(std::slice::from_raw_parts(x_scaling, n).to_vec())
445 } else {
446 None
447 };
448 let g_vec = if !g_scaling.is_null() && m > 0 {
449 Some(std::slice::from_raw_parts(g_scaling, m).to_vec())
450 } else {
451 None
452 };
453 info.user_scaling = Some(UserScaling {
454 obj_scaling,
455 x_scaling: x_vec,
456 g_scaling: g_vec,
457 });
458 TRUE
459}
460
461#[allow(clippy::too_many_arguments)]
475#[no_mangle]
476pub unsafe extern "C" fn IpoptSolve(
477 ipopt_problem: IpoptProblem,
478 x: *mut Number,
479 g: *mut Number,
480 obj_val: *mut Number,
481 mult_g: *mut Number,
482 mult_x_L: *mut Number,
483 mult_x_U: *mut Number,
484 user_data: *mut c_void,
485) -> Index {
486 if ipopt_problem.is_null() {
487 return ApplicationReturnStatus::InternalError as Index;
488 }
489 let info = &mut *ipopt_problem;
490 if info.n < 0 || info.m < 0 {
491 return ApplicationReturnStatus::InvalidProblemDefinition as Index;
492 }
493 if info.n > 0 && x.is_null() {
494 return ApplicationReturnStatus::InvalidProblemDefinition as Index;
495 }
496
497 let n_us = info.n as usize;
498 let m_us = info.m as usize;
499 let initial_x = if n_us > 0 {
500 std::slice::from_raw_parts(x, n_us).to_vec()
501 } else {
502 Vec::new()
503 };
504
505 let bridge = Rc::new(RefCell::new(CCallbackTnlp {
506 n: info.n,
507 m: info.m,
508 nele_jac: info.nele_jac,
509 nele_hess: info.nele_hess,
510 index_style: info.index_style,
511 x_l: info.x_l.clone(),
512 x_u: info.x_u.clone(),
513 g_l: info.g_l.clone(),
514 g_u: info.g_u.clone(),
515 initial_x,
516 eval_f: info.eval_f,
517 eval_grad_f: info.eval_grad_f,
518 eval_g: info.eval_g,
519 eval_jac_g: info.eval_jac_g,
520 eval_h: info.eval_h,
521 user_data,
522 intermediate_cb: info.intermediate_cb,
523 user_scaling: info.user_scaling.clone(),
524 final_status: None,
525 final_x: vec![0.0; n_us],
526 final_z_l: vec![0.0; n_us],
527 final_z_u: vec![0.0; n_us],
528 final_g: vec![0.0; m_us],
529 final_lambda: vec![0.0; m_us],
530 final_obj: 0.0,
531 }));
532
533 let feral_cfg = feral_config_from_options(info.app.options());
542 let bff: InnerBackendFactoryFactory = Box::new(move || default_backend_factory(feral_cfg));
543 let resto_factory = make_default_restoration_factory(
544 RestoAlgorithmBuilder::new(),
545 AlgorithmBuilder::new(),
546 bff,
547 );
548 info.app.set_restoration_factory(resto_factory);
549
550 let bridge_for_solve: Rc<RefCell<dyn TNLP>> = bridge.clone();
551 let status = info.app.optimize_tnlp(bridge_for_solve);
552 info.last_solve = Some(LastSolve {
553 stats: info.app.statistics(),
554 });
555
556 let bridge_ref = bridge.borrow();
557 if !x.is_null() && n_us > 0 {
558 std::ptr::copy_nonoverlapping(bridge_ref.final_x.as_ptr(), x, n_us);
559 }
560 if !g.is_null() && m_us > 0 {
561 std::ptr::copy_nonoverlapping(bridge_ref.final_g.as_ptr(), g, m_us);
562 }
563 if !obj_val.is_null() {
564 *obj_val = bridge_ref.final_obj;
565 }
566 if !mult_g.is_null() && m_us > 0 {
567 std::ptr::copy_nonoverlapping(bridge_ref.final_lambda.as_ptr(), mult_g, m_us);
568 }
569 if !mult_x_L.is_null() && n_us > 0 {
570 std::ptr::copy_nonoverlapping(bridge_ref.final_z_l.as_ptr(), mult_x_L, n_us);
571 }
572 if !mult_x_U.is_null() && n_us > 0 {
573 std::ptr::copy_nonoverlapping(bridge_ref.final_z_u.as_ptr(), mult_x_U, n_us);
574 }
575 status as Index
576}
577
578#[no_mangle]
584pub unsafe extern "C" fn SetIntermediateCallback(
585 ipopt_problem: IpoptProblem,
586 intermediate_cb: Option<Intermediate_CB>,
587) -> Bool {
588 if ipopt_problem.is_null() {
589 return FALSE;
590 }
591 let info = &mut *ipopt_problem;
592 info.intermediate_cb = intermediate_cb;
593 TRUE
594}
595
596#[allow(clippy::too_many_arguments)]
619#[no_mangle]
620pub unsafe extern "C" fn GetIpoptCurrentIterate(
621 ipopt_problem: IpoptProblem,
622 _scaled: Bool,
623 n: Index,
624 x: *mut Number,
625 z_l: *mut Number,
626 z_u: *mut Number,
627 m: Index,
628 g: *mut Number,
629 lambda: *mut Number,
630) -> Bool {
631 if ipopt_problem.is_null() {
632 return FALSE;
633 }
634 let info = &*ipopt_problem;
635 if n != info.n || m != info.m {
636 return FALSE;
637 }
638 let result = ip_intermediate::with_current(|ctx| {
639 let data = ctx.data.borrow();
640 let Some(curr) = data.curr.as_ref() else {
641 return false;
642 };
643 let nlp = ctx.nlp.borrow();
644 let n_us = n as usize;
645 let m_us = m as usize;
646 if !x.is_null() && n_us > 0 {
647 let full_x = nlp.lift_x_to_full(&*curr.x);
648 if full_x.len() != n_us {
649 return false;
650 }
651 std::ptr::copy_nonoverlapping(full_x.as_ptr(), x, n_us);
652 }
653 if !z_l.is_null() && n_us > 0 {
654 let full = nlp.pack_z_l_for_user(&*curr.z_l);
655 if full.len() != n_us {
656 return false;
657 }
658 std::ptr::copy_nonoverlapping(full.as_ptr(), z_l, n_us);
659 }
660 if !z_u.is_null() && n_us > 0 {
661 let full = nlp.pack_z_u_for_user(&*curr.z_u);
662 if full.len() != n_us {
663 return false;
664 }
665 std::ptr::copy_nonoverlapping(full.as_ptr(), z_u, n_us);
666 }
667 if !g.is_null() && m_us > 0 {
668 let cq = ctx.cq.borrow();
669 let full = nlp.pack_g_for_user(&*cq.curr_c(), &*cq.curr_d());
670 if full.len() != m_us {
671 return false;
672 }
673 std::ptr::copy_nonoverlapping(full.as_ptr(), g, m_us);
674 }
675 if !lambda.is_null() && m_us > 0 {
676 let full = nlp.pack_lambda_for_user(&*curr.y_c, &*curr.y_d);
677 if full.len() != m_us {
678 return false;
679 }
680 std::ptr::copy_nonoverlapping(full.as_ptr(), lambda, m_us);
681 }
682 true
683 });
684 if result.unwrap_or(false) {
685 TRUE
686 } else {
687 FALSE
688 }
689}
690
691#[allow(clippy::too_many_arguments)]
706#[no_mangle]
707pub unsafe extern "C" fn GetIpoptCurrentViolations(
708 ipopt_problem: IpoptProblem,
709 _scaled: Bool,
710 n: Index,
711 x_l_violation: *mut Number,
712 x_u_violation: *mut Number,
713 compl_x_l: *mut Number,
714 compl_x_u: *mut Number,
715 grad_lag_x: *mut Number,
716 m: Index,
717 nlp_constraint_violation: *mut Number,
718 compl_g: *mut Number,
719) -> Bool {
720 if ipopt_problem.is_null() {
721 return FALSE;
722 }
723 let info = &*ipopt_problem;
724 if n != info.n || m != info.m {
725 return FALSE;
726 }
727 let result = ip_intermediate::with_current(|ctx| {
728 let data = ctx.data.borrow();
729 let Some(_curr) = data.curr.as_ref() else {
730 return false;
731 };
732 drop(data);
733 let nlp = ctx.nlp.borrow();
734 let cq = ctx.cq.borrow();
735 let n_us = n as usize;
736 let m_us = m as usize;
737 if !x_l_violation.is_null() && n_us > 0 {
743 let mut v = vec![0.0; n_us];
744 let slack = cq.curr_slack_x_l();
745 let z_l_full = nlp.pack_z_l_for_user(&*slack);
746 for (i, s) in z_l_full.iter().enumerate() {
751 v[i] = (-s).max(0.0);
752 }
753 std::ptr::copy_nonoverlapping(v.as_ptr(), x_l_violation, n_us);
754 }
755 if !x_u_violation.is_null() && n_us > 0 {
756 let mut v = vec![0.0; n_us];
757 let slack = cq.curr_slack_x_u();
758 let s_full = nlp.pack_z_u_for_user(&*slack);
759 for (i, s) in s_full.iter().enumerate() {
760 v[i] = (-s).max(0.0);
761 }
762 std::ptr::copy_nonoverlapping(v.as_ptr(), x_u_violation, n_us);
763 }
764 if !compl_x_l.is_null() && n_us > 0 {
765 let v = nlp.pack_z_l_for_user(&*cq.curr_compl_x_l());
766 if v.len() != n_us {
767 return false;
768 }
769 std::ptr::copy_nonoverlapping(v.as_ptr(), compl_x_l, n_us);
770 }
771 if !compl_x_u.is_null() && n_us > 0 {
772 let v = nlp.pack_z_u_for_user(&*cq.curr_compl_x_u());
773 if v.len() != n_us {
774 return false;
775 }
776 std::ptr::copy_nonoverlapping(v.as_ptr(), compl_x_u, n_us);
777 }
778 if !grad_lag_x.is_null() && n_us > 0 {
779 let glx = cq.curr_grad_lag_x();
780 let full = nlp.lift_x_to_full(&*glx);
784 if full.len() != n_us {
785 return false;
786 }
787 std::ptr::copy_nonoverlapping(full.as_ptr(), grad_lag_x, n_us);
788 }
789 if !nlp_constraint_violation.is_null() && m_us > 0 {
790 let zero = vec![0.0; m_us];
796 std::ptr::copy_nonoverlapping(zero.as_ptr(), nlp_constraint_violation, m_us);
797 }
798 if !compl_g.is_null() && m_us > 0 {
799 let zero = vec![0.0; m_us];
802 std::ptr::copy_nonoverlapping(zero.as_ptr(), compl_g, m_us);
803 }
804 true
805 });
806 if result.unwrap_or(false) {
807 TRUE
808 } else {
809 FALSE
810 }
811}
812
813#[no_mangle]
821pub unsafe extern "C" fn GetIpoptVersion(
822 major: *mut c_int,
823 minor: *mut c_int,
824 release: *mut c_int,
825) {
826 let (mj, mn, pt) = parse_pkg_version(env!("CARGO_PKG_VERSION"));
831 if !major.is_null() {
832 *major = mj;
833 }
834 if !minor.is_null() {
835 *minor = mn;
836 }
837 if !release.is_null() {
838 *release = pt;
839 }
840}
841
842fn parse_pkg_version(v: &str) -> (c_int, c_int, c_int) {
843 let mut it = v.split('.').map(|s| s.parse::<c_int>().unwrap_or(0));
844 (
845 it.next().unwrap_or(0),
846 it.next().unwrap_or(0),
847 it.next().unwrap_or(0),
848 )
849}
850
851#[no_mangle]
868pub unsafe extern "C" fn GetIpoptIterCount(ipopt_problem: IpoptProblem) -> Index {
869 last_stat(ipopt_problem, |s| s.iteration_count).unwrap_or(0)
870}
871
872#[no_mangle]
879pub unsafe extern "C" fn GetIpoptSolveTime(ipopt_problem: IpoptProblem) -> Number {
880 last_stat(ipopt_problem, |s| s.total_wallclock_time_secs).unwrap_or(0.0)
881}
882
883#[no_mangle]
890pub unsafe extern "C" fn GetIpoptPrimalInf(ipopt_problem: IpoptProblem) -> Number {
891 last_stat(ipopt_problem, |s| s.final_constr_viol).unwrap_or(0.0)
892}
893
894#[no_mangle]
901pub unsafe extern "C" fn GetIpoptDualInf(ipopt_problem: IpoptProblem) -> Number {
902 last_stat(ipopt_problem, |s| s.final_dual_inf).unwrap_or(0.0)
903}
904
905#[no_mangle]
911pub unsafe extern "C" fn GetIpoptComplInf(ipopt_problem: IpoptProblem) -> Number {
912 last_stat(ipopt_problem, |s| s.final_compl).unwrap_or(0.0)
913}
914
915unsafe fn last_stat<T, F>(ipopt_problem: IpoptProblem, f: F) -> Option<T>
916where
917 F: FnOnce(&SolveStatistics) -> T,
918{
919 if ipopt_problem.is_null() {
920 return None;
921 }
922 (*ipopt_problem).last_solve.as_ref().map(|ls| f(&ls.stats))
923}
924
925pub(crate) struct CCallbackTnlp {
936 pub(crate) n: Index,
937 pub(crate) m: Index,
938 pub(crate) nele_jac: Index,
939 pub(crate) nele_hess: Index,
940 pub(crate) index_style: Index,
941 pub(crate) x_l: Vec<Number>,
942 pub(crate) x_u: Vec<Number>,
943 pub(crate) g_l: Vec<Number>,
944 pub(crate) g_u: Vec<Number>,
945 pub(crate) initial_x: Vec<Number>,
946 pub(crate) eval_f: Option<Eval_F_CB>,
947 pub(crate) eval_grad_f: Option<Eval_Grad_F_CB>,
948 pub(crate) eval_g: Option<Eval_G_CB>,
949 pub(crate) eval_jac_g: Option<Eval_Jac_G_CB>,
950 pub(crate) eval_h: Option<Eval_H_CB>,
951 pub(crate) user_data: *mut c_void,
952 pub(crate) intermediate_cb: Option<Intermediate_CB>,
955 pub(crate) user_scaling: Option<UserScaling>,
957 pub(crate) final_status: Option<pounce_nlp::alg_types::SolverReturn>,
958 pub(crate) final_x: Vec<Number>,
959 pub(crate) final_z_l: Vec<Number>,
960 pub(crate) final_z_u: Vec<Number>,
961 pub(crate) final_g: Vec<Number>,
962 pub(crate) final_lambda: Vec<Number>,
963 pub(crate) final_obj: Number,
964}
965
966impl TNLP for CCallbackTnlp {
967 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
968 Some(NlpInfo {
969 n: self.n as pounce_common::types::Index,
970 m: self.m as pounce_common::types::Index,
971 nnz_jac_g: self.nele_jac as pounce_common::types::Index,
972 nnz_h_lag: self.nele_hess as pounce_common::types::Index,
973 index_style: if self.index_style == 1 {
974 IndexStyle::Fortran
975 } else {
976 IndexStyle::C
977 },
978 })
979 }
980
981 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
982 if !self.x_l.is_empty() {
983 b.x_l.copy_from_slice(&self.x_l);
984 }
985 if !self.x_u.is_empty() {
986 b.x_u.copy_from_slice(&self.x_u);
987 }
988 if !self.g_l.is_empty() {
989 b.g_l.copy_from_slice(&self.g_l);
990 }
991 if !self.g_u.is_empty() {
992 b.g_u.copy_from_slice(&self.g_u);
993 }
994 true
995 }
996
997 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
998 if !self.initial_x.is_empty() {
999 sp.x.copy_from_slice(&self.initial_x);
1000 }
1001 true
1002 }
1003
1004 fn get_scaling_parameters(&mut self, req: ScalingRequest<'_>) -> bool {
1005 let Some(s) = self.user_scaling.as_ref() else {
1006 return false;
1007 };
1008 *req.obj_scaling = s.obj_scaling;
1009 if let Some(x) = s.x_scaling.as_ref() {
1010 if x.len() == req.x_scaling.len() {
1011 req.x_scaling.copy_from_slice(x);
1012 *req.use_x_scaling = true;
1013 }
1014 } else {
1015 *req.use_x_scaling = false;
1016 }
1017 if let Some(g) = s.g_scaling.as_ref() {
1018 if g.len() == req.g_scaling.len() {
1019 req.g_scaling.copy_from_slice(g);
1020 *req.use_g_scaling = true;
1021 }
1022 } else {
1023 *req.use_g_scaling = false;
1024 }
1025 true
1026 }
1027
1028 fn eval_f(&mut self, x: &[Number], new_x: bool) -> Option<Number> {
1029 let cb = self.eval_f?;
1030 let mut obj = 0.0;
1031 let ok = unsafe {
1032 cb(
1033 self.n,
1034 x.as_ptr() as *mut Number,
1035 if new_x { TRUE } else { FALSE },
1036 &mut obj,
1037 self.user_data,
1038 )
1039 };
1040 if ok != FALSE {
1041 Some(obj)
1042 } else {
1043 None
1044 }
1045 }
1046
1047 fn eval_grad_f(&mut self, x: &[Number], new_x: bool, grad_f: &mut [Number]) -> bool {
1048 let Some(cb) = self.eval_grad_f else {
1049 return false;
1050 };
1051 let ok = unsafe {
1052 cb(
1053 self.n,
1054 x.as_ptr() as *mut Number,
1055 if new_x { TRUE } else { FALSE },
1056 grad_f.as_mut_ptr(),
1057 self.user_data,
1058 )
1059 };
1060 ok != FALSE
1061 }
1062
1063 fn eval_g(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
1064 if self.m == 0 {
1065 return true;
1066 }
1067 let Some(cb) = self.eval_g else {
1068 return false;
1069 };
1070 let ok = unsafe {
1071 cb(
1072 self.n,
1073 x.as_ptr() as *mut Number,
1074 if new_x { TRUE } else { FALSE },
1075 self.m,
1076 g.as_mut_ptr(),
1077 self.user_data,
1078 )
1079 };
1080 ok != FALSE
1081 }
1082
1083 fn eval_jac_g(&mut self, x: Option<&[Number]>, new_x: bool, mode: SparsityRequest<'_>) -> bool {
1084 if self.m == 0 || self.nele_jac == 0 {
1085 return true;
1086 }
1087 let Some(cb) = self.eval_jac_g else {
1088 return false;
1089 };
1090 let x_ptr = x
1091 .map(|s| s.as_ptr() as *mut Number)
1092 .unwrap_or(std::ptr::null_mut());
1093 let ok = match mode {
1094 SparsityRequest::Structure { irow, jcol } => unsafe {
1095 cb(
1096 self.n,
1097 x_ptr,
1098 if new_x { TRUE } else { FALSE },
1099 self.m,
1100 self.nele_jac,
1101 irow.as_mut_ptr(),
1102 jcol.as_mut_ptr(),
1103 std::ptr::null_mut(),
1104 self.user_data,
1105 )
1106 },
1107 SparsityRequest::Values { values } => unsafe {
1108 cb(
1109 self.n,
1110 x_ptr,
1111 if new_x { TRUE } else { FALSE },
1112 self.m,
1113 self.nele_jac,
1114 std::ptr::null_mut(),
1115 std::ptr::null_mut(),
1116 values.as_mut_ptr(),
1117 self.user_data,
1118 )
1119 },
1120 };
1121 ok != FALSE
1122 }
1123
1124 fn eval_h(
1125 &mut self,
1126 x: Option<&[Number]>,
1127 new_x: bool,
1128 obj_factor: Number,
1129 lambda: Option<&[Number]>,
1130 new_lambda: bool,
1131 mode: SparsityRequest<'_>,
1132 ) -> bool {
1133 let Some(cb) = self.eval_h else {
1134 return false;
1135 };
1136 if self.nele_hess == 0 {
1137 return true;
1138 }
1139 let x_ptr = x
1140 .map(|s| s.as_ptr() as *mut Number)
1141 .unwrap_or(std::ptr::null_mut());
1142 let lambda_ptr = lambda
1143 .map(|s| s.as_ptr() as *mut Number)
1144 .unwrap_or(std::ptr::null_mut());
1145 let ok = match mode {
1146 SparsityRequest::Structure { irow, jcol } => unsafe {
1147 cb(
1148 self.n,
1149 x_ptr,
1150 if new_x { TRUE } else { FALSE },
1151 obj_factor,
1152 self.m,
1153 lambda_ptr,
1154 if new_lambda { TRUE } else { FALSE },
1155 self.nele_hess,
1156 irow.as_mut_ptr(),
1157 jcol.as_mut_ptr(),
1158 std::ptr::null_mut(),
1159 self.user_data,
1160 )
1161 },
1162 SparsityRequest::Values { values } => unsafe {
1163 cb(
1164 self.n,
1165 x_ptr,
1166 if new_x { TRUE } else { FALSE },
1167 obj_factor,
1168 self.m,
1169 lambda_ptr,
1170 if new_lambda { TRUE } else { FALSE },
1171 self.nele_hess,
1172 std::ptr::null_mut(),
1173 std::ptr::null_mut(),
1174 values.as_mut_ptr(),
1175 self.user_data,
1176 )
1177 },
1178 };
1179 ok != FALSE
1180 }
1181
1182 fn intermediate_callback(
1183 &mut self,
1184 stats: pounce_nlp::tnlp::IterStats,
1185 _ip_data: &IpoptData,
1186 _ip_cq: &IpoptCq,
1187 ) -> bool {
1188 let Some(cb) = self.intermediate_cb else {
1189 return true;
1190 };
1191 let ok = unsafe {
1192 cb(
1193 stats.mode as Index,
1194 stats.iter as Index,
1195 stats.obj_value,
1196 stats.inf_pr,
1197 stats.inf_du,
1198 stats.mu,
1199 stats.d_norm,
1200 stats.regularization_size,
1201 stats.alpha_du,
1202 stats.alpha_pr,
1203 stats.ls_trials as Index,
1204 self.user_data,
1205 )
1206 };
1207 ok != FALSE
1208 }
1209
1210 fn finalize_solution(&mut self, sol: Solution<'_>, _d: &IpoptData, _q: &IpoptCq) {
1211 self.final_status = Some(sol.status);
1212 if !sol.x.is_empty() {
1213 self.final_x.copy_from_slice(sol.x);
1214 }
1215 if !sol.z_l.is_empty() {
1216 self.final_z_l.copy_from_slice(sol.z_l);
1217 }
1218 if !sol.z_u.is_empty() {
1219 self.final_z_u.copy_from_slice(sol.z_u);
1220 }
1221 if !sol.g.is_empty() {
1222 self.final_g.copy_from_slice(sol.g);
1223 }
1224 if !sol.lambda.is_empty() {
1225 self.final_lambda.copy_from_slice(sol.lambda);
1226 }
1227 self.final_obj = sol.obj_value;
1228 }
1229}
1230
1231#[cfg(test)]
1232mod tests {
1233 use super::*;
1234 use std::ffi::CString;
1235
1236 unsafe extern "C" fn dummy_eval_f(
1237 _n: Index,
1238 _x: *const Number,
1239 _new_x: Bool,
1240 _obj_value: *mut Number,
1241 _user_data: *mut c_void,
1242 ) -> Bool {
1243 TRUE
1244 }
1245 unsafe extern "C" fn dummy_eval_grad_f(
1246 _n: Index,
1247 _x: *const Number,
1248 _new_x: Bool,
1249 _grad_f: *mut Number,
1250 _user_data: *mut c_void,
1251 ) -> Bool {
1252 TRUE
1253 }
1254
1255 fn create_unconstrained() -> IpoptProblem {
1256 let xl = [-1.0; 4];
1257 let xu = [1.0; 4];
1258 unsafe {
1259 CreateIpoptProblem(
1260 4,
1261 xl.as_ptr(),
1262 xu.as_ptr(),
1263 0,
1264 std::ptr::null(),
1265 std::ptr::null(),
1266 0,
1267 10,
1268 0,
1269 Some(dummy_eval_f),
1270 None,
1271 Some(dummy_eval_grad_f),
1272 None,
1273 None,
1274 )
1275 }
1276 }
1277
1278 #[test]
1279 fn create_succeeds_for_unconstrained_problem() {
1280 let p = create_unconstrained();
1281 assert!(!p.is_null());
1282 unsafe { FreeIpoptProblem(p) };
1283 }
1284
1285 #[test]
1286 fn create_returns_null_on_missing_required_callbacks() {
1287 let xl = [-1.0; 4];
1288 let xu = [1.0; 4];
1289 let p = unsafe {
1290 CreateIpoptProblem(
1291 4,
1292 xl.as_ptr(),
1293 xu.as_ptr(),
1294 0,
1295 std::ptr::null(),
1296 std::ptr::null(),
1297 0,
1298 10,
1299 0,
1300 None, None,
1302 Some(dummy_eval_grad_f),
1303 None,
1304 None,
1305 )
1306 };
1307 assert!(p.is_null());
1308 }
1309
1310 #[test]
1311 fn create_returns_null_on_negative_n() {
1312 let p = unsafe {
1313 CreateIpoptProblem(
1314 -1,
1315 std::ptr::null(),
1316 std::ptr::null(),
1317 0,
1318 std::ptr::null(),
1319 std::ptr::null(),
1320 0,
1321 10,
1322 0,
1323 Some(dummy_eval_f),
1324 None,
1325 Some(dummy_eval_grad_f),
1326 None,
1327 None,
1328 )
1329 };
1330 assert!(p.is_null());
1331 }
1332
1333 #[test]
1334 fn create_returns_null_on_invalid_index_style() {
1335 let xl = [0.0; 1];
1336 let xu = [1.0; 1];
1337 let p = unsafe {
1338 CreateIpoptProblem(
1339 1,
1340 xl.as_ptr(),
1341 xu.as_ptr(),
1342 0,
1343 std::ptr::null(),
1344 std::ptr::null(),
1345 0,
1346 1,
1347 2, Some(dummy_eval_f),
1349 None,
1350 Some(dummy_eval_grad_f),
1351 None,
1352 None,
1353 )
1354 };
1355 assert!(p.is_null());
1356 }
1357
1358 #[test]
1359 fn add_int_option_forwards_to_application() {
1360 let p = create_unconstrained();
1361 let key = CString::new("print_level").unwrap();
1362 let ok = unsafe { AddIpoptIntOption(p, key.as_ptr(), 5) };
1363 assert_eq!(ok, TRUE);
1364 let info = unsafe { &*p };
1365 let (level, found) = info
1366 .app
1367 .options()
1368 .get_integer_value("print_level", "")
1369 .unwrap();
1370 assert!(found);
1371 assert_eq!(level, 5);
1372 unsafe { FreeIpoptProblem(p) };
1373 }
1374
1375 #[test]
1376 fn add_str_option_with_invalid_key_returns_false() {
1377 let p = create_unconstrained();
1378 let key = CString::new("totally_unknown_option").unwrap();
1379 let val = CString::new("yes").unwrap();
1380 let ok = unsafe { AddIpoptStrOption(p, key.as_ptr(), val.as_ptr()) };
1381 assert_eq!(ok, FALSE);
1382 unsafe { FreeIpoptProblem(p) };
1383 }
1384
1385 #[test]
1386 fn add_options_on_null_problem_returns_false() {
1387 let key = CString::new("print_level").unwrap();
1388 let v = CString::new("yes").unwrap();
1389 unsafe {
1390 assert_eq!(
1391 AddIpoptIntOption(std::ptr::null_mut(), key.as_ptr(), 5),
1392 FALSE
1393 );
1394 assert_eq!(
1395 AddIpoptNumOption(std::ptr::null_mut(), key.as_ptr(), 1.0),
1396 FALSE
1397 );
1398 assert_eq!(
1399 AddIpoptStrOption(std::ptr::null_mut(), key.as_ptr(), v.as_ptr()),
1400 FALSE
1401 );
1402 }
1403 }
1404
1405 unsafe extern "C" fn dummy_intermediate(
1406 _alg_mod: Index,
1407 _iter_count: Index,
1408 _obj_value: Number,
1409 _inf_pr: Number,
1410 _inf_du: Number,
1411 _mu: Number,
1412 _d_norm: Number,
1413 _regularization_size: Number,
1414 _alpha_du: Number,
1415 _alpha_pr: Number,
1416 _ls_trials: Index,
1417 _user_data: *mut c_void,
1418 ) -> Bool {
1419 TRUE
1420 }
1421
1422 #[test]
1423 fn set_intermediate_callback_stores_pointer() {
1424 let p = create_unconstrained();
1425 let ok = unsafe { SetIntermediateCallback(p, Some(dummy_intermediate)) };
1426 assert_eq!(ok, TRUE);
1427 let info = unsafe { &*p };
1428 assert!(info.intermediate_cb.is_some());
1429 unsafe { FreeIpoptProblem(p) };
1430 }
1431
1432 #[test]
1433 fn solve_returns_internal_error_on_null_problem() {
1434 let rc = unsafe {
1435 IpoptSolve(
1436 std::ptr::null_mut(),
1437 std::ptr::null_mut(),
1438 std::ptr::null_mut(),
1439 std::ptr::null_mut(),
1440 std::ptr::null_mut(),
1441 std::ptr::null_mut(),
1442 std::ptr::null_mut(),
1443 std::ptr::null_mut(),
1444 )
1445 };
1446 assert_eq!(rc, -199);
1447 }
1448
1449 #[test]
1450 fn free_null_is_safe() {
1451 unsafe { FreeIpoptProblem(std::ptr::null_mut()) };
1452 }
1453
1454 unsafe extern "C" fn quad_eval_f(
1460 _n: Index,
1461 x: *const Number,
1462 _new_x: Bool,
1463 obj_value: *mut Number,
1464 _user_data: *mut c_void,
1465 ) -> Bool {
1466 let v = *x.offset(0);
1467 *obj_value = (v - 2.0) * (v - 2.0);
1468 TRUE
1469 }
1470 unsafe extern "C" fn quad_eval_grad_f(
1471 _n: Index,
1472 x: *const Number,
1473 _new_x: Bool,
1474 grad: *mut Number,
1475 _user_data: *mut c_void,
1476 ) -> Bool {
1477 let v = *x.offset(0);
1478 *grad.offset(0) = 2.0 * (v - 2.0);
1479 TRUE
1480 }
1481 unsafe extern "C" fn quad_eval_h(
1482 _n: Index,
1483 _x: *const Number,
1484 _new_x: Bool,
1485 obj_factor: Number,
1486 _m: Index,
1487 _lambda: *const Number,
1488 _new_lambda: Bool,
1489 _nele_hess: Index,
1490 irow: *mut Index,
1491 jcol: *mut Index,
1492 values: *mut Number,
1493 _user_data: *mut c_void,
1494 ) -> Bool {
1495 if !irow.is_null() && !jcol.is_null() && values.is_null() {
1496 *irow.offset(0) = 0;
1497 *jcol.offset(0) = 0;
1498 } else if irow.is_null() && jcol.is_null() && !values.is_null() {
1499 *values.offset(0) = 2.0 * obj_factor;
1500 } else {
1501 return FALSE;
1502 }
1503 TRUE
1504 }
1505
1506 #[test]
1507 fn solve_drives_unconstrained_quadratic_through_bridge() {
1508 let xl = [-1.0e20];
1511 let xu = [1.0e20];
1512 let p = unsafe {
1513 CreateIpoptProblem(
1514 1,
1515 xl.as_ptr(),
1516 xu.as_ptr(),
1517 0,
1518 std::ptr::null(),
1519 std::ptr::null(),
1520 0,
1521 1,
1522 0,
1523 Some(quad_eval_f),
1524 None,
1525 Some(quad_eval_grad_f),
1526 None,
1527 Some(quad_eval_h),
1528 )
1529 };
1530 assert!(!p.is_null());
1531 let mut x = [0.0_f64];
1532 let mut obj = 0.0_f64;
1533 let rc = unsafe {
1534 IpoptSolve(
1535 p,
1536 x.as_mut_ptr(),
1537 std::ptr::null_mut(),
1538 &mut obj,
1539 std::ptr::null_mut(),
1540 std::ptr::null_mut(),
1541 std::ptr::null_mut(),
1542 std::ptr::null_mut(),
1543 )
1544 };
1545 assert_eq!(rc, ApplicationReturnStatus::SolveSucceeded as Index);
1546 assert!((x[0] - 2.0).abs() < 1e-6, "x[0] = {}", x[0]);
1547 assert!(obj.abs() < 1e-10, "obj = {}", obj);
1548 unsafe { FreeIpoptProblem(p) };
1549 }
1550
1551 #[test]
1552 fn solve_invalid_problem_definition_when_x_null() {
1553 let p = create_unconstrained();
1554 let rc = unsafe {
1555 IpoptSolve(
1556 p,
1557 std::ptr::null_mut(), std::ptr::null_mut(),
1559 std::ptr::null_mut(),
1560 std::ptr::null_mut(),
1561 std::ptr::null_mut(),
1562 std::ptr::null_mut(),
1563 std::ptr::null_mut(),
1564 )
1565 };
1566 assert_eq!(
1567 rc,
1568 ApplicationReturnStatus::InvalidProblemDefinition as Index
1569 );
1570 unsafe { FreeIpoptProblem(p) };
1571 }
1572
1573 #[test]
1576 fn get_version_writes_pkg_version() {
1577 let (mut mj, mut mn, mut pt) = (-1, -1, -1);
1578 unsafe { GetIpoptVersion(&mut mj, &mut mn, &mut pt) };
1579 let expected = parse_pkg_version(env!("CARGO_PKG_VERSION"));
1580 assert_eq!((mj, mn, pt), expected);
1581 }
1582
1583 #[test]
1584 fn get_version_tolerates_null_buffers() {
1585 unsafe {
1587 GetIpoptVersion(
1588 std::ptr::null_mut(),
1589 std::ptr::null_mut(),
1590 std::ptr::null_mut(),
1591 )
1592 };
1593 }
1594
1595 #[test]
1596 fn set_scaling_stores_user_supplied_arrays() {
1597 let p = create_unconstrained();
1598 let xs = [2.0, 3.0, 4.0, 5.0];
1599 let ok = unsafe { SetIpoptProblemScaling(p, 7.0, xs.as_ptr(), std::ptr::null()) };
1600 assert_eq!(ok, TRUE);
1601 let info = unsafe { &*p };
1602 let s = info.user_scaling.as_ref().unwrap();
1603 assert_eq!(s.obj_scaling, 7.0);
1604 assert_eq!(s.x_scaling.as_deref(), Some(&xs[..]));
1605 assert!(s.g_scaling.is_none());
1606 unsafe { FreeIpoptProblem(p) };
1607 }
1608
1609 #[test]
1610 fn set_scaling_on_null_problem_returns_false() {
1611 let ok = unsafe {
1612 SetIpoptProblemScaling(
1613 std::ptr::null_mut(),
1614 1.0,
1615 std::ptr::null(),
1616 std::ptr::null(),
1617 )
1618 };
1619 assert_eq!(ok, FALSE);
1620 }
1621
1622 #[test]
1623 fn open_output_file_writes_and_attaches_journal() {
1624 let p = create_unconstrained();
1625 let dir = std::env::temp_dir().join("pounce-cinterface-test");
1626 let _ = std::fs::create_dir_all(&dir);
1627 let path = dir.join("output.log");
1628 let cstr = CString::new(path.to_string_lossy().as_bytes()).unwrap();
1629 let ok = unsafe { OpenIpoptOutputFile(p, cstr.as_ptr(), 5) };
1630 assert_eq!(ok, TRUE);
1631 let info = unsafe { &*p };
1633 let (level, found) = info
1634 .app
1635 .options()
1636 .get_integer_value("file_print_level", "")
1637 .unwrap();
1638 assert!(found);
1639 assert_eq!(level, 5);
1640 unsafe { FreeIpoptProblem(p) };
1641 let _ = std::fs::remove_file(&path);
1642 }
1643
1644 #[test]
1645 fn open_output_file_with_null_inputs_returns_false() {
1646 let key = CString::new("nope").unwrap();
1647 unsafe {
1648 assert_eq!(
1649 OpenIpoptOutputFile(std::ptr::null_mut(), key.as_ptr(), 0),
1650 FALSE
1651 );
1652 }
1653 let p = create_unconstrained();
1654 unsafe {
1655 assert_eq!(OpenIpoptOutputFile(p, std::ptr::null(), 0), FALSE);
1656 FreeIpoptProblem(p);
1657 }
1658 }
1659
1660 #[test]
1661 fn get_current_iterate_returns_false_outside_callback() {
1662 let p = create_unconstrained();
1663 let rc = unsafe {
1664 GetIpoptCurrentIterate(
1665 p,
1666 FALSE,
1667 0,
1668 std::ptr::null_mut(),
1669 std::ptr::null_mut(),
1670 std::ptr::null_mut(),
1671 0,
1672 std::ptr::null_mut(),
1673 std::ptr::null_mut(),
1674 )
1675 };
1676 assert_eq!(rc, FALSE);
1677 unsafe { FreeIpoptProblem(p) };
1678 }
1679
1680 #[test]
1681 fn get_current_violations_returns_false_outside_callback() {
1682 let p = create_unconstrained();
1683 let rc = unsafe {
1684 GetIpoptCurrentViolations(
1685 p,
1686 FALSE,
1687 0,
1688 std::ptr::null_mut(),
1689 std::ptr::null_mut(),
1690 std::ptr::null_mut(),
1691 std::ptr::null_mut(),
1692 std::ptr::null_mut(),
1693 0,
1694 std::ptr::null_mut(),
1695 std::ptr::null_mut(),
1696 )
1697 };
1698 assert_eq!(rc, FALSE);
1699 unsafe { FreeIpoptProblem(p) };
1700 }
1701
1702 #[test]
1703 fn post_solve_stats_zero_before_solve() {
1704 let p = create_unconstrained();
1705 unsafe {
1706 assert_eq!(GetIpoptIterCount(p), 0);
1707 assert_eq!(GetIpoptSolveTime(p), 0.0);
1708 assert_eq!(GetIpoptPrimalInf(p), 0.0);
1709 assert_eq!(GetIpoptDualInf(p), 0.0);
1710 assert_eq!(GetIpoptComplInf(p), 0.0);
1711 FreeIpoptProblem(p);
1712 }
1713 }
1714
1715 #[test]
1716 fn post_solve_stats_populated_after_solve() {
1717 let xl = [-1.0e20];
1719 let xu = [1.0e20];
1720 let p = unsafe {
1721 CreateIpoptProblem(
1722 1,
1723 xl.as_ptr(),
1724 xu.as_ptr(),
1725 0,
1726 std::ptr::null(),
1727 std::ptr::null(),
1728 0,
1729 1,
1730 0,
1731 Some(quad_eval_f),
1732 None,
1733 Some(quad_eval_grad_f),
1734 None,
1735 Some(quad_eval_h),
1736 )
1737 };
1738 let mut x = [0.0_f64];
1739 let mut obj = 0.0_f64;
1740 let rc = unsafe {
1741 IpoptSolve(
1742 p,
1743 x.as_mut_ptr(),
1744 std::ptr::null_mut(),
1745 &mut obj,
1746 std::ptr::null_mut(),
1747 std::ptr::null_mut(),
1748 std::ptr::null_mut(),
1749 std::ptr::null_mut(),
1750 )
1751 };
1752 assert_eq!(rc, ApplicationReturnStatus::SolveSucceeded as Index);
1753 unsafe {
1756 assert!(GetIpoptIterCount(p) >= 0);
1757 assert!(GetIpoptSolveTime(p) >= 0.0);
1758 assert!(GetIpoptPrimalInf(p).is_finite());
1759 assert!(GetIpoptDualInf(p).is_finite());
1760 assert!(GetIpoptComplInf(p).is_finite());
1761 FreeIpoptProblem(p);
1762 }
1763 }
1764
1765 unsafe extern "C" fn cb_quad_eval_g(
1772 _n: Index,
1773 x: *const Number,
1774 _new_x: Bool,
1775 _m: Index,
1776 g: *mut Number,
1777 _user_data: *mut c_void,
1778 ) -> Bool {
1779 *g.offset(0) = *x.offset(0);
1780 TRUE
1781 }
1782 unsafe extern "C" fn cb_quad_eval_jac_g(
1783 _n: Index,
1784 _x: *const Number,
1785 _new_x: Bool,
1786 _m: Index,
1787 nele_jac: Index,
1788 irow: *mut Index,
1789 jcol: *mut Index,
1790 values: *mut Number,
1791 _user_data: *mut c_void,
1792 ) -> Bool {
1793 assert_eq!(nele_jac, 1);
1794 if !irow.is_null() {
1795 *irow.offset(0) = 0;
1796 *jcol.offset(0) = 0;
1797 }
1798 if !values.is_null() {
1799 *values.offset(0) = 1.0;
1800 }
1801 TRUE
1802 }
1803 unsafe extern "C" fn cb_quad_eval_h(
1804 _n: Index,
1805 _x: *const Number,
1806 _new_x: Bool,
1807 obj_factor: Number,
1808 _m: Index,
1809 _lambda: *const Number,
1810 _new_lambda: Bool,
1811 _nele_hess: Index,
1812 irow: *mut Index,
1813 jcol: *mut Index,
1814 values: *mut Number,
1815 _user_data: *mut c_void,
1816 ) -> Bool {
1817 if !irow.is_null() {
1818 *irow.offset(0) = 0;
1819 *jcol.offset(0) = 0;
1820 }
1821 if !values.is_null() {
1822 *values.offset(0) = 2.0 * obj_factor;
1823 }
1824 TRUE
1825 }
1826
1827 fn create_callback_test_problem() -> IpoptProblem {
1828 let xl = [-1.0e20];
1830 let xu = [1.0e20];
1831 let gl = [-10.0];
1832 let gu = [10.0];
1833 unsafe {
1834 CreateIpoptProblem(
1835 1,
1836 xl.as_ptr(),
1837 xu.as_ptr(),
1838 1,
1839 gl.as_ptr(),
1840 gu.as_ptr(),
1841 1,
1842 1,
1843 0,
1844 Some(quad_eval_f),
1845 Some(cb_quad_eval_g),
1846 Some(quad_eval_grad_f),
1847 Some(cb_quad_eval_jac_g),
1848 Some(cb_quad_eval_h),
1849 )
1850 }
1851 }
1852
1853 static CB_ITER_COUNTER: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
1854 static CB_LAST_ITER: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(-1);
1855 static CB_INSPECTOR_OK: std::sync::atomic::AtomicBool =
1856 std::sync::atomic::AtomicBool::new(false);
1857
1858 unsafe extern "C" fn counting_cb(
1859 _alg_mod: Index,
1860 iter_count: Index,
1861 _obj_value: Number,
1862 _inf_pr: Number,
1863 _inf_du: Number,
1864 _mu: Number,
1865 _d_norm: Number,
1866 _regularization_size: Number,
1867 _alpha_du: Number,
1868 _alpha_pr: Number,
1869 _ls_trials: Index,
1870 user_data: *mut c_void,
1871 ) -> Bool {
1872 CB_ITER_COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
1873 CB_LAST_ITER.store(iter_count, std::sync::atomic::Ordering::SeqCst);
1874 let problem = user_data as IpoptProblem;
1877 let mut x = [0.0_f64];
1878 let rc = GetIpoptCurrentIterate(
1879 problem,
1880 FALSE,
1881 1,
1882 x.as_mut_ptr(),
1883 std::ptr::null_mut(),
1884 std::ptr::null_mut(),
1885 1,
1886 std::ptr::null_mut(),
1887 std::ptr::null_mut(),
1888 );
1889 if rc == TRUE && x[0].is_finite() {
1890 CB_INSPECTOR_OK.store(true, std::sync::atomic::Ordering::SeqCst);
1891 }
1892 TRUE
1893 }
1894
1895 #[test]
1896 fn intermediate_callback_fires_per_iteration_and_inspector_reads_x() {
1897 CB_ITER_COUNTER.store(0, std::sync::atomic::Ordering::SeqCst);
1898 CB_LAST_ITER.store(-1, std::sync::atomic::Ordering::SeqCst);
1899 CB_INSPECTOR_OK.store(false, std::sync::atomic::Ordering::SeqCst);
1900
1901 let p = create_callback_test_problem();
1902 assert!(!p.is_null());
1903 let ok = unsafe { SetIntermediateCallback(p, Some(counting_cb)) };
1904 assert_eq!(ok, TRUE);
1905 let mut x = [0.0_f64];
1906 let mut obj = 0.0_f64;
1907 let rc = unsafe {
1908 IpoptSolve(
1909 p,
1910 x.as_mut_ptr(),
1911 std::ptr::null_mut(),
1912 &mut obj,
1913 std::ptr::null_mut(),
1914 std::ptr::null_mut(),
1915 std::ptr::null_mut(),
1916 p as *mut c_void,
1917 )
1918 };
1919 assert_eq!(rc, ApplicationReturnStatus::SolveSucceeded as Index);
1920 let n_fires = CB_ITER_COUNTER.load(std::sync::atomic::Ordering::SeqCst);
1922 assert!(n_fires >= 2, "callback fired {n_fires} times, want >=2");
1923 assert!(
1924 CB_LAST_ITER.load(std::sync::atomic::Ordering::SeqCst) >= 1,
1925 "last iter should be >= 1 after at least one accepted step"
1926 );
1927 assert!(
1928 CB_INSPECTOR_OK.load(std::sync::atomic::Ordering::SeqCst),
1929 "GetIpoptCurrentIterate did not return a usable x"
1930 );
1931 unsafe { FreeIpoptProblem(p) };
1932 }
1933
1934 unsafe extern "C" fn user_stop_cb(
1935 _alg_mod: Index,
1936 _iter_count: Index,
1937 _obj_value: Number,
1938 _inf_pr: Number,
1939 _inf_du: Number,
1940 _mu: Number,
1941 _d_norm: Number,
1942 _regularization_size: Number,
1943 _alpha_du: Number,
1944 _alpha_pr: Number,
1945 _ls_trials: Index,
1946 _user_data: *mut c_void,
1947 ) -> Bool {
1948 FALSE
1949 }
1950
1951 #[test]
1952 fn intermediate_callback_false_surfaces_user_requested_stop() {
1953 let p = create_callback_test_problem();
1954 assert!(!p.is_null());
1955 let ok = unsafe { SetIntermediateCallback(p, Some(user_stop_cb)) };
1956 assert_eq!(ok, TRUE);
1957 let mut x = [0.0_f64];
1958 let rc = unsafe {
1959 IpoptSolve(
1960 p,
1961 x.as_mut_ptr(),
1962 std::ptr::null_mut(),
1963 std::ptr::null_mut(),
1964 std::ptr::null_mut(),
1965 std::ptr::null_mut(),
1966 std::ptr::null_mut(),
1967 std::ptr::null_mut(),
1968 )
1969 };
1970 assert_eq!(rc, ApplicationReturnStatus::UserRequestedStop as Index);
1971 unsafe { FreeIpoptProblem(p) };
1972 }
1973
1974 #[test]
1975 fn parse_pkg_version_handles_missing_components() {
1976 assert_eq!(parse_pkg_version("1.2.3"), (1, 2, 3));
1977 assert_eq!(parse_pkg_version("4.5"), (4, 5, 0));
1978 assert_eq!(parse_pkg_version(""), (0, 0, 0));
1979 assert_eq!(parse_pkg_version("1.x.3"), (1, 0, 3));
1980 }
1981
1982 use crate::solver::{
1985 IpoptCreateSolver, IpoptFreeSolver, IpoptSolverGetKktDim, IpoptSolverKktSolve,
1986 IpoptSolverSolve,
1987 };
1988
1989 #[test]
1990 fn solver_create_consumes_problem_handle() {
1991 let mut p = create_unconstrained();
1992 assert!(!p.is_null());
1993 let s = unsafe { IpoptCreateSolver(&mut p) };
1994 assert!(!s.is_null());
1995 assert!(
1996 p.is_null(),
1997 "IpoptCreateSolver should NULL out the caller's handle"
1998 );
1999 unsafe { IpoptFreeSolver(s) };
2000 }
2001
2002 #[test]
2003 fn solver_create_null_inputs_return_null() {
2004 let s = unsafe { IpoptCreateSolver(std::ptr::null_mut()) };
2006 assert!(s.is_null());
2007 let mut p: IpoptProblem = std::ptr::null_mut();
2009 let s = unsafe { IpoptCreateSolver(&mut p) };
2010 assert!(s.is_null());
2011 }
2012
2013 #[test]
2014 fn solver_free_null_is_safe() {
2015 unsafe { IpoptFreeSolver(std::ptr::null_mut()) };
2016 }
2017
2018 #[test]
2019 fn solver_solve_drives_quadratic_and_retains_factor() {
2020 let xl = [-1.0e20];
2021 let xu = [1.0e20];
2022 let mut p = unsafe {
2023 CreateIpoptProblem(
2024 1,
2025 xl.as_ptr(),
2026 xu.as_ptr(),
2027 0,
2028 std::ptr::null(),
2029 std::ptr::null(),
2030 0,
2031 1,
2032 0,
2033 Some(quad_eval_f),
2034 None,
2035 Some(quad_eval_grad_f),
2036 None,
2037 Some(quad_eval_h),
2038 )
2039 };
2040 assert!(!p.is_null());
2041 let s = unsafe { IpoptCreateSolver(&mut p) };
2042 assert!(!s.is_null());
2043 let mut x = [0.0_f64];
2044 let mut obj = 0.0_f64;
2045 let rc = unsafe {
2046 IpoptSolverSolve(
2047 s,
2048 x.as_mut_ptr(),
2049 std::ptr::null_mut(),
2050 &mut obj,
2051 std::ptr::null_mut(),
2052 std::ptr::null_mut(),
2053 std::ptr::null_mut(),
2054 std::ptr::null_mut(),
2055 )
2056 };
2057 assert_eq!(rc, ApplicationReturnStatus::SolveSucceeded as Index);
2058 assert!((x[0] - 2.0).abs() < 1e-6);
2059 assert!(obj.abs() < 1e-10);
2060
2061 let dim = unsafe { IpoptSolverGetKktDim(s) };
2064 assert!(dim > 0, "expected positive KKT dim, got {dim}");
2065 let rhs = vec![0.0_f64; dim as usize];
2066 let mut lhs = vec![1.0_f64; dim as usize];
2067 let ok = unsafe { IpoptSolverKktSolve(s, rhs.as_ptr(), lhs.as_mut_ptr()) };
2068 assert_eq!(ok, TRUE);
2069 for (i, v) in lhs.iter().enumerate() {
2070 assert!(v.abs() < 1e-10, "lhs[{i}] = {v} not ~0");
2071 }
2072 unsafe { IpoptFreeSolver(s) };
2073 }
2074
2075 #[test]
2076 fn solver_kkt_dim_minus_one_before_solve() {
2077 let mut p = create_unconstrained();
2078 let s = unsafe { IpoptCreateSolver(&mut p) };
2079 assert_eq!(unsafe { IpoptSolverGetKktDim(s) }, -1);
2080 unsafe { IpoptFreeSolver(s) };
2081 }
2082}