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 let fud = &mut *(user_data as *mut FortranUserData);
145 let mut ierr: Index = 0;
146 let n_local = n;
147 let new_x_i: Index = new_x as Index;
148 (fud.eval_f)(
149 &n_local,
150 x as *mut Number,
151 &new_x_i,
152 obj_value,
153 fud.idat,
154 fud.ddat,
155 &mut ierr,
156 );
157 if ierr == OK {
158 1
159 } else {
160 0
161 }
162}
163
164unsafe extern "C" fn c_eval_grad_f(
165 n: Index,
166 x: *const Number,
167 new_x: c_int,
168 grad_f: *mut Number,
169 user_data: *mut c_void,
170) -> c_int {
171 let fud = &mut *(user_data as *mut FortranUserData);
172 let mut ierr: Index = 0;
173 let n_local = n;
174 let new_x_i: Index = new_x as Index;
175 (fud.eval_grad_f)(
176 &n_local,
177 x as *mut Number,
178 &new_x_i,
179 grad_f,
180 fud.idat,
181 fud.ddat,
182 &mut ierr,
183 );
184 if ierr == OK {
185 1
186 } else {
187 0
188 }
189}
190
191unsafe extern "C" fn c_eval_g(
192 n: Index,
193 x: *const Number,
194 new_x: c_int,
195 m: Index,
196 g: *mut Number,
197 user_data: *mut c_void,
198) -> c_int {
199 let fud = &mut *(user_data as *mut FortranUserData);
200 let Some(cb) = fud.eval_g else {
201 return 0;
202 };
203 let mut ierr: Index = 0;
204 let n_local = n;
205 let m_local = m;
206 let new_x_i: Index = new_x as Index;
207 cb(
208 &n_local,
209 x as *mut Number,
210 &new_x_i,
211 &m_local,
212 g,
213 fud.idat,
214 fud.ddat,
215 &mut ierr,
216 );
217 if ierr == OK {
218 1
219 } else {
220 0
221 }
222}
223
224unsafe extern "C" fn c_eval_jac_g(
225 n: Index,
226 x: *const Number,
227 new_x: c_int,
228 m: Index,
229 nele_jac: Index,
230 irow: *mut Index,
231 jcol: *mut Index,
232 values: *mut Number,
233 user_data: *mut c_void,
234) -> c_int {
235 let fud = &mut *(user_data as *mut FortranUserData);
236 let Some(cb) = fud.eval_jac_g else {
237 return 0;
238 };
239 let task: Index = if !irow.is_null() && !jcol.is_null() && values.is_null() {
240 0
241 } else if irow.is_null() && jcol.is_null() && !values.is_null() {
242 1
243 } else {
244 return 0;
245 };
246 let mut ierr: Index = 0;
247 let n_local = n;
248 let m_local = m;
249 let nele_local = nele_jac;
250 let new_x_i: Index = new_x as Index;
251 cb(
252 &task,
253 &n_local,
254 x as *mut Number,
255 &new_x_i,
256 &m_local,
257 &nele_local,
258 irow,
259 jcol,
260 values,
261 fud.idat,
262 fud.ddat,
263 &mut ierr,
264 );
265 if ierr == OK {
266 1
267 } else {
268 0
269 }
270}
271
272#[allow(clippy::too_many_arguments)]
273unsafe extern "C" fn c_eval_h(
274 n: Index,
275 x: *const Number,
276 new_x: c_int,
277 obj_factor: Number,
278 m: Index,
279 lambda: *const Number,
280 new_lambda: c_int,
281 nele_hess: Index,
282 irow: *mut Index,
283 jcol: *mut Index,
284 values: *mut Number,
285 user_data: *mut c_void,
286) -> c_int {
287 let fud = &mut *(user_data as *mut FortranUserData);
288 let Some(cb) = fud.eval_hess else {
289 return 0;
290 };
291 let task: Index = if !irow.is_null() && !jcol.is_null() && values.is_null() {
292 0
293 } else if irow.is_null() && jcol.is_null() && !values.is_null() {
294 1
295 } else {
296 return 0;
297 };
298 let mut ierr: Index = 0;
299 let n_local = n;
300 let m_local = m;
301 let nele_local = nele_hess;
302 let new_x_i: Index = new_x as Index;
303 let new_lam_i: Index = new_lambda as Index;
304 cb(
305 &task,
306 &n_local,
307 x as *mut Number,
308 &new_x_i,
309 &obj_factor,
310 &m_local,
311 lambda as *mut Number,
312 &new_lam_i,
313 &nele_local,
314 irow,
315 jcol,
316 values,
317 fud.idat,
318 fud.ddat,
319 &mut ierr,
320 );
321 if ierr == OK {
322 1
323 } else {
324 0
325 }
326}
327
328#[allow(clippy::too_many_arguments)]
329unsafe extern "C" fn c_intermediate(
330 alg_mod: Index,
331 iter_count: Index,
332 obj_value: Number,
333 inf_pr: Number,
334 inf_du: Number,
335 mu: Number,
336 d_norm: Number,
337 regu_size: Number,
338 alpha_du: Number,
339 alpha_pr: Number,
340 ls_trials: Index,
341 user_data: *mut c_void,
342) -> c_int {
343 let fud = &mut *(user_data as *mut FortranUserData);
344 let Some(cb) = fud.intermediate_cb else {
345 return 1;
346 };
347 let mut istop: Index = 0;
348 cb(
349 &alg_mod,
350 &iter_count,
351 &obj_value,
352 &inf_pr,
353 &inf_du,
354 &mu,
355 &d_norm,
356 ®u_size,
357 &alpha_du,
358 &alpha_pr,
359 &ls_trials,
360 fud.idat,
361 fud.ddat,
362 &mut istop,
363 );
364 if istop == OK {
365 1
366 } else {
367 0
368 }
369}
370
371fn f2cstr(buf: *const c_char, slen: c_int) -> Vec<u8> {
377 if buf.is_null() || slen <= 0 {
378 return vec![0];
379 }
380 let bytes = unsafe { std::slice::from_raw_parts(buf as *const u8, slen as usize) };
383 let mut end = bytes.len();
384 while end > 0 && bytes[end - 1] == b' ' {
385 end -= 1;
386 }
387 let mut v = Vec::with_capacity(end + 1);
388 v.extend_from_slice(&bytes[..end]);
389 v.push(0);
390 v
391}
392
393#[allow(clippy::too_many_arguments)]
407#[no_mangle]
408pub unsafe extern "C" fn ipcreate_(
409 n: *const Index,
410 x_l: *const Number,
411 x_u: *const Number,
412 m: *const Index,
413 g_l: *const Number,
414 g_u: *const Number,
415 nele_jac: *const Index,
416 nele_hess: *const Index,
417 idx_sty: *const Index,
418 eval_f: FEval_F_CB,
419 eval_g: Option<FEval_G_CB>,
420 eval_grad_f: FEval_Grad_F_CB,
421 eval_jac_g: Option<FEval_Jac_G_CB>,
422 eval_hess: Option<FEval_Hess_CB>,
423) -> *mut c_void {
424 let problem = CreateIpoptProblem(
425 *n,
426 x_l,
427 x_u,
428 *m,
429 g_l,
430 g_u,
431 *nele_jac,
432 *nele_hess,
433 *idx_sty,
434 Some(c_eval_f),
435 Some(c_eval_g),
436 Some(c_eval_grad_f),
437 Some(c_eval_jac_g),
438 Some(c_eval_h),
439 );
440 if problem.is_null() {
441 return std::ptr::null_mut();
442 }
443 let fud = Box::new(FortranUserData {
444 idat: std::ptr::null_mut(),
445 ddat: std::ptr::null_mut(),
446 eval_f,
447 eval_g,
448 eval_grad_f,
449 eval_jac_g,
450 eval_hess,
451 intermediate_cb: None,
452 problem,
453 });
454 Box::into_raw(fud) as *mut c_void
455}
456
457#[no_mangle]
464pub unsafe extern "C" fn ipfree_(fproblem: *mut *mut c_void) {
465 if fproblem.is_null() || (*fproblem).is_null() {
466 return;
467 }
468 let raw = *fproblem as *mut FortranUserData;
469 let fud = Box::from_raw(raw);
470 FreeIpoptProblem(fud.problem);
471 drop(fud);
472 *fproblem = std::ptr::null_mut();
473}
474
475#[allow(clippy::too_many_arguments)]
482#[no_mangle]
483pub unsafe extern "C" fn ipsolve_(
484 fproblem: *mut *mut c_void,
485 x: *mut Number,
486 g: *mut Number,
487 obj_val: *mut Number,
488 mult_g: *mut Number,
489 mult_x_l: *mut Number,
490 mult_x_u: *mut Number,
491 idat: *mut Index,
492 ddat: *mut Number,
493) -> Index {
494 if fproblem.is_null() || (*fproblem).is_null() {
495 return -199;
496 }
497 let fud = &mut *(*fproblem as *mut FortranUserData);
498 fud.idat = idat;
499 fud.ddat = ddat;
500 let fud_ptr = (*fproblem) as *mut c_void;
501 IpoptSolve(
502 fud.problem,
503 x,
504 g,
505 obj_val,
506 mult_g,
507 mult_x_l,
508 mult_x_u,
509 fud_ptr,
510 )
511}
512
513#[no_mangle]
520pub unsafe extern "C" fn ipaddstroption_(
521 fproblem: *mut *mut c_void,
522 keyword: *const c_char,
523 value: *const c_char,
524 klen: c_int,
525 vlen: c_int,
526) -> Index {
527 if fproblem.is_null() || (*fproblem).is_null() {
528 return NOT_OK;
529 }
530 let fud = &mut *(*fproblem as *mut FortranUserData);
531 let k = f2cstr(keyword, klen);
532 let v = f2cstr(value, vlen);
533 let ok = AddIpoptStrOption(
534 fud.problem,
535 k.as_ptr() as *const c_char,
536 v.as_ptr() as *const c_char,
537 );
538 if ok != 0 {
539 OK
540 } else {
541 NOT_OK
542 }
543}
544
545#[no_mangle]
551pub unsafe extern "C" fn ipaddnumoption_(
552 fproblem: *mut *mut c_void,
553 keyword: *const c_char,
554 value: *const Number,
555 klen: c_int,
556) -> Index {
557 if fproblem.is_null() || (*fproblem).is_null() {
558 return NOT_OK;
559 }
560 let fud = &mut *(*fproblem as *mut FortranUserData);
561 let k = f2cstr(keyword, klen);
562 let ok = AddIpoptNumOption(fud.problem, k.as_ptr() as *const c_char, *value);
563 if ok != 0 {
564 OK
565 } else {
566 NOT_OK
567 }
568}
569
570#[no_mangle]
576pub unsafe extern "C" fn ipaddintoption_(
577 fproblem: *mut *mut c_void,
578 keyword: *const c_char,
579 value: *const Index,
580 klen: c_int,
581) -> Index {
582 if fproblem.is_null() || (*fproblem).is_null() {
583 return NOT_OK;
584 }
585 let fud = &mut *(*fproblem as *mut FortranUserData);
586 let k = f2cstr(keyword, klen);
587 let ok = AddIpoptIntOption(fud.problem, k.as_ptr() as *const c_char, *value);
588 if ok != 0 {
589 OK
590 } else {
591 NOT_OK
592 }
593}
594
595#[no_mangle]
602pub unsafe extern "C" fn ipsetcallback_(fproblem: *mut *mut c_void, inter_cb: FIntermediate_CB) {
603 if fproblem.is_null() || (*fproblem).is_null() {
604 return;
605 }
606 let fud = &mut *(*fproblem as *mut FortranUserData);
607 fud.intermediate_cb = Some(inter_cb);
608 let _: Index = SetIntermediateCallback(fud.problem, Some(c_intermediate as Intermediate_CB));
609}
610
611#[no_mangle]
616pub unsafe extern "C" fn ipunsetcallback_(fproblem: *mut *mut c_void) {
617 if fproblem.is_null() || (*fproblem).is_null() {
618 return;
619 }
620 let fud = &mut *(*fproblem as *mut FortranUserData);
621 fud.intermediate_cb = None;
622 let _: Index = SetIntermediateCallback(fud.problem, None);
623}
624
625const _: Eval_F_CB = c_eval_f;
629const _: Eval_Grad_F_CB = c_eval_grad_f;
630const _: Eval_G_CB = c_eval_g;
631const _: Eval_Jac_G_CB = c_eval_jac_g;
632const _: Eval_H_CB = c_eval_h;
633const _: Intermediate_CB = c_intermediate;
634
635#[cfg(test)]
636mod tests {
637 use super::*;
638
639 #[test]
640 fn f2cstr_strips_trailing_spaces() {
641 let buf = b"hello ";
642 let v = f2cstr(buf.as_ptr() as *const c_char, buf.len() as c_int);
643 assert_eq!(&v[..], b"hello\0");
644 }
645
646 #[test]
647 fn f2cstr_handles_null_buf() {
648 let v = f2cstr(std::ptr::null(), 5);
649 assert_eq!(&v[..], &[0]);
650 }
651
652 #[test]
653 fn f2cstr_keeps_embedded_spaces() {
654 let buf = b"a b c ";
655 let v = f2cstr(buf.as_ptr() as *const c_char, buf.len() as c_int);
656 assert_eq!(&v[..], b"a b c\0");
657 }
658
659 unsafe extern "C" fn fquad_eval_f(
662 _n: *const Index,
663 x: *mut Number,
664 _new_x: *const Index,
665 obj: *mut Number,
666 _idat: *mut Index,
667 _ddat: *mut Number,
668 ierr: *mut Index,
669 ) {
670 let v = *x.offset(0);
671 *obj = (v - 3.0) * (v - 3.0);
672 *ierr = OK;
673 }
674 unsafe extern "C" fn fquad_eval_grad_f(
675 _n: *const Index,
676 x: *mut Number,
677 _new_x: *const Index,
678 grad: *mut Number,
679 _idat: *mut Index,
680 _ddat: *mut Number,
681 ierr: *mut Index,
682 ) {
683 let v = *x.offset(0);
684 *grad.offset(0) = 2.0 * (v - 3.0);
685 *ierr = OK;
686 }
687 unsafe extern "C" fn fquad_eval_hess(
688 task: *const Index,
689 _n: *const Index,
690 _x: *mut Number,
691 _new_x: *const Index,
692 obj_factor: *const Number,
693 _m: *const Index,
694 _lambda: *mut Number,
695 _new_lambda: *const Index,
696 _nnz_hess: *const Index,
697 irow: *mut Index,
698 jcol: *mut Index,
699 values: *mut Number,
700 _idat: *mut Index,
701 _ddat: *mut Number,
702 ierr: *mut Index,
703 ) {
704 if *task == 0 {
705 *irow.offset(0) = 0;
706 *jcol.offset(0) = 0;
707 } else {
708 *values.offset(0) = 2.0 * *obj_factor;
709 }
710 *ierr = OK;
711 }
712
713 #[test]
714 fn fortran_ipsolve_drives_quadratic() {
715 let n: Index = 1;
716 let m: Index = 0;
717 let nele_jac: Index = 0;
718 let nele_hess: Index = 1;
719 let idx_sty: Index = 0;
720 let xl = [-1.0e20];
721 let xu = [1.0e20];
722
723 let mut fp: *mut c_void = unsafe {
724 ipcreate_(
725 &n,
726 xl.as_ptr(),
727 xu.as_ptr(),
728 &m,
729 std::ptr::null(),
730 std::ptr::null(),
731 &nele_jac,
732 &nele_hess,
733 &idx_sty,
734 fquad_eval_f,
735 None,
736 fquad_eval_grad_f,
737 None,
738 Some(fquad_eval_hess),
739 )
740 };
741 assert!(!fp.is_null());
742
743 let mut x = [0.0_f64];
744 let mut obj: Number = 0.0;
745 let mut idat = [0_i32; 1];
746 let mut ddat = [0.0_f64; 1];
747 let rc = unsafe {
748 ipsolve_(
749 &mut fp,
750 x.as_mut_ptr(),
751 std::ptr::null_mut(),
752 &mut obj,
753 std::ptr::null_mut(),
754 std::ptr::null_mut(),
755 std::ptr::null_mut(),
756 idat.as_mut_ptr(),
757 ddat.as_mut_ptr(),
758 )
759 };
760 assert_eq!(rc, 0); assert!((x[0] - 3.0).abs() < 1e-6, "x[0] = {}", x[0]);
762 unsafe { ipfree_(&mut fp) };
763 assert!(fp.is_null());
764 }
765}