Skip to main content

pounce_cinterface/
fortran.rs

1//! Fortran 77 ABI shim — port of `Interfaces/IpStdFInterface.c`.
2//!
3//! Exposes the gfortran-style `ip<name>_` symbols that the upstream
4//! Fortran example programs (`examples/hs071_f`) call. Each function
5//! receives all its arguments as pointers (the F77 ABI), translates
6//! to the C entry points in [`crate`], and translates back to the
7//! Fortran-side `OKRetVal = 0 / NotOKRetVal = 1` convention.
8//!
9//! Trailing `_` matches gfortran / clang-flang's `F77_FUNC` mangling
10//! when no underscores appear in the original name. Names with
11//! embedded underscores would need `__`; none of the names exposed
12//! here have any.
13//!
14//! Strings come in as `(char*, len_in_int)` pairs at the *end* of the
15//! call (clang-flang / gfortran convention) — Fortran callers must
16//! pass the lengths in the order the symbol declares. We accept the
17//! length as an extra trailing `c_int` per string argument and copy
18//! the buffer with trailing-space stripping ([`f2cstr`]).
19
20use 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
27/// Fortran-side OK status (matches `IpStdFInterface.c::OKRetVal`).
28const OK: Index = 0;
29/// Fortran-side error status (matches `IpStdFInterface.c::NotOKRetVal`).
30const NOT_OK: Index = 1;
31
32/// Holds the Fortran user-data table (`IDAT`, `DDAT` integer/double
33/// scratch buffers, plus the user's Fortran callbacks). This is what
34/// `IpStdFInterface.c::FUserData` carries; we keep an opaque
35/// `Box<FortranUserData>` that the C callbacks dereference.
36struct 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
48// Fortran callback function-pointer types. Each argument is by
49// reference, matching `IpStdFInterface.c`.
50
51pub 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
132// ------------------------------------------------------------------
133// C-side trampolines: these implement the C interface's `Eval_F_CB`
134// etc. and unpack the Fortran user_data to call back into Fortran.
135// ------------------------------------------------------------------
136
137unsafe 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            &regu_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
359// ------------------------------------------------------------------
360// String marshalling. F77 passes (char*, len) where the buffer is
361// blank-padded to `len`; we strip trailing spaces and NUL-terminate.
362// ------------------------------------------------------------------
363
364fn f2cstr(buf: *const c_char, slen: c_int) -> Vec<u8> {
365    if buf.is_null() || slen <= 0 {
366        return vec![0];
367    }
368    // SAFETY: caller asserts (buf, slen) are a valid Fortran character
369    // slice of length `slen` bytes.
370    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// ------------------------------------------------------------------
382// Fortran entry points (gfortran trailing-underscore names).
383// ------------------------------------------------------------------
384
385/// `ipcreate_(N, X_L, X_U, M, G_L, G_U, NELE_JAC, NELE_HESS, IDX_STY,
386///            EVAL_F, EVAL_G, EVAL_GRAD_F, EVAL_JAC_G, EVAL_HESS) -> fptr`
387///
388/// Returns an opaque handle (a Box<FortranUserData>) cast to a
389/// pointer; pass back to [`ipfree_`] / [`ipsolve_`].
390///
391/// # Safety
392/// All pointer arguments must be valid for the lifetime of the
393/// returned handle. Bound arrays must hold `*N` / `*M` doubles.
394#[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/// `ipfree_(FProblem)` — frees the handle and zeroes the user's
448/// pointer slot, mirroring `IpStdFInterface.c::F77_FUNC(ipfree)`.
449///
450/// # Safety
451/// `fproblem` must point to a slot that holds a handle previously
452/// returned by [`ipcreate_`], or NULL.
453#[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/// `ipsolve_(FProblem, X, G, OBJ_VAL, MULT_G, MULT_X_L, MULT_X_U, IDAT, DDAT) -> Index`.
468///
469/// # Safety
470/// All pointer arguments must satisfy the contracts documented on
471/// [`crate::IpoptSolve`]. `idat`/`ddat` are scratch arrays passed
472/// back to the user's Fortran callbacks.
473#[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/// `ipaddstroption_(FProblem, KEYWORD, VALUE, klen, vlen) -> Index`.
508/// Returns 0 (`OKRetVal`) on success, 1 on failure.
509///
510/// # Safety
511/// `fproblem` must be valid; the `(KEYWORD, klen)` and `(VALUE, vlen)`
512/// pairs must describe valid Fortran character slices.
513#[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/// `ipaddnumoption_(FProblem, KEYWORD, VALUE, klen) -> Index`.
538///
539/// # Safety
540/// `fproblem` must be valid; the `(KEYWORD, klen)` pair must describe
541/// a valid Fortran character slice.
542#[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/// `ipaddintoption_(FProblem, KEYWORD, VALUE, klen) -> Index`.
561///
562/// # Safety
563/// `fproblem` must be valid; `(KEYWORD, klen)` must describe a valid
564/// Fortran character slice.
565#[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/// `ipsetcallback_(FProblem, INTER_CB)` — install a Fortran-side
584/// intermediate callback.
585///
586/// # Safety
587/// `fproblem` must be valid; `inter_cb` must be a valid Fortran
588/// callback for the lifetime of the problem.
589#[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/// `ipunsetcallback_(FProblem)` — remove the intermediate callback.
603///
604/// # Safety
605/// `fproblem` must be valid.
606#[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
618// Suppress unused import warning when the C ABI types aren't visible
619// to dead-code analysis (they're referenced through public type
620// aliases above).
621const _: 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    /// Drive a 1-D unconstrained quadratic through the Fortran ABI
653    /// path: f(x) = (x - 3)^2.
654    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); // Solve_Succeeded
760        assert!((x[0] - 3.0).abs() < 1e-6, "x[0] = {}", x[0]);
761        unsafe { ipfree_(&mut fp) };
762        assert!(fp.is_null());
763    }
764}