1use crate::{
21 AddIpoptIntOption, AddIpoptNumOption, AddIpoptStrOption, CreateIpoptProblem, Eval_F_CB,
22 Eval_G_CB, Eval_Grad_F_CB, Eval_H_CB, Eval_Jac_G_CB, FreeIpoptProblem, Index, Intermediate_CB,
23 IpoptProblem, IpoptSolve, Number, SetIntermediateCallback,
24};
25use std::ffi::{c_char, c_int, c_void};
26
27const OK: Index = 0;
29const NOT_OK: Index = 1;
31
32struct FortranUserData {
37 idat: *mut Index,
38 ddat: *mut Number,
39 eval_f: FEval_F_CB,
40 eval_g: Option<FEval_G_CB>,
41 eval_grad_f: FEval_Grad_F_CB,
42 eval_jac_g: Option<FEval_Jac_G_CB>,
43 eval_hess: Option<FEval_Hess_CB>,
44 intermediate_cb: Option<FIntermediate_CB>,
45 problem: IpoptProblem,
46}
47
48pub type FEval_F_CB = unsafe extern "C" fn(
52 n: *const Index,
53 x: *mut Number,
54 new_x: *const Index,
55 obj_value: *mut Number,
56 idat: *mut Index,
57 ddat: *mut Number,
58 ierr: *mut Index,
59);
60
61pub type FEval_G_CB = unsafe extern "C" fn(
62 n: *const Index,
63 x: *mut Number,
64 new_x: *const Index,
65 m: *const Index,
66 g: *mut Number,
67 idat: *mut Index,
68 ddat: *mut Number,
69 ierr: *mut Index,
70);
71
72pub type FEval_Grad_F_CB = unsafe extern "C" fn(
73 n: *const Index,
74 x: *mut Number,
75 new_x: *const Index,
76 grad_f: *mut Number,
77 idat: *mut Index,
78 ddat: *mut Number,
79 ierr: *mut Index,
80);
81
82pub type FEval_Jac_G_CB = unsafe extern "C" fn(
83 task: *const Index,
84 n: *const Index,
85 x: *mut Number,
86 new_x: *const Index,
87 m: *const Index,
88 nnz_jac: *const Index,
89 irow: *mut Index,
90 jcol: *mut Index,
91 values: *mut Number,
92 idat: *mut Index,
93 ddat: *mut Number,
94 ierr: *mut Index,
95);
96
97pub type FEval_Hess_CB = unsafe extern "C" fn(
98 task: *const Index,
99 n: *const Index,
100 x: *mut Number,
101 new_x: *const Index,
102 obj_factor: *const Number,
103 m: *const Index,
104 lambda: *mut Number,
105 new_lambda: *const Index,
106 nnz_hess: *const Index,
107 irow: *mut Index,
108 jcol: *mut Index,
109 values: *mut Number,
110 idat: *mut Index,
111 ddat: *mut Number,
112 ierr: *mut Index,
113);
114
115pub type FIntermediate_CB = unsafe extern "C" fn(
116 alg_mode: *const Index,
117 iter_count: *const Index,
118 obj_value: *const Number,
119 inf_pr: *const Number,
120 inf_du: *const Number,
121 mu: *const Number,
122 d_norm: *const Number,
123 regu_size: *const Number,
124 alpha_du: *const Number,
125 alpha_pr: *const Number,
126 ls_trial: *const Index,
127 idat: *mut Index,
128 ddat: *mut Number,
129 istop: *mut Index,
130);
131
132unsafe extern "C" fn c_eval_f(
138 n: Index,
139 x: *const Number,
140 new_x: c_int,
141 obj_value: *mut Number,
142 user_data: *mut c_void,
143) -> c_int {
144 unsafe {
145 let fud = &mut *(user_data as *mut FortranUserData);
146 let mut ierr: Index = 0;
147 let n_local = n;
148 let new_x_i: Index = new_x as Index;
149 (fud.eval_f)(
150 &n_local,
151 x as *mut Number,
152 &new_x_i,
153 obj_value,
154 fud.idat,
155 fud.ddat,
156 &mut ierr,
157 );
158 if ierr == OK { 1 } else { 0 }
159 }
160}
161
162unsafe extern "C" fn c_eval_grad_f(
163 n: Index,
164 x: *const Number,
165 new_x: c_int,
166 grad_f: *mut Number,
167 user_data: *mut c_void,
168) -> c_int {
169 unsafe {
170 let fud = &mut *(user_data as *mut FortranUserData);
171 let mut ierr: Index = 0;
172 let n_local = n;
173 let new_x_i: Index = new_x as Index;
174 (fud.eval_grad_f)(
175 &n_local,
176 x as *mut Number,
177 &new_x_i,
178 grad_f,
179 fud.idat,
180 fud.ddat,
181 &mut ierr,
182 );
183 if ierr == OK { 1 } else { 0 }
184 }
185}
186
187unsafe extern "C" fn c_eval_g(
188 n: Index,
189 x: *const Number,
190 new_x: c_int,
191 m: Index,
192 g: *mut Number,
193 user_data: *mut c_void,
194) -> c_int {
195 unsafe {
196 let fud = &mut *(user_data as *mut FortranUserData);
197 let Some(cb) = fud.eval_g else {
198 return 0;
199 };
200 let mut ierr: Index = 0;
201 let n_local = n;
202 let m_local = m;
203 let new_x_i: Index = new_x as Index;
204 cb(
205 &n_local,
206 x as *mut Number,
207 &new_x_i,
208 &m_local,
209 g,
210 fud.idat,
211 fud.ddat,
212 &mut ierr,
213 );
214 if ierr == OK { 1 } else { 0 }
215 }
216}
217
218unsafe extern "C" fn c_eval_jac_g(
219 n: Index,
220 x: *const Number,
221 new_x: c_int,
222 m: Index,
223 nele_jac: Index,
224 irow: *mut Index,
225 jcol: *mut Index,
226 values: *mut Number,
227 user_data: *mut c_void,
228) -> c_int {
229 unsafe {
230 let fud = &mut *(user_data as *mut FortranUserData);
231 let Some(cb) = fud.eval_jac_g else {
232 return 0;
233 };
234 let task: Index = if !irow.is_null() && !jcol.is_null() && values.is_null() {
235 0
236 } else if irow.is_null() && jcol.is_null() && !values.is_null() {
237 1
238 } else {
239 return 0;
240 };
241 let mut ierr: Index = 0;
242 let n_local = n;
243 let m_local = m;
244 let nele_local = nele_jac;
245 let new_x_i: Index = new_x as Index;
246 cb(
247 &task,
248 &n_local,
249 x as *mut Number,
250 &new_x_i,
251 &m_local,
252 &nele_local,
253 irow,
254 jcol,
255 values,
256 fud.idat,
257 fud.ddat,
258 &mut ierr,
259 );
260 if ierr == OK { 1 } else { 0 }
261 }
262}
263
264#[allow(clippy::too_many_arguments)]
265unsafe extern "C" fn c_eval_h(
266 n: Index,
267 x: *const Number,
268 new_x: c_int,
269 obj_factor: Number,
270 m: Index,
271 lambda: *const Number,
272 new_lambda: c_int,
273 nele_hess: Index,
274 irow: *mut Index,
275 jcol: *mut Index,
276 values: *mut Number,
277 user_data: *mut c_void,
278) -> c_int {
279 unsafe {
280 let fud = &mut *(user_data as *mut FortranUserData);
281 let Some(cb) = fud.eval_hess else {
282 return 0;
283 };
284 let task: Index = if !irow.is_null() && !jcol.is_null() && values.is_null() {
285 0
286 } else if irow.is_null() && jcol.is_null() && !values.is_null() {
287 1
288 } else {
289 return 0;
290 };
291 let mut ierr: Index = 0;
292 let n_local = n;
293 let m_local = m;
294 let nele_local = nele_hess;
295 let new_x_i: Index = new_x as Index;
296 let new_lam_i: Index = new_lambda as Index;
297 cb(
298 &task,
299 &n_local,
300 x as *mut Number,
301 &new_x_i,
302 &obj_factor,
303 &m_local,
304 lambda as *mut Number,
305 &new_lam_i,
306 &nele_local,
307 irow,
308 jcol,
309 values,
310 fud.idat,
311 fud.ddat,
312 &mut ierr,
313 );
314 if ierr == OK { 1 } else { 0 }
315 }
316}
317
318#[allow(clippy::too_many_arguments)]
319unsafe extern "C" fn c_intermediate(
320 alg_mod: Index,
321 iter_count: Index,
322 obj_value: Number,
323 inf_pr: Number,
324 inf_du: Number,
325 mu: Number,
326 d_norm: Number,
327 regu_size: Number,
328 alpha_du: Number,
329 alpha_pr: Number,
330 ls_trials: Index,
331 user_data: *mut c_void,
332) -> c_int {
333 unsafe {
334 let fud = &mut *(user_data as *mut FortranUserData);
335 let Some(cb) = fud.intermediate_cb else {
336 return 1;
337 };
338 let mut istop: Index = 0;
339 cb(
340 &alg_mod,
341 &iter_count,
342 &obj_value,
343 &inf_pr,
344 &inf_du,
345 &mu,
346 &d_norm,
347 ®u_size,
348 &alpha_du,
349 &alpha_pr,
350 &ls_trials,
351 fud.idat,
352 fud.ddat,
353 &mut istop,
354 );
355 if istop == OK { 1 } else { 0 }
356 }
357}
358
359fn f2cstr(buf: *const c_char, slen: c_int) -> Vec<u8> {
365 if buf.is_null() || slen <= 0 {
366 return vec![0];
367 }
368 let bytes = unsafe { std::slice::from_raw_parts(buf as *const u8, slen as usize) };
371 let mut end = bytes.len();
372 while end > 0 && bytes[end - 1] == b' ' {
373 end -= 1;
374 }
375 let mut v = Vec::with_capacity(end + 1);
376 v.extend_from_slice(&bytes[..end]);
377 v.push(0);
378 v
379}
380
381#[allow(clippy::too_many_arguments)]
395#[unsafe(no_mangle)]
396pub unsafe extern "C" fn ipcreate_(
397 n: *const Index,
398 x_l: *const Number,
399 x_u: *const Number,
400 m: *const Index,
401 g_l: *const Number,
402 g_u: *const Number,
403 nele_jac: *const Index,
404 nele_hess: *const Index,
405 idx_sty: *const Index,
406 eval_f: FEval_F_CB,
407 eval_g: Option<FEval_G_CB>,
408 eval_grad_f: FEval_Grad_F_CB,
409 eval_jac_g: Option<FEval_Jac_G_CB>,
410 eval_hess: Option<FEval_Hess_CB>,
411) -> *mut c_void {
412 unsafe {
413 let problem = CreateIpoptProblem(
414 *n,
415 x_l,
416 x_u,
417 *m,
418 g_l,
419 g_u,
420 *nele_jac,
421 *nele_hess,
422 *idx_sty,
423 Some(c_eval_f),
424 Some(c_eval_g),
425 Some(c_eval_grad_f),
426 Some(c_eval_jac_g),
427 Some(c_eval_h),
428 );
429 if problem.is_null() {
430 return std::ptr::null_mut();
431 }
432 let fud = Box::new(FortranUserData {
433 idat: std::ptr::null_mut(),
434 ddat: std::ptr::null_mut(),
435 eval_f,
436 eval_g,
437 eval_grad_f,
438 eval_jac_g,
439 eval_hess,
440 intermediate_cb: None,
441 problem,
442 });
443 Box::into_raw(fud) as *mut c_void
444 }
445}
446
447#[unsafe(no_mangle)]
454pub unsafe extern "C" fn ipfree_(fproblem: *mut *mut c_void) {
455 unsafe {
456 if fproblem.is_null() || (*fproblem).is_null() {
457 return;
458 }
459 let raw = *fproblem as *mut FortranUserData;
460 let fud = Box::from_raw(raw);
461 FreeIpoptProblem(fud.problem);
462 drop(fud);
463 *fproblem = std::ptr::null_mut();
464 }
465}
466
467#[allow(clippy::too_many_arguments)]
474#[unsafe(no_mangle)]
475pub unsafe extern "C" fn ipsolve_(
476 fproblem: *mut *mut c_void,
477 x: *mut Number,
478 g: *mut Number,
479 obj_val: *mut Number,
480 mult_g: *mut Number,
481 mult_x_l: *mut Number,
482 mult_x_u: *mut Number,
483 idat: *mut Index,
484 ddat: *mut Number,
485) -> Index {
486 unsafe {
487 if fproblem.is_null() || (*fproblem).is_null() {
488 return -199;
489 }
490 let fud = &mut *(*fproblem as *mut FortranUserData);
491 fud.idat = idat;
492 fud.ddat = ddat;
493 let fud_ptr = (*fproblem) as *mut c_void;
494 IpoptSolve(
495 fud.problem,
496 x,
497 g,
498 obj_val,
499 mult_g,
500 mult_x_l,
501 mult_x_u,
502 fud_ptr,
503 )
504 }
505}
506
507#[unsafe(no_mangle)]
514pub unsafe extern "C" fn ipaddstroption_(
515 fproblem: *mut *mut c_void,
516 keyword: *const c_char,
517 value: *const c_char,
518 klen: c_int,
519 vlen: c_int,
520) -> Index {
521 unsafe {
522 if fproblem.is_null() || (*fproblem).is_null() {
523 return NOT_OK;
524 }
525 let fud = &mut *(*fproblem as *mut FortranUserData);
526 let k = f2cstr(keyword, klen);
527 let v = f2cstr(value, vlen);
528 let ok = AddIpoptStrOption(
529 fud.problem,
530 k.as_ptr() as *const c_char,
531 v.as_ptr() as *const c_char,
532 );
533 if ok != 0 { OK } else { NOT_OK }
534 }
535}
536
537#[unsafe(no_mangle)]
543pub unsafe extern "C" fn ipaddnumoption_(
544 fproblem: *mut *mut c_void,
545 keyword: *const c_char,
546 value: *const Number,
547 klen: c_int,
548) -> Index {
549 unsafe {
550 if fproblem.is_null() || (*fproblem).is_null() {
551 return NOT_OK;
552 }
553 let fud = &mut *(*fproblem as *mut FortranUserData);
554 let k = f2cstr(keyword, klen);
555 let ok = AddIpoptNumOption(fud.problem, k.as_ptr() as *const c_char, *value);
556 if ok != 0 { OK } else { NOT_OK }
557 }
558}
559
560#[unsafe(no_mangle)]
566pub unsafe extern "C" fn ipaddintoption_(
567 fproblem: *mut *mut c_void,
568 keyword: *const c_char,
569 value: *const Index,
570 klen: c_int,
571) -> Index {
572 unsafe {
573 if fproblem.is_null() || (*fproblem).is_null() {
574 return NOT_OK;
575 }
576 let fud = &mut *(*fproblem as *mut FortranUserData);
577 let k = f2cstr(keyword, klen);
578 let ok = AddIpoptIntOption(fud.problem, k.as_ptr() as *const c_char, *value);
579 if ok != 0 { OK } else { NOT_OK }
580 }
581}
582
583#[unsafe(no_mangle)]
590pub unsafe extern "C" fn ipsetcallback_(fproblem: *mut *mut c_void, inter_cb: FIntermediate_CB) {
591 unsafe {
592 if fproblem.is_null() || (*fproblem).is_null() {
593 return;
594 }
595 let fud = &mut *(*fproblem as *mut FortranUserData);
596 fud.intermediate_cb = Some(inter_cb);
597 let _: Index =
598 SetIntermediateCallback(fud.problem, Some(c_intermediate as Intermediate_CB));
599 }
600}
601
602#[unsafe(no_mangle)]
607pub unsafe extern "C" fn ipunsetcallback_(fproblem: *mut *mut c_void) {
608 unsafe {
609 if fproblem.is_null() || (*fproblem).is_null() {
610 return;
611 }
612 let fud = &mut *(*fproblem as *mut FortranUserData);
613 fud.intermediate_cb = None;
614 let _: Index = SetIntermediateCallback(fud.problem, None);
615 }
616}
617
618const _: Eval_F_CB = c_eval_f;
622const _: Eval_Grad_F_CB = c_eval_grad_f;
623const _: Eval_G_CB = c_eval_g;
624const _: Eval_Jac_G_CB = c_eval_jac_g;
625const _: Eval_H_CB = c_eval_h;
626const _: Intermediate_CB = c_intermediate;
627
628#[cfg(test)]
629mod tests {
630 use super::*;
631
632 #[test]
633 fn f2cstr_strips_trailing_spaces() {
634 let buf = b"hello ";
635 let v = f2cstr(buf.as_ptr() as *const c_char, buf.len() as c_int);
636 assert_eq!(&v[..], b"hello\0");
637 }
638
639 #[test]
640 fn f2cstr_handles_null_buf() {
641 let v = f2cstr(std::ptr::null(), 5);
642 assert_eq!(&v[..], &[0]);
643 }
644
645 #[test]
646 fn f2cstr_keeps_embedded_spaces() {
647 let buf = b"a b c ";
648 let v = f2cstr(buf.as_ptr() as *const c_char, buf.len() as c_int);
649 assert_eq!(&v[..], b"a b c\0");
650 }
651
652 unsafe extern "C" fn fquad_eval_f(
655 _n: *const Index,
656 x: *mut Number,
657 _new_x: *const Index,
658 obj: *mut Number,
659 _idat: *mut Index,
660 _ddat: *mut Number,
661 ierr: *mut Index,
662 ) {
663 unsafe {
664 let v = *x.offset(0);
665 *obj = (v - 3.0) * (v - 3.0);
666 *ierr = OK;
667 }
668 }
669 unsafe extern "C" fn fquad_eval_grad_f(
670 _n: *const Index,
671 x: *mut Number,
672 _new_x: *const Index,
673 grad: *mut Number,
674 _idat: *mut Index,
675 _ddat: *mut Number,
676 ierr: *mut Index,
677 ) {
678 unsafe {
679 let v = *x.offset(0);
680 *grad.offset(0) = 2.0 * (v - 3.0);
681 *ierr = OK;
682 }
683 }
684 unsafe extern "C" fn fquad_eval_hess(
685 task: *const Index,
686 _n: *const Index,
687 _x: *mut Number,
688 _new_x: *const Index,
689 obj_factor: *const Number,
690 _m: *const Index,
691 _lambda: *mut Number,
692 _new_lambda: *const Index,
693 _nnz_hess: *const Index,
694 irow: *mut Index,
695 jcol: *mut Index,
696 values: *mut Number,
697 _idat: *mut Index,
698 _ddat: *mut Number,
699 ierr: *mut Index,
700 ) {
701 unsafe {
702 if *task == 0 {
703 *irow.offset(0) = 0;
704 *jcol.offset(0) = 0;
705 } else {
706 *values.offset(0) = 2.0 * *obj_factor;
707 }
708 *ierr = OK;
709 }
710 }
711
712 #[test]
713 fn fortran_ipsolve_drives_quadratic() {
714 let n: Index = 1;
715 let m: Index = 0;
716 let nele_jac: Index = 0;
717 let nele_hess: Index = 1;
718 let idx_sty: Index = 0;
719 let xl = [-1.0e20];
720 let xu = [1.0e20];
721
722 let mut fp: *mut c_void = unsafe {
723 ipcreate_(
724 &n,
725 xl.as_ptr(),
726 xu.as_ptr(),
727 &m,
728 std::ptr::null(),
729 std::ptr::null(),
730 &nele_jac,
731 &nele_hess,
732 &idx_sty,
733 fquad_eval_f,
734 None,
735 fquad_eval_grad_f,
736 None,
737 Some(fquad_eval_hess),
738 )
739 };
740 assert!(!fp.is_null());
741
742 let mut x = [0.0_f64];
743 let mut obj: Number = 0.0;
744 let mut idat = [0_i32; 1];
745 let mut ddat = [0.0_f64; 1];
746 let rc = unsafe {
747 ipsolve_(
748 &mut fp,
749 x.as_mut_ptr(),
750 std::ptr::null_mut(),
751 &mut obj,
752 std::ptr::null_mut(),
753 std::ptr::null_mut(),
754 std::ptr::null_mut(),
755 idat.as_mut_ptr(),
756 ddat.as_mut_ptr(),
757 )
758 };
759 assert_eq!(rc, 0); assert!((x[0] - 3.0).abs() < 1e-6, "x[0] = {}", x[0]);
761 unsafe { ipfree_(&mut fp) };
762 assert!(fp.is_null());
763 }
764}